// Copyright (C) 2003 Dolphin Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official Git repository and contact information can be found at // https://github.com/dolphin-emu/dolphin #include "Common/Atomic.h" #include "Common/BitSet.h" #include "Common/CommonTypes.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/HW/GPFifo.h" #include "Core/HW/Memmap.h" #include "Core/HW/MMIO.h" #include "Core/PowerPC/PowerPC.h" #include "VideoCommon/VideoBackendBase.h" #ifdef USE_GDBSTUB #include "Core/PowerPC/GDBStub.h" #endif namespace PowerPC { #define HW_PAGE_SIZE 4096 // EFB RE /* GXPeekZ 80322de8: rlwinm r0, r3, 2, 14, 29 (0003fffc) a = x << 2 & 0x3fffc 80322dec: oris r0, r0, 0xC800 a |= 0xc8000000 80322df0: rlwinm r3, r0, 0, 20, 9 (ffc00fff) x = a & 0xffc00fff 80322df4: rlwinm r0, r4, 12, 4, 19 (0ffff000) a = (y << 12) & 0x0ffff000; 80322df8: or r0, r3, r0 a |= x; 80322dfc: rlwinm r0, r0, 0, 10, 7 (ff3fffff) a &= 0xff3fffff 80322e00: oris r3, r0, 0x0040 x = a | 0x00400000 80322e04: lwz r0, 0 (r3) r0 = *r3 80322e08: stw r0, 0 (r5) z = 80322e0c: blr */ // ================================= // From Memmap.cpp // ---------------- // Overloaded byteswap functions, for use within the templated functions below. inline u8 bswap(u8 val) { return val; } inline s8 bswap(s8 val) { return val; } inline u16 bswap(u16 val) { return Common::swap16(val); } inline s16 bswap(s16 val) { return Common::swap16(val); } inline u32 bswap(u32 val) { return Common::swap32(val); } inline u64 bswap(u64 val) { return Common::swap64(val); } // ================= enum XCheckTLBFlag { FLAG_NO_EXCEPTION, FLAG_READ, FLAG_WRITE, FLAG_OPCODE, }; template static u32 TranslateAddress(const u32 address); // Nasty but necessary. Super Mario Galaxy pointer relies on this stuff. static u32 EFB_Read(const u32 addr) { u32 var = 0; // Convert address to coordinates. It's possible that this should be done // differently depending on color depth, especially regarding PEEK_COLOR. int x = (addr & 0xfff) >> 2; int y = (addr >> 12) & 0x3ff; if (addr & 0x00400000) { var = g_video_backend->Video_AccessEFB(PEEK_Z, x, y, 0); DEBUG_LOG(MEMMAP, "EFB Z Read @ %i, %i\t= 0x%08x", x, y, var); } else { var = g_video_backend->Video_AccessEFB(PEEK_COLOR, x, y, 0); DEBUG_LOG(MEMMAP, "EFB Color Read @ %i, %i\t= 0x%08x", x, y, var); } return var; } static void EFB_Write(u32 data, u32 addr) { int x = (addr & 0xfff) >> 2; int y = (addr >> 12) & 0x3ff; if (addr & 0x00400000) { g_video_backend->Video_AccessEFB(POKE_Z, x, y, data); DEBUG_LOG(MEMMAP, "EFB Z Write %08x @ %i, %i", data, x, y); } else { g_video_backend->Video_AccessEFB(POKE_COLOR, x, y, data); DEBUG_LOG(MEMMAP, "EFB Color Write %08x @ %i, %i", data, x, y); } } static void GenerateDSIException(u32 _EffectiveAddress, bool _bWrite); template __forceinline static T ReadFromHardware(const u32 em_address) { int segment = em_address >> 28; bool performTranslation = UReg_MSR(MSR).DR; // Quick check for an address that can't meet any of the following conditions, // to speed up the MMU path. if (!BitSet32(0xCFC)[segment] && performTranslation) { // TODO: Figure out the fastest order of tests for both read and write (they are probably different). if (flag == FLAG_READ && (em_address & 0xF8000000) == 0xC8000000) { if (em_address < 0xcc000000) return EFB_Read(em_address); else return (T)Memory::mmio_mapping->Read::type>(em_address & 0x0FFFFFFF); } if (segment == 0x0 || segment == 0x8 || segment == 0xC) { // Handle RAM; the masking intentionally discards bits (essentially creating // mirrors of memory). // TODO: Only the first REALRAM_SIZE is supposed to be backed by actual memory. return bswap((*(const T*)&Memory::m_pRAM[em_address & Memory::RAM_MASK])); } if (Memory::m_pEXRAM && (segment == 0x9 || segment == 0xD) && (em_address & 0x0FFFFFFF) < Memory::EXRAM_SIZE) { // Handle EXRAM. // TODO: Is this supposed to be mirrored like main RAM? return bswap((*(const T*)&Memory::m_pEXRAM[em_address & 0x0FFFFFFF])); } if (segment == 0xE && (em_address < (0xE0000000 + Memory::L1_CACHE_SIZE))) { return bswap((*(const T*)&Memory::m_pL1Cache[em_address & 0x0FFFFFFF])); } } if (Memory::bFakeVMEM && performTranslation && (segment == 0x7 || segment == 0x4)) { // fake VMEM return bswap((*(const T*)&Memory::m_pFakeVMEM[em_address & Memory::FAKEVMEM_MASK])); } if (!performTranslation) { if (flag == FLAG_READ && (em_address & 0xF8000000) == 0x08000000) { if (em_address < 0x0c000000) return EFB_Read(em_address); else return (T)Memory::mmio_mapping->Read::type>(em_address); } if (segment == 0x0) { // Handle RAM; the masking intentionally discards bits (essentially creating // mirrors of memory). // TODO: Only the first REALRAM_SIZE is supposed to be backed by actual memory. return bswap((*(const T*)&Memory::m_pRAM[em_address & Memory::RAM_MASK])); } if (Memory::m_pEXRAM && segment == 0x1 && (em_address & 0x0FFFFFFF) < Memory::EXRAM_SIZE) { return bswap((*(const T*)&Memory::m_pEXRAM[em_address & 0x0FFFFFFF])); } PanicAlert("Unable to resolve read address %x PC %x", em_address, PC); return 0; } // MMU: Do page table translation u32 tlb_addr = TranslateAddress(em_address); if (tlb_addr == 0) { if (flag == FLAG_READ) GenerateDSIException(em_address, false); return 0; } // Handle loads that cross page boundaries (ewwww) // The alignment check isn't strictly necessary, but since this is a rare slow path, it provides a faster // (1 instruction on x86) bailout. if (sizeof(T) > 1 && (em_address & (sizeof(T) - 1)) && (em_address & (HW_PAGE_SIZE - 1)) > HW_PAGE_SIZE - sizeof(T)) { // This could be unaligned down to the byte level... hopefully this is rare, so doing it this // way isn't too terrible. // TODO: floats on non-word-aligned boundaries should technically cause alignment exceptions. // Note that "word" means 32-bit, so paired singles or doubles might still be 32-bit aligned! u32 em_address_next_page = (em_address + sizeof(T) - 1) & ~(HW_PAGE_SIZE - 1); u32 tlb_addr_next_page = TranslateAddress(em_address_next_page); if (tlb_addr == 0 || tlb_addr_next_page == 0) { if (flag == FLAG_READ) GenerateDSIException(em_address_next_page, false); return 0; } T var = 0; for (u32 addr = em_address; addr < em_address + sizeof(T); addr++, tlb_addr++) { if (addr == em_address_next_page) tlb_addr = tlb_addr_next_page; var = (var << 8) | Memory::physical_base[tlb_addr]; } return var; } // The easy case! return bswap(*(const T*)&Memory::physical_base[tlb_addr]); } template __forceinline static void WriteToHardware(u32 em_address, const T data) { int segment = em_address >> 28; // Quick check for an address that can't meet any of the following conditions, // to speed up the MMU path. bool performTranslation = UReg_MSR(MSR).DR; if (!BitSet32(0xCFC)[segment] && performTranslation) { // First, let's check for FIFO writes, since they are probably the most common // reason we end up in this function: if (flag == FLAG_WRITE && (em_address & 0xFFFFF000) == 0xCC008000) { switch (sizeof(T)) { case 1: GPFifo::Write8((u8)data, em_address); return; case 2: GPFifo::Write16((u16)data, em_address); return; case 4: GPFifo::Write32((u32)data, em_address); return; case 8: GPFifo::Write64((u64)data, em_address); return; } } if (flag == FLAG_WRITE && (em_address & 0xF8000000) == 0xC8000000) { if (em_address < 0xcc000000) { // TODO: This only works correctly for 32-bit writes. EFB_Write((u32)data, em_address); return; } else { Memory::mmio_mapping->Write(em_address & 0x0FFFFFFF, data); return; } } if (segment == 0x0 || segment == 0x8 || segment == 0xC) { // Handle RAM; the masking intentionally discards bits (essentially creating // mirrors of memory). // TODO: Only the first REALRAM_SIZE is supposed to be backed by actual memory. *(T*)&Memory::m_pRAM[em_address & Memory::RAM_MASK] = bswap(data); return; } if (Memory::m_pEXRAM && (segment == 0x9 || segment == 0xD) && (em_address & 0x0FFFFFFF) < Memory::EXRAM_SIZE) { // Handle EXRAM. // TODO: Is this supposed to be mirrored like main RAM? *(T*)&Memory::m_pEXRAM[em_address & 0x0FFFFFFF] = bswap(data); return; } if (segment == 0xE && (em_address < (0xE0000000 + Memory::L1_CACHE_SIZE))) { *(T*)&Memory::m_pL1Cache[em_address & 0x0FFFFFFF] = bswap(data); return; } } if (Memory::bFakeVMEM && performTranslation && (segment == 0x7 || segment == 0x4)) { // fake VMEM *(T*)&Memory::m_pFakeVMEM[em_address & Memory::FAKEVMEM_MASK] = bswap(data); return; } if (!performTranslation) { if (flag == FLAG_WRITE && (em_address & 0xFFFFF000) == 0x0C008000) { switch (sizeof(T)) { case 1: GPFifo::Write8((u8)data, em_address); return; case 2: GPFifo::Write16((u16)data, em_address); return; case 4: GPFifo::Write32((u32)data, em_address); return; case 8: GPFifo::Write64((u64)data, em_address); return; } } if (flag == FLAG_WRITE && (em_address & 0xF8000000) == 0x08000000) { if (em_address < 0x0c000000) { // TODO: This only works correctly for 32-bit writes. EFB_Write((u32)data, em_address); return; } else { Memory::mmio_mapping->Write(em_address, data); return; } } if (segment == 0x0) { // Handle RAM; the masking intentionally discards bits (essentially creating // mirrors of memory). // TODO: Only the first REALRAM_SIZE is supposed to be backed by actual memory. *(T*)&Memory::m_pRAM[em_address & Memory::RAM_MASK] = bswap(data); return; } if (Memory::m_pEXRAM && segment == 0x1 && (em_address & 0x0FFFFFFF) < Memory::EXRAM_SIZE) { *(T*)&Memory::m_pEXRAM[em_address & 0x0FFFFFFF] = bswap(data); return; } PanicAlert("Unable to resolve write address %x PC %x", em_address, PC); return; } // MMU: Do page table translation u32 tlb_addr = TranslateAddress(em_address); if (tlb_addr == 0) { if (flag == FLAG_WRITE) GenerateDSIException(em_address, true); return; } // Handle stores that cross page boundaries (ewwww) if (sizeof(T) > 1 && (em_address & (sizeof(T) - 1)) && (em_address & (HW_PAGE_SIZE - 1)) > HW_PAGE_SIZE - sizeof(T)) { T val = bswap(data); // We need to check both addresses before writing in case there's a DSI. u32 em_address_next_page = (em_address + sizeof(T) - 1) & ~(HW_PAGE_SIZE - 1); u32 tlb_addr_next_page = TranslateAddress(em_address_next_page); if (tlb_addr_next_page == 0) { if (flag == FLAG_WRITE) GenerateDSIException(em_address_next_page, true); return; } for (u32 addr = em_address; addr < em_address + sizeof(T); addr++, tlb_addr++, val >>= 8) { if (addr == em_address_next_page) tlb_addr = tlb_addr_next_page; Memory::physical_base[tlb_addr] = (u8)val; } return; } // The easy case! *(T*)&Memory::physical_base[tlb_addr] = bswap(data); } // ===================== // ================================= /* These functions are primarily called by the Interpreter functions and are routed to the correct location through ReadFromHardware and WriteToHardware */ // ---------------- static void GenerateISIException(u32 effective_address); u32 Read_Opcode(u32 address) { TryReadInstResult result = TryReadInstruction(address); if (!result.valid) { GenerateISIException(address); return 0; } return result.hex; } TryReadInstResult TryReadInstruction(u32 address) { bool from_bat = true; if (UReg_MSR(MSR).IR) { // TODO: Use real translation. if (SConfig::GetInstance().m_LocalCoreStartupParameter.bMMU && (address & Memory::ADDR_MASK_MEM1)) { u32 tlb_addr = TranslateAddress(address); if (tlb_addr == 0) { return TryReadInstResult{ false, false, 0 }; } else { address = tlb_addr; from_bat = false; } } else { int segment = address >> 28; if ((segment == 0x8 || segment == 0x0) && (address & 0x0FFFFFFF) < Memory::REALRAM_SIZE) { address = address & 0x3FFFFFFF; } else if (segment == 0x9 && (address & 0x0FFFFFFF) < Memory::EXRAM_SIZE) { address = address & 0x3FFFFFFF; } else if (Memory::bFakeVMEM && (segment == 0x7 || segment == 0x4)) { u32 hex = bswap((*(const u32*)&Memory::m_pFakeVMEM[address & Memory::FAKEVMEM_MASK])); return TryReadInstResult{ true, true, hex }; } else { return TryReadInstResult{ false, false, 0 }; } } } else { if (address & 0xC0000000) ERROR_LOG(MEMMAP, "Strange program counter with address translation off: 0x%08x", address); } u32 hex = PowerPC::ppcState.iCache.ReadInstruction(address); return TryReadInstResult{ true, from_bat, hex }; } u32 HostRead_Instruction(const u32 address) { UGeckoInstruction inst = HostRead_U32(address); return inst.hex; } static __forceinline void Memcheck(u32 address, u32 var, bool write, int size) { #ifdef ENABLE_MEM_CHECK TMemCheck *mc = PowerPC::memchecks.GetMemCheck(address); if (mc) { mc->numHits++; mc->Action(&PowerPC::debug_interface, var, address, write, size, PC); } #endif } u8 Read_U8(const u32 address) { u8 var = ReadFromHardware(address); Memcheck(address, var, false, 1); return (u8)var; } u16 Read_U16(const u32 address) { u16 var = ReadFromHardware(address); Memcheck(address, var, false, 2); return (u16)var; } u32 Read_U32(const u32 address) { u32 var = ReadFromHardware(address); Memcheck(address, var, false, 4); return var; } u64 Read_U64(const u32 address) { u64 var = ReadFromHardware(address); Memcheck(address, (u32)var, false, 8); return var; } double Read_F64(const u32 address) { union { u64 i; double d; } cvt; cvt.i = Read_U64(address); return cvt.d; } float Read_F32(const u32 address) { union { u32 i; float d; } cvt; cvt.i = Read_U32(address); return cvt.d; } u32 Read_U8_ZX(const u32 address) { return (u32)Read_U8(address); } u32 Read_U16_ZX(const u32 address) { return (u32)Read_U16(address); } void Write_U8(const u8 var, const u32 address) { Memcheck(address, var, true, 1); WriteToHardware(address, var); } void Write_U16(const u16 var, const u32 address) { Memcheck(address, var, true, 2); WriteToHardware(address, var); } void Write_U16_Swap(const u16 var, const u32 address) { Memcheck(address, var, true, 2); Write_U16(Common::swap16(var), address); } void Write_U32(const u32 var, const u32 address) { Memcheck(address, var, true, 4); WriteToHardware(address, var); } void Write_U32_Swap(const u32 var, const u32 address) { Memcheck(address, var, true, 4); Write_U32(Common::swap32(var), address); } void Write_U64(const u64 var, const u32 address) { Memcheck(address, (u32)var, true, 8); WriteToHardware(address, var); } void Write_U64_Swap(const u64 var, const u32 address) { Memcheck(address, (u32)var, true, 8); Write_U64(Common::swap64(var), address); } void Write_F64(const double var, const u32 address) { union { u64 i; double d; } cvt; cvt.d = var; Write_U64(cvt.i, address); } u8 HostRead_U8(const u32 address) { u8 var = ReadFromHardware(address); return var; } u16 HostRead_U16(const u32 address) { u16 var = ReadFromHardware(address); return var; } u32 HostRead_U32(const u32 address) { u32 var = ReadFromHardware(address); return var; } void HostWrite_U8(const u8 var, const u32 address) { WriteToHardware(address, var); } void HostWrite_U16(const u16 var, const u32 address) { WriteToHardware(address, var); } void HostWrite_U32(const u32 var, const u32 address) { WriteToHardware(address, var); } void HostWrite_U64(const u64 var, const u32 address) { WriteToHardware(address, var); } std::string HostGetString(u32 address, size_t size) { std::string s; do { if (!HostIsRAMAddress(address)) break; u8 res = HostRead_U8(address); if (!res) break; ++address; } while (size == 0 || s.length() < size); return s; } bool IsOptimizableRAMAddress(const u32 address) { if (!UReg_MSR(MSR).DR) return false; int segment = address >> 28; return (((segment == 0x8 || segment == 0xC || segment == 0x0) && (address & 0x0FFFFFFF) < Memory::REALRAM_SIZE) || (Memory::m_pEXRAM && (segment == 0x9 || segment == 0xD) && (address & 0x0FFFFFFF) < Memory::EXRAM_SIZE) || (segment == 0xE && (address < (0xE0000000 + Memory::L1_CACHE_SIZE)))); } bool HostIsRAMAddress(u32 address) { // TODO: This needs to be rewritten; it makes incorrect assumptions // about BATs and page tables. bool performTranslation = UReg_MSR(MSR).DR; int segment = address >> 28; if (performTranslation) { if ((segment == 0x8 || segment == 0xC || segment == 0x0) && (address & 0x0FFFFFFF) < Memory::REALRAM_SIZE) return true; else if (Memory::m_pEXRAM && (segment == 0x9 || segment == 0xD) && (address & 0x0FFFFFFF) < Memory::EXRAM_SIZE) return true; else if (Memory::bFakeVMEM && (segment == 0x7 || segment == 0x4)) return true; else if (segment == 0xE && (address < (0xE0000000 + Memory::L1_CACHE_SIZE))) return true; address = TranslateAddress(address); if (!address) return false; } if (segment == 0x0 && (address & 0x0FFFFFFF) < Memory::REALRAM_SIZE) return true; else if (Memory::m_pEXRAM && segment == 0x1 && (address & 0x0FFFFFFF) < Memory::EXRAM_SIZE) return true; return false; } void DMA_LCToMemory(const u32 memAddr, const u32 cacheAddr, const u32 numBlocks) { // TODO: It's not completely clear this is the right spot for this code; // what would happen if, for example, the DVD drive tried to write to the EFB? // TODO: This is terribly slow. // TODO: Refactor. // Avatar: The Last Airbender (GC) uses this for videos. if ((memAddr & 0x0F000000) == 0x08000000) { for (u32 i = 0; i < 32 * numBlocks; i += 4) { u32 data = bswap(*(u32*)(Memory::m_pL1Cache + ((cacheAddr + i) & 0x3FFFF))); EFB_Write(data, memAddr + i); } return; } // No known game uses this; here for completeness. // TODO: Refactor. if ((memAddr & 0x0F000000) == 0x0C000000) { for (u32 i = 0; i < 32 * numBlocks; i += 4) { u32 data = bswap(*(u32*)(Memory::m_pL1Cache + ((cacheAddr + i) & 0x3FFFF))); Memory::mmio_mapping->Write(memAddr + i, data); } return; } const u8* src = Memory::m_pL1Cache + (cacheAddr & 0x3FFFF); u8* dst = Memory::GetPointer(memAddr); if (dst == nullptr) return; memcpy(dst, src, 32 * numBlocks); } void DMA_MemoryToLC(const u32 cacheAddr, const u32 memAddr, const u32 numBlocks) { const u8* src = Memory::GetPointer(memAddr); u8* dst = Memory::m_pL1Cache + (cacheAddr & 0x3FFFF); // No known game uses this; here for completeness. // TODO: Refactor. if ((memAddr & 0x0F000000) == 0x08000000) { for (u32 i = 0; i < 32 * numBlocks; i += 4) { u32 data = EFB_Read(memAddr + i); *(u32*)(Memory::m_pL1Cache + ((cacheAddr + i) & 0x3FFFF)) = bswap(data); } return; } // No known game uses this. // TODO: Refactor. if ((memAddr & 0x0F000000) == 0x0C000000) { for (u32 i = 0; i < 32 * numBlocks; i += 4) { u32 data = Memory::mmio_mapping->Read(memAddr + i); *(u32*)(Memory::m_pL1Cache + ((cacheAddr + i) & 0x3FFFF)) = bswap(data); } return; } if (src == nullptr) return; memcpy(dst, src, 32 * numBlocks); } void ClearCacheLine(const u32 address) { // FIXME: does this do the right thing if dcbz is run on hardware memory, e.g. // the FIFO? Do games even do that? Probably not, but we should try to be correct... for (u32 i = 0; i < 32; i += 8) Write_U64(0, address + i); } u32 IsOptimizableMMIOAccess(u32 address, u32 accessSize) { if (!UReg_MSR(MSR).DR) return 0; if ((address & 0xF0000000) != 0xC0000000) return 0; unsigned translated = address & 0x0FFFFFFF; bool aligned = (translated & ((accessSize >> 3) - 1)) == 0; if (!aligned || !MMIO::IsMMIOAddress(translated)) return 0; return translated; } bool IsOptimizableGatherPipeWrite(u32 address) { if (!UReg_MSR(MSR).DR) return false; return address == 0xCC008000; } // ********************************************************************************* // Warning: Test Area // // This code is for TESTING and it works in interpreter mode ONLY. Some games (like // COD iirc) work thanks to this basic TLB emulation. // It is just a small hack and we have never spend enough time to finalize it. // Cheers PearPC! // // ********************************************************************************* /* * PearPC * ppc_mmu.cc * * Copyright (C) 2003, 2004 Sebastian Biallas (sb@biallas.net) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #define PPC_EXC_DSISR_PAGE (1<<30) #define PPC_EXC_DSISR_PROT (1<<27) #define PPC_EXC_DSISR_STORE (1<<25) #define SDR1_HTABORG(v) (((v)>>16)&0xffff) #define SDR1_HTABMASK(v) ((v)&0x1ff) #define SDR1_PAGETABLE_BASE(v) ((v)&0xffff) #define SR_T (1<<31) #define SR_Ks (1<<30) #define SR_Kp (1<<29) #define SR_N (1<<28) #define SR_VSID(v) ((v)&0xffffff) #define SR_BUID(v) (((v)>>20)&0x1ff) #define SR_CNTRL_SPEC(v) ((v)&0xfffff) #define EA_SR(v) (((v)>>28)&0xf) #define EA_PageIndex(v) (((v)>>12)&0xffff) #define EA_Offset(v) ((v)&0xfff) #define EA_API(v) (((v)>>22)&0x3f) #define PA_RPN(v) (((v)>>12)&0xfffff) #define PA_Offset(v) ((v)&0xfff) #define PTE1_V (1<<31) #define PTE1_VSID(v) (((v)>>7)&0xffffff) #define PTE1_H (1<<6) #define PTE1_API(v) ((v)&0x3f) #define PTE2_RPN(v) ((v)&0xfffff000) #define PTE2_R (1<<8) #define PTE2_C (1<<7) #define PTE2_WIMG(v) (((v)>>3)&0xf) #define PTE2_PP(v) ((v)&3) // Hey! these duplicate a structure in Gekko.h union UPTE1 { struct { u32 API : 6; u32 H : 1; u32 VSID : 24; u32 V : 1; }; u32 Hex; }; union UPTE2 { struct { u32 PP : 2; u32 : 1; u32 WIMG : 4; u32 C : 1; u32 R : 1; u32 : 3; u32 RPN : 20; }; u32 Hex; }; static void GenerateDSIException(u32 effectiveAddress, bool write) { // DSI exceptions are only supported in MMU mode. if (!SConfig::GetInstance().m_LocalCoreStartupParameter.bMMU) { PanicAlertT("Invalid %s to 0x%08x, PC = 0x%08x ", write ? "Write to" : "Read from", effectiveAddress, PC); return; } if (effectiveAddress) PowerPC::ppcState.spr[SPR_DSISR] = PPC_EXC_DSISR_PAGE | PPC_EXC_DSISR_STORE; else PowerPC::ppcState.spr[SPR_DSISR] = PPC_EXC_DSISR_PAGE; PowerPC::ppcState.spr[SPR_DAR] = effectiveAddress; PowerPC::ppcState.Exceptions |= EXCEPTION_DSI; } static void GenerateISIException(u32 _EffectiveAddress) { // Address of instruction could not be translated NPC = _EffectiveAddress; PowerPC::ppcState.Exceptions |= EXCEPTION_ISI; } void SDRUpdated() { u32 htabmask = SDR1_HTABMASK(PowerPC::ppcState.spr[SPR_SDR]); u32 x = 1; u32 xx = 0; int n = 0; while ((htabmask & x) && (n < 9)) { n++; xx|=x; x<<=1; } if (htabmask & ~xx) { return; } u32 htaborg = SDR1_HTABORG(PowerPC::ppcState.spr[SPR_SDR]); if (htaborg & xx) { return; } PowerPC::ppcState.pagetable_base = htaborg<<16; PowerPC::ppcState.pagetable_hashmask = ((xx<<10)|0x3ff); } enum TLBLookupResult { TLB_FOUND, TLB_NOTFOUND, TLB_UPDATE_C }; static __forceinline TLBLookupResult LookupTLBPageAddress(const XCheckTLBFlag flag, const u32 vpa, u32 *paddr) { u32 tag = vpa >> HW_PAGE_INDEX_SHIFT; PowerPC::tlb_entry *tlbe = &PowerPC::ppcState.tlb[flag == FLAG_OPCODE][tag & HW_PAGE_INDEX_MASK]; if (tlbe->tag[0] == tag) { // Check if C bit requires updating if (flag == FLAG_WRITE) { UPTE2 PTE2; PTE2.Hex = tlbe->pte[0]; if (PTE2.C == 0) { PTE2.C = 1; tlbe->pte[0] = PTE2.Hex; return TLB_UPDATE_C; } } if (flag != FLAG_NO_EXCEPTION) tlbe->recent = 0; *paddr = tlbe->paddr[0] | (vpa & 0xfff); return TLB_FOUND; } if (tlbe->tag[1] == tag) { // Check if C bit requires updating if (flag == FLAG_WRITE) { UPTE2 PTE2; PTE2.Hex = tlbe->pte[1]; if (PTE2.C == 0) { PTE2.C = 1; tlbe->pte[1] = PTE2.Hex; return TLB_UPDATE_C; } } if (flag != FLAG_NO_EXCEPTION) tlbe->recent = 1; *paddr = tlbe->paddr[1] | (vpa & 0xfff); return TLB_FOUND; } return TLB_NOTFOUND; } static __forceinline void UpdateTLBEntry(const XCheckTLBFlag flag, UPTE2 PTE2, const u32 address) { if (flag == FLAG_NO_EXCEPTION) return; int tag = address >> HW_PAGE_INDEX_SHIFT; PowerPC::tlb_entry *tlbe = &PowerPC::ppcState.tlb[flag == FLAG_OPCODE][tag & HW_PAGE_INDEX_MASK]; int index = tlbe->recent == 0 && tlbe->tag[0] != TLB_TAG_INVALID; tlbe->recent = index; tlbe->paddr[index] = PTE2.RPN << HW_PAGE_INDEX_SHIFT; tlbe->pte[index] = PTE2.Hex; tlbe->tag[index] = tag; } void InvalidateTLBEntry(u32 address) { PowerPC::tlb_entry *tlbe = &PowerPC::ppcState.tlb[0][(address >> HW_PAGE_INDEX_SHIFT) & HW_PAGE_INDEX_MASK]; tlbe->tag[0] = TLB_TAG_INVALID; tlbe->tag[1] = TLB_TAG_INVALID; PowerPC::tlb_entry *tlbe_i = &PowerPC::ppcState.tlb[1][(address >> HW_PAGE_INDEX_SHIFT) & HW_PAGE_INDEX_MASK]; tlbe_i->tag[0] = TLB_TAG_INVALID; tlbe_i->tag[1] = TLB_TAG_INVALID; } // Page Address Translation static __forceinline u32 TranslatePageAddress(const u32 address, const XCheckTLBFlag flag) { // TLB cache // This catches 99%+ of lookups in practice, so the actual page table entry code below doesn't benefit // much from optimization. u32 translatedAddress = 0; TLBLookupResult res = LookupTLBPageAddress(flag , address, &translatedAddress); if (res == TLB_FOUND) return translatedAddress; u32 sr = PowerPC::ppcState.sr[EA_SR(address)]; u32 offset = EA_Offset(address); // 12 bit u32 page_index = EA_PageIndex(address); // 16 bit u32 VSID = SR_VSID(sr); // 24 bit u32 api = EA_API(address); // 6 bit (part of page_index) // hash function no 1 "xor" .360 u32 hash = (VSID ^ page_index); u32 pte1 = bswap((VSID << 7) | api | PTE1_V); for (int hash_func = 0; hash_func < 2; hash_func++) { // hash function no 2 "not" .360 if (hash_func == 1) { hash = ~hash; pte1 |= PTE1_H << 24; } u32 pteg_addr = ((hash & PowerPC::ppcState.pagetable_hashmask) << 6) | PowerPC::ppcState.pagetable_base; for (int i = 0; i < 8; i++, pteg_addr += 8) { if (pte1 == *(u32*)&Memory::physical_base[pteg_addr]) { UPTE2 PTE2; PTE2.Hex = bswap((*(u32*)&Memory::physical_base[pteg_addr + 4])); // set the access bits switch (flag) { case FLAG_NO_EXCEPTION: break; case FLAG_READ: PTE2.R = 1; break; case FLAG_WRITE: PTE2.R = 1; PTE2.C = 1; break; case FLAG_OPCODE: PTE2.R = 1; break; } if (flag != FLAG_NO_EXCEPTION) *(u32*)&Memory::physical_base[pteg_addr + 4] = bswap(PTE2.Hex); // We already updated the TLB entry if this was caused by a C bit. if (res != TLB_UPDATE_C) UpdateTLBEntry(flag, PTE2, address); return (PTE2.RPN << 12) | offset; } } } return 0; } // Translate effective address using BAT or PAT. Returns 0 if the address cannot be translated. template __forceinline u32 TranslateAddress(const u32 address) { // TODO: Perform BAT translation. (At the moment, we hardcode an assumed BAT // configuration, so there's no reason to actually check the registers.) return TranslatePageAddress(address, flag); } } // namespace