Merge pull request #22681 from hrydgard/mmio-jit

Compile kernel loads/stores (MMIO under the JITs), sceKernelRegisterIntrHandler, and an eret hardware test
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-10-20 14:08:39 +02:00
commit 5471f5fe88
16 files changed
+338 -60

No files matched your search

+53 -10
View File
@@ -191,6 +191,7 @@ SubIntrHandler* IntrHandler::get(int subIntrNum)
void IntrHandler::clear()
{
subIntrHandlers.clear();
setBaseHandler(0, 0);
}
int IntrHandler::queueUp(int subintr) {
@@ -212,12 +213,19 @@ int IntrHandler::queueUp(int subintr) {
void IntrHandler::DoState(PointerWrap &p)
{
auto s = p.Section("IntrHandler", 1);
auto s = p.Section("IntrHandler", 1, 2);
if (!s)
return;
Do(p, intrNumber);
Do<int, SubIntrHandler>(p, subIntrHandlers);
if (s >= 2) {
Do(p, baseHandler_);
Do(p, baseArg_);
Do(p, baseEnabled_);
} else {
setBaseHandler(0, 0);
}
if (p.mode == p.MODE_READ && (intrNumber < 0 || intrNumber >= PSP_NUMBER_INTERRUPTS)) {
ERROR_LOG(Log::sceIntc, "Savestate failure: invalid interrupt handler %d", intrNumber);
p.SetError(p.ERROR_FAILURE);
@@ -1039,6 +1047,46 @@ void Register_InterruptManager()
}
// What these return is from tests/intr/kernel/intrhandler, recorded on a PSP. Interrupts there go up to
// 67, one more than PSP_NUMBER_INTERRUPTS, which the firmware's drivers use anyway.
static int sceKernelRegisterIntrHandler(int intrNumber, u32 unknown, u32 handler, u32 handlerArg, u32 opt) {
if (intrNumber < 0 || intrNumber >= PSP_NUMBER_INTERRUPTS || handler == 0)
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE);
IntrHandler *intr = intrHandlers[intrNumber];
if (intr->hasBaseHandler())
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_FOUND_HANDLER);
intr->setBaseHandler(handler, handlerArg);
return hleLogDebug(Log::sceIntc, 0);
}
static int sceKernelReleaseIntrHandler(int intrNumber) {
if (intrNumber < 0 || intrNumber >= PSP_NUMBER_INTERRUPTS)
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE);
IntrHandler *intr = intrHandlers[intrNumber];
if (!intr->hasBaseHandler())
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_NOTFOUND_HANDLER);
intr->setBaseHandler(0, 0);
return hleLogDebug(Log::sceIntc, 0);
}
static int SetIntrEnabled(int intrNumber, bool enable) {
if (intrNumber < 0 || intrNumber >= PSP_NUMBER_INTERRUPTS)
return SCE_KERNEL_ERROR_ILLEGAL_INTRCODE;
IntrHandler *intr = intrHandlers[intrNumber];
if (!intr->hasBaseHandler())
return SCE_KERNEL_ERROR_NOTFOUND_HANDLER;
intr->enableBase(enable);
return 0;
}
static int sceKernelEnableIntr(int intrNumber) {
return hleLogDebugOrError(Log::sceIntc, SetIntrEnabled(intrNumber, true));
}
static int sceKernelDisableIntr(int intrNumber) {
return hleLogDebugOrError(Log::sceIntc, SetIntrEnabled(intrNumber, false));
}
static int sceKernelIsIntrContext() {
return hleLogDebug(Log::sceKernel, __IsInInterrupt() ? 1 : 0);
}
@@ -1065,15 +1113,10 @@ const HLEFunction InterruptManagerForKernel[] =
{0X05572A5F, &WrapV_V<sceKernelExitGame>, "sceKernelExitGame", 'v', "" ,HLE_KERNEL_SYSCALL },
{0X4AC57943, &WrapI_I<sceKernelRegisterExitCallback>, "sceKernelRegisterExitCallback", 'i', "i" ,HLE_KERNEL_SYSCALL },
{0XFE28C6D9, &WrapI_V<sceKernelIsIntrContext>, "sceKernelIsIntrContext", 'i', "" ,HLE_KERNEL_SYSCALL },
// NOT added on purpose, even though JPCSP implements all four: sceKernelRegisterIntrHandler
// (0x58DD8978), sceKernelReleaseIntrHandler (0xF987B1F0), sceKernelEnableIntr (0x4D6E7305)
// and sceKernelDisableIntr (0xD774BA45). JPCSP can honour them because it emulates the
// interrupt controller as MMIO; we dispatch the few interrupts we emulate ourselves (see
// __RegisterIntrHandler and its callers in sceGe/sceKernelAlarm/sceKernelVTimer) and have no
// way to run a guest handler for one. Stubbing them to return success is therefore a lie the
// real flash0 drivers act on - measured while booting the VSH, they make 31 such calls
// (interrupts 4, 12, 15-18, 20-24, 31), and the boot then stalls in GE list execution without
// ever starting a plugin module, where leaving them unresolved reaches the shell.
{0X58DD8978, &WrapI_IUUUU<sceKernelRegisterIntrHandler>, "sceKernelRegisterIntrHandler", 'i', "ixxxx", HLE_KERNEL_SYSCALL },
{0XF987B1F0, &WrapI_I<sceKernelReleaseIntrHandler>, "sceKernelReleaseIntrHandler", 'i', "i" ,HLE_KERNEL_SYSCALL },
{0X4D6E7305, &WrapI_I<sceKernelEnableIntr>, "sceKernelEnableIntr", 'i', "i" ,HLE_KERNEL_SYSCALL },
{0XD774BA45, &WrapI_I<sceKernelDisableIntr>, "sceKernelDisableIntr", 'i', "i" ,HLE_KERNEL_SYSCALL },
};
void Register_InterruptManagerForKernel()
+12
View File
@@ -129,12 +129,24 @@ public:
SubIntrHandler *get(int subIntrNum);
void clear();
// The handler a kernel driver registered for the whole interrupt (sceKernelRegisterIntrHandler).
// Nothing raises those interrupts yet, so it's only kept.
bool hasBaseHandler() const { return baseHandler_ != 0; }
void setBaseHandler(u32 handler, u32 arg) {
baseHandler_ = handler;
baseArg_ = arg;
baseEnabled_ = false;
}
void enableBase(bool enable) { baseEnabled_ = enable; }
void DoState(PointerWrap &p);
private:
int intrNumber;
std::map<int, SubIntrHandler> subIntrHandlers;
u32 baseHandler_ = 0;
u32 baseArg_ = 0;
bool baseEnabled_ = false;
int entryCycles_ = 0;
int exitCycles_ = 0;
};
+1 -19
View File
@@ -19,19 +19,12 @@
#include "Common/Log.h"
#include "Common/Serialize/Serializer.h"
#include "Core/Core.h"
#include "Core/HW/GpioMMIO.h"
#include "Core/HW/MMIO.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPS.h"
namespace MMIO {
// Unhandled reads return a distinctive poison value (not 0) so it's obvious in a live trace when
// some later computation's input traces back to an unimplemented register, rather than looking
// like an ordinary, legitimate zero.
constexpr u32 UNKNOWN_MMIO_POISON = 0x1337BEEF;
static std::vector<MMIODevice *> g_devices;
void Init() {
@@ -65,27 +58,16 @@ static MMIODevice *FindDevice(u32 address) {
return nullptr;
}
static bool CheckKernelMode(MIPSState *mips, u32 address, int size, MemoryExceptionType type) {
if (Memory::IsKernelCodeAddress(mips->pc))
return true;
Core_MemoryException(address, size, mips->pc, type, "Kernel mode only");
return false;
}
u32 Read(MIPSState *mips, u32 address, int size) {
if (!CheckKernelMode(mips, address, size, MemoryExceptionType::READ_WORD))
return UNKNOWN_MMIO_POISON;
MMIODevice *device = FindDevice(address);
u32 value;
if (device && device->Read(mips, address - device->Base(), size, &value))
return value;
WARN_LOG(Log::CPU, "Unhandled MMIO Read%d at %08x%s%s", size * 8, address, device ? " in " : "", device ? device->Name() : "");
return UNKNOWN_MMIO_POISON;
return UNHANDLED_READ;
}
void Write(MIPSState *mips, u32 address, int size, u32 value) {
if (!CheckKernelMode(mips, address, size, MemoryExceptionType::WRITE_WORD))
return;
MMIODevice *device = FindDevice(address);
if (device && device->Write(mips, address - device->Base(), size, value))
return;
+7 -2
View File
@@ -51,13 +51,18 @@ private:
namespace MMIO {
// What an unhandled read returns. Distinctive (not 0) so it's obvious in a live trace when some later
// computation's input traces back to an unimplemented register, rather than looking like an
// ordinary, legitimate zero.
constexpr u32 UNHANDLED_READ = 0x1337BEEF;
// Registers the devices, in their power-on state.
void Init();
void Shutdown();
void DoState(PointerWrap &p);
// For an address Memory::IsMMIOAccess() accepts. size is 1, 2 or 4. An access no device handles is
// logged, and reads as poison.
// For an address Memory::IsMMIOAccess() accepts, from kernel code (the caller checks). size is 1, 2
// or 4. An access no device handles is logged, and reads as UNHANDLED_READ.
u32 Read(MIPSState *mips, u32 address, int size);
void Write(MIPSState *mips, u32 address, int size, u32 value);
+17 -7
View File
@@ -53,18 +53,28 @@ namespace MIPSComp {
CheckMemoryBreakpoint(rs, offset);
if (js.kernelMode) {
// Send all memory accesses to the interpreter in kernel mode.
// TODO: Do something faster - but it hardly matters, currently this is VSH-only.
DISABLE;
return;
}
if (((op >> 29) & 1) == 0 && rt == MIPS_REG_ZERO) {
// Don't load anything into $zr
return;
}
if (js.kernelMode) {
// Kernel code may reach hardware registers. ResolveKernelMemory turns the plain memory
// accesses back into ordinary ones where it can tell. The rest go to the interpreter.
switch (o) {
case 32: ir.Write(IROp::LoadKernel8Ext, rt, rs, 0, offset); return;
case 33: ir.Write(IROp::LoadKernel16Ext, rt, rs, 0, offset); return;
case 35: ir.Write(IROp::LoadKernel32, rt, rs, 0, offset); return;
case 36: ir.Write(IROp::LoadKernel8, rt, rs, 0, offset); return;
case 37: ir.Write(IROp::LoadKernel16, rt, rs, 0, offset); return;
case 40: ir.Write(IROp::StoreKernel8, rt, rs, 0, offset); return;
case 41: ir.Write(IROp::StoreKernel16, rt, rs, 0, offset); return;
case 43: ir.Write(IROp::StoreKernel32, rt, rs, 0, offset); return;
default:
DISABLE;
}
}
switch (o) {
// Load
case 35:
+1
View File
@@ -347,6 +347,7 @@ void IRFrontend::DoJit(u32 em_address, std::vector<IRInst> &instructions, u32 &m
&RemoveLoadStoreLeftRight,
&OptimizeFPMoves,
&PropagateConstants,
&ResolveKernelMemory,
&PurgeTemps,
&ReduceVec4Flush,
&OptimizeLoadsAfterStores,
+9
View File
@@ -95,6 +95,15 @@ static const IRMeta irMeta[] = {
{ IROp::Store32Conditional, "Store32Conditional", "GGC", IRFLAG_SRC3DST },
{ IROp::StoreFloat, "StoreFloat", "FGC", IRFLAG_SRC3 },
{ IROp::StoreVec4, "StoreVec4", "VGC", IRFLAG_SRC3 },
// Barriers: a hardware register access has side effects, and mustn't move or merge.
{ IROp::LoadKernel8, "LoadKernel8", "GGC", IRFLAG_BARRIER },
{ IROp::LoadKernel8Ext, "LoadKernel8Ext", "GGC", IRFLAG_BARRIER },
{ IROp::LoadKernel16, "LoadKernel16", "GGC", IRFLAG_BARRIER },
{ IROp::LoadKernel16Ext, "LoadKernel16Ext", "GGC", IRFLAG_BARRIER },
{ IROp::LoadKernel32, "LoadKernel32", "GGC", IRFLAG_BARRIER },
{ IROp::StoreKernel8, "StoreKernel8", "GGC", IRFLAG_SRC3 | IRFLAG_BARRIER },
{ IROp::StoreKernel16, "StoreKernel16", "GGC", IRFLAG_SRC3 | IRFLAG_BARRIER },
{ IROp::StoreKernel32, "StoreKernel32", "GGC", IRFLAG_SRC3 | IRFLAG_BARRIER },
{ IROp::FAdd, "FAdd", "FFF" },
{ IROp::FSub, "FSub", "FFF" },
{ IROp::FMul, "FMul", "FFF" },
+11
View File
@@ -110,6 +110,17 @@ enum class IROp : uint8_t {
StoreFloat,
StoreVec4,
// Kernel code's loads and stores, which may reach hardware registers (MMIO). See
// ResolveKernelMemory.
LoadKernel8,
LoadKernel8Ext,
LoadKernel16,
LoadKernel16Ext,
LoadKernel32,
StoreKernel8,
StoreKernel16,
StoreKernel32,
Ext8to32,
Ext16to32,
ReverseBits,
+79
View File
@@ -15,6 +15,7 @@
#include "Core/Debugger/Breakpoints.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/ReplaceTables.h"
#include "Core/HW/MMIO.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSTables.h"
@@ -70,6 +71,51 @@ u32 IRRunMemCheck(u32 pc, u32 addr) {
return coreState != CORE_RUNNING_CPU ? 1 : 0;
}
// LoadKernel* and StoreKernel*: memory, or a hardware register, like the interpreter's loads and stores.
template <int size>
static bool IsValidAlignedAddress(u32 addr) {
switch (size) {
case 1: return Memory::IsValidAddress(addr);
case 2: return Memory::IsValid2AlignedAddress(addr);
default: return Memory::IsValid4AlignedAddress(addr);
}
}
template <int size, bool sign>
static u32 RunKernelLoad(MIPSState *mips, u32 addr) {
u32 value;
if (IsValidAlignedAddress<size>(addr)) {
switch (size) {
case 1: value = Memory::ReadUnchecked_U8(addr); break;
case 2: value = Memory::ReadUnchecked_U16(addr); break;
default: value = Memory::ReadUnchecked_U32(addr); break;
}
} else if (Memory::IsMMIOAccess(addr)) {
value = MMIO::Read(mips, addr, size);
} else {
Core_MemoryException(addr, size, mips->pc, MemoryExceptionType::READ_WORD, "kernel load");
return 0;
}
if (sign)
value = size == 1 ? SignExtend8ToU32(value) : SignExtend16ToU32(value);
return value;
}
template <int size>
static void RunKernelStore(MIPSState *mips, u32 addr, u32 value) {
if (IsValidAlignedAddress<size>(addr)) {
switch (size) {
case 1: Memory::WriteUnchecked_U8((u8)value, addr); break;
case 2: Memory::WriteUnchecked_U16((u16)value, addr); break;
default: Memory::WriteUnchecked_U32(value, addr); break;
}
} else if (Memory::IsMMIOAccess(addr)) {
MMIO::Write(mips, addr, size, value);
} else {
Core_MemoryException(addr, size, mips->pc, MemoryExceptionType::WRITE_WORD, "kernel store");
}
}
// With GCC and Clang, each op jumps straight to the next op's handler through a table ("threaded"
// dispatch), which predicts much better than the one shared indirect jump of a switch. Other
// compilers get the switch. Ops missing from the table fall back to the switch, so they still work.
@@ -158,6 +204,14 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const IRInst *chainArena) {
dispatch[(int)IROp::StoreFloat] = &&L_StoreFloat;
dispatch[(int)IROp::LoadVec4] = &&L_LoadVec4;
dispatch[(int)IROp::StoreVec4] = &&L_StoreVec4;
dispatch[(int)IROp::LoadKernel8] = &&L_LoadKernel8;
dispatch[(int)IROp::LoadKernel8Ext] = &&L_LoadKernel8Ext;
dispatch[(int)IROp::LoadKernel16] = &&L_LoadKernel16;
dispatch[(int)IROp::LoadKernel16Ext] = &&L_LoadKernel16Ext;
dispatch[(int)IROp::LoadKernel32] = &&L_LoadKernel32;
dispatch[(int)IROp::StoreKernel8] = &&L_StoreKernel8;
dispatch[(int)IROp::StoreKernel16] = &&L_StoreKernel16;
dispatch[(int)IROp::StoreKernel32] = &&L_StoreKernel32;
dispatch[(int)IROp::Vec4Init] = &&L_Vec4Init;
dispatch[(int)IROp::Vec4Shuffle] = &&L_Vec4Shuffle;
dispatch[(int)IROp::Vec4Blend] = &&L_Vec4Blend;
@@ -467,6 +521,31 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const IRInst *chainArena) {
IR_NEXT;
}
IR_CASE(LoadKernel8)
mips->r[inst->dest] = RunKernelLoad<1, false>(mips, mips->r[inst->src1] + inst->constant);
IR_NEXT;
IR_CASE(LoadKernel8Ext)
mips->r[inst->dest] = RunKernelLoad<1, true>(mips, mips->r[inst->src1] + inst->constant);
IR_NEXT;
IR_CASE(LoadKernel16)
mips->r[inst->dest] = RunKernelLoad<2, false>(mips, mips->r[inst->src1] + inst->constant);
IR_NEXT;
IR_CASE(LoadKernel16Ext)
mips->r[inst->dest] = RunKernelLoad<2, true>(mips, mips->r[inst->src1] + inst->constant);
IR_NEXT;
IR_CASE(LoadKernel32)
mips->r[inst->dest] = RunKernelLoad<4, false>(mips, mips->r[inst->src1] + inst->constant);
IR_NEXT;
IR_CASE(StoreKernel8)
RunKernelStore<1>(mips, mips->r[inst->src1] + inst->constant, mips->r[inst->src3]);
IR_NEXT;
IR_CASE(StoreKernel16)
RunKernelStore<2>(mips, mips->r[inst->src1] + inst->constant, mips->r[inst->src3]);
IR_NEXT;
IR_CASE(StoreKernel32)
RunKernelStore<4>(mips, mips->r[inst->src1] + inst->constant, mips->r[inst->src3]);
IR_NEXT;
IR_CASE(Vec4Init)
{
memcpy(&mips->f[inst->dest], vec4InitValues[inst->src1], 4 * sizeof(float));
+12
View File
@@ -463,6 +463,18 @@ void IRNativeBackend::CompileIRInst(IRInst inst) {
CompIR_Interpret(inst);
break;
case IROp::LoadKernel8:
case IROp::LoadKernel8Ext:
case IROp::LoadKernel16:
case IROp::LoadKernel16Ext:
case IROp::LoadKernel32:
case IROp::StoreKernel8:
case IROp::StoreKernel16:
case IROp::StoreKernel32:
// Kernel code only, and mostly hardware registers once ResolveKernelMemory has run.
CompIR_Generic(inst);
break;
case IROp::Syscall:
case IROp::SyscallUnresolved:
case IROp::CallReplacement:
+57
View File
@@ -691,6 +691,27 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
}
break;
case IROp::LoadKernel8:
case IROp::LoadKernel8Ext:
case IROp::LoadKernel16:
case IROp::LoadKernel16Ext:
case IROp::LoadKernel32:
case IROp::StoreKernel8:
case IROp::StoreKernel16:
case IROp::StoreKernel32:
// Barriers, so everything is written out, but they can't change the GPRs, so the values
// stay known. A constant address goes in the op, for ResolveKernelMemory.
if (gpr.IsImm(inst.src1)) {
const u32 addr = gpr.GetImm(inst.src1) + inst.constant;
gpr.FlushAll(true);
if ((GetIRMeta(inst.op)->flags & IRFLAG_SRC3) == 0)
gpr.MapDirty(inst.dest);
out.Write(inst.op, inst.dest, 0, 0, addr);
break;
}
gpr.FlushAll(true);
goto doDefault;
case IROp::Load8:
case IROp::Load8Ext:
case IROp::Load16:
@@ -2373,6 +2394,42 @@ bool OptimizeLoadsAfterStores(const IRWriter &in, IRWriter &out, const IROptions
return false;
}
// The plain op for a kernel load or store, and its size, or Nop.
static IROp PlainMemoryOp(IROp op, int *size) {
switch (op) {
case IROp::LoadKernel8: *size = 1; return IROp::Load8;
case IROp::LoadKernel8Ext: *size = 1; return IROp::Load8Ext;
case IROp::LoadKernel16: *size = 2; return IROp::Load16;
case IROp::LoadKernel16Ext: *size = 2; return IROp::Load16Ext;
case IROp::LoadKernel32: *size = 4; return IROp::Load32;
case IROp::StoreKernel8: *size = 1; return IROp::Store8;
case IROp::StoreKernel16: *size = 2; return IROp::Store16;
case IROp::StoreKernel32: *size = 4; return IROp::Store32;
default: return IROp::Nop;
}
}
bool ResolveKernelMemory(const IRWriter &in, IRWriter &out, const IROptions &opts) {
CONDITIONAL_DISABLE;
for (IRInst inst : in.GetInstructions()) {
int size = 0;
const IROp plain = PlainMemoryOp(inst.op, &size);
if (plain != IROp::Nop && inst.src1 == MIPS_REG_ZERO) {
const u32 addr = inst.constant;
bool valid;
switch (size) {
case 1: valid = Memory::IsValidAddress(addr); break;
case 2: valid = Memory::IsValid2AlignedAddress(addr); break;
default: valid = Memory::IsValid4AlignedAddress(addr); break;
}
if (valid)
inst.op = plain;
}
out.Write(inst);
}
return false;
}
bool OptimizeForInterpreter(const IRWriter &in, IRWriter &out, const IROptions &opts) {
CONDITIONAL_DISABLE;
// This tells us to skip an AND op that has been optimized out.
+3
View File
@@ -18,4 +18,7 @@ bool ApplyMemoryValidation(const IRWriter &in, IRWriter &out, const IROptions &o
bool ReduceVec4Flush(const IRWriter &in, IRWriter &out, const IROptions &opts);
bool OptimizeLoadsAfterStores(const IRWriter &in, IRWriter &out, const IROptions &opts);
// Kernel code's loads and stores at a constant address become plain ones where that's memory, so only
// hardware registers (and addresses only known when it runs) stay LoadKernel*/StoreKernel*.
bool ResolveKernelMemory(const IRWriter &in, IRWriter &out, const IROptions &opts);
bool OptimizeForInterpreter(const IRWriter &in, IRWriter &out, const IROptions &opts);
+33 -16
View File
@@ -99,6 +99,23 @@ int MIPS_InterpretSingleStep(MIPSState *mips) {
return 1;
}
// Hardware registers, for kernel code only.
static u32 ReadMMIO(MIPSState *mips, u32 addr, int size) {
if (!Memory::IsKernelCodeAddress(mips->pc)) {
Core_MemoryException(addr, size, mips->pc, MemoryExceptionType::READ_WORD, "Kernel mode only");
return MMIO::UNHANDLED_READ;
}
return MMIO::Read(mips, addr, size);
}
static void WriteMMIO(MIPSState *mips, u32 addr, int size, u32 value) {
if (!Memory::IsKernelCodeAddress(mips->pc)) {
Core_MemoryException(addr, size, mips->pc, MemoryExceptionType::WRITE_WORD, "Kernel mode only");
return;
}
MMIO::Write(mips, addr, size, value);
}
namespace MIPSInt {
void Int_Cache(MIPSState *mips, MIPSOpcode op) {
int imm = SignExtend16ToS32(op);
@@ -356,7 +373,7 @@ namespace MIPSInt {
if (rt != 0) {
if (!Memory::IsValid4AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
R(rt) = MMIO::Read(mips, addr, 4);
R(rt) = ReadMMIO(mips, addr, 4);
} else {
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "ll");
R(rt) = 0;
@@ -371,7 +388,7 @@ namespace MIPSInt {
if (mips->llBit) {
if (!Memory::IsValid4AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr))
MMIO::Write(mips, addr, 4, R(rt));
WriteMMIO(mips, addr, 4, R(rt));
else
Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "sc");
} else {
@@ -448,7 +465,7 @@ namespace MIPSInt {
if (!Memory::IsValidAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
// This is a read from MMIO, so we can handle it.
R(rt) = SignExtend8ToU32(MMIO::Read(mips, addr, 1));
R(rt) = SignExtend8ToU32(ReadMMIO(mips, addr, 1));
break;
}
Core_MemoryException(addr, 1, PC, MemoryExceptionType::READ_WORD, "lb");
@@ -461,7 +478,7 @@ namespace MIPSInt {
if (!Memory::IsValid2AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
// This is a read from MMIO, so we can handle it.
R(rt) = SignExtend16ToU32(MMIO::Read(mips, addr, 2));
R(rt) = SignExtend16ToU32(ReadMMIO(mips, addr, 2));
break;
}
Core_MemoryException(addr, 2, PC, MemoryExceptionType::READ_WORD, "lh");
@@ -474,7 +491,7 @@ namespace MIPSInt {
if (!Memory::IsValid4AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
// This is a read from MMIO, so we can handle it.
R(rt) = MMIO::Read(mips, addr, 4);
R(rt) = ReadMMIO(mips, addr, 4);
break;
}
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lw");
@@ -487,7 +504,7 @@ namespace MIPSInt {
if (!Memory::IsValidAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
// This is a read from MMIO, so we can handle it.
R(rt) = MMIO::Read(mips, addr, 1);
R(rt) = ReadMMIO(mips, addr, 1);
break;
}
Core_MemoryException(addr, 1, PC, MemoryExceptionType::READ_WORD, "lbu");
@@ -500,7 +517,7 @@ namespace MIPSInt {
if (!Memory::IsValid2AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
// This is a read from MMIO, so we can handle it.
R(rt) = MMIO::Read(mips, addr, 2);
R(rt) = ReadMMIO(mips, addr, 2);
break;
}
Core_MemoryException(addr, 2, PC, MemoryExceptionType::READ_WORD, "lhu");
@@ -513,7 +530,7 @@ namespace MIPSInt {
if (!Memory::IsValidAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
// This is a write to MMIO, so we can handle it.
MMIO::Write(mips, addr, 1, (u8)R(rt));
WriteMMIO(mips, addr, 1, (u8)R(rt));
break;
}
Core_MemoryException(addr, 1, PC, MemoryExceptionType::WRITE_WORD, "sb");
@@ -525,7 +542,7 @@ namespace MIPSInt {
if (!Memory::IsValid2AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
// This is a write to MMIO, so we can handle it.
MMIO::Write(mips, addr, 2, (u16)R(rt));
WriteMMIO(mips, addr, 2, (u16)R(rt));
break;
}
Core_MemoryException(addr, 2, PC, MemoryExceptionType::WRITE_WORD, "sh");
@@ -537,7 +554,7 @@ namespace MIPSInt {
if (!Memory::IsValid4AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
// This is a write to MMIO, so we can handle it.
MMIO::Write(mips, addr, 4, R(rt));
WriteMMIO(mips, addr, 4, R(rt));
break;
}
Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "sw");
@@ -555,7 +572,7 @@ namespace MIPSInt {
if (Memory::IsValidAddress(addr)) {
mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc);
} else if (Memory::IsMMIOAccess(addr)) {
mem = MMIO::Read(mips, addr & 0xfffffffc, 4);
mem = ReadMMIO(mips, addr & 0xfffffffc, 4);
} else {
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lwl");
}
@@ -572,7 +589,7 @@ namespace MIPSInt {
if (Memory::IsValidAddress(addr)) {
mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc);
} else if (Memory::IsMMIOAccess(addr)) {
mem = MMIO::Read(mips, addr & 0xfffffffc, 4);
mem = ReadMMIO(mips, addr & 0xfffffffc, 4);
} else {
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lwr");
}
@@ -628,7 +645,7 @@ namespace MIPSInt {
case 49:
if (!Memory::IsValid4AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
FI(ft) = MMIO::Read(mips, addr, 4);
FI(ft) = ReadMMIO(mips, addr, 4);
break;
}
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lwc1");
@@ -640,7 +657,7 @@ namespace MIPSInt {
case 57:
if (!Memory::IsValid4AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
MMIO::Write(mips, addr, 4, FI(ft));
WriteMMIO(mips, addr, 4, FI(ft));
break;
}
Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "swc1");
@@ -763,8 +780,8 @@ namespace MIPSInt {
PC += 4;
}
// Return from an exception, as MIPS32 defines it (not checked on a PSP): to ErrorEPC (30) if
// Status.ERL is set, clearing it, otherwise to EPC (14), clearing Status.EXL. No delay slot.
// Return from an exception, as MIPS32 defines it (cpu/cop0/eret): to ErrorEPC (30) if Status.ERL
// is set, clearing only that, otherwise to EPC (14), clearing Status.EXL. No delay slot.
void Int_Eret(MIPSState *mips, MIPSOpcode op) {
u32 &status = mips->cop0[12];
if (status & 4) {
+2
View File
@@ -85,6 +85,7 @@ class Command(object):
# These have worked and should keep working always - regression tests.
# -g flag runs these.
tests_good = [
"cpu/cop0/eret",
"cpu/cpu_alu/cpu_alu",
"cpu/cpu_alu/cpu_branch",
"cpu/cpu_alu/branch_link_ra",
@@ -321,6 +322,7 @@ tests_good = [
"hash/sha1ctx",
"hle/check_not_used_uids",
"intr/intr",
"intr/kernel/intrhandler",
"intr/mfic",
"intr/enablesub",
"intr/registersub",
+40 -5
View File
@@ -513,8 +513,9 @@ bool TestCPUEret() {
}
// Kernel code reaching the GPIO block's registers on every core: a write, an acknowledge clearing a
// status bit, reads back through lw and lwc1, and poison from a register nothing handles. Then the
// registers through a savestate.
// status bit, reads back through lw and lwc1, and poison from a register nothing handles. Kernel
// RAM too, at a constant address (which the IR makes a plain access) and through a register. Then
// the registers through a savestate.
bool TestMMIO() {
SetupJitHarness();
MMIO::Init();
@@ -523,6 +524,9 @@ bool TestMMIO() {
const u32 base = 0x88000100;
auto sw = [](int rt, int rs, int imm) { return (0x2Bu << 26) | (rs << 21) | (rt << 16) | (imm & 0xFFFF); };
auto lwc1 = [](int ft, int rs, int imm) { return (0x31u << 26) | (rs << 21) | (ft << 16) | (imm & 0xFFFF); };
auto sb = [](int rt, int rs, int imm) { return (0x28u << 26) | (rs << 21) | (rt << 16) | (imm & 0xFFFF); };
auto lb = [](int rt, int rs, int imm) { return (0x20u << 26) | (rs << 21) | (rt << 16) | (imm & 0xFFFF); };
const u32 ram = 0x88001000;
const u32 code[] = {
MIPS_MAKE_LUI(MIPS_REG_T0, 0xBE24), // GPIO
MIPS_MAKE_ORI(MIPS_REG_T1, MIPS_REG_ZERO, 0x30),
@@ -533,6 +537,12 @@ bool TestMMIO() {
lwc1(0, MIPS_REG_T0, 0x20),
MIPS_MAKE_LUI(MIPS_REG_T2, 0xBC10),
MIPS_MAKE_LW(MIPS_REG_V1, MIPS_REG_T2, 0),
MIPS_MAKE_LUI(MIPS_REG_T3, ram >> 16),
sw(MIPS_REG_T1, MIPS_REG_T3, ram & 0xFFFF),
MIPS_MAKE_LW(MIPS_REG_A0, MIPS_REG_T3, ram & 0xFFFF),
MIPS_MAKE_ORI(MIPS_REG_T4, MIPS_REG_ZERO, 0x80),
sb(MIPS_REG_T4, MIPS_REG_A1, 4), // a1 is ram, unknown to the IR.
lb(MIPS_REG_A2, MIPS_REG_A1, 4),
MIPS_MAKE_SYSCALL("UnitTestFakeSyscalls", "UnitTestTerminator"),
MIPS_MAKE_BREAK(1),
};
@@ -550,17 +560,42 @@ bool TestMMIO() {
MMIO::Init();
currentMIPS->r[MIPS_REG_V0] = 0;
currentMIPS->r[MIPS_REG_V1] = 0;
currentMIPS->r[MIPS_REG_A0] = 0;
currentMIPS->r[MIPS_REG_A1] = ram;
currentMIPS->r[MIPS_REG_A2] = 0;
currentMIPS->fi[0] = 0;
currentMIPS->pc = base;
coreState = CORE_RUNNING_CPU;
while (coreState == CORE_RUNNING_CPU)
mipsr4k.RunLoopUntil(CoreTiming::GetTicks(currentMIPS) + 1000000);
if (currentMIPS->r[MIPS_REG_V0] != 0x20 || currentMIPS->fi[0] != 0x20 || currentMIPS->r[MIPS_REG_V1] != 0x1337BEEF) {
printf("MMIO on core %d: v0=%08x f0=%08x v1=%08x, expected 00000020, 00000020, 1337beef\n", (int)core,
currentMIPS->r[MIPS_REG_V0], currentMIPS->fi[0], currentMIPS->r[MIPS_REG_V1]);
if (currentMIPS->r[MIPS_REG_V0] != 0x20 || currentMIPS->fi[0] != 0x20 || currentMIPS->r[MIPS_REG_V1] != MMIO::UNHANDLED_READ ||
currentMIPS->r[MIPS_REG_A0] != 0x10 || currentMIPS->r[MIPS_REG_A2] != 0xFFFFFF80) {
printf("MMIO on core %d: v0=%08x f0=%08x v1=%08x a0=%08x a2=%08x, expected 00000020, 00000020, %08x, 00000010, ffffff80\n", (int)core,
currentMIPS->r[MIPS_REG_V0], currentMIPS->fi[0], currentMIPS->r[MIPS_REG_V1], currentMIPS->r[MIPS_REG_A0], currentMIPS->r[MIPS_REG_A2], MMIO::UNHANDLED_READ);
success = false;
}
if (mipsr4k.jit) {
// The registers stay checked accesses, the constant RAM ones become plain.
JitBlockCacheDebugInterface *blocks = mipsr4k.jit->GetBlockCacheDebugInterface();
const JitBlockDebugInfo info = blocks->GetBlockDebugInfo(blocks->GetBlockNumberFromStartAddress(base));
int kernelOps = 0, plainOps = 0;
for (const std::string &line : info.irDisasm) {
if (line.find("LoadKernel") != std::string::npos || line.find("StoreKernel") != std::string::npos)
kernelOps++;
else if (line.find("Load32 ") != std::string::npos || line.find("Store32 ") != std::string::npos)
plainOps++;
}
// sw/sw/lw/lw to registers, sb/lb through a1. lwc1 is left to the interpreter. The RAM store
// is plain, and the load after it plain too, or gone (the value forwarded).
if (kernelOps != 6 || plainOps < 1) {
printf("MMIO on core %d: %d kernel and %d plain loads/stores, expected 6 and at least 1\n", (int)core, kernelOps, plainOps);
for (const std::string &line : info.irDisasm)
printf(" %s\n", line.c_str());
success = false;
}
}
}
mipsr4k.UpdateCore(CPUCore::INTERPRETER);