pureikyubu/SRC/Hardware/MI.cpp
2020-08-27 22:01:45 +03:00

585 lines
13 KiB
C++

// MI - memory interface.
//
// MI is used in __OSInitMemoryProtection and by few games for debug.
// MI is implemented only for HW2 consoles! it is not back-compatible.
#include "pch.h"
// TODO: While exploring the Flipper architecture, I misunderstood the purpose of the PI and MEM (MI) components.
// In fact, PI is used to access Flipper's memory and registers from the Gekko side. MEM is used by various Flipper subsystems to access main memory (1T-SRAM).
// Now all memory access handlers are in the MI.cpp module, but in theory they should be in PI.cpp. Let's leave it as it is for now.
using namespace Debug;
// hardware traps tables.
void (*hw_read8[0x10000])(uint32_t, uint32_t*);
void (*hw_write8[0x10000])(uint32_t, uint32_t);
void (*hw_read16[0x10000])(uint32_t, uint32_t*);
void (*hw_write16[0x10000])(uint32_t, uint32_t);
void (*hw_read32[0x10000])(uint32_t, uint32_t*);
void (*hw_write32[0x10000])(uint32_t, uint32_t);
// stubs for MI registers
static void no_write(uint32_t addr, uint32_t data) {}
static void no_read(uint32_t addr, uint32_t *reg) { *reg = 0; }
MIControl mi;
void MIReadByte(uint32_t pa, uint32_t* reg)
{
uint8_t* ptr;
if (mi.ram == nullptr)
{
*reg = 0;
return;
}
if (pa >= BOOTROM_START_ADDRESS)
{
if (mi.BootromPresent)
{
ptr = &mi.bootrom[pa - BOOTROM_START_ADDRESS];
*reg = (uint32_t)*ptr;
}
else
{
*reg = 0xFF;
}
return;
}
// hardware trap
if (pa >= HW_BASE)
{
hw_read8[pa & 0xffff](pa, reg);
return;
}
// bus load byte
if (pa < mi.ramSize)
{
ptr = &mi.ram[pa];
*reg = (uint32_t)*ptr;
}
else
{
*reg = 0;
}
}
void MIWriteByte(uint32_t pa, uint32_t data)
{
uint8_t* ptr;
if (mi.ram == nullptr)
{
return;
}
if (pa >= BOOTROM_START_ADDRESS)
{
return;
}
// hardware trap
if (pa >= HW_BASE)
{
hw_write8[pa & 0xffff](pa, (uint8_t)data);
return;
}
// bus store byte
if (pa < mi.ramSize)
{
ptr = &mi.ram[pa];
*ptr = (uint8_t)data;
}
}
void MIReadHalf(uint32_t pa, uint32_t* reg)
{
uint8_t* ptr;
if (mi.ram == nullptr)
{
*reg = 0;
return;
}
if (pa >= BOOTROM_START_ADDRESS)
{
if (mi.BootromPresent)
{
ptr = &mi.bootrom[pa - BOOTROM_START_ADDRESS];
*reg = (uint32_t)_BYTESWAP_UINT16(*(uint16_t*)ptr);
}
else
{
*reg = 0xFFFF;
}
return;
}
// hardware trap
if (pa >= HW_BASE)
{
hw_read16[pa & 0xfffe](pa, reg);
return;
}
// bus load halfword
if (pa < mi.ramSize)
{
ptr = &mi.ram[pa];
*reg = (uint32_t)_BYTESWAP_UINT16(*(uint16_t*)ptr);
}
else
{
*reg = 0;
}
}
void MIWriteHalf(uint32_t pa, uint32_t data)
{
uint8_t* ptr;
if (mi.ram == nullptr)
{
return;
}
if (pa >= BOOTROM_START_ADDRESS)
{
return;
}
// hardware trap
if (pa >= HW_BASE)
{
hw_write16[pa & 0xfffe](pa, data);
return;
}
// bus store halfword
if (pa < mi.ramSize)
{
ptr = &mi.ram[pa];
*(uint16_t*)ptr = _BYTESWAP_UINT16((uint16_t)data);
}
}
void MIReadWord(uint32_t pa, uint32_t* reg)
{
uint8_t* ptr;
if (mi.ram == nullptr)
{
*reg = 0;
return;
}
// bus load word
if (pa < mi.ramSize)
{
ptr = &mi.ram[pa];
*reg = _BYTESWAP_UINT32(*(uint32_t*)ptr);
return;
}
if (pa >= BOOTROM_START_ADDRESS)
{
if (mi.BootromPresent)
{
ptr = &mi.bootrom[pa - BOOTROM_START_ADDRESS];
*reg = _BYTESWAP_UINT32(*(uint32_t*)ptr);
}
else
{
*reg = 0xFFFFFFFF;
}
return;
}
// hardware trap
if (pa >= HW_BASE)
{
hw_read32[pa & 0xfffc](pa, reg);
return;
}
// embedded frame buffer
if ((pa & PI_EFB_ADDRESS_MASK) == PI_MEMSPACE_EFB)
{
*reg = Flipper::Gx->EfbPeek(pa);
return;
}
*reg = 0;
}
void MIWriteWord(uint32_t pa, uint32_t data)
{
uint8_t* ptr;
if (mi.ram == nullptr)
{
return;
}
if (pa >= BOOTROM_START_ADDRESS)
{
return;
}
// hardware trap
if (pa >= HW_BASE)
{
hw_write32[pa & 0xfffc](pa, data);
return;
}
// embedded frame buffer
if ((pa & PI_EFB_ADDRESS_MASK) == PI_MEMSPACE_EFB)
{
Flipper::Gx->EfbPoke(pa, data);
return;
}
// bus store word
if (pa < mi.ramSize)
{
ptr = &mi.ram[pa];
*(uint32_t*)ptr = _BYTESWAP_UINT32(data);
}
}
//
// longlongs are never used in GC hardware access
//
void MIReadDouble(uint32_t pa, uint64_t* reg)
{
if (pa >= BOOTROM_START_ADDRESS)
{
assert(true);
}
if (pa >= RAMSIZE || mi.ram == nullptr)
{
*reg = 0;
return;
}
uint8_t* buf = &mi.ram[pa];
// bus load doubleword
*reg = _BYTESWAP_UINT64(*(uint64_t*)buf);
}
void MIWriteDouble(uint32_t pa, uint64_t* data)
{
if (pa >= BOOTROM_START_ADDRESS)
{
return;
}
if (pa >= RAMSIZE || mi.ram == nullptr)
{
return;
}
uint8_t* buf = &mi.ram[pa];
// bus store doubleword
*(uint64_t*)buf = _BYTESWAP_UINT64 (*data);
}
void MIReadBurst(uint32_t phys_addr, uint8_t burstData[32])
{
if ((phys_addr + 32) > RAMSIZE)
return;
memcpy(burstData, &mi.ram[phys_addr], 32);
}
void MIWriteBurst(uint32_t phys_addr, uint8_t burstData[32])
{
if (phys_addr == PI_REGSPACE_GX_FIFO)
{
Flipper::Gx->FifoWriteBurst(burstData);
return;
}
if ((phys_addr + 32) > RAMSIZE)
return;
memcpy(&mi.ram[phys_addr], burstData, 32);
}
// ---------------------------------------------------------------------------
// default hardware R/W operations.
// emulation is halted on unknown register access.
static void def_hw_read8(uint32_t addr, uint32_t* reg)
{
Halt("MI: Unhandled HW access: R8 %08X", addr);
}
static void def_hw_write8(uint32_t addr, uint32_t data)
{
Halt("MI: Unhandled HW access: W8 %08X = %02X", addr, (uint8_t)data);
}
static void def_hw_read16(uint32_t addr, uint32_t* reg)
{
Halt("MI: Unhandled HW access: R16 %08X", addr);
}
static void def_hw_write16(uint32_t addr, uint32_t data)
{
Halt("MI: Unhandled HW access: W16 %08X = %04X", addr, (uint16_t)data);
}
static void def_hw_read32(uint32_t addr, uint32_t* reg)
{
Halt("MI: Unhandled HW access: R32 %08X", addr);
}
static void def_hw_write32(uint32_t addr, uint32_t data)
{
Halt("MI: Unhandled HW access: W32 %08X = %08X", addr, data);
}
// ---------------------------------------------------------------------------
// traps API
static void MISetTrap8(
uint32_t addr,
void (*rdTrap)(uint32_t, uint32_t*),
void (*wrTrap)(uint32_t, uint32_t))
{
if (rdTrap == NULL) rdTrap = def_hw_read8;
if (wrTrap == NULL) wrTrap = def_hw_write8;
hw_read8[addr & 0xffff] = rdTrap;
hw_write8[addr & 0xffff] = wrTrap;
}
static void MISetTrap16(
uint32_t addr,
void (*rdTrap)(uint32_t, uint32_t*),
void (*wrTrap)(uint32_t, uint32_t))
{
if (rdTrap == NULL) rdTrap = def_hw_read16;
if (wrTrap == NULL) wrTrap = def_hw_write16;
hw_read16[addr & 0xfffe] = rdTrap;
hw_write16[addr & 0xfffe] = wrTrap;
}
static void MISetTrap32(
uint32_t addr,
void (*rdTrap)(uint32_t, uint32_t*),
void (*wrTrap)(uint32_t, uint32_t))
{
if (rdTrap == NULL) rdTrap = def_hw_read32;
if (wrTrap == NULL) wrTrap = def_hw_write32;
hw_read32[addr & 0xfffc] = rdTrap;
hw_write32[addr & 0xfffc] = wrTrap;
}
// wrapper
void MISetTrap(
uint32_t type, // 8, 16 or 32
uint32_t addr, // physical trap address
void (*rdTrap)(uint32_t, uint32_t*), // register read trap
void (*wrTrap)(uint32_t, uint32_t)) // register write trap
{
// address must be in correct range
if (!((addr >= HW_BASE) && (addr < (HW_BASE + HW_MAX_KNOWN))))
{
Halt(
"MI: Trap address is out of GAMECUBE registers range.\n"
"address : %08X\n", addr
);
}
// select trap type
switch (type)
{
case 8:
MISetTrap8(addr, rdTrap, wrTrap);
break;
case 16:
MISetTrap16(addr, rdTrap, wrTrap);
break;
case 32:
MISetTrap32(addr, rdTrap, wrTrap);
break;
// should never happen
default:
throw "Unknown trap type";
}
}
// set default traps for any access,
// called every time when emu restarted
static void MIClearTraps()
{
uint32_t addr;
// possible errors, if greater 0xffff
assert(HW_MAX_KNOWN < 0x10000);
// for 8-bit registers
for (addr = HW_BASE; addr < (HW_BASE + HW_MAX_KNOWN); addr++)
{
MISetTrap8(addr, NULL, NULL);
}
// for 16-bit registers
for (addr = HW_BASE; addr < (HW_BASE + HW_MAX_KNOWN); addr += 2)
{
MISetTrap16(addr, NULL, NULL);
}
// for 32-bit registers
for (addr = HW_BASE; addr < (HW_BASE + HW_MAX_KNOWN); addr += 4)
{
MISetTrap32(addr, NULL, NULL);
}
}
// Load and descramble bootrom.
// This implementation makes working with Bootrom easier, since we do not need to monitor cache transactions ("bursts") from the processor.
void LoadBootrom(HWConfig* config)
{
mi.BootromPresent = false;
mi.bootromSize = BOOTROM_SIZE;
// Load bootrom image
if (wcslen(config->BootromFilename) == 0)
{
Report(Channel::MI, "Bootrom not loaded (not specified)\n");
return;
}
auto bootrom = Util::FileLoad(config->BootromFilename);
if (bootrom.empty())
{
Report(Channel::MI, "Cannot load Bootrom: %s\n", config->BootromFilename);
return;
}
mi.bootrom = new uint8_t[mi.bootromSize];
if (bootrom.size() != mi.bootromSize)
{
delete [] mi.bootrom;
mi.bootrom = nullptr;
return;
}
memcpy(mi.bootrom, bootrom.data(), bootrom.size());
// Determine size of encrypted data (find first empty cache burst line)
const size_t strideSize = 0x20;
uint8_t zeroStride[strideSize] = { 0 };
size_t beginOffset = 0x100;
size_t endOffset = mi.bootromSize - strideSize;
size_t offset = beginOffset;
while (offset < endOffset)
{
if (!memcmp(&mi.bootrom[offset], zeroStride, sizeof(zeroStride)))
{
break;
}
offset += strideSize;
}
if (offset == endOffset)
{
// Empty cacheline not found, something wrong with the image
delete[] mi.bootrom;
mi.bootrom = nullptr;
return;
}
// Descramble
IPLDescrambler(&mi.bootrom[beginOffset], (offset - beginOffset));
mi.BootromPresent = true;
// Show version
Report(Channel::MI, "Loaded and descrambled valid Bootrom\n");
Report(Channel::Norm, "%s\n", (char*)mi.bootrom);
}
void MIOpen(HWConfig * config)
{
Report(Channel::MI, "Flipper memory interface\n");
// now any access will generate unhandled warning,
// if emulator try to read or write register,
// so we need to set traps for missing regs:
// clear all traps
MIClearTraps();
mi.ramSize = config->ramsize;
mi.ram = new uint8_t[mi.ramSize];
memset(mi.ram, 0, mi.ramSize);
for(uint32_t ofs=0; ofs<=0x28; ofs+=2)
{
MISetTrap(16, 0x0C004000 | ofs, no_read, no_write);
}
LoadBootrom(config);
}
void MIClose()
{
if (mi.ram)
{
delete [] mi.ram;
mi.ram = nullptr;
}
if (mi.bootrom)
{
delete[] mi.bootrom;
mi.bootrom = nullptr;
}
MIClearTraps();
}
uint8_t* MITranslatePhysicalAddress(uint32_t physAddr, size_t bytes)
{
if (!mi.ram || bytes == 0)
return nullptr;
if (physAddr < (RAMSIZE - bytes))
{
return &mi.ram[physAddr];
}
if (physAddr >= BOOTROM_START_ADDRESS && mi.BootromPresent)
{
return &mi.bootrom[physAddr - BOOTROM_START_ADDRESS];
}
return nullptr;
}