diff --git a/WindCore/WindCore.pro b/WindCore/WindCore.pro index 74c4a85..71ef9ce 100644 --- a/WindCore/WindCore.pro +++ b/WindCore/WindCore.pro @@ -5,10 +5,11 @@ #------------------------------------------------- QT -= core gui +QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.14 TARGET = WindCore TEMPLATE = lib -CONFIG += staticlib +CONFIG += staticlib c++17 # The following define makes your compiler emit warnings if you use # any feature of Qt which has been marked as deprecated (the exact warnings @@ -22,6 +23,7 @@ DEFINES += QT_DEPRECATED_WARNINGS #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ + arm710a.cpp \ etna.cpp \ wind.cpp \ isa-arm.c \ @@ -31,6 +33,7 @@ SOURCES += \ emu.cpp HEADERS += \ + arm710a.h \ etna.h \ wind_hw.h \ wind.h \ diff --git a/WindCore/arm710a.cpp b/WindCore/arm710a.cpp new file mode 100644 index 0000000..41434f3 --- /dev/null +++ b/WindCore/arm710a.cpp @@ -0,0 +1,976 @@ +#include "arm710a.h" +#include "common.h" + +// this will need changing if this code ever compiles on big-endian procs +inline uint32_t read32LE(uint8_t *p) { + return *((uint32_t *)p); +} +inline void write32LE(uint8_t *p, uint32_t v) { + *((uint32_t *)p) = v; +} + + +void ARM710a::switchBank(BankIndex newBank) { + if (newBank != bank) { + // R13 and R14 need saving/loading for all banks + allModesBankedRegisters[bank][0] = GPRs[13]; + allModesBankedRegisters[bank][1] = GPRs[14]; + GPRs[13] = allModesBankedRegisters[newBank][0]; + GPRs[14] = allModesBankedRegisters[newBank][1]; + + // R8 to R12 are only banked in FIQ mode + auto oldBankR8to12 = (bank == FiqBank) ? FiqBank : MainBank; + auto newBankR8to12 = (newBank == FiqBank) ? FiqBank : MainBank; + if (oldBankR8to12 != newBankR8to12) { + // swap these sets around + for (int i = 0; i < 5; i++) + fiqBankedRegisters[oldBankR8to12][i] = GPRs[8 + i]; + for (int i = 0; i < 5; i++) + GPRs[8 + i] = fiqBankedRegisters[newBankR8to12][i]; + } + + bank = newBank; + } +} + + +void ARM710a::switchMode(Mode newMode) { + auto oldMode = currentMode(); + if (newMode != oldMode) { + switchBank(modeToBank[newMode & 0xF]); + + CPSR &= ~CPSR_ModeMask; + CPSR |= newMode; + } +} + +void ARM710a::raiseException(Mode mode, uint32_t savedPC, uint32_t newPC) { + auto bankIndex = modeToBank[mode & 0xF]; + SPSRs[bankIndex] = CPSR; + + switchMode(mode); + + prefetchCount = 0; + GPRs[14] = savedPC; + GPRs[15] = newPC; +} + +void ARM710a::requestFIQ() { + raiseException(FIQ32, GPRs[15], 0x1C); + CPSR |= CPSR_FIQDisable; + CPSR |= CPSR_IRQDisable; +} + +void ARM710a::requestIRQ() { + raiseException(FIQ32, GPRs[15], 0x18); + CPSR |= CPSR_IRQDisable; +} + +void ARM710a::reset() { + clearCache(); + raiseException(Supervisor32, 0, 0); +} + + + +uint32_t ARM710a::tick() { + // pop an instruction off the end of the pipeline + bool haveInsn = false; + uint32_t insn; + MMUFault insnFault; + if (prefetchCount == 2) { + haveInsn = true; + insn = prefetch[1]; + insnFault = prefetchFaults[1]; + } + + // move the instruction we fetched last tick once along + if (prefetchCount >= 1) { + prefetch[1] = prefetch[0]; + prefetchFaults[1] = prefetchFaults[0]; + } + + // fetch a new instruction + auto newInsn = readVirtual(GPRs[15], V32); + GPRs[15] += 4; + prefetch[0] = newInsn.first.value_or(0); + prefetchFaults[0] = newInsn.second; + if (prefetchCount < 2) + prefetchCount++; + + // now deal with the one we popped + uint32_t clocks = 1; + if (haveInsn) { + if (insnFault != NoFault) { + // Raise a prefetch error + // These do not set FSR or FAR + raiseException(Abort32, GPRs[15] - 8, 0xC); + } else { + clocks += executeInstruction(insn); + } + } + + if (faultTriggeredThisCycle) { + // data abort time! + faultTriggeredThisCycle = false; + raiseException(Abort32, GPRs[15] - 4, 0x10); + } + + return clocks; +} + + +static inline uint32_t extract(uint32_t value, uint32_t hiBit, uint32_t loBit) { + return (value >> loBit) & ((1 << (hiBit - loBit + 1)) - 1); +// return (value >> (32 - offset - length)) & ((1 << length) - 1); +} +static inline bool extract1(uint32_t value, uint32_t bit) { + return (value >> bit) & 1; +} + + +uint32_t ARM710a::executeInstruction(uint32_t i) { + uint32_t cycles = 1; + + // a big old dispatch thing here + // but first, conditions! + if (!checkCondition(extract(i, 31, 28))) + return cycles; + + if ((i & 0x0F000000) == 0x0F000000) + raiseException(Supervisor32, GPRs[15] - 8, 0x08); + else if ((i & 0x0F000F10) == 0x0E000F10) + cycles += execCP15RegisterTransfer(extract(i,23,21), extract1(i,20), extract(i,19,16), extract(i,15,12), extract(i,7,5), extract(i,3,0)); + else if ((i & 0x0E000000) == 0x0A000000) + cycles += execBranch(extract1(i,24), extract(i,23,0)); + else if ((i & 0x0E000000) == 0x08000000) + cycles += execBlockDataTransfer(extract(i,24,20), extract(i,19,16), extract(i,15,0)); + else if ((i & 0x0C000000) == 0x04000000) + cycles += execSingleDataTransfer(extract(i,25,20), extract(i,19,16), extract(i,15,12), extract(i,11,0)); + else if ((i & 0x0FB00FF0) == 0x01000090) + cycles += execSingleDataSwap(extract1(i,22), extract(i,19,16), extract(i,15,12), extract(i,3,0)); + else if ((i & 0x0F8000F0) == 0x00000090) + cycles += execMultiply(extract(i,21,20), extract(i,19,16), extract(i,15,12), extract(i,11,8), extract(i,3,0)); + else if ((i & 0x0C000000) == 0x00000000) + cycles += execDataProcessing(extract1(i,25), extract(i,24,21), extract1(i,20), extract(i,19,16), extract(i,15,12), extract(i,11,0)); + else + raiseException(Undefined32, GPRs[15] - 8, 0x04); + + return cycles; +} + +uint32_t ARM710a::execDataProcessing(bool I, uint32_t Opcode, bool S, uint32_t Rn, uint32_t Rd, uint32_t Operand2) +{ + uint32_t cycles = 0; // TODO increment me semi-accurately + bool shifterCarryOutput; + + // compute our Op1 (may be unnecessary but that's ok) + uint32_t op1 = GPRs[Rn]; + + // compute our Op2 + uint32_t op2; + if (!I) { + // REGISTER + uint32_t Rm = extract(Operand2, 3, 0); + op2 = GPRs[Rm]; + + uint8_t shiftBy; + + // this is the real painful one, honestly + if (extract(Operand2, 4, 4)) { + // Shift by Register + uint32_t Rs = extract(Operand2, 11, 8); + shiftBy = GPRs[Rs] & 0xFF; + } else { + // Shift by Immediate + shiftBy = extract(Operand2, 11, 7); + + if (Rn == 15) // if PC is fetched... + op1 -= 4; // compensate for prefetching + if (Rm == 15) + op2 -= 4; + } + + switch (extract(Operand2, 6, 5)) { + case 0: // Logical Left (LSL) + if (shiftBy == 0) { + shifterCarryOutput = flagC(); + // no change to op2! + } else if (shiftBy <= 31) { + shifterCarryOutput = extract1(op2, 31 - shiftBy); + op2 <<= shiftBy; + } else if (shiftBy == 32) { + shifterCarryOutput = extract1(op2, 0); + op2 = 0; + } else /*if (shiftBy >= 33)*/ { + shifterCarryOutput = false; + op2 = 0; + } + break; + case 1: // Logical Right (LSR) + if (shiftBy == 0 || shiftBy == 32) { + shifterCarryOutput = extract1(op2, 31); + op2 = 0; + } else if (shiftBy <= 31) { + shifterCarryOutput = extract1(op2, shiftBy - 1); + op2 >>= shiftBy; + } else /*if (shiftBy >= 33)*/ { + shifterCarryOutput = false; + op2 = 0; + } + break; + case 2: // Arithmetic Right (ASR) + if (shiftBy == 0 || shiftBy >= 32) { + shifterCarryOutput = extract1(op2, 31); + op2 = (int32_t)op2 >> 31; + } else /*if (shiftBy <= 31)*/ { + shifterCarryOutput = extract1(op2, shiftBy - 1); + op2 = (int32_t)op2 >> shiftBy; + } + break; + case 3: // Rotate Right (ROR) + if (shiftBy == 0) { // treated as RRX + shifterCarryOutput = op2 & 1; + op2 >>= 1; + op2 |= flagC() ? 0x80000000 : 0; + } else { + shiftBy %= 32; + if (shiftBy == 0) { // like 32 + shifterCarryOutput = extract1(op2, 31); + // no change to op2 + } else { + shifterCarryOutput = extract1(op2, shiftBy - 1); + op2 = ROR(op2, shiftBy); + } + } + break; + } + } else { + // IMMEDIATE + if (Rn == 15) // if PC is fetched... + op1 -= 4; // compensate for prefetching + + uint32_t Rotate = extract(Operand2, 11, 8); + uint32_t Imm = extract(Operand2, 7, 0); + Imm = (uint32_t)(int8_t)Imm; + op2 = ROR(Imm, Rotate * 2); + shifterCarryOutput = flagC(); // correct? unsure... + } + + // we have our operands, what next + uint64_t result = 0; + uint32_t flags = 0; + +#define LOGICAL_OP(v) \ + result = v; \ + flags |= (result & 0xFFFFFFFF) ? 0 : CPSR_Z; \ + flags |= (result & 0x80000000) ? CPSR_N : 0; \ + flags |= shifterCarryOutput ? CPSR_C : 0; \ + flags |= (CPSR & CPSR_V); + +#define ADD_OP(a, b, c) \ + result = a + b + (uint32_t)(c); \ + flags |= (result & 0xFFFFFFFF) ? 0 : CPSR_Z; \ + flags |= (result & 0x80000000) ? CPSR_N : 0; \ + flags |= (result & 0x100000000) ? CPSR_C : 0; \ + flags |= (result >= 0x100000000) ? CPSR_V : 0; + +#define SUB_OP(a, b, c) ADD_OP(a, ~b, c) + + + switch (Opcode) { + case 0: LOGICAL_OP(op1 & op2); break; // AND + case 1: LOGICAL_OP(op1 ^ op2); break; // EOR + case 2: SUB_OP(op1, op2, 1); break; // SUB + case 3: SUB_OP(op2, op1, 1); break; // RSB + case 4: ADD_OP(op1, op2, 0); break; // ADD + case 5: ADD_OP(op1, op2, flagC()); break; // ADC + case 6: SUB_OP(op1, op2, flagC()); break; // SBC + case 7: SUB_OP(op2, op1, flagC()); break; // RSC + case 8: LOGICAL_OP(op1 & op2); break; // TST + case 9: LOGICAL_OP(op1 ^ op2); break; // TEQ + case 0xA: SUB_OP(op1, op2, 1); break; // CMP + case 0xB: ADD_OP(op1, op2, 0); break; // CMN + case 0xC: LOGICAL_OP(op1 | op2); break; // ORR + case 0xD: LOGICAL_OP(op2); break; // MOV + case 0xE: LOGICAL_OP(op1 & ~op2); break; // BIC + case 0xF: LOGICAL_OP(~op2); break; // MVN + } + + if (Opcode >= 8 && Opcode <= 0xB) { + // Output-less opcodes: special behaviour + if (S) { + CPSR = (CPSR & ~CPSR_FlagMask) | flags; + } else if (Opcode == 8) { + // MRS, CPSR -> Reg + GPRs[Rd] = CPSR; + } else if (Opcode == 9) { + // MSR, Reg -> CPSR + bool canChangeMode = extract1(Rn, 0); + if (canChangeMode && isPrivileged()) { + auto newCPSR = GPRs[extract(Operand2, 3, 0)]; + switchMode(modeFromCPSR(newCPSR)); + CPSR = newCPSR; + } else { + // for the flag-only version, immediates are allowed + // so we just re-use what was calculated earlier... + auto newFlag = I ? op2 : GPRs[extract(Operand2, 3, 0)]; + CPSR &= ~CPSR_FlagMask; + CPSR |= (newFlag & CPSR_FlagMask); + } + } else if (Opcode == 0xA) { + // MRS, SPSR -> Reg + if (isPrivileged()) + GPRs[Rd] = SPSRs[currentBank()]; + } else /*if (Opcode == 0xB)*/ { + bool canChangeMode = extract1(Rn, 0); + if (isPrivileged()) { + if (canChangeMode) { + SPSRs[currentBank()] = GPRs[extract(Operand2, 3, 0)]; + } else { + // same hat + auto newFlag = I ? op2 : GPRs[extract(Operand2, 3, 0)]; + SPSRs[currentBank()] &= ~CPSR_FlagMask; + SPSRs[currentBank()] |= (newFlag & CPSR_FlagMask); + } + } + } + } else { + GPRs[Rd] = result & 0xFFFFFFFF; + + if (Rd == 15) { + // Writing to PC + // Special things occur here! + prefetchCount = 0; + if (S && isPrivileged()) { + // We SHOULD be privileged + // (Raise an error otherwise...?) + auto saved = SPSRs[currentBank()]; + switchMode(modeFromCPSR(saved)); + CPSR = saved; + } + } else if (S) { + CPSR = (CPSR & ~CPSR_FlagMask) | flags; + } + } + + return cycles; +} + +uint32_t ARM710a::execMultiply(uint32_t AS, uint32_t Rd, uint32_t Rn, uint32_t Rs, uint32_t Rm) +{ + // no need for R15 fuckery + // datasheet says it's not allowed here + if (AS & 2) + GPRs[Rd] = GPRs[Rm] * GPRs[Rs] + GPRs[Rn]; + else + GPRs[Rd] = GPRs[Rm] * GPRs[Rs]; + + if (AS & 1) { + CPSR &= ~(CPSR_N | CPSR_Z); + CPSR |= GPRs[Rd] ? 0 : CPSR_Z; + CPSR |= (GPRs[Rd] & 0x80000000) ? CPSR_N : 0; + } + + return 0; +} + +uint32_t ARM710a::execSingleDataSwap(bool B, uint32_t Rn, uint32_t Rd, uint32_t Rm) +{ + auto valueSize = B ? V8 : V32; + auto readResult = readVirtual(GPRs[Rn], valueSize); + auto fault = readResult.second; + + if (fault == NoFault) { + fault = writeVirtual(GPRs[Rm], GPRs[Rn], valueSize); + if (fault == NoFault) + GPRs[Rd] = readResult.first.value(); + } + + if (fault != NoFault) + reportFault(fault); + + return 1; +} + +uint32_t ARM710a::execSingleDataTransfer(uint32_t IPUBWL, uint32_t Rn, uint32_t Rd, uint32_t offset) +{ + bool load = extract1(IPUBWL, 0); + bool writeback = extract1(IPUBWL, 1); + auto valueSize = extract1(IPUBWL, 2) ? V8 : V32; + bool up = extract1(IPUBWL, 3); + bool preIndex = extract1(IPUBWL, 4); + bool immediate = extract1(IPUBWL, 5); + + // calculate the offset + uint32_t calcOffset; + if (!immediate) { + // REGISTER + uint32_t Rm = extract(offset, 3, 0); + calcOffset = GPRs[Rm]; + + uint8_t shiftBy = extract(offset, 11, 7); + + switch (extract(offset, 6, 5)) { + case 0: // Logical Left (LSL) + if (shiftBy > 0) + calcOffset <<= shiftBy; + break; + case 1: // Logical Right (LSR) + if (shiftBy == 0) + calcOffset = 0; + else + calcOffset >>= shiftBy; + break; + case 2: // Arithmetic Right (ASR) + if (shiftBy == 0) + calcOffset = (int32_t)calcOffset >> 31; + else + calcOffset = (int32_t)calcOffset >> shiftBy; + break; + case 3: // Rotate Right (ROR) + if (shiftBy == 0) { // treated as RRX + calcOffset >>= 1; + calcOffset |= flagC() ? 0x80000000 : 0; + } else + calcOffset = ROR(calcOffset, shiftBy); + break; + } + } else { + // IMMEDIATE + uint32_t Rotate = extract(offset, 11, 8); + uint32_t Imm = extract(offset, 7, 0); + Imm = (uint32_t)(int8_t)Imm; + calcOffset = ROR(Imm, Rotate * 2); + } + + uint32_t base = GPRs[Rn]; + if (Rn == 15) base -= 4; // prefetch adjustment + uint32_t modifiedBase = up ? (base + calcOffset) : (base - calcOffset); + uint32_t transferAddr = preIndex ? modifiedBase : base; + + bool changeModes = !preIndex && writeback && isPrivileged(); + auto saveMode = currentMode(); + + MMUFault fault; + + if (load) { + if (changeModes) switchMode(User32); + auto readResult = readVirtual(transferAddr, valueSize); + if (changeModes) switchMode(saveMode); + if (readResult.first.has_value()) + GPRs[Rd] = readResult.first.value(); + fault = readResult.second; + } else { + uint32_t value = GPRs[Rd]; + if (changeModes) switchMode(User32); + fault = writeVirtual(value, transferAddr, valueSize); + if (changeModes) switchMode(saveMode); + } + + if (preIndex && writeback) + GPRs[Rn] = modifiedBase; + + if (fault != NoFault) + reportFault(fault); + + return 2; +} + +uint32_t ARM710a::execBlockDataTransfer(uint32_t PUSWL, uint32_t Rn, uint32_t registerList) +{ + bool load = extract1(PUSWL, 0); + bool store = !load; + bool writeback = extract1(PUSWL, 1); + bool psrForceUser = extract1(PUSWL, 2); + bool up = extract1(PUSWL, 3); + bool preIndex = extract1(PUSWL, 4); + + MMUFault fault = NoFault; + uint32_t base = GPRs[Rn] & ~3; + uint32_t blockSize = popcount32(registerList) * 4; + + uint32_t lowAddr, updatedBase; + if (up) { + updatedBase = base + blockSize; + lowAddr = base + (preIndex ? 4 : 0); + } else { + updatedBase = base - blockSize; + lowAddr = updatedBase + (preIndex ? 0 : 4); + } + + auto saveBank = bank; + if (psrForceUser && (store || !(registerList & 0x8000))) + switchBank(MainBank); + + bool doneWriteback = false; + if (load && writeback) { + doneWriteback = true; + GPRs[Rn] = updatedBase; + } + + uint32_t addr = lowAddr; + for (int i = 0; i < 16; i++) { + if (registerList & (1 << i)) { + // work on this one + if (load) { + // handling for LDM faults may be kinda iffy... + // wording on datasheet is a bit unclear + auto readResult = readVirtual(addr, V32); + if (readResult.first.has_value()) + GPRs[i] = readResult.first.value(); + if (readResult.second != NoFault) { + fault = readResult.second; + break; + } + } else { + auto newFault = writeVirtual(GPRs[i], addr, V32); + if (newFault != NoFault) + fault = newFault; + } + + addr += 4; + + if (writeback && !doneWriteback) { + doneWriteback = true; + GPRs[Rn] = updatedBase; + } + } + } + + // datasheet specifies that base register must be + // restored if an error occurs during LDM + if (load && fault != NoFault) + GPRs[Rn] = writeback ? updatedBase : base; + + if (psrForceUser && (!load || !(registerList & 0x8000))) + switchBank(saveBank); + + if (fault != NoFault) + reportFault(fault); + + return 0; // fixme +} + +uint32_t ARM710a::execBranch(bool L, uint32_t offset) +{ + if (L) + GPRs[14] = GPRs[15] - 8; + + // start with 24 bits, shift left 2, sign extend to 32 + int32_t sextOffset = (int32_t)(offset << 8) >> 6; + + prefetchCount = 0; + GPRs[15] -= 4; // account for our prefetch being +4 too much + GPRs[15] += sextOffset; + return 0; +} + +uint32_t ARM710a::execCP15RegisterTransfer(uint32_t CPOpc, bool L, uint32_t CRn, uint32_t Rd, uint32_t CP, uint32_t CRm) +{ + (void)CPOpc; // not used by ARM CP15 + (void)CP; + (void)CRm; + + if (!isPrivileged()) + return 0; + + if (L) { + // read a value + uint32_t what = 0; + + switch (CRn) { + case 0: what = cp15_id; break; + case 5: what = cp15_faultStatus; break; + case 6: what = cp15_faultAddress; break; + } + + if (Rd == 15) + CPSR = (CPSR & ~CPSR_FlagMask) | (what & CPSR_FlagMask); + else + GPRs[Rd] = what; + } else { + // store a value + uint32_t what = GPRs[Rd]; + + switch (CRn) { + case 1: cp15_control = what; break; + case 2: cp15_translationTableBase = what; break; + case 3: cp15_domainAccessControl = what; break; + case 5: flushTlb(); break; + case 6: flushTlb(what); break; + case 7: clearCache(); break; + } + } + + return 0; +} + + + +void ARM710a::clearCache() { + for (uint32_t i = 0; i < CacheSets; i++) { + for (uint32_t j = 0; j < CacheBlocksPerSet; j++) { + cacheBlockTags[i][j] = 0; + } + } +} + +uint8_t *ARM710a::findCacheLine(uint32_t virtAddr) { + uint32_t set = virtAddr & CacheAddressSetMask; + uint32_t tag = virtAddr & CacheAddressTagMask; + set >>= CacheAddressSetShift; + + for (uint32_t i = 0; i < CacheBlocksPerSet; i++) { + if (cacheBlockTags[set][i] & CacheBlockEnabled) { + if ((cacheBlockTags[set][i] & ~CacheBlockEnabled) == tag) + return &cacheBlocks[set][i][0]; + } + } + + return nullptr; +} + +pair ARM710a::addCacheLineAndRead(uint32_t physAddr, uint32_t virtAddr, ValueSize valueSize, int domain, bool isPage) { + uint32_t set = virtAddr & CacheAddressSetMask; + uint32_t tag = virtAddr & CacheAddressTagMask; + set >>= CacheAddressSetShift; + + // "it will be randomly placed in a cache bank" + // - the ARM710a data sheet, 6-2 (p90) + uint32_t i = rand() % CacheBlocksPerSet; + uint8_t *block = &cacheBlocks[set][i][0]; + MaybeU32 result; + MMUFault fault = NoFault; + + for (uint32_t j = 0; j < CacheBlockSize; j += 4) { + auto word = readPhysical(physAddr + j, V32); + if (word.has_value()) { + write32LE(&block[j], word.value()); + if (valueSize == V8 && j == (virtAddr & CacheAddressLineMask & ~3)) + result = (word.value() >> ((virtAddr & 3) * 8)) & 0xFF; + else if (valueSize == V32 && j == (virtAddr & CacheAddressLineMask)) + result = word.value(); + } else { + // read error, great + // TODO: should probably prioritise specific kinds of faults over others + fault = encodeFaultSorP(SorPLinefetchError, isPage, domain, virtAddr & ~CacheAddressLineMask); + break; + } + } + + // the cache block is only stored if it's complete + if (fault == NoFault) + cacheBlockTags[set][i] = tag | CacheBlockEnabled; + + return make_pair(result, fault); +} + +MaybeU32 ARM710a::readCached(uint32_t virtAddr, ValueSize valueSize) { + uint8_t *line = findCacheLine(virtAddr); + if (line) { + if (valueSize == V8) + return line[virtAddr & CacheAddressLineMask]; + else /*if (valueSize == V32)*/ + return read32LE(&line[virtAddr & CacheAddressLineMask]); + } + return {}; +} + + +bool ARM710a::writeCached(uint32_t value, uint32_t virtAddr, ValueSize valueSize) { + uint8_t *line = findCacheLine(virtAddr); + if (line) { + if (valueSize == V8) + line[virtAddr & CacheAddressLineMask] = value & 0xFF; + else /*if (valueSize == V32)*/ + write32LE(&line[virtAddr & CacheAddressLineMask], value); + return true; + } + return false; +} + + +uint32_t ARM710a::physAddrFromTlbEntry(TlbEntry *tlbEntry, uint32_t virtAddr) { + if ((tlbEntry->lv2Entry & 3) == 2) { + // Smøl page + return (tlbEntry->lv2Entry & 0xFFFFF000) | (virtAddr & 0xFFF); + } else if ((tlbEntry->lv2Entry & 3) == 1) { + // Lørge page + return (tlbEntry->lv2Entry & 0xFFFF0000) | (virtAddr & 0xFFFF); + } else { + // Section + return (tlbEntry->lv1Entry & 0xFFF00000) | (virtAddr & 0xFFFFF); + } +} + + +MaybeU32 ARM710a::virtToPhys(uint32_t virtAddr) { + if (!isMMUEnabled()) + return virtAddr; + + TlbEntry tempEntry; + auto translated = translateAddressUsingTlb(virtAddr, &tempEntry); + if (holds_alternative(translated)) { + auto tlbEntry = get(translated); + return physAddrFromTlbEntry(tlbEntry, virtAddr); + } else { + return MaybeU32(); + } +} + + +MaybeU32 ARM710a::readVirtualDebug(uint32_t virtAddr, ValueSize valueSize) { + if (auto v = virtToPhys(virtAddr); v.has_value()) + return readPhysical(v.value(), valueSize); + else + return {}; +} + + +pair ARM710a::readVirtual(uint32_t virtAddr, ValueSize valueSize) { + if (isAlignmentFaultEnabled() && valueSize == V32 && virtAddr & 3) + return make_pair(MaybeU32(), encodeFault(AlignmentFault, 0, virtAddr)); + + // fast path: cache + if (auto v = readCached(virtAddr, valueSize); v.has_value()) + return make_pair(v.value(), NoFault); + + if (!isMMUEnabled()) { + // things are very simple without a MMU + if (auto v = readPhysical(virtAddr, valueSize); v.has_value()) + return make_pair(v.value(), NoFault); + else + return make_pair(MaybeU32(), NonMMUError); + } + + auto translated = translateAddressUsingTlb(virtAddr); + if (holds_alternative(translated)) + return make_pair(MaybeU32(), get(translated)); + + // resolve this boy + auto tlbEntry = get(translated); + + if (auto f = checkAccessPermissions(tlbEntry, virtAddr, false); f != NoFault) + return make_pair(MaybeU32(), f); + + int domain = (tlbEntry->lv1Entry >> 5) & 0xF; + bool isPage = (tlbEntry->lv2Entry != 0); + + uint32_t physAddr = physAddrFromTlbEntry(tlbEntry, virtAddr); + bool cacheable = tlbEntry->lv2Entry ? (tlbEntry->lv2Entry & 8) : (tlbEntry->lv1Entry & 8); + + if (cacheable && isCacheEnabled()) + return addCacheLineAndRead(physAddr, virtAddr, valueSize, domain, isPage); + else if (auto result = readPhysical(physAddr, valueSize); result.has_value()) + return make_pair(result, NoFault); + else + return make_pair(result, encodeFaultSorP(SorPOtherBusError, isPage, domain, virtAddr)); +} + +ARM710a::MMUFault ARM710a::writeVirtual(uint32_t value, uint32_t virtAddr, ValueSize valueSize) { + if (isAlignmentFaultEnabled() && valueSize == V32 && virtAddr & 3) + return encodeFault(AlignmentFault, 0, virtAddr); + + if (!isMMUEnabled()) { + // direct virtual -> physical mapping, sans MMU + if (!writePhysical(value, virtAddr, valueSize)) + return NonMMUError; + } else { + auto translated = translateAddressUsingTlb(virtAddr); + if (holds_alternative(translated)) + return get(translated); + + // resolve this boy + auto tlbEntry = get(translated); + + if (auto f = checkAccessPermissions(tlbEntry, virtAddr, true); f != NoFault) + return f; + + uint32_t physAddr = physAddrFromTlbEntry(tlbEntry, virtAddr); + int domain = (tlbEntry->lv1Entry >> 5) & 0xF; + bool isPage = (tlbEntry->lv2Entry != 0); + + if (!writePhysical(value, physAddr, valueSize)) + return encodeFaultSorP(SorPOtherBusError, isPage, domain, virtAddr); + } + + // commit to cache if all was good + writeCached(value, virtAddr, valueSize); + return NoFault; +} + + + +// TLB +void ARM710a::flushTlb() { + for (TlbEntry &e : tlb) + e = {0, 0, 0, 0}; +} +void ARM710a::flushTlb(uint32_t virtAddr) { + for (TlbEntry &e : tlb) { + if (e.addrMask && (virtAddr & e.addrMask) == e.addr) { + e = {0, 0, 0, 0}; + break; + } + } +} + +ARM710a::TlbEntry *ARM710a::_allocateTlbEntry(uint32_t addrMask, uint32_t addr) { + TlbEntry *entry = &tlb[nextTlbIndex]; + entry->addrMask = addrMask; + entry->addr = addr & addrMask; + nextTlbIndex = (nextTlbIndex + 1) % TlbSize; + return entry; +} + +variant ARM710a::translateAddressUsingTlb(uint32_t virtAddr, TlbEntry *useMe) { + // first things first, do we have a matching entry in the TLB? + for (TlbEntry &e : tlb) { + if (e.addrMask && (virtAddr & e.addrMask) == e.addr) + return &e; + } + + // no, so do a page table walk + TlbEntry *entry; + uint32_t tableIndex = virtAddr >> 20; + + // fetch the Level 1 entry + auto lv1EntryOpt = readPhysical(cp15_translationTableBase | (tableIndex << 2), V32); + if (!lv1EntryOpt.has_value()) + return Lv1TranslationError; + auto lv1Entry = lv1EntryOpt.value(); + int domain = (lv1Entry >> 5) & 0xF; + + switch (lv1Entry & 3) { + case 0: + case 3: + // invalid! + return encodeFault(SectionTranslationFault, domain, virtAddr); + case 2: + // a Section entry is straightforward + // we just throw that immediately into the TLB + entry = useMe ? useMe : _allocateTlbEntry(0xFFF00000, virtAddr); + entry->lv1Entry = lv1Entry; + entry->lv2Entry = 0; + return entry; + case 1: + // a Page requires a Level 2 read + uint32_t pageTableAddr = lv1Entry & 0xFFFFFC00; + uint32_t lv2TableIndex = (virtAddr >> 12) & 0xFF; + + auto lv2EntryOpt = readPhysical(pageTableAddr | (lv2TableIndex << 2), V32); + if (!lv2EntryOpt.has_value()) + return encodeFault(Lv2TranslationError, domain, virtAddr); + auto lv2Entry = lv2EntryOpt.value(); + + switch (lv2Entry & 3) { + case 0: + case 3: + // invalid! + return encodeFault(PageTranslationFault, domain, virtAddr); + case 1: + // Large 64kb page + entry = useMe ? useMe : _allocateTlbEntry(0xFFFF0000, virtAddr); + entry->lv1Entry = lv1Entry; + entry->lv2Entry = lv2Entry; + return entry; + case 2: + // Small 4kb page + entry = useMe ? useMe : _allocateTlbEntry(0xFFFFF000, virtAddr); + entry->lv1Entry = lv1Entry; + entry->lv2Entry = lv2Entry; + return entry; + } + } + + // we should never get here as the switch covers 0, 1, 2, 3 + // but this satisfies a compiler warning + return SectionTranslationFault; +} + + + +ARM710a::MMUFault ARM710a::checkAccessPermissions(ARM710a::TlbEntry *entry, uint32_t virtAddr, bool isWrite) const { + int domain; + int accessPerms; + bool isPage; + + // extract info from the entries + domain = (entry->lv1Entry >> 5) & 0xF; + if (entry->lv2Entry) { + // Page + accessPerms = (entry->lv2Entry >> 4) & 0xFF; + + int permIndex; + if ((entry->lv2Entry & 3) == 1) // Large 64kb + permIndex = (virtAddr >> 14) & 3; + else // Small 4kb + permIndex = (virtAddr >> 10) & 3; + + accessPerms >>= (permIndex * 2); + accessPerms &= 3; + isPage = true; + } else { + // Section + accessPerms = (entry->lv1Entry >> 10) & 3; + isPage = false; + } + + // now, do our checks + int primaryAccessControls = (cp15_domainAccessControl >> (domain * 2)) & 3; + + // Manager: always allowed + if (primaryAccessControls == 3) + return NoFault; + + // Client: enforce checks! + if (primaryAccessControls == 1) { +#define OK_IF_TRUE(b) return ((b) ? NoFault : encodeFaultSorP(SorPPermissionFault, isPage, domain, virtAddr)) + bool System = cp15_control & 0x100; + bool ROM = cp15_control & 0x200; + + if (accessPerms == 0) { + if (!System && !ROM) { + // 00/0/0: Any access generates a permission fault + OK_IF_TRUE(false); + } else if (System && !ROM) { + // 00/1/0: Supervisor read only permitted + OK_IF_TRUE(!isWrite && isPrivileged()); + } else if (!System && ROM) { + // 00/0/1: Any write generates a permission fault + OK_IF_TRUE(!isWrite); + } else /*if (System && ROM)*/ { + // Reserved + OK_IF_TRUE(false); + } + } else if (accessPerms == 1) { + // 01/x/x: Access allowed only in Supervisor mode + OK_IF_TRUE(isPrivileged()); + } else if (accessPerms == 2) { + // 10/x/x: Writes in User mode cause permission fault + OK_IF_TRUE(!isWrite || isPrivileged()); + } else /*if (accessPerms == 3)*/ { + // 11/x/x: All access types permitted in both modes + OK_IF_TRUE(true); + } +#undef OK_IF_TRUE + } + + // No Access or Reserved: never allowed (Domain Fault) + return encodeFaultSorP(SorPDomainFault, isPage, domain, virtAddr); +} + + +void ARM710a::reportFault(MMUFault fault) { + if (fault != NoFault) { + if ((fault & 0xF) != NonMMUError) { + cp15_faultStatus = fault & 0xFFFF; + cp15_faultAddress = fault >> 32; + } + + // this signals a branch to DataAbort after the + // instruction is done executing + faultTriggeredThisCycle = true; + } +} diff --git a/WindCore/arm710a.h b/WindCore/arm710a.h new file mode 100644 index 0000000..f4116d6 --- /dev/null +++ b/WindCore/arm710a.h @@ -0,0 +1,261 @@ +#pragma once +#include +#include +#include + +using namespace std; + +// ASSUMPTIONS: +// - Little-endian will be used +// - 26-bit address spaces will not be used +// - Alignment faults will always be on + +// Write buffer is 4 address FIFO, 8 data FIFO +// TLB is 64 entries + +typedef optional MaybeU32; + +class ARM710a +{ +public: + enum ValueSize { V8 = 0, V32 = 1 }; + + enum MMUFault : uint64_t { + // ref: datasheet 9-13 (p111) + NoFault = 0, + AlignmentFault = 1, + // the ARM gods say there is to be no fault 2 or 3 + SectionLinefetchError = 4, + SectionTranslationFault = 5, + PageLinefetchError = 6, + PageTranslationFault = 7, + SectionOtherBusError = 8, + SectionDomainFault = 9, + PageOtherBusError = 0xA, + PageDomainFault = 0xB, + Lv1TranslationError = 0xC, + SectionPermissionFault = 0xD, + Lv2TranslationError = 0xE, + PagePermissionFault = 0xF, + + // not actually in the ARM datasheet + // so we are reusing it for nefarious purposes + NonMMUError = 3, + + MMUFaultDomainMask = 0xF0, + MMUFaultAddressMask = 0xFFFFFFFF00000000 + }; + + + + ARM710a() { + cp15_id = 0x41047100; + clearAllValues(); + } + virtual ~ARM710a() { } + + void clearAllValues() { + bank = MainBank; + CPSR = 0; + for (int i = 0; i < 16; i++) GPRs[i] = 0; + for (int i = 0; i < 5; i++) { + fiqBankedRegisters[0][i] = 0; + fiqBankedRegisters[1][i] = 0; + SPSRs[i] = 0; + } + for (int i = 0; i < 6; i++) { + allModesBankedRegisters[i][0] = 0; + allModesBankedRegisters[i][1] = 0; + } + + cp15_control = 0; + cp15_translationTableBase = 0; + cp15_domainAccessControl = 0; + cp15_faultStatus = 0; + cp15_faultAddress = 0; + prefetchCount = 0; + clearCache(); + flushTlb(); + } + + void setProcessorID(uint32_t v) { cp15_id = v; } + void requestFIQ(); // pull nFIQ low + void requestIRQ(); // pull nIRQ low + void reset(); // pull nRESET low + + uint32_t tick(); // run the chip for at least 1 clock cycle + + MaybeU32 readVirtualDebug(uint32_t virtAddr, ValueSize valueSize); + MaybeU32 virtToPhys(uint32_t virtAddr); + + pair readVirtual(uint32_t virtAddr, ValueSize valueSize); + virtual MaybeU32 readPhysical(uint32_t physAddr, ValueSize valueSize) = 0; + MMUFault writeVirtual(uint32_t value, uint32_t virtAddr, ARM710a::ValueSize valueSize); + virtual bool writePhysical(uint32_t value, uint32_t physAddr, ARM710a::ValueSize valueSize) = 0; + + uint32_t getGPR(int index) const { return GPRs[index]; } + +private: + enum { Nop = 0xE1A00000 }; + + enum Mode : uint8_t { + User32 = 0x10, + FIQ32 = 0x11, + IRQ32 = 0x12, + Supervisor32 = 0x13, + Abort32 = 0x17, + Undefined32 = 0x1B + }; + + enum BankIndex : uint8_t { + FiqBank, + IrqBank, + SvcBank, + AbtBank, + UndBank, + MainBank + }; + + constexpr static const BankIndex modeToBank[16] = { + MainBank, FiqBank, IrqBank, SvcBank, + MainBank, MainBank, MainBank, AbtBank, + MainBank, MainBank, MainBank, UndBank, + MainBank, MainBank, MainBank, MainBank + }; + + enum : uint32_t { + CPSR_ModeMask = 0x0000001F, + CPSR_FIQDisable = 0x00000040, + CPSR_IRQDisable = 0x00000080, + CPSR_V = 0x10000000, + CPSR_C = 0x20000000, + CPSR_Z = 0x40000000, + CPSR_N = 0x80000000, + CPSR_FlagMask = 0xF0000000 + }; + + // active state + BankIndex bank; + uint32_t CPSR; + uint32_t GPRs[16]; + + // saved state + uint32_t fiqBankedRegisters[2][5]; // R8..R12 inclusive + uint32_t allModesBankedRegisters[6][2]; // R13, R14 + uint32_t SPSRs[5]; + + // coprocessor 15 + uint32_t cp15_id; // 0: read-only + uint32_t cp15_control; // 1: write-only + uint32_t cp15_translationTableBase; // 2: write-only + uint32_t cp15_domainAccessControl; // 3: write-only + uint8_t cp15_faultStatus; // 5: read-only (writing has unrelated effects) + uint32_t cp15_faultAddress; // 6: read-only (writing has unrelated effects) + + bool flagV() const { return CPSR & CPSR_V; } + bool flagC() const { return CPSR & CPSR_C; } + bool flagZ() const { return CPSR & CPSR_Z; } + bool flagN() const { return CPSR & CPSR_N; } + bool checkCondition(int cond) const { + switch (cond) { + case 0: return flagZ(); + case 1: return !flagZ(); + case 2: return flagC(); + case 3: return !flagC(); + case 4: return flagN(); + case 5: return !flagN(); + case 6: return flagV(); + case 7: return !flagV(); + case 8: return flagC() && !flagZ(); + case 9: return !flagC() || flagZ(); + case 0xA: return flagN() == flagV(); + case 0xB: return flagN() != flagV(); + case 0xC: return !flagZ() && (flagN() == flagV()); + case 0xD: return flagZ() || (flagN() != flagV()); + case 0xE: return true; + /*case 0xF:*/ + default: return false; + } + } + + static Mode modeFromCPSR(uint32_t v) { return (Mode)(v & CPSR_ModeMask); } + Mode currentMode() const { return modeFromCPSR(CPSR); } + BankIndex currentBank() const { return modeToBank[(Mode)(CPSR & 0xF)]; } + bool isPrivileged() const { return (CPSR & 0x1F) > User32; } + bool isMMUEnabled() const { return (cp15_control & 1); } + bool isAlignmentFaultEnabled() const { return (cp15_control & 2); } + bool isCacheEnabled() const { return (cp15_control & 4); } + bool isWriteBufferEnabled() const { return (cp15_control & 8); } + + void switchMode(Mode mode); + void switchBank(BankIndex bank); + void raiseException(Mode mode, uint32_t savedPC, uint32_t newPC); + + // MMU/TLB + enum MMUFaultSorP : uint64_t { + SorPLinefetchError = 4, + SorPTranslationFault = 5, + SorPOtherBusError = 8, + SorPDomainFault = 9, + SorPPermissionFault = 0xD, + }; + + MMUFault encodeFault(MMUFault fault, int domain, uint32_t virtAddr) const { + return (MMUFault)(fault | (domain << 4) | ((uint64_t)virtAddr << 32)); + } + MMUFault encodeFaultSorP(MMUFaultSorP baseFault, bool isPage, int domain, uint32_t virtAddr) const { + return (MMUFault)(baseFault | (isPage ? 2 : 0) | (domain << 4) | ((uint64_t)virtAddr << 32)); + } + + enum { TlbSize = 64 }; + struct TlbEntry { uint32_t addrMask, addr, lv1Entry, lv2Entry; }; + TlbEntry tlb[TlbSize]; + int nextTlbIndex = 0; + + void flushTlb(); + void flushTlb(uint32_t virtAddr); + variant translateAddressUsingTlb(uint32_t virtAddr, TlbEntry *useMe=nullptr); + TlbEntry *_allocateTlbEntry(uint32_t addrMask, uint32_t addr); + + static uint32_t physAddrFromTlbEntry(TlbEntry *tlbEntry, uint32_t virtAddr); + MMUFault checkAccessPermissions(TlbEntry *entry, uint32_t virtAddr, bool isWrite) const; + + bool faultTriggeredThisCycle = false; + void reportFault(MMUFault fault); + + // Instruction/Data Cache + enum { + CacheSets = 4, + CacheBlocksPerSet = 128, + CacheBlockSize = 0x10, + + CacheAddressLineMask = 0x0000000F, + CacheAddressSetMask = 0x00000030, CacheAddressSetShift = 4, + CacheAddressTagMask = 0xFFFFFFC0, + + CacheBlockEnabled = 1 + }; + uint32_t cacheBlockTags[CacheSets][CacheBlocksPerSet]; + uint8_t cacheBlocks[CacheSets][CacheBlocksPerSet][CacheBlockSize]; + + void clearCache(); + uint8_t *findCacheLine(uint32_t virtAddr); + pair addCacheLineAndRead(uint32_t physAddr, uint32_t virtAddr, ValueSize valueSize, int domain, bool isPage); + MaybeU32 readCached(uint32_t virtAddr, ValueSize valueSize); + bool writeCached(uint32_t value, uint32_t virtAddr, ValueSize valueSize); + + // Instruction Loop + int prefetchCount; + uint32_t prefetch[2]; + MMUFault prefetchFaults[2]; + + uint32_t executeInstruction(uint32_t insn); + + uint32_t execDataProcessing(bool I, uint32_t Opcode, bool S, uint32_t Rn, uint32_t Rd, uint32_t Operand2); + uint32_t execMultiply(uint32_t AS, uint32_t Rd, uint32_t Rn, uint32_t Rs, uint32_t Rm); + uint32_t execSingleDataSwap(bool B, uint32_t Rn, uint32_t Rd, uint32_t Rm); + uint32_t execSingleDataTransfer(uint32_t IPUBWL, uint32_t Rn, uint32_t Rd, uint32_t offset); + uint32_t execBlockDataTransfer(uint32_t PUSWL, uint32_t Rn, uint32_t registerList); + uint32_t execBranch(bool L, uint32_t offset); + uint32_t execCP15RegisterTransfer(uint32_t CPOpc, bool L, uint32_t CRn, uint32_t Rd, uint32_t CP, uint32_t CRm); +}; diff --git a/WindCore/emu.cpp b/WindCore/emu.cpp index d815224..4234af9 100644 --- a/WindCore/emu.cpp +++ b/WindCore/emu.cpp @@ -2,6 +2,7 @@ #include "wind.h" #include "wind_hw.h" #include +#include "common.h" #define INCLUDE_BANK1 @@ -41,19 +42,19 @@ uint32_t Emu::readReg8(uint32_t reg) { } else if (reg == PDDDR) { return portDirections & 0xFF; } else { -// printf("RegRead8 unknown:: pc=%08x lr=%08x reg=%03x\n", cpu.gprs[ARM_PC]-4, cpu.gprs[ARM_LR], reg); +// printf("RegRead8 unknown:: pc=%08x lr=%08x reg=%03x\n", getGPR(15)-4, getGPR(14), reg); return 0xFF; } } uint32_t Emu::readReg32(uint32_t reg) { if (reg == LCDCTL) { - printf("LCD control read pc=%08x lr=%08x !!!\n", cpu.gprs[ARM_PC], cpu.gprs[ARM_LR]); + printf("LCD control read pc=%08x lr=%08x !!!\n", getGPR(15), getGPR(14)); return lcdControl; } else if (reg == LCDST) { - printf("LCD state read pc=%08x lr=%08x !!!\n", cpu.gprs[ARM_PC], cpu.gprs[ARM_LR]); + printf("LCD state read pc=%08x lr=%08x !!!\n", getGPR(15), getGPR(14)); return 0xFFFFFFFF; } else if (reg == PWRSR) { -// printf("!!! PWRSR read pc=%08x lr=%08x !!!\n", cpu.gprs[ARM_PC], cpu.gprs[ARM_LR]); +// printf("!!! PWRSR read pc=%08x lr=%08x !!!\n", getGPR(15), getGPR(14)); return pwrsr; } else if (reg == INTSR) { return pendingInterrupts & interruptMask; @@ -85,7 +86,7 @@ uint32_t Emu::readReg32(uint32_t reg) { } else if (reg == KSCAN) { return kScan; } else { -// printf("RegRead32 unknown:: pc=%08x lr=%08x reg=%03x\n", cpu.gprs[ARM_PC]-4, cpu.gprs[ARM_LR], reg); +// printf("RegRead32 unknown:: pc=%08x lr=%08x reg=%03x\n", getGPR(15)-4, getGPR(14), reg); return 0xFFFFFFFF; } } @@ -108,13 +109,13 @@ void Emu::writeReg8(uint32_t reg, uint8_t value) { uint32_t oldPorts = portValues; portValues &= 0xFF00FFFF; portValues |= (uint32_t)value << 16; - if ((portValues & 0x10000) && !(oldPorts & 0x10000)) - etna.setPromBit0High(); - else if (!(portValues & 0x10000) && (oldPorts & 0x10000)) - etna.setPromBit0Low(); - if ((portValues & 0x20000) && !(oldPorts & 0x20000)) - etna.setPromBit1High(); - diffPorts(oldPorts, portValues); + if ((portValues & 0x10000) && !(oldPorts & 0x10000)) + etna.setPromBit0High(); + else if (!(portValues & 0x10000) && (oldPorts & 0x10000)) + etna.setPromBit0Low(); + if ((portValues & 0x20000) && !(oldPorts & 0x20000)) + etna.setPromBit1High(); + diffPorts(oldPorts, portValues); } else if (reg == PCDR) { uint32_t oldPorts = portValues; portValues &= 0xFFFF00FF; @@ -140,7 +141,7 @@ void Emu::writeReg8(uint32_t reg, uint8_t value) { } else if (reg == KSCAN) { kScan = value; } else { -// printf("RegWrite8 unknown:: pc=%08x reg=%03x value=%02x\n", cpu.gprs[ARM_PC]-4, reg, value); +// printf("RegWrite8 unknown:: pc=%08x reg=%03x value=%02x\n", getGPR(15)-4, reg, value); } } void Emu::writeReg32(uint32_t reg, uint32_t value) { @@ -163,7 +164,7 @@ void Emu::writeReg32(uint32_t reg, uint32_t value) { // diffInterrupts(interruptMask, interruptMask &~ value); interruptMask &= ~value; } else if (reg == HALT) { - cpu.halted = true; + halted = true; // BLEOI = 0x410, // MCEOI = 0x414, } else if (reg == TEOI) { @@ -184,511 +185,280 @@ void Emu::writeReg32(uint32_t reg, uint32_t value) { } else if (reg == TC2EOI) { pendingInterrupts &= ~(1 << TC2OI); } else { -// printf("RegWrite32 unknown:: pc=%08x reg=%03x value=%08x\n", cpu.gprs[ARM_PC]-4, reg, value); +// printf("RegWrite32 unknown:: pc=%08x reg=%03x value=%08x\n", getGPR(15)-4, reg, value); } } bool Emu::isPhysAddressValid(uint32_t physAddress) const { - uint8_t region = (physAddress >> 24) & 0xF1; - switch (region) { - case 0: return true; - case 0x80: return (physAddress <= 0x80000FFF); - case 0xC0: return true; - case 0xC1: return true; - case 0xD0: return true; - case 0xD1: return true; - default: return false; - } + uint8_t region = (physAddress >> 24) & 0xF1; + switch (region) { + case 0: return true; + case 0x80: return (physAddress <= 0x80000FFF); + case 0xC0: return true; + case 0xC1: return true; + case 0xD0: return true; + case 0xD1: return true; + default: return false; + } } -uint32_t Emu::readPhys8(uint32_t physAddress) { - uint32_t result = 0xFF; - uint8_t region = (physAddress >> 24) & 0xF1; - if (region == 0) - result = ROM[physAddress & 0xFFFFFF]; - else if (region == 0x20 && physAddress <= 0x20000FFF) - result = etna.readReg8(physAddress & 0xFFF); - else if (region == 0x80 && physAddress <= 0x80000FFF) - result = readReg8(physAddress & 0xFFF); - else if (region == 0xC0) - result = MemoryBlockC0[physAddress & MemoryBlockMask]; + +MaybeU32 Emu::readPhysical(uint32_t physAddr, ValueSize valueSize) { + uint8_t region = (physAddr >> 24) & 0xF1; + if (valueSize == V8) { + if (region == 0) + return ROM[physAddr & 0xFFFFFF]; + else if (region == 0x20 && physAddr <= 0x20000FFF) + return etna.readReg8(physAddr & 0xFFF); + else if (region == 0x80 && physAddr <= 0x80000FFF) + return readReg8(physAddr & 0xFFF); + else if (region == 0xC0) + return MemoryBlockC0[physAddr & MemoryBlockMask]; #ifdef INCLUDE_BANK1 - else if (region == 0xC1) - result = MemoryBlockC1[physAddress & MemoryBlockMask]; + else if (region == 0xC1) + return MemoryBlockC1[physAddr & MemoryBlockMask]; #endif - else if (region == 0xD0) - result = MemoryBlockD0[physAddress & MemoryBlockMask]; + else if (region == 0xD0) + return MemoryBlockD0[physAddr & MemoryBlockMask]; #ifdef INCLUDE_BANK1 - else if (region == 0xD1) - result = MemoryBlockD1[physAddress & MemoryBlockMask]; + else if (region == 0xD1) + return MemoryBlockD1[physAddr & MemoryBlockMask]; #endif -// else -// printf("<%08x> unmapped read8 addr p:%08x\n", cpu.gprs[ARM_PC] - 4, physAddress); - return result; -} -uint32_t Emu::readPhys16(uint32_t physAddress) { - uint32_t result = 0xFFFFFFFF; - uint8_t region = (physAddress >> 24) & 0xF1; - if (region == 0) - LOAD_16LE(result, physAddress & 0xFFFFFF, ROM); - else if (region == 0xC0) - LOAD_16LE(result, physAddress & MemoryBlockMask, MemoryBlockC0); + } else { + uint32_t result; + if (region == 0) + LOAD_32LE(result, physAddr & 0xFFFFFF, ROM); + else if (region == 0x20 && physAddr <= 0x20000FFF) + result = etna.readReg32(physAddr & 0xFFF); + else if (region == 0x80 && physAddr <= 0x80000FFF) + result = readReg32(physAddr & 0xFFF); + else if (region == 0xC0) + LOAD_32LE(result, physAddr & MemoryBlockMask, MemoryBlockC0); #ifdef INCLUDE_BANK1 - else if (region == 0xC1) - LOAD_16LE(result, physAddress & MemoryBlockMask, MemoryBlockC1); + else if (region == 0xC1) + LOAD_32LE(result, physAddr & MemoryBlockMask, MemoryBlockC1); #endif - else if (region == 0xD0) - LOAD_16LE(result, physAddress & MemoryBlockMask, MemoryBlockD0); + else if (region == 0xD0) + LOAD_32LE(result, physAddr & MemoryBlockMask, MemoryBlockD0); #ifdef INCLUDE_BANK1 - else if (region == 0xD1) - LOAD_16LE(result, physAddress & MemoryBlockMask, MemoryBlockD1); + else if (region == 0xD1) + LOAD_32LE(result, physAddr & MemoryBlockMask, MemoryBlockD1); #endif -// else -// printf("<%08x> unmapped read16 addr p:%08x\n", cpu.gprs[ARM_PC] - 4, physAddress); - return result; -} -uint32_t Emu::readPhys32(uint32_t physAddress) { - uint32_t result = 0xFFFFFFFF; - uint8_t region = (physAddress >> 24) & 0xF1; - if (region == 0) - LOAD_32LE(result, physAddress & 0xFFFFFF, ROM); - else if (region == 0x20 && physAddress <= 0x20000FFF) - result = etna.readReg32(physAddress & 0xFFF); - else if (region == 0x80 && physAddress <= 0x80000FFF) - result = readReg32(physAddress & 0xFFF); - else if (region == 0xC0) - LOAD_32LE(result, physAddress & MemoryBlockMask, MemoryBlockC0); -#ifdef INCLUDE_BANK1 - else if (region == 0xC1) - LOAD_32LE(result, physAddress & MemoryBlockMask, MemoryBlockC1); -#endif - else if (region == 0xD0) - LOAD_32LE(result, physAddress & MemoryBlockMask, MemoryBlockD0); -#ifdef INCLUDE_BANK1 - else if (region == 0xD1) - LOAD_32LE(result, physAddress & MemoryBlockMask, MemoryBlockD1); -#endif -// else -// printf("<%08x> unmapped read32 addr p:%08x\n", cpu.gprs[ARM_PC] - 4, physAddress); - return result; + else + return {}; + return result; + } + + return {}; } -void Emu::writePhys8(uint32_t physAddress, uint8_t value) { - uint8_t region = (physAddress >> 24) & 0xF1; - if (region == 0xC0) - MemoryBlockC0[physAddress & MemoryBlockMask] = (uint8_t)value; +bool Emu::writePhysical(uint32_t value, uint32_t physAddr, ValueSize valueSize) { + uint8_t region = (physAddr >> 24) & 0xF1; + if (valueSize == V8) { + if (region == 0xC0) + MemoryBlockC0[physAddr & MemoryBlockMask] = (uint8_t)value; #ifdef INCLUDE_BANK1 - else if (region == 0xC1) - MemoryBlockC1[physAddress & MemoryBlockMask] = (uint8_t)value; + else if (region == 0xC1) + MemoryBlockC1[physAddr & MemoryBlockMask] = (uint8_t)value; #endif - else if (region == 0xD0) - MemoryBlockD0[physAddress & MemoryBlockMask] = (uint8_t)value; + else if (region == 0xD0) + MemoryBlockD0[physAddr & MemoryBlockMask] = (uint8_t)value; #ifdef INCLUDE_BANK1 - else if (region == 0xD1) - MemoryBlockD1[physAddress & MemoryBlockMask] = (uint8_t)value; + else if (region == 0xD1) + MemoryBlockD1[physAddr & MemoryBlockMask] = (uint8_t)value; #endif - else if (region == 0x20 && physAddress <= 0x20000FFF) - etna.writeReg8(physAddress & 0xFFF, value); - else if (region == 0x80 && physAddress <= 0x80000FFF) - writeReg8(physAddress & 0xFFF, value); -// else -// printf("<%08x> unmapped write8 addr p:%08x :: %02x\n", cpu.gprs[ARM_PC] - 4, physAddress, value); -} -void Emu::writePhys16(uint32_t physAddress, uint16_t value) { - uint8_t region = (physAddress >> 24) & 0xF1; - if (region == 0xC0) - STORE_16LE(value, physAddress & MemoryBlockMask, MemoryBlockC0); + else if (region == 0x20 && physAddr <= 0x20000FFF) + etna.writeReg8(physAddr & 0xFFF, value); + else if (region == 0x80 && physAddr <= 0x80000FFF) + writeReg8(physAddr & 0xFFF, value); + else + return false; + } else { + uint8_t region = (physAddr >> 24) & 0xF1; + if (region == 0xC0) + STORE_32LE(value, physAddr & MemoryBlockMask, MemoryBlockC0); #ifdef INCLUDE_BANK1 - else if (region == 0xC1) - STORE_16LE(value, physAddress & MemoryBlockMask, MemoryBlockC1); + else if (region == 0xC1) + STORE_32LE(value, physAddr & MemoryBlockMask, MemoryBlockC1); #endif - else if (region == 0xD0) - STORE_16LE(value, physAddress & MemoryBlockMask, MemoryBlockD0); + else if (region == 0xD0) + STORE_32LE(value, physAddr & MemoryBlockMask, MemoryBlockD0); #ifdef INCLUDE_BANK1 - else if (region == 0xD1) - STORE_16LE(value, physAddress & MemoryBlockMask, MemoryBlockD1); + else if (region == 0xD1) + STORE_32LE(value, physAddr & MemoryBlockMask, MemoryBlockD1); #endif -// else -// printf("<%08x> unmapped write16 addr p:%08x :: %04x\n", cpu.gprs[ARM_PC] - 4, physAddress, value); -} -void Emu::writePhys32(uint32_t physAddress, uint32_t value) { - uint8_t region = (physAddress >> 24) & 0xF1; - if (region == 0xC0) - STORE_32LE(value, physAddress & MemoryBlockMask, MemoryBlockC0); -#ifdef INCLUDE_BANK1 - else if (region == 0xC1) - STORE_32LE(value, physAddress & MemoryBlockMask, MemoryBlockC1); -#endif - else if (region == 0xD0) - STORE_32LE(value, physAddress & MemoryBlockMask, MemoryBlockD0); -#ifdef INCLUDE_BANK1 - else if (region == 0xD1) - STORE_32LE(value, physAddress & MemoryBlockMask, MemoryBlockD1); -#endif - else if (region == 0x20 && physAddress <= 0x20000FFF) - etna.writeReg32(physAddress & 0xFFF, value); - else if (region == 0x80 && physAddress <= 0x80000FFF) - writeReg32(physAddress & 0xFFF, value); -// else -// printf("<%08x> unmapped write32 addr p:%08x :: %08x\n", cpu.gprs[ARM_PC] - 4, physAddress, value); + else if (region == 0x20 && physAddr <= 0x20000FFF) + etna.writeReg32(physAddr & 0xFFF, value); + else if (region == 0x80 && physAddr <= 0x80000FFF) + writeReg32(physAddr & 0xFFF, value); + else + return false; + } + return true; } -uint32_t Emu::virtToPhys(uint32_t virtAddress) { - if (!isMMU()) - return virtAddress; - - // find the TTB - uint32_t ttbEntryAddr = translationTableBase & 0xFFFFC000; - ttbEntryAddr |= ((virtAddress & 0xFFF00000) >> 18); - uint32_t ttbEntry = readPhys32(ttbEntryAddr); - - if ((ttbEntry & 3) == 1) { - // Page - uint32_t pageTableAddr = ttbEntry & 0xFFFFFC00; - pageTableAddr |= ((virtAddress & 0x000FF000) >> 10); - uint32_t pageTableEntry = readPhys32(pageTableAddr); - if ((pageTableEntry & 3) == 1) { - // Large Page - uint32_t lpBaseAddr = pageTableEntry & 0xFFFF0000; - return lpBaseAddr | (virtAddress & 0x0000FFFF); - } else if ((pageTableEntry & 3) == 2) { - // Small Page - uint32_t lpBaseAddr = pageTableEntry & 0xFFFFF000; - return lpBaseAddr | (virtAddress & 0x00000FFF); - } else { - // Fault/Reserved - // TODO: should raise Abort here? - printf("!!! lv2 bad entry=%d vaddr=%08x !!!\n", pageTableEntry & 3, virtAddress); - return 0xFFFFFFFF; - } - } else if ((ttbEntry & 3) == 2) { - // Section - uint32_t sectBaseAddr = ttbEntry & 0xFFF00000; - return sectBaseAddr | (virtAddress & 0x000FFFFF); - } else { - // Fault/Reserved - // TODO: should raise Abort here? - printf("!!! lv1 bad entry=%d vaddr=%08x !!!\n", ttbEntry & 3, virtAddress); - return 0xFFFFFFFF; - } -} void Emu::configure() { - if (configured) return; - configured = true; + if (configured) return; + configured = true; - uart1.cpu = &cpu; - uart2.cpu = &cpu; - memset(&tc1, 0, sizeof(tc1)); - memset(&tc2, 0, sizeof(tc1)); - cpu.owner = this; + uart1.cpu = this; + uart2.cpu = this; + memset(&tc1, 0, sizeof(tc1)); + memset(&tc2, 0, sizeof(tc1)); - nextTickAt = TICK_INTERVAL; - rtc = getRTC(); + nextTickAt = TICK_INTERVAL; + rtc = getRTC(); - configureMemoryBindings(); - configureCpuHandlers(); - - ARMReset(&cpu); -} - -void Emu::configureMemoryBindings() { - cpu.memory.load8 = [](struct ARMCore *cpu, uint32_t address, int *) { - return ((Emu *)cpu->owner)->readVirt8(address); - }; - cpu.memory.load16 = [](struct ARMCore *cpu, uint32_t address, int *) { - return ((Emu *)cpu->owner)->readVirt16(address); - }; - cpu.memory.load32 = [](struct ARMCore *cpu, uint32_t address, int *) { - return ((Emu *)cpu->owner)->readVirt32(address); - }; - cpu.memory.loadMultiple = [](struct ARMCore *cpu, uint32_t address, int mask, enum LSMDirection direction, int *cycleCounter) { - uint32_t value; - int i, offset = 4, popcount = 0; - - if (direction & LSM_D) { - offset = -4; - popcount = popcount32(mask); - address -= (popcount << 2) - 4; - } - if (direction & LSM_B) - address += offset; - - if (!mask) { - value = cpu->memory.load32(cpu, address, cycleCounter); - cpu->gprs[ARM_PC] = value; - address += 64; - } - for (i = 0; i < 16; i++) { - if (mask & (1 << i)) { - value = cpu->memory.load32(cpu, address, cycleCounter); - cpu->gprs[i] = value; - address += 4; - } - } - - if (direction & LSM_B) - address -= offset; - if (direction & LSM_D) - address -= (popcount << 2) + 4; - - return address; - }; - - cpu.memory.store8 = [](struct ARMCore *cpu, uint32_t address, int8_t value, int *) { - ((Emu *)cpu->owner)->writeVirt8(address, value); - }; - cpu.memory.store16 = [](struct ARMCore *cpu, uint32_t address, int16_t value, int *) { - ((Emu *)cpu->owner)->writeVirt16(address, value); - }; - cpu.memory.store32 = [](struct ARMCore *cpu, uint32_t address, int32_t value, int *) { - ((Emu *)cpu->owner)->writeVirt32(address, value); - }; - cpu.memory.storeMultiple = [](struct ARMCore *cpu, uint32_t address, int mask, enum LSMDirection direction, int *cycleCounter) { - uint32_t value; - int i, offset = 4, popcount = 0; - - if (direction & LSM_D) { - offset = -4; - popcount = popcount32(mask); - address -= (popcount << 2) - 4; - } - if (direction & LSM_B) - address += offset; - - if (!mask) { - value = cpu->gprs[ARM_PC] + 4; - cpu->memory.store32(cpu, address, value, cycleCounter); - address += 64; - } - for (i = 0; i < 16; i++) { - if (mask & (1 << i)) { - value = cpu->gprs[i]; - if (i == ARM_PC) value += 4; - cpu->memory.store32(cpu, address, value, cycleCounter); - address += 4; - } - } - - if (direction & LSM_B) - address -= offset; - if (direction & LSM_D) - address -= (popcount << 2) + 4; - - return address; - }; - - cpu.memory.activeSeqCycles32 = 0; - cpu.memory.activeNonseqCycles32 = 0; - cpu.memory.stall = [](struct ARMCore *cpu, int32_t wait) { - return 0; - }; -} - -void Emu::configureCpuHandlers() { - cpu.irqh.reset = [](struct ARMCore *cpu) { - printf("reset...\n"); - }; - cpu.irqh.processEvents = [](struct ARMCore *cpu) { - // printf("processEvents...\n"); - }; - cpu.irqh.swi32 = [](struct ARMCore *cpu, int immediate) { - ARMRaiseSWI(cpu); - }; - cpu.irqh.hitIllegal = [](struct ARMCore *cpu, uint32_t opcode) { - printf("hitIllegal... %08x\n", opcode); - }; - cpu.irqh.bkpt32 = [](struct ARMCore *cpu, int immediate) { - printf("bkpt32... %08x\n", immediate); - }; - cpu.irqh.readCPSR = [](struct ARMCore *cpu) { - // printf("readCPSR...\n"); - // printf("at %08x our priv became %s\n", cpu->gprs[ARM_PC]-4, privname(cpu)); - }; - cpu.irqh.hitStub = [](struct ARMCore *cpu, uint32_t opcode) { - Emu *emu = (Emu *)cpu->owner; - if ((opcode & 0x0F100F10) == 0x0E100F10) { - // coprocessor read - int cpReg = (opcode & 0x000F0000) >> 16; - int armReg = (opcode & 0x0000F000) >> 12; - if (cpReg == 0) - cpu->gprs[armReg] = 0x41807100; //5mx device id - } else if ((opcode & 0x0F100F10) == 0x0E000F10) { - // coprocessor write - int cpReg = (opcode & 0x000F0000) >> 16; - int armReg = (opcode & 0x0000F000) >> 12; - if (cpReg == 1) { - emu->controlReg = cpu->gprs[armReg]; - printf("mmu is now %s\n", emu->isMMU() ? "on" : "off"); - } else if (cpReg == 2) { - emu->translationTableBase = cpu->gprs[armReg]; - } else if (cpReg == 3) { - emu->domainAccessControl = cpu->gprs[armReg]; - } - } else { - printf("hitStub... %08x\n", opcode); - } - }; + reset(); } void Emu::loadROM(const char *path) { - FILE *f = fopen(path, "rb"); - fread(ROM, 1, sizeof(ROM), f); - fclose(f); + FILE *f = fopen(path, "rb"); + fread(ROM, 1, sizeof(ROM), f); + fclose(f); } void Emu::executeUntil(int64_t cycles) { - if (!configured) - configure(); + if (!configured) + configure(); - while (!asleep && cpu.cycles < cycles) { - if (cpu.cycles >= nextTickAt) { - // increment RTCDIV - if ((pwrsr & 0x3F) == 0x3F) { - rtc++; - pwrsr &= ~0x3F; - } else { - pwrsr++; - } + while (!asleep && passedCycles < cycles) { + if (passedCycles >= nextTickAt) { + // increment RTCDIV + if ((pwrsr & 0x3F) == 0x3F) { + rtc++; + pwrsr &= ~0x3F; + } else { + pwrsr++; + } - nextTickAt += TICK_INTERVAL; - pendingInterrupts |= (1<= 30000000) { -// static bool lcdtest = false; -// if (!lcdtest) { -// printf("lcdtest\n"); -// pendingInterrupts |= (1<"); - return; - } - int size = readVirt32(str); - for (int i = 0; i < size; i++) { - buf[i] = readVirt8(str + 4 + i); - } - buf[size] = 0; + if (str == 0) { + strcpy(buf, ""); + return; + } + int size = readVirtualDebug(str, V32).value(); + for (int i = 0; i < size; i++) { + buf[i] = readVirtualDebug(str + 4 + i, V8).value(); + } + buf[size] = 0; } void Emu::fetchName(uint32_t obj, char *buf) { - fetchStr(readVirt32(obj + 0x10), buf); + fetchStr(readVirtualDebug(obj + 0x10, V32).value(), buf); } void Emu::fetchProcessFilename(uint32_t obj, char *buf) { - fetchStr(readVirt32(obj + 0x3C), buf); + fetchStr(readVirtualDebug(obj + 0x3C, V32).value(), buf); } void Emu::debugPC(uint32_t pc) { - char objName[1000]; - if (pc == 0x2CBC4) { - // CObjectCon::AddL() - uint32_t container = cpu.gprs[0]; - uint32_t obj = cpu.gprs[1]; - const char *wut = identifyObjectCon(container); - if (wut) { - fetchName(obj, objName); - printf("OBJS: added %s at %08x <%s>", wut, obj, objName); - if (strcmp(wut, "process") == 0) { - fetchProcessFilename(obj, objName); - printf(" <%s>", objName); - } - printf("\n"); - } - } + char objName[1000]; + if (pc == 0x2CBC4) { + // CObjectCon::AddL() + uint32_t container = getGPR(0); + uint32_t obj = getGPR(1); + const char *wut = identifyObjectCon(container); + if (wut) { + fetchName(obj, objName); + printf("OBJS: added %s at %08x <%s>", wut, obj, objName); + if (strcmp(wut, "process") == 0) { + fetchProcessFilename(obj, objName); + printf(" <%s>", objName); + } + printf("\n"); + } + } } const uint8_t *Emu::getLCDBuffer() const { - if ((lcdAddress >> 24) == 0xC0) - return &MemoryBlockC0[lcdAddress & MemoryBlockMask]; - else - return nullptr; + if ((lcdAddress >> 24) == 0xC0) + return &MemoryBlockC0[lcdAddress & MemoryBlockMask]; + else + return nullptr; } uint8_t Emu::readKeyboard() { - uint8_t val = 0; - if (kScan & 8) { - // Select one keyboard - int whichColumn = kScan & 7; - for (int i = 0; i < 7; i++) - if (keyboardKeys[whichColumn * 7 + i]) - val |= (1 << i); - } else if (kScan == 0) { - // Report all columns combined - // EPOC's keyboard driver relies on this... - for (int i = 0; i < 8*7; i++) - if (keyboardKeys[i]) - val |= (1 << (i % 7)); - } - return val; + uint8_t val = 0; + if (kScan & 8) { + // Select one keyboard + int whichColumn = kScan & 7; + for (int i = 0; i < 7; i++) + if (keyboardKeys[whichColumn * 7 + i]) + val |= (1 << i); + } else if (kScan == 0) { + // Report all columns combined + // EPOC's keyboard driver relies on this... + for (int i = 0; i < 8*7; i++) + if (keyboardKeys[i]) + val |= (1 << (i % 7)); + } + return val; } diff --git a/WindCore/emu.h b/WindCore/emu.h index 3a97c4c..05bb5e2 100644 --- a/WindCore/emu.h +++ b/WindCore/emu.h @@ -1,10 +1,10 @@ #pragma once -#include "arm.h" +#include "arm710a.h" #include "wind_hw.h" #include "etna.h" #include -class Emu { +class Emu : public ARM710a { public: uint8_t ROM[0x1000000]; uint8_t MemoryBlockC0[0x800000]; @@ -14,9 +14,6 @@ public: enum { MemoryBlockMask = 0x7FFFFF }; private: - uint32_t controlReg; - uint32_t translationTableBase; - uint32_t domainAccessControl; uint16_t pendingInterrupts = 0; uint16_t interruptMask = 0; uint32_t portValues = 0; @@ -27,19 +24,14 @@ private: uint32_t kScan = 0; uint32_t rtc = 0; + int64_t passedCycles = 0; int64_t nextTickAt = 0; Timer tc1, tc2; UART uart1, uart2; - Etna etna; - bool asleep = false; + Etna etna; + bool halted = false, asleep = false; - std::unordered_set _breakpoints; - - struct ARMCore cpu; - - inline bool isMMU() { - return (controlReg & 1); - } + std::unordered_set _breakpoints; uint32_t getRTC(); @@ -49,31 +41,15 @@ private: void writeReg32(uint32_t reg, uint32_t value); public: - bool isPhysAddressValid(uint32_t physAddress) const; - - uint32_t readPhys8(uint32_t physAddress); - uint32_t readPhys16(uint32_t physAddress); - uint32_t readPhys32(uint32_t physAddress); - void writePhys8(uint32_t physAddress, uint8_t value); - void writePhys16(uint32_t physAddress, uint16_t value); - void writePhys32(uint32_t physAddress, uint32_t value); - - uint32_t virtToPhys(uint32_t virtAddress); - - uint32_t readVirt8(uint32_t virtAddress) { return readPhys8(virtToPhys(virtAddress)); } - uint32_t readVirt16(uint32_t virtAddress) { return readPhys16(virtToPhys(virtAddress)); } - uint32_t readVirt32(uint32_t virtAddress) { return readPhys32(virtToPhys(virtAddress)); } - void writeVirt8(uint32_t virtAddress, uint8_t value) { writePhys8(virtToPhys(virtAddress), value); } - void writeVirt16(uint32_t virtAddress, uint16_t value) { writePhys16(virtToPhys(virtAddress), value); } - void writeVirt32(uint32_t virtAddress, uint32_t value) { writePhys32(virtToPhys(virtAddress), value); } + bool isPhysAddressValid(uint32_t addr) const; + MaybeU32 readPhysical(uint32_t physAddr, ValueSize valueSize) override; + bool writePhysical(uint32_t value, uint32_t physAddr, ValueSize valueSize) override; const uint8_t *getLCDBuffer() const; private: bool configured = false; void configure(); - void configureMemoryBindings(); - void configureCpuHandlers(); void printRegs(); const char *identifyObjectCon(uint32_t ptr); @@ -90,8 +66,7 @@ public: Emu(); void loadROM(const char *path); void dumpRAM(const char *path); - void executeUntil(int64_t cycles); - int64_t currentCycles() const { return cpu.cycles; } - uint32_t getGPR(int index) const { return cpu.gprs[index]; } - std::unordered_set &breakpoints() { return _breakpoints; } + void executeUntil(int64_t cycles); + std::unordered_set &breakpoints() { return _breakpoints; } + uint64_t currentCycles() const { return passedCycles; } }; diff --git a/WindCore/wind_hw.h b/WindCore/wind_hw.h index 6ff4624..cf0c95c 100644 --- a/WindCore/wind_hw.h +++ b/WindCore/wind_hw.h @@ -1,10 +1,10 @@ #pragma once #include "wind.h" -#include "arm.h" +#include "arm710a.h" #include struct Timer { - struct ARMCore *cpu; + ARM710a *cpu; enum { TICK_INTERVAL_SLOW = CLOCK_SPEED / 2000, @@ -50,7 +50,7 @@ struct Timer { }; struct UART { - struct ARMCore *cpu; + ARM710a *cpu; enum { IntRx = 1, @@ -105,7 +105,7 @@ struct UART { // UART0INTM? // UART0INTR? } else { - printf("unhandled 8bit uart read %x at pc=%08x lr=%08x\n", reg, cpu->gprs[ARM_PC], cpu->gprs[ARM_LR]); + printf("unhandled 8bit uart read %x at pc=%08x lr=%08x\n", reg, cpu->getGPR(15), cpu->getGPR(14)); return 0xFF; } } @@ -118,7 +118,7 @@ struct UART { // we pretend we are never busy, never have full fifo return FlagReceiveFifoEmpty; } else { - printf("unhandled 32bit uart read %x at pc=%08x lr=%08x\n", reg, cpu->gprs[ARM_PC], cpu->gprs[ARM_LR]); + printf("unhandled 32bit uart read %x at pc=%08x lr=%08x\n", reg, cpu->getGPR(15), cpu->getGPR(14)); return 0xFFFFFFFF; } } @@ -132,7 +132,7 @@ struct UART { printf("uart interruptmask updated: %d\n", value); // UART0INTR? } else { - printf("unhandled 8bit uart write %x value %02x at pc=%08x lr=%08x\n", reg, value, cpu->gprs[ARM_PC], cpu->gprs[ARM_LR]); + printf("unhandled 8bit uart write %x value %02x at pc=%08x lr=%08x\n", reg, value, cpu->getGPR(15), cpu->getGPR(14)); } } void writeReg32(uint32_t reg, uint32_t value) { @@ -151,7 +151,7 @@ struct UART { printf("uart interrupts %x -> %x\n", interrupts, value); interrupts = value; } else { - printf("unhandled 32bit uart write %x value %08x at pc=%08x lr=%08x\n", reg, value, cpu->gprs[ARM_PC], cpu->gprs[ARM_LR]); + printf("unhandled 32bit uart write %x value %08x at pc=%08x lr=%08x\n", reg, value, cpu->getGPR(15), cpu->getGPR(14)); } } }; diff --git a/WindQt/WindQt.pro b/WindQt/WindQt.pro index 5612e6a..aae565c 100644 --- a/WindQt/WindQt.pro +++ b/WindQt/WindQt.pro @@ -20,7 +20,8 @@ DEFINES += QT_DEPRECATED_WARNINGS # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 -CONFIG += c++11 +CONFIG += c++17 +QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.14 SOURCES += \ main.cpp \ diff --git a/WindQt/mainwindow.cpp b/WindQt/mainwindow.cpp index b273ab9..36a385e 100644 --- a/WindQt/mainwindow.cpp +++ b/WindQt/mainwindow.cpp @@ -12,13 +12,13 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); emu = new Emu; - emu->loadROM("/Users/ash/src/psion/Sys$rom.bin"); + emu->loadROM("/Users/ash/src/psion/Sys$rom.bin"); timer = new QTimer(this); timer->setInterval(1000/64); connect(timer, SIGNAL(timeout()), SLOT(execTimer())); - updateScreen(); + updateScreen(); } MainWindow::~MainWindow() @@ -30,10 +30,10 @@ void MainWindow::updateScreen() { ui->cycleCounter->setText(QString("Cycles: %1").arg(emu->currentCycles())); - updateMemory(); + updateMemory(); ui->regsLabel->setText( - QString("R0: %1 / R1: %2 / R2: %3 / R3: %4 / R4: %5 / R5: %6 / R6: %7 / R7: %8\nR8: %9 / R9: %10 / R10:%11 / R11:%12 / R12:%13 / SP: %14 / LR: %15 / PC: %16") + QString("R0: %1 / R1: %2 / R2: %3 / R3: %4 / R4: %5 / R5: %6 / R6: %7 / R7: %8\nR8: %9 / R9: %10 / R10:%11 / R11:%12 / R12:%13 / SP: %14 / LR: %15 / PC: %16") .arg(emu->getGPR(0), 8, 16) .arg(emu->getGPR(1), 8, 16) .arg(emu->getGPR(2), 8, 16) @@ -56,24 +56,27 @@ void MainWindow::updateScreen() const int context = 8 * 4; uint32_t pc = emu->getGPR(15) - 4; uint32_t minCode = pc - context; - if (minCode >= (UINT32_MAX - context)) + if (minCode >= (UINT32_MAX - context)) minCode = 0; uint32_t maxCode = pc + context; - if (maxCode < context) + if (maxCode < context) maxCode = UINT32_MAX; - QStringList codeLines; - for (uint32_t addr = minCode; addr >= minCode && addr <= maxCode; addr += 4) { - const char *prefix = (addr == pc) ? "==>" : " "; + QStringList codeLines; + for (uint32_t addr = minCode; addr >= minCode && addr <= maxCode; addr += 4) { + const char *prefix = (addr == pc) ? "==>" : " "; struct ARMInstructionInfo info; char buffer[512]; - uint32_t opcode = emu->readVirt32(addr); - ARMDecodeARM(opcode, &info); - ARMDisassemble(&info, addr, buffer, sizeof(buffer)); - codeLines.append(QString("%1 %2 | %3 | %4").arg(prefix).arg(addr, 8, 16).arg(opcode, 8, 16).arg(buffer)); + auto result = emu->readVirtual(addr, ARM710a::V32); + if (result.first.has_value()) { + uint32_t opcode = result.first.value(); + ARMDecodeARM(opcode, &info); + ARMDisassemble(&info, addr, buffer, sizeof(buffer)); + codeLines.append(QString("%1 %2 | %3 | %4").arg(prefix).arg(addr, 8, 16).arg(opcode, 8, 16).arg(buffer)); + } } - ui->codeLabel->setText(codeLines.join('\n')); + ui->codeLabel->setText(codeLines.join('\n')); // now, the actual screen const uint8_t *lcdBuf = emu->getLCDBuffer(); @@ -95,12 +98,12 @@ void MainWindow::updateScreen() for (int x = 0; x < img.width(); x++) { uint8_t byte = lcdBuf[lineOffs + (x / ppb)]; int shift = (x & (ppb - 1)) * bpp; - int mask = (1 << bpp) - 1; + int mask = (1 << bpp) - 1; int palIdx = (byte >> shift) & mask; int palValue = palette[palIdx]; - palValue |= (palValue << 4); - scanline[x] = palValue ^ 0xFF; + palValue |= (palValue << 4); + scanline[x] = palValue ^ 0xFF; } } @@ -198,7 +201,7 @@ void MainWindow::keyPressEvent(QKeyEvent *event) void MainWindow::keyReleaseEvent(QKeyEvent *event) { int k = resolveKey(event->key()); - if (k >= 0) + if (k >= 0) emu->keyboardKeys[k] = false; } @@ -243,71 +246,72 @@ void MainWindow::execTimer() void MainWindow::on_addBreakButton_clicked() { - uint32_t addr = ui->breakpointAddress->text().toUInt(nullptr, 16); - emu->breakpoints().insert(addr); - updateBreakpointsList(); + uint32_t addr = ui->breakpointAddress->text().toUInt(nullptr, 16); + emu->breakpoints().insert(addr); + updateBreakpointsList(); } void MainWindow::on_removeBreakButton_clicked() { - uint32_t addr = ui->breakpointAddress->text().toUInt(nullptr, 16); - emu->breakpoints().erase(addr); - updateBreakpointsList(); + uint32_t addr = ui->breakpointAddress->text().toUInt(nullptr, 16); + emu->breakpoints().erase(addr); + updateBreakpointsList(); } void MainWindow::updateBreakpointsList() { - ui->breakpointsList->clear(); - for (uint32_t addr : emu->breakpoints()) { - ui->breakpointsList->addItem(QString::number(addr, 16)); - } + ui->breakpointsList->clear(); + for (uint32_t addr : emu->breakpoints()) { + ui->breakpointsList->addItem(QString::number(addr, 16)); + } } void MainWindow::on_memoryViewAddress_textEdited(const QString &) { - updateMemory(); + updateMemory(); } void MainWindow::updateMemory() { - uint32_t virtBase = ui->memoryViewAddress->text().toUInt(nullptr, 16) & ~0xFF; - uint32_t physBase = emu->virtToPhys(virtBase); - bool ok = (physBase != 0xFFFFFFFF) && emu->isPhysAddressValid(physBase); - if (ok && (virtBase != physBase)) - ui->physicalAddressLabel->setText(QStringLiteral("Physical: %1").arg(physBase, 8, 16, QLatin1Char('0'))); + uint32_t virtBase = ui->memoryViewAddress->text().toUInt(nullptr, 16) & ~0xFF; + auto physBaseOpt = emu->virtToPhys(virtBase); + auto physBase = physBaseOpt.value_or(0xFFFFFFFF); + bool ok = physBaseOpt.has_value() && emu->isPhysAddressValid(physBase); + if (ok && (virtBase != physBase)) + ui->physicalAddressLabel->setText(QStringLiteral("Physical: %1").arg(physBase, 8, 16, QLatin1Char('0'))); - uint8_t block[0x100]; - if (ok) { - for (int i = 0; i < 0x100; i++) { - block[i] = emu->readPhys8(physBase + i); - } - } + uint8_t block[0x100]; + if (ok) { + for (int i = 0; i < 0x100; i++) { + block[i] = emu->readPhysical(physBase + i, ARM710a::V8).value(); + } + } - QStringList output; - for (int row = 0; row < 16; row++) { - QString outLine; - outLine.reserve(8 + 2 + (2 * 16) + 3 + 16); - outLine.append(QStringLiteral("%1 |").arg(virtBase + (row * 16), 8, 16)); - for (int col = 0; col < 16; col++) { - if (ok) - outLine.append(QStringLiteral(" %1").arg(block[row*16+col], 2, 16, QLatin1Char('0'))); - else - outLine.append(QStringLiteral(" ??")); - } - outLine.append(QStringLiteral(" | ")); - for (int col = 0; col < 16; col++) { - uint8_t byte = block[row*16+col]; - if (!ok) - outLine.append('?'); - else if (byte >= 0x20 && byte <= 0x7E) - outLine.append(byte); - else - outLine.append('.'); - } - output.append(outLine); - } + QStringList output; + for (int row = 0; row < 16; row++) { + QString outLine; + outLine.reserve(8 + 2 + (2 * 16) + 3 + 16); + outLine.append(QStringLiteral("%1 |").arg(virtBase + (row * 16), 8, 16)); + for (int col = 0; col < 16; col++) { + if (ok) + outLine.append(QStringLiteral(" %1").arg(block[row*16+col], 2, 16, QLatin1Char('0'))); + else + outLine.append(QStringLiteral(" ??")); + } + outLine.append(QStringLiteral(" | ")); + for (int col = 0; col < 16; col++) { + uint8_t byte = block[row*16+col]; + if (!ok) + outLine.append('?'); + else if (byte >= 0x20 && byte <= 0x7E) + outLine.append(byte); + else + outLine.append('.'); + } + output.append(outLine); + } - ui->memoryViewLabel->setText(output.join('\n')); + ui->memoryViewLabel->setText(output.join('\n')); } void MainWindow::on_memoryAdd1_clicked() { adjustMemoryAddress(1); } @@ -320,24 +324,24 @@ void MainWindow::on_memorySub10_clicked() { adjustMemoryAddress(-0x10); } void MainWindow::on_memorySub100_clicked() { adjustMemoryAddress(-0x100); } void MainWindow::adjustMemoryAddress(int offset) { - uint32_t address = ui->memoryViewAddress->text().toUInt(nullptr, 16); - address += offset; - ui->memoryViewAddress->setText(QString("%1").arg(address, 8, 16, QLatin1Char('0'))); - updateMemory(); + uint32_t address = ui->memoryViewAddress->text().toUInt(nullptr, 16); + address += offset; + ui->memoryViewAddress->setText(QString("%1").arg(address, 8, 16, QLatin1Char('0'))); + updateMemory(); } void MainWindow::on_writeByteButton_clicked() { - uint32_t address = ui->memoryViewAddress->text().toUInt(nullptr, 16); - uint8_t value = (uint8_t)ui->memoryWriteValue->text().toUInt(nullptr, 16); - emu->writeVirt8(address, value); - updateMemory(); + uint32_t address = ui->memoryViewAddress->text().toUInt(nullptr, 16); + uint8_t value = (uint8_t)ui->memoryWriteValue->text().toUInt(nullptr, 16); + emu->writeVirtual(value, address, ARM710a::V8); + updateMemory(); } void MainWindow::on_writeDwordButton_clicked() { - uint32_t address = ui->memoryViewAddress->text().toUInt(nullptr, 16); - uint32_t value = ui->memoryWriteValue->text().toUInt(nullptr, 16); - emu->writeVirt32(address, value); - updateMemory(); + uint32_t address = ui->memoryViewAddress->text().toUInt(nullptr, 16); + uint32_t value = ui->memoryWriteValue->text().toUInt(nullptr, 16); + emu->writeVirtual(value, address, ARM710a::V32); + updateMemory(); }