#include "mmu.h" #include "hw/mem/addrspace.h" #include "hw/sh4/sh4_if.h" #include "hw/sh4/sh4_interrupts.h" #include "hw/sh4/sh4_core.h" #include "debug/gdb_server.h" #include "serialize.h" TLB_Entry UTLB[64]; TLB_Entry ITLB[4]; static u32 ITLB_LRU_USE[64]; bool mmuOn; //SQ fast remap , mainly hackish , assumes 1MB pages //max 64MB can be remapped on SQ // Used when FullMMU is off u32 sq_remap[64]; /* MMU support code This is mostly hacked-on as the core was never meant to have mmu support There are two modes, one with 'full' mmu emulation (for wince/bleem/wtfever) and a fast-hack mode for 1mb sqremaps (for katana) */ #ifndef FAST_MMU #include "ccn.h" #endif #include "hw/sh4/sh4_mem.h" //#define TRACE_WINCE_SYSCALLS #ifdef TRACE_WINCE_SYSCALLS #include "wince.h" u32 unresolved_ascii_string; u32 unresolved_unicode_string; #endif #define printf_mmu(...) DEBUG_LOG(SH4, __VA_ARGS__) constexpr u32 ITLB_LRU_OR[4] = { 0x00,//000xxx 0x20,//1xx00x 0x14,//x1x1x0 0x0B,//xx1x11 }; constexpr u32 ITLB_LRU_AND[4] = { 0x07,//000xxx 0x39,//1xx00x 0x3E,//x1x1x0 0x3F,//xx1x11 }; #ifndef FAST_MMU //sync mem mapping to mmu , suspend compiled blocks if needed.entry is a UTLB entry # , -1 is for full sync bool UTLB_Sync(u32 entry) { printf_mmu("UTLB MEM remap %d : 0x%X to 0x%X : %d asid %d size %d", entry, UTLB[entry].Address.VPN << 10, UTLB[entry].Data.PPN << 10, UTLB[entry].Data.V, UTLB[entry].Address.ASID, UTLB[entry].Data.SZ0 + UTLB[entry].Data.SZ1 * 2); if (UTLB[entry].Data.V == 0) return true; if ((UTLB[entry].Address.VPN & (0xFC000000 >> 10)) == (0xE0000000 >> 10)) { // Used when FullMMU is off u32 vpn_sq = ((UTLB[entry].Address.VPN & 0x7FFFF) >> 10) & 0x3F;//upper bits are always known [0xE0/E1/E2/E3] sq_remap[vpn_sq] = UTLB[entry].Data.PPN << 10; return true; } else { return false; } } //sync mem mapping to mmu , suspend compiled blocks if needed.entry is a ITLB entry # , -1 is for full sync void ITLB_Sync(u32 entry) { printf_mmu("ITLB MEM remap %d : 0x%X to 0x%X : %d", entry, ITLB[entry].Address.VPN << 10, ITLB[entry].Data.PPN << 10, ITLB[entry].Data.V); } #endif template static void mmuException(MmuError mmu_error, u32 address, u32 am, F raise) { printf_mmu("MMU exception -> pc = 0x%X : ", Sh4cntx.pc); CCN_TEA = address; CCN_PTEH.VPN = address >> 10; switch (mmu_error) { case MmuError::NONE: die("Error: mmu_error == MmuError::NONE)"); return; case MmuError::TLB_MISS: printf_mmu("MmuError::UTLB_MISS 0x%X, handled", address); if (am == MMU_TT_DWRITE) raise(Sh4Ex_TlbMissWrite); else raise(Sh4Ex_TlbMissRead); return; case MmuError::TLB_MHIT: ERROR_LOG(SH4, "MmuError::TLB_MHIT @ 0x%X", address); raise(Sh4Ex_TlbMultiHit); break; //Mem is read/write protected (depends on translation type) case MmuError::PROTECTED: printf_mmu("MmuError::PROTECTED 0x%X, handled", address); if (am == MMU_TT_DWRITE) raise(Sh4Ex_TlbProtViolWrite); else raise(Sh4Ex_TlbProtViolRead); return; //Mem is write protected , firstwrite case MmuError::FIRSTWRITE: printf_mmu("MmuError::FIRSTWRITE"); verify(am == MMU_TT_DWRITE); raise(Sh4Ex_TlbInitPageWrite); return; //data read/write misaligned case MmuError::BADADDR: if (am == MMU_TT_DWRITE) //WADDERR - Write Data Address Error { printf_mmu("MmuError::BADADDR(dw) 0x%X", address); raise(Sh4Ex_AddressErrorWrite); } else if (am == MMU_TT_DREAD) //RADDERR - Read Data Address Error { printf_mmu("MmuError::BADADDR(dr) 0x%X", address); raise(Sh4Ex_AddressErrorRead); } else //IADDERR - Instruction Address Error { #ifdef TRACE_WINCE_SYSCALLS if (!print_wince_syscall(address)) #endif printf_mmu("MmuError::BADADDR(i) 0x%X", address); raise(Sh4Ex_AddressErrorRead); } return; default: die("Unknown mmu_error"); } } [[noreturn]] void mmu_raise_exception(MmuError mmu_error, u32 address, u32 am) { mmuException(mmu_error, address, am, [](Sh4ExceptionCode event) { debugger::debugTrap(event); // FIXME CCN_TEA and CCN_PTEH have been updated already throw SH4ThrownException(Sh4cntx.pc - 2, event); }); die("Unknown mmu_error"); } void DoMMUException(u32 address, MmuError mmu_error, u32 access_type) { mmuException(mmu_error, address, access_type, [](Sh4ExceptionCode event) { Do_Exception(Sh4cntx.pc, event); }); } bool mmu_match(u32 va, CCN_PTEH_type Address, CCN_PTEL_type Data) { if (Data.V == 0) return false; u32 sz = Data.SZ1 * 2 + Data.SZ0; u32 mask = mmu_mask[sz]; if ((((Address.VPN << 10) & mask) == (va & mask))) { bool needAsidMatch = Data.SH == 0 && (Sh4cntx.sr.MD == 0 || CCN_MMUCR.SV == 0); if (!needAsidMatch || Address.ASID == CCN_PTEH.ASID) return true; } return false; } #ifndef FAST_MMU //Do a full lookup on the UTLB entry's MmuError mmu_full_lookup(u32 va, const TLB_Entry** tlb_entry_ret, u32& rv) { CCN_MMUCR.URC++; if (CCN_MMUCR.URB == CCN_MMUCR.URC) CCN_MMUCR.URC = 0; *tlb_entry_ret = nullptr; for (const TLB_Entry& tlb_entry : UTLB) { if (mmu_match(va, tlb_entry.Address, tlb_entry.Data)) { if (*tlb_entry_ret != nullptr) return MmuError::TLB_MHIT; *tlb_entry_ret = &tlb_entry; u32 sz = tlb_entry.Data.SZ1 * 2 + tlb_entry.Data.SZ0; u32 mask = mmu_mask[sz]; //VPN->PPN | low bits rv = ((tlb_entry.Data.PPN << 10) & mask) | (va & ~mask); } } if (*tlb_entry_ret == nullptr) return MmuError::TLB_MISS; else return MmuError::NONE; } //Simple QACR translation for mmu (when AT is off) static u32 mmu_QACR_SQ(u32 va) { int sqi = (va >> 5) & 1; u32 addr = (sqi ? CCN_QACR1.Area : CCN_QACR0.Area) << 26; addr |= va & 0x03ffffe0; return addr; } template MmuError mmu_full_SQ(u32 va, u32& rv) { if ((va & 3) || (CCN_MMUCR.SQMD == 1 && Sh4cntx.sr.MD == 0)) //here, or after ? return MmuError::BADADDR; if (CCN_MMUCR.AT) { //Address=Dest&0xFFFFFFE0; const TLB_Entry *entry; MmuError lookup = mmu_full_lookup(va, &entry, rv); rv &= ~31;//lower 5 bits are forced to 0 if (lookup != MmuError::NONE) return lookup; u32 md = entry->Data.PR >> 1; //Priv mode protection if (md == 0 && Sh4cntx.sr.MD == 0) return MmuError::PROTECTED; //Write Protection (Lock or FW) if (translation_type == MMU_TT_DWRITE) { if ((entry->Data.PR & 1) == 0) return MmuError::PROTECTED; else if (entry->Data.D == 0) return MmuError::FIRSTWRITE; } } else { rv = mmu_QACR_SQ(va); } return MmuError::NONE; } template MmuError mmu_full_SQ(u32 va, u32& rv); template MmuError mmu_full_SQ(u32 va, u32& rv); template MmuError mmu_data_translation(u32 va, u32& rv) { if (translation_type == MMU_TT_DWRITE) { if ((va & 0xFC000000) == 0xE0000000) { MmuError lookup = mmu_full_SQ(va, rv); if (lookup != MmuError::NONE) return lookup; rv = va; //SQ writes are not translated, only write backs are. return MmuError::NONE; } } if (Sh4cntx.sr.MD == 0 && (va & 0x80000000) != 0) //if on kernel, and not SQ addr -> error return MmuError::BADADDR; if ((va & 0xFC000000) == 0x7C000000) { // 7C000000 to 7FFFFFFF in P0/U0 not translated rv = va; return MmuError::NONE; } if (fast_reg_lut[va >> 29] != 0) { // P1, P2 and P4 aren't translated rv = va; return MmuError::NONE; } const TLB_Entry *entry; MmuError lookup = mmu_full_lookup(va, &entry, rv); if (lookup != MmuError::NONE) return lookup; #ifdef TRACE_WINCE_SYSCALLS if (unresolved_unicode_string != 0) { if (va == unresolved_unicode_string) { unresolved_unicode_string = 0; INFO_LOG(SH4, "RESOLVED %s", get_unicode_string(va).c_str()); } } #endif u32 md = entry->Data.PR >> 1; //0X & User mode-> protection violation //Priv mode protection if (md == 0 && Sh4cntx.sr.MD == 0) return MmuError::PROTECTED; //X0 -> read olny //X1 -> read/write , can be FW //Write Protection (Lock or FW) if (translation_type == MMU_TT_DWRITE) { if ((entry->Data.PR & 1) == 0) return MmuError::PROTECTED; else if (entry->Data.D == 0) return MmuError::FIRSTWRITE; } if ((rv & 0x1C000000) == 0x1C000000) // map 1C000000-1FFFFFFF to P4 memory-mapped registers rv |= 0xF0000000; return MmuError::NONE; } template MmuError mmu_data_translation(u32 va, u32& rv); template MmuError mmu_data_translation(u32 va, u32& rv); MmuError mmu_instruction_translation(u32 va, u32& rv) { if (Sh4cntx.sr.MD == 0 && (va & 0x80000000) != 0) // User mode on kernel address return MmuError::BADADDR; if ((va >> 29) == 7) // P4 not executable return MmuError::BADADDR; if (fast_reg_lut[va >> 29] != 0) { // P1 and P2 aren't translated rv = va; return MmuError::NONE; } const TLB_Entry *entry; MmuError lookup = mmu_instruction_lookup(va, &entry, rv); if (lookup != MmuError::NONE) return lookup; u32 md = entry->Data.PR >> 1; //0X & User mode-> protection violation //Priv mode protection if (md == 0 && Sh4cntx.sr.MD == 0) return MmuError::PROTECTED; return MmuError::NONE; } #endif MmuError mmu_instruction_lookup(u32 va, const TLB_Entry** tlb_entry_ret, u32& rv) { bool mmach = false; retry_ITLB_Match: *tlb_entry_ret = nullptr; for (const TLB_Entry& entry : ITLB) { if (entry.Data.V == 0) continue; u32 sz = entry.Data.SZ1 * 2 + entry.Data.SZ0; u32 mask = mmu_mask[sz]; if ((((entry.Address.VPN << 10) & mask) == (va & mask))) { bool needAsidMatch = entry.Data.SH == 0 && (Sh4cntx.sr.MD == 0 || CCN_MMUCR.SV == 0); if (!needAsidMatch || entry.Address.ASID == CCN_PTEH.ASID) { if (*tlb_entry_ret != nullptr) return MmuError::TLB_MHIT; *tlb_entry_ret = &entry; //VPN->PPN | low bits rv = ((entry.Data.PPN << 10) & mask) | (va & ~mask); } } } if (*tlb_entry_ret == nullptr) { #ifndef FAST_MMU verify(!mmach); #else // the matching may be approximative if (mmach) return MmuError::TLB_MISS; #endif const TLB_Entry *tlb_entry; MmuError lookup = mmu_full_lookup(va, &tlb_entry, rv); if (lookup != MmuError::NONE) return lookup; u32 replace_index = ITLB_LRU_USE[CCN_MMUCR.LRUI]; verify(replace_index != 0xFFFFFFFF); ITLB[replace_index] = *tlb_entry; ITLB_Sync(replace_index); mmach = true; goto retry_ITLB_Match; } CCN_MMUCR.LRUI &= ITLB_LRU_AND[*tlb_entry_ret - ITLB]; CCN_MMUCR.LRUI |= ITLB_LRU_OR[*tlb_entry_ret - ITLB]; return MmuError::NONE; } void mmu_set_state() { if (CCN_MMUCR.AT == 1) { // Detect if we're running Windows CE static const char magic[] = { 'S', 0, 'H', 0, '-', 0, '4', 0, ' ', 0, 'K', 0, 'e', 0, 'r', 0, 'n', 0, 'e', 0, 'l', 0 }; if (memcmp(GetMemPtr(0x8c0110a8, 4), magic, sizeof(magic)) == 0 || memcmp(GetMemPtr(0x8c011118, 4), magic, sizeof(magic)) == 0) { mmuOn = true; NOTICE_LOG(SH4, "Enabling Full MMU support"); } } else { mmuOn = false; } SetMemoryHandlers(); setSqwHandler(); } #ifdef FAST_MMU u32 mmuAddressLUT[0x100000]; #endif void MMU_init() { memset(ITLB_LRU_USE, 0xFF, sizeof(ITLB_LRU_USE)); for (u32 e = 0; e<4; e++) { u32 match_key = ((~ITLB_LRU_AND[e]) & 0x3F); u32 match_mask = match_key | ITLB_LRU_OR[e]; for (u32 i = 0; i < std::size(ITLB_LRU_USE); i++) { if ((i & match_mask) == match_key) { verify(ITLB_LRU_USE[i] == 0xFFFFFFFF); ITLB_LRU_USE[i] = e; } } } mmu_set_state(); #ifdef FAST_MMU // pre-fill kernel memory for (u32 vpn = std::size(mmuAddressLUT) / 2; vpn < std::size(mmuAddressLUT); vpn++) mmuAddressLUT[vpn] = vpn << 12; #endif } void MMU_reset() { memset(UTLB, 0, sizeof(UTLB)); memset(ITLB, 0, sizeof(ITLB)); mmu_set_state(); mmu_flush_table(); memset(sq_remap, 0, sizeof(sq_remap)); } void MMU_term() { } #ifndef FAST_MMU void mmu_flush_table() { for (TLB_Entry& entry : ITLB) entry.Data.V = 0; for (TLB_Entry& entry : UTLB) entry.Data.V = 0; } #endif template T DYNACALL mmu_ReadMem(u32 adr) { if (adr & (std::min((int)sizeof(T), 4) - 1)) // Unaligned mmu_raise_exception(MmuError::BADADDR, adr, MMU_TT_DREAD); u32 addr; MmuError rv = mmu_data_translation(adr, addr); if (rv != MmuError::NONE) mmu_raise_exception(rv, adr, MMU_TT_DREAD); return addrspace::readt(addr); } template u8 mmu_ReadMem(u32 adr); template u16 mmu_ReadMem(u32 adr); template u32 mmu_ReadMem(u32 adr); template u64 mmu_ReadMem(u32 adr); u16 DYNACALL mmu_IReadMem16(u32 vaddr) { if (vaddr & (sizeof(u16) - 1)) // Unaligned mmu_raise_exception(MmuError::BADADDR, vaddr, MMU_TT_IREAD); u32 addr; MmuError rv = mmu_instruction_translation(vaddr, addr); if (rv != MmuError::NONE) mmu_raise_exception(rv, vaddr, MMU_TT_IREAD); return addrspace::read16(addr); } template void DYNACALL mmu_WriteMem(u32 adr, T data) { if (adr & (std::min((int)sizeof(T), 4) - 1)) // Unaligned mmu_raise_exception(MmuError::BADADDR, adr, MMU_TT_DWRITE); u32 addr; MmuError rv = mmu_data_translation(adr, addr); if (rv != MmuError::NONE) mmu_raise_exception(rv, adr, MMU_TT_DWRITE); addrspace::writet(addr, data); } template void mmu_WriteMem(u32 adr, u8 data); template void mmu_WriteMem(u32 adr, u16 data); template void mmu_WriteMem(u32 adr, u32 data); template void mmu_WriteMem(u32 adr, u64 data); void mmu_TranslateSQW(u32 adr, u32 *out) { if (!mmuOn) { //This will only work for 1 mb pages .. hopefully nothing else is used //*FIXME* to work for all page sizes ? *out = sq_remap[(adr >> 20) & 0x3F] | (adr & 0xFFFE0); } else { u32 addr; MmuError tv = mmu_full_SQ(adr, addr); if (tv != MmuError::NONE) mmu_raise_exception(tv, adr, MMU_TT_DREAD); *out = addr; } } void mmu_serialize(Serializer& ser) { ser << UTLB; ser << ITLB; ser << sq_remap; } void mmu_deserialize(Deserializer& deser) { deser.skip(8, Deserializer::V33); // CCN_QACR_TR deser >> UTLB; deser >> ITLB; deser >> sq_remap; deser.skip(64 * 4, Deserializer::V23); // ITLB_LRU_USE }