mirror of
https://github.com/emu-russia/pureikyubu.git
synced 2024-06-02 03:27:30 -04:00
2122 lines
42 KiB
C++
2122 lines
42 KiB
C++
// CPU controls
|
||
#include "pch.h"
|
||
|
||
using namespace Debug;
|
||
|
||
namespace Gekko
|
||
{
|
||
// The main driving force behind the entire emulator. All other threads are based on changing the TBR Gekko register.
|
||
void GekkoCore::GekkoThreadProc(void* Parameter)
|
||
{
|
||
GekkoCore* core = (GekkoCore*)Parameter;
|
||
|
||
if (core->suspended)
|
||
{
|
||
Thread::Sleep(50);
|
||
return;
|
||
}
|
||
|
||
if (core->EnableTestBreakpoints)
|
||
{
|
||
if (core->TestBreakpoints()) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
core->interp->ExecuteOpcode();
|
||
}
|
||
|
||
GekkoCore::GekkoCore()
|
||
{
|
||
cache = new Cache(this);
|
||
icache = new Cache(this);
|
||
|
||
// DEBUG
|
||
//cache->SetLogLevel(CacheLogLevel::MemOps);
|
||
//icache->SetLogLevel(CacheLogLevel::MemOps);
|
||
|
||
gatherBuffer = new GatherBuffer(this);
|
||
|
||
interp = new Interpreter(this);
|
||
|
||
gekkoThread = EMUCreateThread(GekkoThreadProc, false, this, "GekkoCore");
|
||
|
||
Reset();
|
||
}
|
||
|
||
GekkoCore::~GekkoCore()
|
||
{
|
||
StopOpcodeStatsThread();
|
||
EMUJoinThread(gekkoThread);
|
||
delete interp;
|
||
delete gatherBuffer;
|
||
}
|
||
|
||
// Reset processor
|
||
void GekkoCore::Reset()
|
||
{
|
||
one_second = CPU_TIMER_CLOCK;
|
||
intFlag = false;
|
||
exception = false;
|
||
decreq = false;
|
||
RESERVE = false;
|
||
ops = 0;
|
||
|
||
// BAT registers are scattered across the SPR address space. This is not very convenient, we will make it convenient.
|
||
|
||
dbatu[0] = ®s.spr[SPR::DBAT0U];
|
||
dbatu[1] = ®s.spr[SPR::DBAT1U];
|
||
dbatu[2] = ®s.spr[SPR::DBAT2U];
|
||
dbatu[3] = ®s.spr[SPR::DBAT3U];
|
||
|
||
dbatl[0] = ®s.spr[SPR::DBAT0L];
|
||
dbatl[1] = ®s.spr[SPR::DBAT1L];
|
||
dbatl[2] = ®s.spr[SPR::DBAT2L];
|
||
dbatl[3] = ®s.spr[SPR::DBAT3L];
|
||
|
||
ibatu[0] = ®s.spr[SPR::IBAT0U];
|
||
ibatu[1] = ®s.spr[SPR::IBAT1U];
|
||
ibatu[2] = ®s.spr[SPR::IBAT2U];
|
||
ibatu[3] = ®s.spr[SPR::IBAT3U];
|
||
|
||
ibatl[0] = ®s.spr[SPR::IBAT0L];
|
||
ibatl[1] = ®s.spr[SPR::IBAT1L];
|
||
ibatl[2] = ®s.spr[SPR::IBAT2L];
|
||
ibatl[3] = ®s.spr[SPR::IBAT3L];
|
||
|
||
// Registers
|
||
|
||
memset(®s, 0, sizeof(regs));
|
||
|
||
// Disable translation for now
|
||
regs.msr &= ~(MSR_DR | MSR_IR);
|
||
|
||
regs.tb.uval = 0;
|
||
regs.spr[SPR::HID1] = 0x8000'0000;
|
||
regs.spr[SPR::DEC] = 0;
|
||
regs.spr[SPR::CTR] = 0;
|
||
|
||
gatherBuffer->Reset();
|
||
|
||
dtlb.InvalidateAll();
|
||
itlb.InvalidateAll();
|
||
cache->Reset();
|
||
cache->Enable(false);
|
||
icache->Reset();
|
||
icache->Enable(false);
|
||
}
|
||
|
||
// Modify CPU counters
|
||
void GekkoCore::Tick()
|
||
{
|
||
regs.tb.uval += CounterStep; // timer
|
||
|
||
uint32_t old = regs.spr[SPR::DEC];
|
||
regs.spr[SPR::DEC] -= DecrementerStep; // decrementer
|
||
if ((old ^ regs.spr[SPR::DEC]) & 0x80000000)
|
||
{
|
||
if (regs.msr & MSR_EE)
|
||
{
|
||
decreq = 1;
|
||
Report(Channel::CPU, "decrementer exception (OS alarm), pc:%08X\n", regs.pc);
|
||
}
|
||
}
|
||
}
|
||
|
||
int64_t GekkoCore::GetTicks()
|
||
{
|
||
return regs.tb.sval;
|
||
}
|
||
|
||
// 1 second of emulated CPU time.
|
||
int64_t GekkoCore::OneSecond()
|
||
{
|
||
return one_second;
|
||
}
|
||
|
||
// Swap longs (no need in assembly, used by tools)
|
||
void GekkoCore::SwapArea(uint32_t* addr, int count)
|
||
{
|
||
uint32_t* until = addr + count / sizeof(uint32_t);
|
||
|
||
while (addr != until)
|
||
{
|
||
*addr = _BYTESWAP_UINT32(*addr);
|
||
addr++;
|
||
}
|
||
}
|
||
|
||
// Swap shorts (no need in assembly, used by tools)
|
||
void GekkoCore::SwapAreaHalf(uint16_t* addr, int count)
|
||
{
|
||
uint16_t* until = addr + count / sizeof(uint16_t);
|
||
|
||
while (addr != until)
|
||
{
|
||
*addr = _BYTESWAP_UINT16(*addr);
|
||
addr++;
|
||
}
|
||
}
|
||
|
||
void GekkoCore::Step()
|
||
{
|
||
interp->ExecuteOpcode();
|
||
}
|
||
|
||
void GekkoCore::AssertInterrupt()
|
||
{
|
||
intFlag = true;
|
||
}
|
||
|
||
void GekkoCore::ClearInterrupt()
|
||
{
|
||
intFlag = false;
|
||
}
|
||
|
||
void GekkoCore::Exception(Gekko::Exception code)
|
||
{
|
||
if (break_on_exception)
|
||
{
|
||
Halt("Gekko Exception: #%04X\n", (uint16_t)code);
|
||
}
|
||
|
||
if (trace_exceptions)
|
||
{
|
||
Report(Channel::CPU, "Gekko Exception: #%04X\n", (uint16_t)code);
|
||
}
|
||
|
||
if (exception)
|
||
{
|
||
Halt("CPU Double Fault!\n");
|
||
}
|
||
|
||
// save regs
|
||
|
||
regs.spr[Gekko::SPR::SRR0] = regs.pc;
|
||
regs.spr[Gekko::SPR::SRR1] = regs.msr;
|
||
|
||
// Special processing for MMU
|
||
if (code == Exception::EXCEPTION_ISI)
|
||
{
|
||
regs.spr[Gekko::SPR::SRR1] &= 0x0fff'ffff;
|
||
|
||
switch (MmuLastResult)
|
||
{
|
||
case MmuResult::PageFault:
|
||
regs.spr[Gekko::SPR::SRR1] |= 0x4000'0000;
|
||
break;
|
||
|
||
case MmuResult::ProtectedFetch:
|
||
regs.spr[Gekko::SPR::SRR1] |= 0x0800'0000;
|
||
break;
|
||
|
||
case MmuResult::NoExecute:
|
||
regs.spr[Gekko::SPR::SRR1] |= 0x1000'0000;
|
||
break;
|
||
|
||
case MmuResult::DirectStore:
|
||
regs.spr[Gekko::SPR::SRR1] |= 0x1000'0000;
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
if (break_on_ISI) {
|
||
Halt("Gekko ISI Exception at %08X\n", regs.pc);
|
||
}
|
||
}
|
||
else if (code == Exception::EXCEPTION_DSI)
|
||
{
|
||
regs.spr[Gekko::SPR::DSISR] = 0;
|
||
|
||
switch (MmuLastResult)
|
||
{
|
||
case MmuResult::PageFault:
|
||
regs.spr[Gekko::SPR::DSISR] |= 0x4000'0000;
|
||
break;
|
||
|
||
case MmuResult::ProtectedRead:
|
||
regs.spr[Gekko::SPR::DSISR] |= 0x0800'0000;
|
||
break;
|
||
|
||
case MmuResult::ProtectedWrite:
|
||
regs.spr[Gekko::SPR::DSISR] |= 0x0A00'0000;
|
||
break;
|
||
|
||
case MmuResult::DirectStore:
|
||
regs.spr[Gekko::SPR::DSISR] |= 0x0400'0000;
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
if (break_on_DSI) {
|
||
Halt("Gekko DSI Exception at %08X.%s\n", regs.pc, regs.spr[Gekko::SPR::SDR1] != 0 ?
|
||
"\nThe sneaky game uses the vm library, to emulate virtual memory, which is not yet well implemented." : "");
|
||
}
|
||
}
|
||
|
||
// Special processing for Program
|
||
else if (code == Exception::EXCEPTION_PROGRAM)
|
||
{
|
||
regs.spr[Gekko::SPR::SRR1] &= 0x0000'ffff;
|
||
|
||
switch (PrCause)
|
||
{
|
||
case PrivilegedCause::FpuEnabled:
|
||
regs.spr[Gekko::SPR::SRR1] |= 0x0010'0000;
|
||
break;
|
||
case PrivilegedCause::IllegalInstruction:
|
||
regs.spr[Gekko::SPR::SRR1] |= 0x0008'0000;
|
||
break;
|
||
case PrivilegedCause::Privileged:
|
||
regs.spr[Gekko::SPR::SRR1] |= 0x0004'0000;
|
||
break;
|
||
case PrivilegedCause::Trap:
|
||
regs.spr[Gekko::SPR::SRR1] |= 0x0002'0000;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
// disable address translation
|
||
regs.msr &= ~(MSR_IR | MSR_DR);
|
||
|
||
regs.msr &= ~MSR_RI;
|
||
|
||
regs.msr &= ~MSR_EE;
|
||
|
||
// change PC and set exception flag
|
||
regs.pc = (uint32_t)code;
|
||
exception = true;
|
||
}
|
||
|
||
uint32_t GekkoCore::EffectiveToPhysical(uint32_t ea, MmuAccess type, int& WIMG)
|
||
{
|
||
return EffectiveToPhysicalMmu(ea, type, WIMG);
|
||
}
|
||
}
|
||
|
||
|
||
// Gekko Memory interface
|
||
|
||
namespace Gekko
|
||
{
|
||
|
||
// Centralized hub which attracts all memory access requests from the interpreter or recompiler
|
||
// (as well as those who they pretend, for example HLE or Debugger).
|
||
|
||
void GekkoCore::ReadByte(uint32_t addr, uint32_t* reg)
|
||
{
|
||
int WIMG;
|
||
if (EnableTestReadBreakpoints)
|
||
{
|
||
TestReadBreakpoints(addr);
|
||
}
|
||
|
||
uint32_t pa = EffectiveToPhysical(addr, MmuAccess::Read, WIMG);
|
||
if (pa == BadAddress)
|
||
{
|
||
regs.spr[(int)SPR::DAR] = addr;
|
||
Exception(Exception::EXCEPTION_DSI);
|
||
return;
|
||
}
|
||
|
||
if (cache->IsEnabled() && (WIMG & WIMG_I) == 0)
|
||
{
|
||
cache->ReadByte(pa, reg);
|
||
return;
|
||
}
|
||
|
||
PIReadByte(pa, reg);
|
||
}
|
||
|
||
void GekkoCore::WriteByte(uint32_t addr, uint32_t data)
|
||
{
|
||
int WIMG;
|
||
if (EnableTestWriteBreakpoints)
|
||
{
|
||
TestWriteBreakpoints(addr);
|
||
}
|
||
|
||
uint32_t pa = EffectiveToPhysical(addr, MmuAccess::Write, WIMG);
|
||
if (pa == BadAddress)
|
||
{
|
||
regs.spr[(int)SPR::DAR] = addr;
|
||
Exception(Exception::EXCEPTION_DSI);
|
||
return;
|
||
}
|
||
|
||
if (RESERVE && pa == RESERVE_ADDR)
|
||
{
|
||
RESERVE = false;
|
||
}
|
||
|
||
if (regs.spr[Gekko::SPR::HID2] & HID2_WPE)
|
||
{
|
||
if ((pa & ~0x1f) == (regs.spr[Gekko::SPR::WPAR] & ~0x1f))
|
||
{
|
||
gatherBuffer->Write8((uint8_t)data);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (cache->IsEnabled() && (WIMG & WIMG_I) == 0)
|
||
{
|
||
cache->WriteByte(pa, data);
|
||
if ((WIMG & WIMG_W) == 0)
|
||
return;
|
||
}
|
||
|
||
PIWriteByte(pa, data);
|
||
}
|
||
|
||
void GekkoCore::ReadHalf(uint32_t addr, uint32_t* reg)
|
||
{
|
||
int WIMG;
|
||
if (EnableTestReadBreakpoints)
|
||
{
|
||
TestReadBreakpoints(addr);
|
||
}
|
||
|
||
uint32_t pa = EffectiveToPhysical(addr, MmuAccess::Read, WIMG);
|
||
if (pa == BadAddress)
|
||
{
|
||
regs.spr[(int)SPR::DAR] = addr;
|
||
Exception(Exception::EXCEPTION_DSI);
|
||
return;
|
||
}
|
||
|
||
if (cache->IsEnabled() && (WIMG & WIMG_I) == 0)
|
||
{
|
||
cache->ReadHalf(pa, reg);
|
||
return;
|
||
}
|
||
|
||
PIReadHalf(pa, reg);
|
||
}
|
||
|
||
void GekkoCore::WriteHalf(uint32_t addr, uint32_t data)
|
||
{
|
||
int WIMG;
|
||
if (EnableTestWriteBreakpoints)
|
||
{
|
||
TestWriteBreakpoints(addr);
|
||
}
|
||
|
||
uint32_t pa = EffectiveToPhysical(addr, MmuAccess::Write, WIMG);
|
||
if (pa == BadAddress)
|
||
{
|
||
regs.spr[(int)SPR::DAR] = addr;
|
||
Exception(Exception::EXCEPTION_DSI);
|
||
return;
|
||
}
|
||
|
||
if (RESERVE && pa == RESERVE_ADDR)
|
||
{
|
||
RESERVE = false;
|
||
}
|
||
|
||
if (regs.spr[Gekko::SPR::HID2] & HID2_WPE)
|
||
{
|
||
if ((pa & ~0x1f) == (regs.spr[Gekko::SPR::WPAR] & ~0x1f))
|
||
{
|
||
gatherBuffer->Write16((uint16_t)data);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (cache->IsEnabled() && (WIMG & WIMG_I) == 0)
|
||
{
|
||
cache->WriteHalf(pa, data);
|
||
if ((WIMG & WIMG_W) == 0)
|
||
return;
|
||
}
|
||
|
||
PIWriteHalf(pa, data);
|
||
}
|
||
|
||
void GekkoCore::ReadWord(uint32_t addr, uint32_t* reg)
|
||
{
|
||
int WIMG;
|
||
if (EnableTestReadBreakpoints)
|
||
{
|
||
TestReadBreakpoints(addr);
|
||
}
|
||
|
||
uint32_t pa = EffectiveToPhysical(addr, MmuAccess::Read, WIMG);
|
||
if (pa == BadAddress)
|
||
{
|
||
regs.spr[(int)SPR::DAR] = addr;
|
||
Exception(Exception::EXCEPTION_DSI);
|
||
return;
|
||
}
|
||
|
||
if (cache->IsEnabled() && (WIMG & WIMG_I) == 0)
|
||
{
|
||
cache->ReadWord(pa, reg);
|
||
return;
|
||
}
|
||
|
||
PIReadWord(pa, reg);
|
||
}
|
||
|
||
void GekkoCore::WriteWord(uint32_t addr, uint32_t data)
|
||
{
|
||
int WIMG;
|
||
if (EnableTestWriteBreakpoints)
|
||
{
|
||
TestWriteBreakpoints(addr);
|
||
}
|
||
|
||
uint32_t pa = EffectiveToPhysical(addr, MmuAccess::Write, WIMG);
|
||
if (pa == BadAddress)
|
||
{
|
||
regs.spr[(int)SPR::DAR] = addr;
|
||
Exception(Exception::EXCEPTION_DSI);
|
||
return;
|
||
}
|
||
|
||
if (RESERVE && pa == RESERVE_ADDR)
|
||
{
|
||
RESERVE = false;
|
||
}
|
||
|
||
if (regs.spr[Gekko::SPR::HID2] & HID2_WPE)
|
||
{
|
||
if ((pa & ~0x1f) == (regs.spr[Gekko::SPR::WPAR] & ~0x1f))
|
||
{
|
||
gatherBuffer->Write32(data);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (cache->IsEnabled() && (WIMG & WIMG_I) == 0)
|
||
{
|
||
cache->WriteWord(pa, data);
|
||
if ((WIMG & WIMG_W) == 0)
|
||
return;
|
||
}
|
||
|
||
PIWriteWord(pa, data);
|
||
}
|
||
|
||
void GekkoCore::ReadDouble(uint32_t addr, uint64_t* reg)
|
||
{
|
||
int WIMG;
|
||
if (EnableTestReadBreakpoints)
|
||
{
|
||
TestReadBreakpoints(addr);
|
||
}
|
||
|
||
uint32_t pa = EffectiveToPhysical(addr, MmuAccess::Read, WIMG);
|
||
if (pa == BadAddress)
|
||
{
|
||
regs.spr[(int)SPR::DAR] = addr;
|
||
Exception(Exception::EXCEPTION_DSI);
|
||
return;
|
||
}
|
||
|
||
if (cache->IsEnabled() && (WIMG & WIMG_I) == 0)
|
||
{
|
||
cache->ReadDouble(pa, reg);
|
||
return;
|
||
}
|
||
|
||
// It is suspected that this type of single-beat transaction is not supported by Flipper PI.
|
||
|
||
PIReadDouble(pa, reg);
|
||
}
|
||
|
||
void GekkoCore::WriteDouble(uint32_t addr, uint64_t* data)
|
||
{
|
||
int WIMG;
|
||
if (EnableTestWriteBreakpoints)
|
||
{
|
||
TestWriteBreakpoints(addr);
|
||
}
|
||
|
||
uint32_t pa = EffectiveToPhysical(addr, MmuAccess::Write, WIMG);
|
||
if (pa == BadAddress)
|
||
{
|
||
regs.spr[(int)SPR::DAR] = addr;
|
||
Exception(Exception::EXCEPTION_DSI);
|
||
return;
|
||
}
|
||
|
||
if (RESERVE && pa == RESERVE_ADDR)
|
||
{
|
||
RESERVE = false;
|
||
}
|
||
|
||
if (regs.spr[Gekko::SPR::HID2] & HID2_WPE)
|
||
{
|
||
if ((pa & ~0x1f) == (regs.spr[Gekko::SPR::WPAR] & ~0x1f))
|
||
{
|
||
gatherBuffer->Write64(*data);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (cache->IsEnabled() && (WIMG & WIMG_I) == 0)
|
||
{
|
||
cache->WriteDouble(pa, data);
|
||
if ((WIMG & WIMG_W) == 0)
|
||
return;
|
||
}
|
||
|
||
// It is suspected that this type of single-beat transaction is not supported by Flipper PI.
|
||
|
||
PIWriteDouble(pa, data);
|
||
}
|
||
|
||
void GekkoCore::Fetch(uint32_t addr, uint32_t* reg)
|
||
{
|
||
int WIMG;
|
||
|
||
uint32_t pa = EffectiveToPhysical(addr, MmuAccess::Execute, WIMG);
|
||
if (pa == BadAddress)
|
||
{
|
||
Exception(Exception::EXCEPTION_ISI);
|
||
return;
|
||
}
|
||
|
||
// You don't need to use the ICache in BS1, even if it is enabled
|
||
if (pa >= BOOTROM_START_ADDRESS) {
|
||
PIReadWord(pa, reg);
|
||
return;
|
||
}
|
||
|
||
if (icache->IsEnabled() && (WIMG & WIMG_I) == 0)
|
||
{
|
||
icache->ReadWord(pa, reg);
|
||
return;
|
||
}
|
||
|
||
PIReadWord(pa, reg);
|
||
}
|
||
|
||
uint8_t* GekkoCore::GetDataCachePointer(uint32_t phys_addr)
|
||
{
|
||
return cache->GetCachePointer(phys_addr);
|
||
}
|
||
|
||
}
|
||
|
||
|
||
namespace Gekko
|
||
{
|
||
bool TLB::Exists(uint32_t ea, uint32_t& pa, int& WIMG)
|
||
{
|
||
auto it = tlb.find(ea >> 12);
|
||
if (it != tlb.end())
|
||
{
|
||
TLBEntry* entry = it->second;
|
||
pa = (entry->addressTag << 12) | (ea & 0xfff);
|
||
WIMG = entry->wimg;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void TLB::Map(uint32_t ea, uint32_t pa, uint32_t pc, int WIMG)
|
||
{
|
||
TLBEntry* entry = new TLBEntry;
|
||
entry->addressTag = pa >> 12;
|
||
entry->wimg = WIMG;
|
||
entry->pc = pc;
|
||
tlb[ea >> 12] = entry;
|
||
}
|
||
|
||
void TLB::Invalidate(uint32_t ea)
|
||
{
|
||
auto it = tlb.find(ea >> 12);
|
||
if (it != tlb.end())
|
||
{
|
||
delete it->second;
|
||
tlb.erase(it);
|
||
}
|
||
}
|
||
|
||
void TLB::InvalidateAll()
|
||
{
|
||
for (auto it = tlb.begin(); it != tlb.end(); ++it)
|
||
{
|
||
delete it->second;
|
||
}
|
||
tlb.clear();
|
||
}
|
||
|
||
void TLB::Dump()
|
||
{
|
||
for (auto it = tlb.begin(); it != tlb.end(); ++it)
|
||
{
|
||
uint32_t ea = it->first << 12;
|
||
TLBEntry* entry = it->second;
|
||
Report(Channel::CPU, "EA 0x%08X -> PA 0x%08X (wimg: %d, pc: 0x%08X)\n", ea, entry->addressTag << 12, entry->wimg, entry->pc);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
// Support for breakpoints.
|
||
|
||
// After switching the Gekko emulation as a separate thread, the implementation of breakpoints is trivial.
|
||
// When a breakpoint occurs, we just do Suspend of the Gekko thread.
|
||
// And since all the other subsystems are tied to the Gekko thread (except for the UI and Debugger, it has an independent thread)
|
||
// the whole system stops with the processor.
|
||
|
||
namespace Gekko
|
||
{
|
||
void GekkoCore::AddBreakpoint(uint32_t addr)
|
||
{
|
||
breakPointsLock.Lock();
|
||
bool exists = false;
|
||
for (auto it = breakPointsExecute.begin(); it != breakPointsExecute.end(); ++it)
|
||
{
|
||
if (*it == addr)
|
||
{
|
||
exists = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!exists)
|
||
{
|
||
Report(Channel::CPU, "Breakpoint added: 0x%08X\n", addr);
|
||
breakPointsExecute.push_back(addr);
|
||
EnableTestBreakpoints = true;
|
||
}
|
||
breakPointsLock.Unlock();
|
||
}
|
||
|
||
void GekkoCore::RemoveBreakpoint(uint32_t addr)
|
||
{
|
||
breakPointsLock.Lock();
|
||
bool exists = false;
|
||
for (auto it = breakPointsExecute.begin(); it != breakPointsExecute.end(); ++it)
|
||
{
|
||
if (*it == addr)
|
||
{
|
||
exists = true;
|
||
break;
|
||
}
|
||
}
|
||
if (exists)
|
||
{
|
||
Report(Channel::CPU, "Breakpoint removed: 0x%08X\n", addr);
|
||
breakPointsExecute.remove(addr);
|
||
}
|
||
if (breakPointsExecute.size() == 0)
|
||
{
|
||
EnableTestBreakpoints = false;
|
||
}
|
||
breakPointsLock.Unlock();
|
||
}
|
||
|
||
void GekkoCore::AddReadBreak(uint32_t addr)
|
||
{
|
||
breakPointsLock.Lock();
|
||
breakPointsRead.push_back(addr);
|
||
breakPointsLock.Unlock();
|
||
EnableTestReadBreakpoints = true;
|
||
}
|
||
|
||
void GekkoCore::AddWriteBreak(uint32_t addr)
|
||
{
|
||
breakPointsLock.Lock();
|
||
breakPointsWrite.push_back(addr);
|
||
breakPointsLock.Unlock();
|
||
EnableTestWriteBreakpoints = true;
|
||
}
|
||
|
||
void GekkoCore::ClearBreakpoints()
|
||
{
|
||
breakPointsLock.Lock();
|
||
breakPointsExecute.clear();
|
||
breakPointsRead.clear();
|
||
breakPointsWrite.clear();
|
||
breakPointsLock.Unlock();
|
||
EnableTestBreakpoints = false;
|
||
EnableTestReadBreakpoints = false;
|
||
EnableTestWriteBreakpoints = false;
|
||
}
|
||
|
||
bool GekkoCore::TestBreakpoints()
|
||
{
|
||
if (oneShotBreakpoint != BadAddress && regs.pc == oneShotBreakpoint)
|
||
{
|
||
Halt("One shot breakpoint at addr: 0x%08X\n", oneShotBreakpoint);
|
||
oneShotBreakpoint = BadAddress;
|
||
return true;
|
||
}
|
||
|
||
uint32_t addr = BadAddress;
|
||
|
||
breakPointsLock.Lock();
|
||
for (auto it = breakPointsExecute.begin(); it != breakPointsExecute.end(); ++it)
|
||
{
|
||
if (*it == regs.pc)
|
||
{
|
||
addr = *it;
|
||
break;
|
||
}
|
||
}
|
||
breakPointsLock.Unlock();
|
||
|
||
if (addr != BadAddress)
|
||
{
|
||
Halt("Gekko suspended at addr: 0x%08X\n", addr);
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
void GekkoCore::TestReadBreakpoints(uint32_t accessAddress)
|
||
{
|
||
uint32_t addr = BadAddress;
|
||
|
||
breakPointsLock.Lock();
|
||
for (auto it = breakPointsRead.begin(); it != breakPointsRead.end(); ++it)
|
||
{
|
||
if (*it == accessAddress)
|
||
{
|
||
addr = *it;
|
||
break;
|
||
}
|
||
}
|
||
breakPointsLock.Unlock();
|
||
|
||
if (addr != BadAddress)
|
||
{
|
||
Halt("Gekko suspended trying to read: 0x%08X\n", addr);
|
||
}
|
||
}
|
||
|
||
void GekkoCore::TestWriteBreakpoints(uint32_t accessAddress)
|
||
{
|
||
uint32_t addr = BadAddress;
|
||
|
||
breakPointsLock.Lock();
|
||
for (auto it = breakPointsWrite.begin(); it != breakPointsWrite.end(); ++it)
|
||
{
|
||
if (*it == accessAddress)
|
||
{
|
||
addr = *it;
|
||
break;
|
||
}
|
||
}
|
||
breakPointsLock.Unlock();
|
||
|
||
if (addr != BadAddress)
|
||
{
|
||
Halt("Gekko suspended trying to write: 0x%08X\n", addr);
|
||
}
|
||
}
|
||
|
||
void GekkoCore::AddOneShotBreakpoint(uint32_t addr)
|
||
{
|
||
oneShotBreakpoint = addr;
|
||
EnableTestBreakpoints = true;
|
||
}
|
||
|
||
void GekkoCore::ToggleBreakpoint(uint32_t addr)
|
||
{
|
||
if (IsBreakpoint(addr))
|
||
RemoveBreakpoint(addr);
|
||
else
|
||
AddBreakpoint(addr);
|
||
}
|
||
|
||
bool GekkoCore::IsBreakpoint(uint32_t addr)
|
||
{
|
||
bool exists = false;
|
||
breakPointsLock.Lock();
|
||
for (auto it = breakPointsExecute.begin(); it != breakPointsExecute.end(); ++it)
|
||
{
|
||
if (*it == addr)
|
||
{
|
||
exists = true;
|
||
break;
|
||
}
|
||
}
|
||
breakPointsLock.Unlock();
|
||
return exists;
|
||
}
|
||
}
|
||
|
||
|
||
// Gekko caches support (including Data locked cache)
|
||
|
||
// As defined by the PowerPC architecture, they are physically indexed.
|
||
|
||
// Emulation of access to the cache is simple - a copy is created for the main memory, same size as the main RAM.
|
||
// If cached access is performed, all reads and writes are made from this buffer, otherwise from RAM.
|
||
// Invalidation causes new data to be loaded from RAM into the cache buffer.
|
||
|
||
// We do not support scattering for a locked cache and assume that it is locked as a fixed chunk.
|
||
|
||
|
||
namespace Gekko
|
||
{
|
||
Cache::Cache(GekkoCore* core)
|
||
{
|
||
this->core = core;
|
||
|
||
cacheData = new uint8_t[cacheSize];
|
||
|
||
modifiedBlocks = new bool[cacheSize >> 5];
|
||
|
||
invalidBlocks = new bool[cacheSize >> 5];
|
||
|
||
LockedCache = new uint8_t[16 * 1024];
|
||
|
||
Reset();
|
||
}
|
||
|
||
Cache::~Cache()
|
||
{
|
||
delete[] cacheData;
|
||
delete[] modifiedBlocks;
|
||
delete[] invalidBlocks;
|
||
delete[] LockedCache;
|
||
}
|
||
|
||
void Cache::Reset()
|
||
{
|
||
Report(Channel::CPU, "Cache::Reset\n");
|
||
FlashInvalidate();
|
||
}
|
||
|
||
void Cache::Enable(bool enable)
|
||
{
|
||
enabled = enable;
|
||
|
||
if (log >= CacheLogLevel::Commands)
|
||
{
|
||
Report(Channel::CPU, "Cache::Enable %i\n", enable ? 1 : 0);
|
||
}
|
||
}
|
||
|
||
void Cache::Freeze(bool freeze)
|
||
{
|
||
frozen = freeze;
|
||
|
||
if (log >= CacheLogLevel::Commands)
|
||
{
|
||
Report(Channel::CPU, "Cache::Freeze %i\n", freeze ? 1 : 0);
|
||
}
|
||
}
|
||
|
||
void Cache::LockedEnable(bool enable)
|
||
{
|
||
lcenabled = enable;
|
||
|
||
if (log >= CacheLogLevel::Commands)
|
||
{
|
||
Report(Channel::CPU, "Cache::LockedEnable %i\n", enable ? 1 : 0);
|
||
}
|
||
}
|
||
|
||
bool Cache::IsDirty(uint32_t pa)
|
||
{
|
||
size_t blockNum = pa >> 5;
|
||
return modifiedBlocks[blockNum];
|
||
}
|
||
|
||
void Cache::SetDirty(uint32_t pa, bool dirty)
|
||
{
|
||
size_t blockNum = pa >> 5;
|
||
|
||
if (dirty == modifiedBlocks[blockNum])
|
||
return;
|
||
|
||
modifiedBlocks[blockNum] = dirty;
|
||
|
||
if (log >= CacheLogLevel::MemOps && dirty)
|
||
{
|
||
Report(Channel::CPU, "Cache::SetDirty. pa: 0x%08X\n", pa & ~0x1f);
|
||
}
|
||
}
|
||
|
||
bool Cache::IsInvalid(uint32_t pa)
|
||
{
|
||
size_t blockNum = pa >> 5;
|
||
return invalidBlocks[blockNum];
|
||
}
|
||
|
||
void Cache::SetInvalid(uint32_t pa, bool invalid)
|
||
{
|
||
size_t blockNum = pa >> 5;
|
||
|
||
if (invalid == invalidBlocks[blockNum])
|
||
return;
|
||
|
||
invalidBlocks[blockNum] = invalid;
|
||
|
||
if (log >= CacheLogLevel::MemOps && invalid)
|
||
{
|
||
Report(Channel::CPU, "Cache::SetInvalid. pa: 0x%08X\n", pa & ~0x1f);
|
||
}
|
||
}
|
||
|
||
void Cache::Flush(uint32_t pa)
|
||
{
|
||
if (pa >= cacheSize)
|
||
return;
|
||
|
||
if (IsDirty(pa) && !IsInvalid(pa))
|
||
{
|
||
CastOut(pa);
|
||
SetDirty(pa, false);
|
||
}
|
||
SetInvalid(pa, true);
|
||
|
||
if (log >= CacheLogLevel::Commands)
|
||
{
|
||
Report(Channel::CPU, "Cache::Flush 0x%08X, pc: 0x%08X\n", pa, core->regs.pc);
|
||
}
|
||
}
|
||
|
||
void Cache::Invalidate(uint32_t pa)
|
||
{
|
||
if (pa >= cacheSize)
|
||
return;
|
||
|
||
SetInvalid(pa, true);
|
||
|
||
if (log >= CacheLogLevel::Commands)
|
||
{
|
||
Report(Channel::CPU, "Cache::Invalidate 0x%08X, pc: 0x%08X\n", pa, core->regs.pc);
|
||
}
|
||
}
|
||
|
||
void Cache::FlashInvalidate()
|
||
{
|
||
size_t blocks_num = cacheSize >> 5;
|
||
for (size_t n = 0; n < blocks_num; n++) {
|
||
modifiedBlocks[n] = false;
|
||
invalidBlocks[n] = true;
|
||
}
|
||
}
|
||
|
||
void Cache::Store(uint32_t pa)
|
||
{
|
||
if (pa >= cacheSize)
|
||
return;
|
||
|
||
if (IsDirty(pa) && !IsInvalid(pa))
|
||
{
|
||
CastOut(pa);
|
||
SetDirty(pa, false);
|
||
}
|
||
|
||
if (log >= CacheLogLevel::Commands)
|
||
{
|
||
Report(Channel::CPU, "Cache::Store 0x%08X\n", pa);
|
||
}
|
||
}
|
||
|
||
void Cache::Touch(uint32_t pa)
|
||
{
|
||
if (pa >= cacheSize)
|
||
return;
|
||
|
||
if (!IsDirty(pa))
|
||
{
|
||
CastIn(pa);
|
||
SetInvalid(pa, false);
|
||
SetDirty(pa, false); // Valid & Not Dirty
|
||
}
|
||
|
||
if (log >= CacheLogLevel::Commands)
|
||
{
|
||
Report(Channel::CPU, "Cache::Touch 0x%08X\n", pa);
|
||
}
|
||
}
|
||
|
||
void Cache::TouchForStore(uint32_t pa)
|
||
{
|
||
if (pa >= cacheSize)
|
||
return;
|
||
|
||
if (!IsDirty(pa))
|
||
{
|
||
CastIn(pa);
|
||
SetInvalid(pa, false);
|
||
SetDirty(pa, true); // Valid & Dirty
|
||
}
|
||
|
||
if (log >= CacheLogLevel::Commands)
|
||
{
|
||
Report(Channel::CPU, "Cache::TouchForStore 0x%08X\n", pa);
|
||
}
|
||
}
|
||
|
||
void Cache::Zero(uint32_t pa)
|
||
{
|
||
if (pa >= cacheSize)
|
||
return;
|
||
|
||
memset(&cacheData[pa & ~0x1f], 0, 32);
|
||
SetDirty(pa, true);
|
||
SetInvalid(pa, false);
|
||
|
||
if (log >= CacheLogLevel::Commands)
|
||
{
|
||
Report(Channel::CPU, "Cache::Zero 0x%08X\n", pa);
|
||
}
|
||
}
|
||
|
||
void Cache::ZeroLocked(uint32_t pa)
|
||
{
|
||
if (log >= CacheLogLevel::Commands)
|
||
{
|
||
Report(Channel::CPU, "Cache::ZeroLocked 0x%08X\n", pa);
|
||
}
|
||
|
||
LockedCacheAddr = pa & ~0x3FFF;
|
||
}
|
||
|
||
// The documentation says that the cache is casted by single-beat transactions, but for speed we will do casting by burts.
|
||
|
||
void Cache::CastIn(uint32_t pa)
|
||
{
|
||
assert(pa < cacheSize);
|
||
|
||
if (frozen)
|
||
return;
|
||
|
||
if (log >= CacheLogLevel::MemOps)
|
||
{
|
||
Report(Channel::CPU, "Cache::CastIn: 0x%08X\n", pa & ~0x1f);
|
||
}
|
||
|
||
PIReadBurst(pa & ~0x1f, &cacheData[pa & ~0x1f]);
|
||
}
|
||
|
||
void Cache::CastOut(uint32_t pa)
|
||
{
|
||
assert(pa < cacheSize);
|
||
|
||
if (frozen)
|
||
return;
|
||
|
||
if (log >= CacheLogLevel::MemOps)
|
||
{
|
||
Report(Channel::CPU, "Cache::CastOut: 0x%08X\n", pa & ~0x1f);
|
||
}
|
||
|
||
PIWriteBurst(pa & ~0x1f, &cacheData[pa & ~0x1f]);
|
||
}
|
||
|
||
void Cache::ReadByte(uint32_t addr, uint32_t* reg)
|
||
{
|
||
// Locked cache
|
||
if (IsLockedEnable())
|
||
{
|
||
if ((addr & ~0x3fff) == LockedCacheAddr)
|
||
{
|
||
uint8_t* ptr = &LockedCache[addr & 0x3fff];
|
||
*reg = (uint32_t)*ptr;
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (addr >= cacheSize)
|
||
return;
|
||
|
||
if (IsInvalid(addr))
|
||
{
|
||
CastIn(addr);
|
||
SetInvalid(addr, false);
|
||
SetDirty(addr, false);
|
||
}
|
||
*reg = cacheData[addr];
|
||
|
||
if (log >= CacheLogLevel::MemOps)
|
||
{
|
||
Report(Channel::CPU, "Cache::ReadByte. addr: 0x%08X, *reg: 0x%08X\n", addr, *reg);
|
||
}
|
||
}
|
||
|
||
void Cache::WriteByte(uint32_t addr, uint32_t data)
|
||
{
|
||
// Locked cache
|
||
if (IsLockedEnable())
|
||
{
|
||
if ((addr & ~0x3fff) == LockedCacheAddr)
|
||
{
|
||
uint8_t* ptr = &LockedCache[addr & 0x3fff];
|
||
*ptr = (uint8_t)data;
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (addr >= cacheSize)
|
||
return;
|
||
|
||
if (IsInvalid(addr))
|
||
{
|
||
CastIn(addr);
|
||
SetInvalid(addr, false);
|
||
}
|
||
cacheData[addr] = (uint8_t)data;
|
||
|
||
if (log >= CacheLogLevel::MemOps)
|
||
{
|
||
Report(Channel::CPU, "Cache::WriteByte. addr: 0x%08X, data: 0x%08X\n", addr, data);
|
||
}
|
||
|
||
SetDirty(addr, true);
|
||
}
|
||
|
||
void Cache::ReadHalf(uint32_t addr, uint32_t* reg)
|
||
{
|
||
// Locked cache
|
||
if (IsLockedEnable())
|
||
{
|
||
if ((addr & ~0x3fff) == LockedCacheAddr)
|
||
{
|
||
uint8_t* ptr = &LockedCache[addr & 0x3fff];
|
||
*reg = (uint32_t)_BYTESWAP_UINT16(*(uint16_t*)ptr);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (addr >= cacheSize)
|
||
return;
|
||
|
||
if (IsInvalid(addr))
|
||
{
|
||
CastIn(addr);
|
||
SetInvalid(addr, false);
|
||
SetDirty(addr, false);
|
||
}
|
||
|
||
if ((addr & 0x1f) > (32 - sizeof(uint16_t)))
|
||
{
|
||
if (complain_unaligned) {
|
||
Report(Channel::CPU, "Cache::ReadHalf: Unaligned cache access addr:0x%08X!\n", addr);
|
||
}
|
||
|
||
uint32_t nextCacheLineAddr = addr + sizeof(uint16_t);
|
||
|
||
if (IsInvalid(nextCacheLineAddr))
|
||
{
|
||
CastIn(nextCacheLineAddr);
|
||
SetInvalid(nextCacheLineAddr, false);
|
||
SetDirty(nextCacheLineAddr, false);
|
||
}
|
||
}
|
||
|
||
*reg = _BYTESWAP_UINT16(*(uint16_t*)&cacheData[addr]);
|
||
|
||
if (log >= CacheLogLevel::MemOps)
|
||
{
|
||
Report(Channel::CPU, "Cache::ReadHalf. addr: 0x%08X, *reg: 0x%08X\n", addr, *reg);
|
||
}
|
||
}
|
||
|
||
void Cache::WriteHalf(uint32_t addr, uint32_t data)
|
||
{
|
||
// Locked cache
|
||
if (IsLockedEnable())
|
||
{
|
||
if ((addr & ~0x3fff) == LockedCacheAddr)
|
||
{
|
||
uint8_t* ptr = &LockedCache[addr & 0x3fff];
|
||
*(uint16_t*)ptr = _BYTESWAP_UINT16((uint16_t)data);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (addr >= cacheSize)
|
||
return;
|
||
|
||
if (IsInvalid(addr))
|
||
{
|
||
CastIn(addr);
|
||
SetInvalid(addr, false);
|
||
}
|
||
|
||
if ((addr & 0x1f) > (32 - sizeof(uint16_t)))
|
||
{
|
||
if (complain_unaligned) {
|
||
Report(Channel::CPU, "Cache::WriteHalf: Unaligned cache access addr:0x%08X!\n", addr);
|
||
}
|
||
|
||
uint32_t nextCacheLineAddr = addr + sizeof(uint16_t);
|
||
|
||
if (IsInvalid(nextCacheLineAddr))
|
||
{
|
||
CastIn(nextCacheLineAddr);
|
||
SetInvalid(nextCacheLineAddr, false);
|
||
SetDirty(nextCacheLineAddr, false);
|
||
}
|
||
|
||
SetDirty(nextCacheLineAddr, true);
|
||
}
|
||
|
||
*(uint16_t*)&cacheData[addr] = _BYTESWAP_UINT16((uint16_t)data);
|
||
|
||
if (log >= CacheLogLevel::MemOps)
|
||
{
|
||
Report(Channel::CPU, "Cache::WriteHalf. addr: 0x%08X, data: 0x%08X\n", addr, data);
|
||
}
|
||
|
||
SetDirty(addr, true);
|
||
}
|
||
|
||
void Cache::ReadWord(uint32_t addr, uint32_t* reg)
|
||
{
|
||
// Locked cache
|
||
if (IsLockedEnable())
|
||
{
|
||
if ((addr & ~0x3fff) == LockedCacheAddr)
|
||
{
|
||
uint8_t* ptr = &LockedCache[addr & 0x3fff];
|
||
*reg = _BYTESWAP_UINT32(*(uint32_t*)ptr);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (addr >= cacheSize)
|
||
return;
|
||
|
||
if (IsInvalid(addr))
|
||
{
|
||
CastIn(addr);
|
||
SetInvalid(addr, false);
|
||
SetDirty(addr, false);
|
||
}
|
||
|
||
if ((addr & 0x1f) > (32 - sizeof(uint32_t)))
|
||
{
|
||
if (complain_unaligned) {
|
||
Report(Channel::CPU, "Cache::ReadWord: Unaligned cache access addr:0x%08X!\n", addr);
|
||
}
|
||
|
||
uint32_t nextCacheLineAddr = addr + sizeof(uint32_t);
|
||
|
||
if (IsInvalid(nextCacheLineAddr))
|
||
{
|
||
CastIn(nextCacheLineAddr);
|
||
SetInvalid(nextCacheLineAddr, false);
|
||
SetDirty(nextCacheLineAddr, false);
|
||
}
|
||
}
|
||
|
||
*reg = _BYTESWAP_UINT32(*(uint32_t*)&cacheData[addr]);
|
||
|
||
if (log >= CacheLogLevel::MemOps)
|
||
{
|
||
Report(Channel::CPU, "Cache::ReadWord. addr: 0x%08X, *reg: 0x%08X\n", addr, *reg);
|
||
}
|
||
}
|
||
|
||
void Cache::WriteWord(uint32_t addr, uint32_t data)
|
||
{
|
||
// Locked cache
|
||
if (IsLockedEnable())
|
||
{
|
||
if ((addr & ~0x3fff) == LockedCacheAddr)
|
||
{
|
||
uint8_t* ptr = &LockedCache[addr & 0x3fff];
|
||
*(uint32_t*)ptr = _BYTESWAP_UINT32(data);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (addr >= cacheSize)
|
||
return;
|
||
|
||
if (IsInvalid(addr))
|
||
{
|
||
CastIn(addr);
|
||
SetInvalid(addr, false);
|
||
}
|
||
|
||
if ((addr & 0x1f) > (32 - sizeof(uint32_t)))
|
||
{
|
||
if (complain_unaligned) {
|
||
Report(Channel::CPU, "Cache::WriteWord: Unaligned cache access addr:0x%08X!\n", addr);
|
||
}
|
||
|
||
uint32_t nextCacheLineAddr = addr + sizeof(uint32_t);
|
||
|
||
if (IsInvalid(nextCacheLineAddr))
|
||
{
|
||
CastIn(nextCacheLineAddr);
|
||
SetInvalid(nextCacheLineAddr, false);
|
||
SetDirty(nextCacheLineAddr, false);
|
||
}
|
||
|
||
SetDirty(nextCacheLineAddr, true);
|
||
}
|
||
|
||
*(uint32_t*)&cacheData[addr] = _BYTESWAP_UINT32(data);
|
||
|
||
if (log >= CacheLogLevel::MemOps)
|
||
{
|
||
Report(Channel::CPU, "Cache::WriteWord. addr: 0x%08X, data: 0x%08X\n", addr, data);
|
||
}
|
||
|
||
SetDirty(addr, true);
|
||
}
|
||
|
||
void Cache::ReadDouble(uint32_t addr, uint64_t* reg)
|
||
{
|
||
// Locked cache
|
||
if (IsLockedEnable())
|
||
{
|
||
if ((addr & ~0x3fff) == LockedCacheAddr)
|
||
{
|
||
uint8_t* buf = &LockedCache[addr & 0x3fff];
|
||
*reg = _BYTESWAP_UINT64(*(uint64_t*)buf);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (addr >= cacheSize)
|
||
return;
|
||
|
||
if (IsInvalid(addr))
|
||
{
|
||
CastIn(addr);
|
||
SetInvalid(addr, false);
|
||
SetDirty(addr, false);
|
||
}
|
||
|
||
if ((addr & 0x1f) > (32 - sizeof(uint64_t)))
|
||
{
|
||
if (complain_unaligned) {
|
||
Report(Channel::CPU, "Cache::ReadDouble: Unaligned cache access addr:0x%08X!\n", addr);
|
||
}
|
||
|
||
uint32_t nextCacheLineAddr = addr + sizeof(uint64_t);
|
||
|
||
if (IsInvalid(nextCacheLineAddr))
|
||
{
|
||
CastIn(nextCacheLineAddr);
|
||
SetInvalid(nextCacheLineAddr, false);
|
||
SetDirty(nextCacheLineAddr, false);
|
||
}
|
||
}
|
||
|
||
*reg = _BYTESWAP_UINT64(*(uint64_t*)&cacheData[addr]);
|
||
|
||
if (log >= CacheLogLevel::MemOps)
|
||
{
|
||
Report(Channel::CPU, "Cache::ReadDouble. addr: 0x%08X, *reg: 0x%llX\n", addr, *reg);
|
||
}
|
||
}
|
||
|
||
void Cache::WriteDouble(uint32_t addr, uint64_t* data)
|
||
{
|
||
// Locked cache
|
||
if (IsLockedEnable())
|
||
{
|
||
if ((addr & ~0x3fff) == LockedCacheAddr)
|
||
{
|
||
uint8_t* buf = &LockedCache[addr & 0x3fff];
|
||
*(uint64_t*)buf = _BYTESWAP_UINT64(*data);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (addr >= cacheSize)
|
||
return;
|
||
|
||
if (IsInvalid(addr))
|
||
{
|
||
CastIn(addr);
|
||
SetInvalid(addr, false);
|
||
}
|
||
|
||
if ((addr & 0x1f) > (32 - sizeof(uint64_t)))
|
||
{
|
||
if (complain_unaligned) {
|
||
Report(Channel::CPU, "Cache::WriteDouble: Unaligned cache access addr:0x%08X!\n", addr);
|
||
}
|
||
|
||
uint32_t nextCacheLineAddr = addr + sizeof(uint64_t);
|
||
|
||
if (IsInvalid(nextCacheLineAddr))
|
||
{
|
||
CastIn(nextCacheLineAddr);
|
||
SetInvalid(nextCacheLineAddr, false);
|
||
SetDirty(nextCacheLineAddr, false);
|
||
}
|
||
|
||
SetDirty(nextCacheLineAddr, true);
|
||
}
|
||
|
||
*(uint64_t*)&cacheData[addr] = _BYTESWAP_UINT64(*data);
|
||
|
||
if (log >= CacheLogLevel::MemOps)
|
||
{
|
||
Report(Channel::CPU, "Cache::WriteDouble. addr: 0x%08X, data: 0x%llX\n", addr, data);
|
||
}
|
||
|
||
SetDirty(addr, true);
|
||
}
|
||
|
||
void Cache::LockedCacheDma(bool MemToCache, uint32_t memaddr, uint32_t lcaddr, size_t bursts)
|
||
{
|
||
if (MemToCache)
|
||
{ // 1 load - transfer from external memory to locked cache
|
||
|
||
if (log >= CacheLogLevel::MemOps)
|
||
{
|
||
Report(Channel::CPU, "Load Locked Cache: memadr: 0x%08X, lcaddr: 0x%08X, bursts: %i\n", memaddr, lcaddr, bursts);
|
||
}
|
||
|
||
for (size_t i = 0; i < bursts; i++)
|
||
{
|
||
PIReadBurst(memaddr, &LockedCache[lcaddr & 0x3fff]);
|
||
memaddr += 32;
|
||
lcaddr += 32;
|
||
}
|
||
}
|
||
else
|
||
{ // 0 store - transfer from locked cache to external memory
|
||
|
||
if (log >= CacheLogLevel::MemOps)
|
||
{
|
||
Report(Channel::CPU, "Store Locked Cache: memadr: 0x%08X, lcaddr: 0x%08X, bursts: %i\n", memaddr, lcaddr, bursts);
|
||
}
|
||
|
||
for (size_t i = 0; i < bursts; i++)
|
||
{
|
||
PIWriteBurst(memaddr, &LockedCache[lcaddr & 0x3fff]);
|
||
memaddr += 32;
|
||
lcaddr += 32;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
namespace Gekko
|
||
{
|
||
void GatherBuffer::Reset()
|
||
{
|
||
memset(fifo, 0, sizeof(fifo));
|
||
readPtr = 0;
|
||
writePtr = 0;
|
||
retireTimeout = 0;
|
||
|
||
if (log)
|
||
{
|
||
Report(Channel::CPU, "GatherBuffer::Reset");
|
||
}
|
||
}
|
||
|
||
size_t GatherBuffer::GatherSize()
|
||
{
|
||
if (writePtr >= readPtr)
|
||
{
|
||
return writePtr - readPtr;
|
||
}
|
||
else
|
||
{
|
||
return (sizeof(fifo) - readPtr) + writePtr;
|
||
}
|
||
}
|
||
|
||
void GatherBuffer::WriteBytes(uint8_t* data, size_t size)
|
||
{
|
||
if (log)
|
||
{
|
||
char byteText[10];
|
||
std::string text;
|
||
|
||
for (int i = 0; i < size; i++)
|
||
{
|
||
sprintf(byteText, "%02X ", data[i]);
|
||
text += byteText;
|
||
}
|
||
|
||
Report(Channel::CPU, "GatherBuffer::WriteBytes: %s", text.c_str());
|
||
}
|
||
|
||
if (size < 4)
|
||
{
|
||
for (int i = 0; i < size; i++)
|
||
{
|
||
fifo[writePtr] = data[i];
|
||
writePtr++;
|
||
if (writePtr >= sizeof(fifo))
|
||
{
|
||
writePtr = 0;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if ((writePtr + size) < sizeof(fifo))
|
||
{
|
||
memcpy(&fifo[writePtr], data, size);
|
||
writePtr += size;
|
||
}
|
||
else
|
||
{
|
||
size_t part1Size = sizeof(fifo) - writePtr;
|
||
memcpy(&fifo[writePtr], data, part1Size);
|
||
writePtr = size - part1Size;
|
||
memcpy(fifo, data + part1Size, writePtr);
|
||
}
|
||
}
|
||
|
||
if (GatherSize() >= 32)
|
||
{
|
||
uint8_t burstData[32];
|
||
|
||
for (int i = 0; i < sizeof(burstData); i++)
|
||
{
|
||
burstData[i] = fifo[readPtr];
|
||
readPtr++;
|
||
if (readPtr >= sizeof(fifo))
|
||
{
|
||
readPtr = 0;
|
||
}
|
||
}
|
||
|
||
if (log)
|
||
{
|
||
Report(Channel::CPU, "Burst gather buffer. Bytes left: %zi\n", GatherSize());
|
||
}
|
||
|
||
PIWriteBurst(core->regs.spr[(int)SPR::WPAR] & ~0x1f, burstData);
|
||
}
|
||
}
|
||
|
||
void GatherBuffer::Write8(uint8_t value)
|
||
{
|
||
WriteBytes(&value, 1);
|
||
}
|
||
|
||
void GatherBuffer::Write16(uint16_t value)
|
||
{
|
||
uint8_t data[2];
|
||
*(uint16_t*)data = _BYTESWAP_UINT16(value);
|
||
WriteBytes(data, 2);
|
||
}
|
||
|
||
void GatherBuffer::Write32(uint32_t value)
|
||
{
|
||
uint8_t data[4];
|
||
*(uint32_t*)data = _BYTESWAP_UINT32(value);
|
||
WriteBytes(data, 4);
|
||
}
|
||
|
||
void GatherBuffer::Write64(uint64_t value)
|
||
{
|
||
uint8_t data[8];
|
||
*(uint64_t*)data = _BYTESWAP_UINT64(value);
|
||
WriteBytes(data, 8);
|
||
}
|
||
|
||
bool GatherBuffer::NotEmpty()
|
||
{
|
||
// The GatherBuffer has an undocumented feature - after a certain number of cycles the data in it is destroyed and it becomes free
|
||
|
||
retireTimeout++;
|
||
if (retireTimeout >= GEKKOCORE_GATHER_BUFFER_RETIRE_TICKS)
|
||
{
|
||
Reset();
|
||
}
|
||
|
||
return readPtr != writePtr;
|
||
}
|
||
}
|
||
|
||
|
||
// MMU never throws Gekko exceptions. If something went wrong, BadAddress is returned. Then the consumer decides what to do.
|
||
|
||
// The result of the MMU is placed in MmuLastResult and translated address as output.
|
||
|
||
// We also use BadAddress as a signal that the translation did not pass (a special address that is usually not used by anyone).
|
||
|
||
namespace Gekko
|
||
{
|
||
// Native address translation defined by PowerPC architecture. There are some alien moments (Hash for Page Tables), but overall its fine.
|
||
|
||
uint32_t GekkoCore::EffectiveToPhysicalMmu(uint32_t ea, MmuAccess type, int& WIMG)
|
||
{
|
||
uint32_t pa;
|
||
|
||
WIMG = 0;
|
||
|
||
// Try TLB
|
||
|
||
#if GEKKOCORE_USE_TLB
|
||
TLB* tlb = (type == MmuAccess::Execute) ? &itlb : &dtlb;
|
||
|
||
if (tlb->Exists(ea, pa, WIMG))
|
||
{
|
||
return pa;
|
||
}
|
||
#endif
|
||
|
||
// First, try the block translation, if it doesn<73>t work, try the Page Table.
|
||
|
||
if (!BlockAddressTranslation(ea, pa, type, WIMG))
|
||
{
|
||
pa = SegmentTranslation(ea, type, WIMG);
|
||
}
|
||
|
||
return pa;
|
||
}
|
||
|
||
bool GekkoCore::BlockAddressTranslation(uint32_t ea, uint32_t& pa, MmuAccess type, int& WIMG)
|
||
{
|
||
// Ignore BAT access rights for now (not used in embodiment system)
|
||
|
||
if (type == MmuAccess::Execute)
|
||
{
|
||
if ((regs.msr & MSR_IR) == 0)
|
||
{
|
||
WIMG = WIMG_G;
|
||
pa = ea;
|
||
return true;
|
||
}
|
||
|
||
for (int n = 0; n < 4; n++)
|
||
{
|
||
bool valid = (*ibatu[n] & 3) != 0;
|
||
if (!valid)
|
||
continue;
|
||
|
||
uint32_t bepi = BATBEPI(*ibatu[n]);
|
||
uint32_t bl = BATBL(*ibatu[n]);
|
||
uint32_t tst = (ea >> 17) & (0x7800 | ~bl);
|
||
if (bepi == tst)
|
||
{
|
||
pa = BATBRPN(*ibatl[n]) | ((ea >> 17) & bl);
|
||
pa = (pa << 17) | (ea & 0x1ffff);
|
||
WIMG = (*ibatl[n] >> 3) & 0xf;
|
||
MmuLastResult = MmuResult::Ok;
|
||
// Put in TLB
|
||
#if GEKKOCORE_USE_TLB
|
||
itlb.Map(ea, pa, Core->regs.pc, WIMG);
|
||
#endif
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if ((regs.msr & MSR_DR) == 0)
|
||
{
|
||
WIMG = WIMG_M | WIMG_G;
|
||
pa = ea;
|
||
return true;
|
||
}
|
||
|
||
for (int n = 0; n < 4; n++)
|
||
{
|
||
bool valid = (*dbatu[n] & 3) != 0;
|
||
if (!valid)
|
||
continue;
|
||
|
||
uint32_t bepi = BATBEPI(*dbatu[n]);
|
||
uint32_t bl = BATBL(*dbatu[n]);
|
||
uint32_t tst = (ea >> 17) & (0x7800 | ~bl);
|
||
if (bepi == tst)
|
||
{
|
||
pa = BATBRPN(*dbatl[n]) | ((ea >> 17) & bl);
|
||
pa = (pa << 17) | (ea & 0x1ffff);
|
||
WIMG = (*dbatl[n] >> 3) & 0xf;
|
||
MmuLastResult = MmuResult::Ok;
|
||
// Put in TLB
|
||
#if GEKKOCORE_USE_TLB
|
||
dtlb.Map(ea, pa, Core->regs.pc, WIMG);
|
||
#endif
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
// No BAT match, continue page table translation
|
||
|
||
return false;
|
||
}
|
||
|
||
uint32_t GekkoCore::SegmentTranslation(uint32_t ea, MmuAccess type, int& WIMG)
|
||
{
|
||
int ptegUpper;
|
||
|
||
uint32_t sr = regs.sr[ea >> 28];
|
||
|
||
// Direct Store (T=1) is used by default by Dolphin OS to throw a DSI/ISI exception
|
||
if (sr & 0x8000'0000)
|
||
{
|
||
MmuLastResult = MmuResult::DirectStore;
|
||
return BadAddress;
|
||
}
|
||
|
||
if (sr & 0x1000'0000 && type == MmuAccess::Execute)
|
||
{
|
||
MmuLastResult = MmuResult::NoExecute;
|
||
return BadAddress;
|
||
}
|
||
|
||
uint32_t key;
|
||
if (regs.msr & MSR_PR)
|
||
{
|
||
key = sr & 0x2000'0000 ? 4 : 0; // Kp
|
||
}
|
||
else
|
||
{
|
||
key = sr & 0x4000'0000 ? 4 : 0; // Ks
|
||
}
|
||
|
||
// Calculate PTEG physical addresses
|
||
|
||
uint64_t vpn = (((uint64_t)sr & 0x00ffffff) << 16) | ((ea >> 12) & 0xffff);
|
||
uint32_t hash = ((uint32_t)(vpn >> 16) & 0x7ffff) ^ ((uint32_t)vpn & 0xffff);
|
||
|
||
uint32_t sdr = regs.spr[(int)SPR::SDR1];
|
||
|
||
uint32_t primaryPteAddr = 0;
|
||
ptegUpper = (sdr >> 16) & 0x1ff;
|
||
ptegUpper |= (sdr & 0x1ff) & ((hash >> 10) & 0x1ff);
|
||
primaryPteAddr |= sdr & 0xfe00'0000;
|
||
primaryPteAddr |= ptegUpper << 16;
|
||
primaryPteAddr |= (hash & 0x3ff) << 6;
|
||
|
||
hash = ~hash;
|
||
|
||
uint32_t secondaryPteAddr = 0;
|
||
ptegUpper = (sdr >> 16) & 0x1ff;
|
||
ptegUpper |= (sdr & 0x1ff) & ((hash >> 10) & 0x1ff);
|
||
secondaryPteAddr |= sdr & 0xfe00'0000;
|
||
secondaryPteAddr |= ptegUpper << 16;
|
||
secondaryPteAddr |= (hash & 0x3ff) << 6;
|
||
|
||
// TODO: unify these loops
|
||
|
||
// Try Primary PTEGs
|
||
|
||
for (int i = 0; i < 8; i++)
|
||
{
|
||
// Load PTE
|
||
|
||
uint32_t pte[2];
|
||
|
||
PIReadWord(primaryPteAddr, &pte[0]);
|
||
PIReadWord(primaryPteAddr + 4, &pte[1]);
|
||
|
||
// Check Hash Bit
|
||
|
||
if ((pte[0] & 0x40) != 0)
|
||
{
|
||
primaryPteAddr += 8;
|
||
continue;
|
||
}
|
||
|
||
// Valid and suitable? (PTE [VSID, API, V] = Seg Desc [VSID], EA[API], 1)
|
||
|
||
if (pte[0] & 0x8000'0000 &&
|
||
((pte[0] >> 7) & 0xffffff) == ((vpn >> 16) & 0xffffff) &&
|
||
(pte[0] & 0x3f) == ((vpn >> 10) & 0x3f))
|
||
{
|
||
// Referenced
|
||
pte[1] |= 0x100;
|
||
|
||
// Check Protection
|
||
uint32_t pp = key | (pte[1] & 3);
|
||
bool protectViolation = false;
|
||
if (type == MmuAccess::Read || type == MmuAccess::Execute)
|
||
{
|
||
if (pp == 0b100)
|
||
{
|
||
protectViolation = true;
|
||
}
|
||
}
|
||
else if (type == MmuAccess::Write)
|
||
{
|
||
if (pp == 0b011 || pp == 0b100 || pp == 0b101 || pp == 0b111)
|
||
{
|
||
protectViolation = true;
|
||
}
|
||
}
|
||
|
||
if (type == MmuAccess::Write && !protectViolation)
|
||
{
|
||
pte[1] |= 0x80; // Changed
|
||
}
|
||
PIWriteWord(primaryPteAddr + 4, pte[1]);
|
||
|
||
if (protectViolation)
|
||
{
|
||
switch (type)
|
||
{
|
||
case MmuAccess::Read:
|
||
MmuLastResult = MmuResult::ProtectedRead;
|
||
break;
|
||
case MmuAccess::Write:
|
||
MmuLastResult = MmuResult::ProtectedWrite;
|
||
break;
|
||
case MmuAccess::Execute:
|
||
MmuLastResult = MmuResult::ProtectedFetch;
|
||
break;
|
||
}
|
||
|
||
return BadAddress;
|
||
}
|
||
|
||
uint32_t pa = (pte[1] & ~0xfff) | (ea & 0xfff);
|
||
if (type != MmuAccess::Execute)
|
||
{
|
||
WIMG = (pte[1] >> 3) & 0xf;
|
||
}
|
||
// Put in TLB
|
||
#if GEKKOCORE_USE_TLB
|
||
if (type == MmuAccess::Execute) {
|
||
itlb.Map(ea, pa, Core->regs.pc, WIMG);
|
||
}
|
||
else {
|
||
dtlb.Map(ea, pa, Core->regs.pc, WIMG);
|
||
}
|
||
#endif
|
||
MmuLastResult = MmuResult::Ok;
|
||
return pa;
|
||
}
|
||
else
|
||
{
|
||
// Referenced
|
||
pte[1] |= 0x100;
|
||
PIWriteWord(primaryPteAddr + 4, pte[1]);
|
||
}
|
||
|
||
primaryPteAddr += 8;
|
||
}
|
||
|
||
// Try Secondary PTEGs
|
||
|
||
for (int i = 0; i < 8; i++)
|
||
{
|
||
// Load PTE
|
||
|
||
uint32_t pte[2];
|
||
|
||
PIReadWord(secondaryPteAddr, &pte[0]);
|
||
PIReadWord(secondaryPteAddr + 4, &pte[1]);
|
||
|
||
// Check Hash Bit
|
||
|
||
if ((pte[0] & 0x40) == 0)
|
||
{
|
||
secondaryPteAddr += 8;
|
||
continue;
|
||
}
|
||
|
||
// Valid and suitable? (PTE [VSID, API, V] = Seg Desc [VSID], EA[API], 1)
|
||
|
||
if (pte[0] & 0x8000'0000 &&
|
||
((pte[0] >> 7) & 0xffffff) == ((vpn >> 16) & 0xffffff) &&
|
||
(pte[0] & 0x3f) == ((vpn >> 10) & 0x3f))
|
||
{
|
||
// Referenced
|
||
pte[1] |= 0x100;
|
||
|
||
// Check Protection
|
||
uint32_t pp = key | (pte[1] & 3);
|
||
bool protectViolation = false;
|
||
if (type == MmuAccess::Read || type == MmuAccess::Execute)
|
||
{
|
||
if (pp == 0b100)
|
||
{
|
||
protectViolation = true;
|
||
}
|
||
}
|
||
else if (type == MmuAccess::Write)
|
||
{
|
||
if (pp == 0b011 || pp == 0b100 || pp == 0b101 || pp == 0b111)
|
||
{
|
||
protectViolation = true;
|
||
}
|
||
}
|
||
|
||
// Changed
|
||
if (type == MmuAccess::Write && !protectViolation)
|
||
{
|
||
pte[1] |= 0x80;
|
||
}
|
||
PIWriteWord(secondaryPteAddr + 4, pte[1]);
|
||
|
||
if (protectViolation)
|
||
{
|
||
switch (type)
|
||
{
|
||
case MmuAccess::Read:
|
||
MmuLastResult = MmuResult::ProtectedRead;
|
||
break;
|
||
case MmuAccess::Write:
|
||
MmuLastResult = MmuResult::ProtectedWrite;
|
||
break;
|
||
case MmuAccess::Execute:
|
||
MmuLastResult = MmuResult::ProtectedFetch;
|
||
break;
|
||
}
|
||
|
||
return BadAddress;
|
||
}
|
||
|
||
uint32_t pa = (pte[1] & ~0xfff) | (ea & 0xfff);
|
||
if (type != MmuAccess::Execute)
|
||
{
|
||
WIMG = (pte[1] >> 3) & 0xf;
|
||
}
|
||
// Put in TLB
|
||
#if GEKKOCORE_USE_TLB
|
||
if (type == MmuAccess::Execute) {
|
||
itlb.Map(ea, pa, Core->regs.pc, WIMG);
|
||
}
|
||
else {
|
||
dtlb.Map(ea, pa, Core->regs.pc, WIMG);
|
||
}
|
||
#endif
|
||
MmuLastResult = MmuResult::Ok;
|
||
return pa;
|
||
}
|
||
else
|
||
{
|
||
// Referenced
|
||
pte[1] |= 0x100;
|
||
PIWriteWord(secondaryPteAddr + 4, pte[1]);
|
||
}
|
||
|
||
secondaryPteAddr += 8;
|
||
}
|
||
|
||
MmuLastResult = MmuResult::PageFault;
|
||
return BadAddress;
|
||
}
|
||
|
||
void GekkoCore::DumpDTLB()
|
||
{
|
||
dtlb.Dump();
|
||
}
|
||
|
||
void GekkoCore::DumpITLB()
|
||
{
|
||
itlb.Dump();
|
||
}
|
||
|
||
void GekkoCore::InvalidateTLBAll()
|
||
{
|
||
dtlb.InvalidateAll();
|
||
itlb.InvalidateAll();
|
||
}
|
||
|
||
}
|
||
|
||
|
||
// This module deals with the maintenance of statistics on the use of opcodes.
|
||
|
||
namespace Gekko
|
||
{
|
||
struct OpcodeSortedEntry
|
||
{
|
||
Instruction instr;
|
||
int count;
|
||
};
|
||
|
||
bool GekkoCore::IsOpcodeStatsEnabled()
|
||
{
|
||
return opcodeStatsEnabled;
|
||
}
|
||
|
||
void GekkoCore::EnableOpcodeStats(bool enable)
|
||
{
|
||
opcodeStatsEnabled = enable;
|
||
}
|
||
|
||
static int OpcodeStatsCompare(const void* a, const void* b)
|
||
{
|
||
OpcodeSortedEntry* as = (OpcodeSortedEntry*)a;
|
||
OpcodeSortedEntry* bs = (OpcodeSortedEntry*)b;
|
||
return bs->count - as->count;
|
||
}
|
||
|
||
void GekkoCore::PrintOpcodeStats(size_t maxCount)
|
||
{
|
||
OpcodeSortedEntry unsorted[(size_t)Instruction::Max];
|
||
|
||
// Sort statistics
|
||
|
||
for (size_t i = 0; i < (size_t)Instruction::Max; i++)
|
||
{
|
||
unsorted[i].instr = (Instruction)i;
|
||
unsorted[i].count = opcodeStats[i];
|
||
}
|
||
|
||
qsort(unsorted, (size_t)Instruction::Max, sizeof(OpcodeSortedEntry), OpcodeStatsCompare);
|
||
|
||
// Print out
|
||
|
||
if (maxCount > (size_t)Instruction::Max)
|
||
{
|
||
maxCount = (size_t)Instruction::Max;
|
||
}
|
||
|
||
for (size_t i = 0; i < maxCount; i++)
|
||
{
|
||
DecoderInfo info;
|
||
|
||
info.instr = unsorted[i].instr;
|
||
Report(Channel::CPU, "%s: %i\n", GekkoDisasm::InstrToString(&info).c_str(), unsorted[i].count);
|
||
}
|
||
|
||
Report(Channel::CPU, " \n");
|
||
}
|
||
|
||
void GekkoCore::ResetOpcodeStats()
|
||
{
|
||
memset(opcodeStats, 0, sizeof(opcodeStats));
|
||
}
|
||
|
||
// Thread that displays statistics on the use of opcodes once per second.
|
||
|
||
void GekkoCore::OpcodeStatsThreadProc(void* Parameter)
|
||
{
|
||
GekkoCore* core = (GekkoCore*)Parameter;
|
||
|
||
if (core->opcodeStatsEnabled && core->IsRunning())
|
||
{
|
||
core->PrintOpcodeStats(10);
|
||
core->ResetOpcodeStats();
|
||
}
|
||
|
||
Thread::Sleep(1000);
|
||
}
|
||
|
||
void GekkoCore::RunOpcodeStatsThread()
|
||
{
|
||
if (opcodeStatsThread == nullptr)
|
||
{
|
||
opcodeStatsThread = EMUCreateThread(OpcodeStatsThreadProc, false, this, "OpcodeStats");
|
||
EnableOpcodeStats(true);
|
||
}
|
||
}
|
||
|
||
void GekkoCore::StopOpcodeStatsThread()
|
||
{
|
||
if (opcodeStatsThread != nullptr)
|
||
{
|
||
EMUJoinThread(opcodeStatsThread);
|
||
opcodeStatsThread = nullptr;
|
||
EnableOpcodeStats(false);
|
||
}
|
||
}
|
||
|
||
}
|