mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-05-12 21:46:49 +03:00

psq_st performs one store, and psq_ld one load, from the perspective of the MMU; getting this wrong leads to potentially incorrect behavior (incorrect page faults, weirdness with the gather pipe, etc.). Fix this, and stop masking the address when checking for gather pipe writes. Also a bunch of cleanup.
1075 lines
28 KiB
C++
1075 lines
28 KiB
C++
// 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 <const XCheckTLBFlag flag> 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 <XCheckTLBFlag flag, typename T>
|
|
__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<typename std::make_unsigned<T>::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<typename std::make_unsigned<T>::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<flag>(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<flag>(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 <XCheckTLBFlag flag, typename T>
|
|
__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 == 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<flag>(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<flag>(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<FLAG_OPCODE>(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<FLAG_READ, u8>(address);
|
|
Memcheck(address, var, false, 1);
|
|
return (u8)var;
|
|
}
|
|
|
|
u16 Read_U16(const u32 address)
|
|
{
|
|
u16 var = ReadFromHardware<FLAG_READ, u16>(address);
|
|
Memcheck(address, var, false, 2);
|
|
return (u16)var;
|
|
}
|
|
|
|
u32 Read_U32(const u32 address)
|
|
{
|
|
u32 var = ReadFromHardware<FLAG_READ, u32>(address);
|
|
Memcheck(address, var, false, 4);
|
|
return var;
|
|
}
|
|
|
|
u64 Read_U64(const u32 address)
|
|
{
|
|
u64 var = ReadFromHardware<FLAG_READ, u64>(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<FLAG_WRITE, u8>(address, var);
|
|
}
|
|
|
|
void Write_U16(const u16 var, const u32 address)
|
|
{
|
|
Memcheck(address, var, true, 2);
|
|
WriteToHardware<FLAG_WRITE, u16>(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<FLAG_WRITE, u32>(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<FLAG_WRITE, u64>(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<FLAG_NO_EXCEPTION, u8>(address);
|
|
return var;
|
|
}
|
|
|
|
u16 HostRead_U16(const u32 address)
|
|
{
|
|
u16 var = ReadFromHardware<FLAG_NO_EXCEPTION, u16>(address);
|
|
return var;
|
|
}
|
|
|
|
u32 HostRead_U32(const u32 address)
|
|
{
|
|
u32 var = ReadFromHardware<FLAG_NO_EXCEPTION, u32>(address);
|
|
return var;
|
|
}
|
|
|
|
void HostWrite_U8(const u8 var, const u32 address)
|
|
{
|
|
WriteToHardware<FLAG_NO_EXCEPTION, u8>(address, var);
|
|
}
|
|
|
|
void HostWrite_U16(const u16 var, const u32 address)
|
|
{
|
|
WriteToHardware<FLAG_NO_EXCEPTION, u16>(address, var);
|
|
}
|
|
|
|
void HostWrite_U32(const u32 var, const u32 address)
|
|
{
|
|
WriteToHardware<FLAG_NO_EXCEPTION, u32>(address, var);
|
|
}
|
|
|
|
void HostWrite_U64(const u64 var, const u32 address)
|
|
{
|
|
WriteToHardware<FLAG_NO_EXCEPTION, u64>(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<FLAG_NO_EXCEPTION>(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<u32>(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 <const XCheckTLBFlag flag>
|
|
__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
|