#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 log("prefetch error!"); 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; // log("executing insn %08x @ %08x", i, GPRs[15] - 0xC); // 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; } if (extract(Operand2, 4, 4) && (shiftBy == 0)) { // register shift by 0 never does anything shifterCarryOutput = flagC(); } else { 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); 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 = (uint64_t)(a) + (uint64_t)(b) + (uint64_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; // log("CPSR setflags=%08x results in CPSR=%08x", flags, CPSR); } else if (Opcode == 8) { // MRS, CPSR -> Reg GPRs[Rd] = CPSR; log("r%d <- CPSR(%08x)", Rd, GPRs[Rd]); } 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; log("CPSR change privileged: %08x", CPSR); } 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); log("CPSR change unprivileged: new=%08x result=%08x", newFlag, CPSR); } } else if (Opcode == 0xA) { // MRS, SPSR -> Reg if (isPrivileged()) { GPRs[Rd] = SPSRs[currentBank()]; log("r%d <- SPSR(%08x)", Rd, GPRs[Rd]); } } else /*if (Opcode == 0xB)*/ { bool canChangeMode = extract1(Rn, 0); if (isPrivileged()) { if (canChangeMode) { SPSRs[currentBank()] = GPRs[extract(Operand2, 3, 0)]; log("SPSR change privileged: %08x", SPSRs[currentBank()]); } else { // same hat auto newFlag = I ? op2 : GPRs[extract(Operand2, 3, 0)]; SPSRs[currentBank()] &= ~CPSR_FlagMask; SPSRs[currentBank()] |= (newFlag & CPSR_FlagMask); log("SPSR change unprivileged: new=%08x result=%08x", newFlag, SPSRs[currentBank()]); } } } } 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; log("dataproc restore CPSR: %08x", CPSR); } } else if (S) { CPSR = (CPSR & ~CPSR_FlagMask) | flags; // log("dataproc flag change: flags=%08x CPSR=%08x", flags, CPSR); } } 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 // No rotation or anything here calcOffset = offset; } 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(); if (Rd == 15) prefetchCount = 0; } 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; } } } if (registerList & 0x8000) prefetchCount = 0; // 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(), encodeFault(NonMMUError, 0, virtAddr)); } 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 encodeFault(NonMMUError, 0, virtAddr); } 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 & (MMUFaultTypeMask | MMUFaultDomainMask); cp15_faultAddress = fault >> MMUFaultAddressShift; } static const char *faultTypes[] = { "NoFault", "AlignmentFault", "???", "NonMMUError", "SectionLinefetchError", "SectionTranslationFault", "PageLinefetchError", "PageTranslationFault", "SectionOtherBusError", "SectionDomainFault", "PageOtherBusError", "PageDomainFault", "Lv1TranslationError", "SectionPermissionFault", "Lv2TranslationError", "PagePermissionFault" }; log("⚠️ Fault type=%s domain=%d address=%08x pc=%08x lr=%08x", faultTypes[fault & MMUFaultTypeMask], (fault & MMUFaultDomainMask) >> MMUFaultDomainShift, fault >> MMUFaultAddressShift, GPRs[15], GPRs[14]); // this signals a branch to DataAbort after the // instruction is done executing faultTriggeredThisCycle = true; } } void ARM710a::log(const char *format, ...) { if (logger) { char buffer[1024]; va_list vaList; va_start(vaList, format); vsnprintf(buffer, sizeof(buffer), format, vaList); va_end(vaList); logger(buffer); } } void ARM710a::test() { uint64_t result; uint32_t flags = 0; uint32_t v = 0x10000000; SUB_OP(v, v, 1); log("RESULT:%llx FLAGS:%08x", result, flags); }