diff --git a/.gitignore b/.gitignore
index 46319071187f..d92bd8362a58 100644
--- a/.gitignore
+++ b/.gitignore
@@ -151,3 +151,4 @@ RAPrefs_PPSSPP.cfg
cmake-build-*/
/.vscode/
+smw.sfc
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ea57576f0439..998766f1fc9b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2444,6 +2444,12 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/HLE/sceNp2.h
Core/HLE/scePauth.cpp
Core/HLE/scePauth.h
+ Core/HLE/sceSysEvent.cpp
+ Core/HLE/sceSysEvent.h
+ Core/HLE/sceSysreg.cpp
+ Core/HLE/sceSysreg.h
+ Core/HLE/sceMeCore.cpp
+ Core/HLE/sceMeCore.h
Core/HW/SimpleAudioDec.cpp
Core/HW/SimpleAudioDec.h
Core/HW/Atrac3Standalone.cpp
diff --git a/Common/ExceptionHandlerSetup.cpp b/Common/ExceptionHandlerSetup.cpp
index f1b977888591..8b1e2a04f55b 100644
--- a/Common/ExceptionHandlerSetup.cpp
+++ b/Common/ExceptionHandlerSetup.cpp
@@ -99,7 +99,7 @@ void UninstallExceptionHandler() {
g_badAccessHandler = nullptr;
}
-#elif defined(__APPLE__)
+#elif defined(__APPLE__) && PPSSPP_ARCH(AMD64)
static void CheckKR(const char* name, kern_return_t kr) {
_assert_msg_(kr == 0, "%s failed: kr=%x", name, kr);
diff --git a/Common/MachineContext.h b/Common/MachineContext.h
index b704b2e6a24c..839c8f425b92 100644
--- a/Common/MachineContext.h
+++ b/Common/MachineContext.h
@@ -101,6 +101,15 @@ typedef x86_thread_state64_t SContext;
#define CTX_R15 __r15
#define CTX_RIP __rip
+#elif PPSSPP_ARCH(ARM64)
+
+#define MACHINE_CONTEXT_SUPPORTED
+
+typedef _STRUCT_MCONTEXT64 SContext;
+#define CTX_REG(x) __ss.__x[(x)]
+#define CTX_SP __ss.__sp
+#define CTX_PC __ss.__pc
+
#else
// No context definition for architecture
diff --git a/Core/Config.cpp b/Core/Config.cpp
index 8e7372bed4c6..a62fdd5168ac 100644
--- a/Core/Config.cpp
+++ b/Core/Config.cpp
@@ -398,6 +398,7 @@ static const ConfigSetting achievementSettings[] = {
static const ConfigSetting cpuSettings[] = {
ConfigSetting("CPUCore", SETTING(g_Config, iCpuCore), &DefaultCpuCore, CfgFlag::PER_GAME | CfgFlag::REPORT),
+ ConfigSetting("MECpuCore", SETTING(g_Config, iMECpuCore), (int)CPUCore::JIT_IR, CfgFlag::PER_GAME),
ConfigSetting("SeparateSASThread", SETTING(g_Config, bSeparateSASThread), &DefaultSasThread, CfgFlag::PER_GAME | CfgFlag::REPORT),
ConfigSetting("IOTimingMethod", SETTING(g_Config, iIOTimingMethod), IOTIMING_FAST, CfgFlag::PER_GAME | CfgFlag::REPORT),
ConfigSetting("FastMemoryAccess", SETTING(g_Config, bFastMemory), true, CfgFlag::PER_GAME),
diff --git a/Core/Config.h b/Core/Config.h
index cde4d4db839a..5cb3f41965ab 100644
--- a/Core/Config.h
+++ b/Core/Config.h
@@ -215,6 +215,7 @@ struct Config : public ConfigBlock {
bool bFastMemory;
int iCpuCore;
+ int iMECpuCore; // Media Engine CPU backend (0=interpreter, 2=IR interpreter)
bool bCheckForNewVersion;
bool bForceLagSync;
bool bFuncReplacements;
diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj
index 9a5aef32ec05..7d19f693354e 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -639,8 +639,11 @@
+
+
+
@@ -1137,8 +1140,11 @@
+
+
+
diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters
index 2cd01b785327..e75e0b69d7d7 100644
--- a/Core/Core.vcxproj.filters
+++ b/Core/Core.vcxproj.filters
@@ -469,9 +469,18 @@
ELF
+
+ HLE\Libraries
+
HLE\Libraries
+
+ HLE\Libraries
+
+
+ HLE\Libraries
+
HLE\Libraries
@@ -1746,9 +1755,18 @@
ELF
+
+ HLE\Libraries
+
HLE\Libraries
+
+ HLE\Libraries
+
+
+ HLE\Libraries
+
HLE\Libraries
diff --git a/Core/HLE/HLETables.cpp b/Core/HLE/HLETables.cpp
index 043952089838..9d2a9e0e1e7e 100644
--- a/Core/HLE/HLETables.cpp
+++ b/Core/HLE/HLETables.cpp
@@ -87,6 +87,9 @@
#include "sceNetResolver.h"
// #include "sceNp2.h"
#include "sceNet_lib.h"
+#include "sceSysEvent.h"
+#include "sceSysreg.h"
+#include "sceMeCore.h"
#define N(s) s
@@ -203,7 +206,6 @@ const HLEFunction pspeDebug[] =
{0XDEADBEAF, nullptr, "pspeDebugWrite", '?', "" },
};
-
const HLEModule moduleList[] =
{
{"FakeSysCalls", ARRAY_SIZE(FakeSysCalls), FakeSysCalls},
@@ -328,6 +330,10 @@ void RegisterAllModules() {
// Not ready to enable this due to apparent softlocks in Patapon 3.
// Register_sceNpMatching2();
+ // Media Engine HLE modules.
+ Register_sceSysEventForKernel();
+ Register_sceSysreg_driver();
+ Register_sceMeCore_driver();
+
// add new modules here.
}
-
diff --git a/Core/HLE/sceKernelInterrupt.cpp b/Core/HLE/sceKernelInterrupt.cpp
index a0d4bf767c17..e0753239643e 100644
--- a/Core/HLE/sceKernelInterrupt.cpp
+++ b/Core/HLE/sceKernelInterrupt.cpp
@@ -131,6 +131,16 @@ static void sceKernelCpuResumeIntrWithSync(u32 enable)
bool IntrHandler::run(PendingInterrupt& pend)
{
+ if (pend.subintr == PSP_INTR_SUB_NONE) {
+ if (baseHandlerAddress_ == 0 || !baseEnabled_) {
+ WARN_LOG(Log::sceIntc, "Ignoring base interrupt %d without enabled handler.", pend.intr);
+ return false;
+ }
+
+ copyArgsToCPU(pend);
+ return true;
+ }
+
SubIntrHandler *handler = get(pend.subintr);
if (!handler) {
WARN_LOG(Log::sceIntc, "Ignoring interrupt, already been released.");
@@ -144,6 +154,14 @@ bool IntrHandler::run(PendingInterrupt& pend)
void IntrHandler::copyArgsToCPU(PendingInterrupt& pend)
{
+ if (pend.subintr == PSP_INTR_SUB_NONE) {
+ DEBUG_LOG(Log::CPU, "Entering base interrupt handler %08x", baseHandlerAddress_);
+ currentMIPS->pc = baseHandlerAddress_;
+ currentMIPS->r[MIPS_REG_A0] = pend.intr;
+ currentMIPS->r[MIPS_REG_A1] = baseHandlerArg_;
+ return;
+ }
+
SubIntrHandler* handler = get(pend.subintr);
DEBUG_LOG(Log::CPU, "Entering interrupt handler %08x", handler->handlerAddress);
currentMIPS->pc = handler->handlerAddress;
@@ -189,11 +207,35 @@ SubIntrHandler* IntrHandler::get(int subIntrNum)
}
void IntrHandler::clear()
{
+ baseEnabled_ = false;
+ baseHandlerAddress_ = 0;
+ baseHandlerArg_ = 0;
subIntrHandlers.clear();
}
+void IntrHandler::setBase(u32 handlerAddress, u32 handlerArg) {
+ baseHandlerAddress_ = handlerAddress;
+ baseHandlerArg_ = handlerArg;
+}
+
+void IntrHandler::clearBase() {
+ baseEnabled_ = false;
+ baseHandlerAddress_ = 0;
+ baseHandlerArg_ = 0;
+}
+
+void IntrHandler::enableBase() {
+ baseEnabled_ = true;
+}
+
+void IntrHandler::disableBase() {
+ baseEnabled_ = false;
+}
+
void IntrHandler::queueUp(int subintr) {
if (subintr == PSP_INTR_SUB_NONE) {
+ // Always queue here. Derived handlers may override run(), and the base
+ // handler check belongs in IntrHandler::run().
pendingInterrupts.push_back(PendingInterrupt(intrNumber, subintr));
} else {
// Just call execute on all the subintr handlers for this interrupt.
@@ -590,6 +632,50 @@ static int QueryIntrHandlerInfo()
return 0;
}
+static u32 sceKernelRegisterIntrHandler(u32 intrNumber, u32 unknown, u32 handler, u32 handlerArg, u32 subCount) {
+ (void)unknown;
+ (void)subCount;
+ if (intrNumber >= PSP_NUMBER_INTERRUPTS) {
+ return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "invalid interrupt");
+ }
+ IntrHandler *intr = intrHandlers[intrNumber];
+ if (handler == 0) {
+ return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_ADDR, "NULL handler");
+ }
+ if (intr == nullptr) {
+ return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "missing interrupt");
+ }
+ if (intrNumber == PSP_MECODEC_INTR) {
+ DEBUG_LOG(Log::sceIntc, "Registering base handler for MECODEC intr at %08x", handler);
+ }
+ intr->setBase(handler, handlerArg);
+ return hleLogDebug(Log::sceIntc, 0);
+}
+
+static u32 sceKernelReleaseIntrHandler(u32 intrNumber) {
+ if (intrNumber >= PSP_NUMBER_INTERRUPTS) {
+ return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "invalid interrupt");
+ }
+ IntrHandler *intr = intrHandlers[intrNumber];
+ if (intr == nullptr) {
+ return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "missing interrupt");
+ }
+ intr->clearBase();
+ return hleLogDebug(Log::sceIntc, 0);
+}
+
+static u32 sceKernelEnableIntr(u32 intrNumber) {
+ if (intrNumber >= PSP_NUMBER_INTERRUPTS) {
+ return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "invalid interrupt");
+ }
+ IntrHandler *intr = intrHandlers[intrNumber];
+ if (intr == nullptr) {
+ return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "missing interrupt");
+ }
+ intr->enableBase();
+ return hleLogDebug(Log::sceIntc, 0);
+}
+
static u32 sceKernelMemset(u32 addr, u32 fillc, u32 n) {
u8 c = fillc & 0xff;
bool skip = false;
@@ -1053,6 +1139,10 @@ const HLEFunction InterruptManagerForKernel[] =
{0XFA835CDE, &WrapI_I, "sceKernelGetTlsAddr", 'i', "i" ,HLE_KERNEL_SYSCALL },
{0X05572A5F, &WrapV_V, "sceKernelExitGame", 'v', "" ,HLE_KERNEL_SYSCALL },
{0X4AC57943, &WrapI_I, "sceKernelRegisterExitCallback", 'i', "i" ,HLE_KERNEL_SYSCALL },
+ // Media Engine base interrupt handlers (added at end to preserve syscall numbering).
+ {0XF987B1F0, &WrapU_U, "sceKernelReleaseIntrHandler", 'x', "x" ,HLE_KERNEL_SYSCALL },
+ {0X58DD8978, &WrapU_UUUUU, "sceKernelRegisterIntrHandler", 'x', "xxxxx",HLE_KERNEL_SYSCALL },
+ {0X4D6E7305, &WrapU_U, "sceKernelEnableIntr", 'x', "x" ,HLE_KERNEL_SYSCALL },
};
void Register_InterruptManagerForKernel()
diff --git a/Core/HLE/sceKernelInterrupt.h b/Core/HLE/sceKernelInterrupt.h
index 4111ed360d84..4b23dc4340be 100644
--- a/Core/HLE/sceKernelInterrupt.h
+++ b/Core/HLE/sceKernelInterrupt.h
@@ -119,12 +119,19 @@ class IntrHandler
void disable(int subIntrNum);
SubIntrHandler *get(int subIntrNum);
void clear();
+ void setBase(u32 handlerAddress, u32 handlerArg);
+ void clearBase();
+ void enableBase();
+ void disableBase();
void DoState(PointerWrap &p);
private:
int intrNumber;
+ bool baseEnabled_ = false;
+ u32 baseHandlerAddress_ = 0;
+ u32 baseHandlerArg_ = 0;
std::map subIntrHandlers;
};
diff --git a/Core/HLE/sceMeCore.cpp b/Core/HLE/sceMeCore.cpp
new file mode 100644
index 000000000000..ea2c81cd44f5
--- /dev/null
+++ b/Core/HLE/sceMeCore.cpp
@@ -0,0 +1,36 @@
+// Copyright (c) 2012- PPSSPP 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 or later versions.
+
+// 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/hrydgard/ppsspp and http://www.ppsspp.org/.
+
+#include "Core/HLE/HLE.h"
+#include "Core/HLE/FunctionWrappers.h"
+
+static u32 sceMeBootStartStub(u32 arg) { return 0; }
+
+const HLEFunction sceMeCore_driver[] = {
+ {0X47DB48C2, &WrapU_U, "sceMeBootStart", 'x', "x" },
+ {0XC287AD90, &WrapU_U, "sceMeBootStart371", 'x', "x" },
+ {0XD857CF93, &WrapU_U, "sceMeBootStart380", 'x', "x" },
+ {0X8988AD49, &WrapU_U, "sceMeBootStart395", 'x', "x" },
+ {0X051C1601, &WrapU_U, "sceMeBootStart500", 'x', "x" },
+ {0X3A2E60BB, &WrapU_U, "sceMeBootStart620", 'x', "x" },
+ {0X99E4DBFA, &WrapU_U, "sceMeBootStart635", 'x', "x" },
+ {0X5DFF5C50, &WrapU_U, "sceMeBootStart660", 'x', "x" },
+};
+
+void Register_sceMeCore_driver() {
+ RegisterHLEModule("sceMeCore_driver", ARRAY_SIZE(sceMeCore_driver), sceMeCore_driver);
+}
diff --git a/Core/HLE/sceMeCore.h b/Core/HLE/sceMeCore.h
new file mode 100644
index 000000000000..2ad6c7044787
--- /dev/null
+++ b/Core/HLE/sceMeCore.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2012- PPSSPP 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 or later versions.
+
+// 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/hrydgard/ppsspp and http://www.ppsspp.org/.
+
+#pragma once
+
+void Register_sceMeCore_driver();
diff --git a/Core/HLE/sceSysEvent.cpp b/Core/HLE/sceSysEvent.cpp
new file mode 100644
index 000000000000..5c4aaf626aad
--- /dev/null
+++ b/Core/HLE/sceSysEvent.cpp
@@ -0,0 +1,56 @@
+// Copyright (c) 2012- PPSSPP 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 or later versions.
+
+// 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/hrydgard/ppsspp and http://www.ppsspp.org/.
+
+#include "Core/HLE/HLE.h"
+#include "Core/HLE/FunctionWrappers.h"
+#include "Core/MemMap.h"
+
+// Provide a minimal PspSysEventHandler for ME startup code.
+// It uses a fixed kernel address and the name "SceMeRpc".
+static u32 sceKernelReferSysEventHandler() {
+ const u32 handlerAddr = 0x88000100;
+ const u32 nameAddr = handlerAddr + 0x40; // name string after struct
+
+ // Write name string "SceMeRpc\0"
+ Memory::Write_U32(0x4D656353, nameAddr); // "SceM" (little-endian: 'S','c','e','M')
+ Memory::Write_U32(0x63705265, nameAddr + 4); // "eRpc" (little-endian: 'e','R','p','c')
+ Memory::Write_U8(0, nameAddr + 8); // null terminator
+
+ // Write the fixed handler record.
+ Memory::Write_U32(64, handlerAddr); // size
+ Memory::Write_U32(nameAddr, handlerAddr + 4); // name pointer
+ Memory::Write_U32(0xFFFF00, handlerAddr + 8); // type_mask
+ Memory::Write_U32(0, handlerAddr + 12); // handler (will be patched by kinit)
+ Memory::Write_U32(0, handlerAddr + 16); // r28
+ Memory::Write_U32(0, handlerAddr + 20); // busy
+ Memory::Write_U32(0, handlerAddr + 24); // next = NULL (end of list)
+
+ return handlerAddr;
+}
+
+static u32 sceKernelRegisterSysEventHandler(u32 handler) { return 0; }
+static u32 sceKernelUnregisterSysEventHandler(u32 handler) { return 0; }
+
+const HLEFunction sceSysEventForKernel[] = {
+ {0X68D55505, &WrapU_V, "sceKernelReferSysEventHandler", 'x', "" },
+ {0XCD9E4BB5, &WrapU_U, "sceKernelRegisterSysEventHandler", 'x', "x" },
+ {0XD7D3FDCD, &WrapU_U, "sceKernelUnregisterSysEventHandler", 'x', "x" },
+};
+
+void Register_sceSysEventForKernel() {
+ RegisterHLEModule("sceSysEventForKernel", ARRAY_SIZE(sceSysEventForKernel), sceSysEventForKernel);
+}
diff --git a/Core/HLE/sceSysEvent.h b/Core/HLE/sceSysEvent.h
new file mode 100644
index 000000000000..c8a0dcf364a0
--- /dev/null
+++ b/Core/HLE/sceSysEvent.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2012- PPSSPP 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 or later versions.
+
+// 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/hrydgard/ppsspp and http://www.ppsspp.org/.
+
+#pragma once
+
+void Register_sceSysEventForKernel();
diff --git a/Core/HLE/sceSysreg.cpp b/Core/HLE/sceSysreg.cpp
new file mode 100644
index 000000000000..dbaee4c03940
--- /dev/null
+++ b/Core/HLE/sceSysreg.cpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2012- PPSSPP 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 or later versions.
+
+// 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/hrydgard/ppsspp and http://www.ppsspp.org/.
+
+#include "Core/HLE/HLE.h"
+#include "Core/HLE/FunctionWrappers.h"
+#include "Core/MIPS/MIPS.h"
+
+static u32 sceSysregMeResetEnable371() { return 0; }
+static u32 sceSysregMeBusClockEnable371() { return 0; }
+static u32 sceSysregMeResetDisable371() { Core_EnableME(); return 0; }
+static u32 sceSysregVmeResetEnable371() { return 0; }
+static u32 sceSysregAvcResetEnable371() { return 0; }
+static u32 sceSysregMeBusClockDisable371() { return 0; }
+
+const HLEFunction sceSysreg_driver[] = {
+ // FW 3.71+ NIDs:
+ {0XA9997109, &WrapU_V, "sceSysregMeResetEnable371", 'x', "" },
+ {0X3199CF1C, &WrapU_V, "sceSysregMeBusClockEnable371", 'x', "" },
+ {0X76220E94, &WrapU_V, "sceSysregMeResetDisable371", 'x', "" },
+ {0X17A22D51, &WrapU_V, "sceSysregVmeResetEnable371", 'x', "" },
+ {0XE5B3D348, &WrapU_V, "sceSysregAvcResetEnable371", 'x', "" },
+ {0X07881A0B, &WrapU_V, "sceSysregMeBusClockDisable371", 'x', "" },
+ // Pre-3.71 NIDs (same functions, different NID hashes):
+ {0XDE59DACB, &WrapU_V, "sceSysregMeResetEnable", 'x', "" },
+ {0X2DB0EB28, &WrapU_V, "sceSysregMeResetDisable", 'x', "" },
+ {0XD20581EA, &WrapU_V, "sceSysregVmeResetEnable", 'x', "" },
+ {0X9BB70D34, &WrapU_V, "sceSysregAvcResetEnable", 'x', "" },
+ {0X44F6CDA7, &WrapU_V, "sceSysregMeBusClockEnable", 'x', "" },
+ {0X158AD4FC, &WrapU_V, "sceSysregMeBusClockDisable", 'x', "" },
+};
+
+void Register_sceSysreg_driver() {
+ RegisterHLEModule("sceSysreg_driver", ARRAY_SIZE(sceSysreg_driver), sceSysreg_driver);
+}
diff --git a/Core/HLE/sceSysreg.h b/Core/HLE/sceSysreg.h
new file mode 100644
index 000000000000..053de1e93363
--- /dev/null
+++ b/Core/HLE/sceSysreg.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2012- PPSSPP 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 or later versions.
+
+// 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/hrydgard/ppsspp and http://www.ppsspp.org/.
+
+#pragma once
+
+void Register_sceSysreg_driver();
diff --git a/Core/MIPS/ARM64/Arm64IRAsm.cpp b/Core/MIPS/ARM64/Arm64IRAsm.cpp
index fd518d4a35c0..1443356ca292 100644
--- a/Core/MIPS/ARM64/Arm64IRAsm.cpp
+++ b/Core/MIPS/ARM64/Arm64IRAsm.cpp
@@ -22,6 +22,7 @@
#include "Common/Log.h"
#include "Core/CoreTiming.h"
#include "Core/MemMap.h"
+#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/ARM64/Arm64IRJit.h"
#include "Core/MIPS/ARM64/Arm64IRRegCache.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
@@ -284,6 +285,205 @@ void Arm64JitBackend::GenerateFixedCode(MIPSState *mipsState) {
UpdateFCR31(mipsState);
}
+// ME variant of the fixed dispatcher code.
+// Budgeting and exit checks stay in the C++ caller.
+void Arm64MEJitBackend::GenerateFixedCode(MIPSState *mipsState) {
+ const u8 *start = AlignCodePage();
+ if (DebugProfilerEnabled()) {
+ ProtectMemoryPages(start, GetMemoryProtectPageSize(), MEM_PROT_READ | MEM_PROT_WRITE);
+ hooks_.profilerPC = (uint32_t *)GetWritableCodePtr();
+ Write32(0);
+ hooks_.profilerStatus = (IRProfilerStatus *)GetWritableCodePtr();
+ Write32(0);
+ }
+
+ const u8 *disasmStart = AlignCodePage();
+ BeginWrite(GetMemoryProtectPageSize());
+
+ // Helper stubs shared with the base class.
+ if (jo.useStaticAlloc) {
+ saveStaticRegisters_ = AlignCode16();
+ STR(INDEX_UNSIGNED, DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
+ regs_.EmitSaveStaticRegisters();
+ RET();
+
+ loadStaticRegisters_ = AlignCode16();
+ regs_.EmitLoadStaticRegisters();
+ LDR(INDEX_UNSIGNED, DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
+ RET();
+ } else {
+ saveStaticRegisters_ = nullptr;
+ loadStaticRegisters_ = nullptr;
+ }
+
+ restoreRoundingMode_ = AlignCode16();
+ {
+ MRS(SCRATCH2_64, FIELD_FPCR);
+ uint32_t mask = ~(4 << 22);
+ mask &= ~(3 << 22);
+ ANDI2R(SCRATCH2, SCRATCH2, mask);
+ _MSR(FIELD_FPCR, SCRATCH2_64);
+ RET();
+ }
+
+ applyRoundingMode_ = AlignCode16();
+ {
+ LDR(INDEX_UNSIGNED, SCRATCH1, CTXREG, offsetof(MIPSState, fcr31));
+ ANDI2R(SCRATCH2, SCRATCH1, 3);
+ FixupBranch skip1 = TBZ(SCRATCH1, 24);
+ ADDI2R(SCRATCH2, SCRATCH2, 4);
+ SetJumpTarget(skip1);
+
+ FixupBranch skip = CBZ(SCRATCH2);
+
+ ANDI2R(SCRATCH1, SCRATCH2, 3);
+ CMPI2R(SCRATCH1, 1);
+ FixupBranch skipadd = B(CC_NEQ);
+ ADDI2R(SCRATCH2, SCRATCH2, 2);
+ SetJumpTarget(skipadd);
+ FixupBranch skipsub = B(CC_LE);
+ SUBI2R(SCRATCH2, SCRATCH2, 1);
+ SetJumpTarget(skipsub);
+
+ MRS(SCRATCH1_64, FIELD_FPCR);
+ ANDI2R(SCRATCH1, SCRATCH1, ~((4 | 3) << 22));
+ ORR(SCRATCH1, SCRATCH1, SCRATCH2, ArithOption(SCRATCH2, ST_LSL, 22));
+ _MSR(FIELD_FPCR, SCRATCH1_64);
+
+ SetJumpTarget(skip);
+ RET();
+ }
+
+ updateRoundingMode_ = AlignCode16();
+ {
+ LDR(INDEX_UNSIGNED, SCRATCH1, CTXREG, offsetof(MIPSState, fcr31));
+ ANDI2R(SCRATCH2, SCRATCH1, 3);
+ FixupBranch skip = TBZ(SCRATCH1, 24);
+ ADDI2R(SCRATCH2, SCRATCH2, 4);
+ SetJumpTarget(skip);
+
+ MOVP2R(SCRATCH1_64, convertS0ToSCRATCH1_);
+ LSL(SCRATCH2, SCRATCH2, 3);
+ LDR(SCRATCH2_64, SCRATCH1_64, SCRATCH2);
+ MOVP2R(SCRATCH1_64, ¤tRoundingFunc_);
+ STR(INDEX_UNSIGNED, SCRATCH2_64, SCRATCH1_64, 0);
+ RET();
+ }
+
+ // Entry point.
+ hooks_.enterDispatcher = (IRNativeFuncNoArg)AlignCode16();
+
+ uint32_t regs_to_save = Arm64Gen::ALL_CALLEE_SAVED;
+ uint32_t regs_to_save_fp = Arm64Gen::ALL_CALLEE_SAVED_FP;
+ fp_.ABI_PushRegisters(regs_to_save, regs_to_save_fp);
+
+ MOVP2R(MEMBASEREG, Memory::base);
+ MOVP2R(CTXREG, mipsState);
+ MOVI2R(JITBASEREG, (intptr_t)GetBasePtr() - MIPS_EMUHACK_OPCODE);
+
+ LoadStaticRegisters();
+ ApplyRoundingMode(true);
+
+ // First entry uses the PC already stored in mipsState.
+ FixupBranch skipFirstMovToPC = B();
+
+ // Compiled blocks jump here with the next PC in SCRATCH1.
+ dispatcherPCInSCRATCH1_ = GetCodePtr();
+ outerLoopPCInSCRATCH1_ = GetCodePtr();
+ outerLoop_ = GetCodePtr();
+ MovToPC(SCRATCH1);
+
+ hooks_.dispatcher = GetCodePtr();
+ SetJumpTarget(skipFirstMovToPC);
+
+ // Stop when downcount < 0.
+ FixupBranch bail = TBNZ(DOWNCOUNTREG, 31);
+
+ dispatcherNoCheck_ = GetCodePtr();
+ dispatcherCheckCoreState_ = GetCodePtr();
+
+ hooks_.dispatchFetch = GetCodePtr();
+
+ // Fast cache lookup.
+ // Read pc from MIPSState into SCRATCH1 (W16)
+ MovFromPC(SCRATCH1);
+ // Compute cache index: (pc >> 2) & 0xFFF, then *16 for 16-byte entries.
+ LSR(SCRATCH2, SCRATCH1, 2);
+ ANDI2R(SCRATCH2, SCRATCH2, 0xFFF);
+ LSL(SCRATCH2, SCRATCH2, 4); // index * sizeof(FastCacheEntry) = index * 16
+ // Load base pointer to fast cache into X0
+ MOVP2R(X0, &MIPSComp::Arm64MEIRJit::g_meFastCache);
+ LDR(INDEX_UNSIGNED, X0, X0, 0); // X0 = g_meFastCache (deref pointer-to-pointer)
+ // Index into cache: X0 = &fastCache_[idx]
+ ADD(X0, X0, SCRATCH2_64);
+ // Compare entry.pc (offset 0) with current pc (SCRATCH1 = W16)
+ LDR(INDEX_UNSIGNED, SCRATCH2, X0, 0); // entry.pc (W17)
+ CMP(SCRATCH1, SCRATCH2);
+ FixupBranch cacheMiss = B(CC_NEQ);
+ // Cache hit: load entry.nativeEntry (offset 8)
+ LDR(INDEX_UNSIGNED, X0, X0, 8); // entry.nativeEntry
+ FixupBranch cacheNullEntry = CBZ(X0);
+ // Jump straight to the native block.
+ BR(X0);
+
+ // Cache miss: call back into C++.
+ SetJumpTarget(cacheMiss);
+ SetJumpTarget(cacheNullEntry);
+ SaveStaticRegisters();
+ RestoreRoundingMode(true);
+ QuickCallFunction(SCRATCH1_64, &MECompileAndLookup);
+ ApplyRoundingMode(true);
+ LoadStaticRegisters();
+
+ // MECompileAndLookup returns a native pointer in X0.
+ FixupBranch compileFailed = CBZ(X0);
+ BR(X0);
+
+ // Exit path.
+ SetJumpTarget(bail);
+ SetJumpTarget(compileFailed);
+
+ SaveStaticRegisters();
+ RestoreRoundingMode(true);
+ fp_.ABI_PopRegisters(regs_to_save, regs_to_save_fp);
+ RET();
+
+ // --- Crash handler ---
+ hooks_.crashHandler = GetCodePtr();
+ MOVP2R(SCRATCH1_64, &coreState);
+ MOVI2R(SCRATCH2, CORE_RUNTIME_ERROR);
+ STR(INDEX_UNSIGNED, SCRATCH2, SCRATCH1_64, 0);
+ SaveStaticRegisters();
+ RestoreRoundingMode(true);
+ fp_.ABI_PopRegisters(regs_to_save, regs_to_save_fp);
+ RET();
+
+ // --- Integer conversion stubs ---
+ static const RoundingMode roundModes[8] = { ROUND_N, ROUND_Z, ROUND_P, ROUND_M, ROUND_N, ROUND_Z, ROUND_P, ROUND_M };
+ for (size_t i = 0; i < ARRAY_SIZE(roundModes); ++i) {
+ convertS0ToSCRATCH1_[i] = AlignCode16();
+ fp_.MVNI(32, EncodeRegToDouble(SCRATCHF2), 0x80, 24);
+ fp_.FCMP(S0, S0);
+ fp_.FCVTS(S0, S0, roundModes[i]);
+ fp_.FCSEL(S0, S0, SCRATCHF2, CC_VC);
+ RET();
+ }
+
+ if (enableDisasm) {
+ std::vector lines = DisassembleArm64(disasmStart, (int)(GetCodePtr() - disasmStart));
+ for (auto s : lines) {
+ INFO_LOG(Log::JIT, "%s", s.c_str());
+ }
+ }
+
+ AlignCodePage();
+ jitStartOffset_ = (int)(GetCodePtr() - start);
+ FlushIcache();
+ EndWrite();
+
+ UpdateFCR31(mipsState);
+}
+
} // namespace MIPSComp
#endif
diff --git a/Core/MIPS/ARM64/Arm64IRCompLoadStore.cpp b/Core/MIPS/ARM64/Arm64IRCompLoadStore.cpp
index d0fde9f6f2fc..ed1e601e90f3 100644
--- a/Core/MIPS/ARM64/Arm64IRCompLoadStore.cpp
+++ b/Core/MIPS/ARM64/Arm64IRCompLoadStore.cpp
@@ -38,6 +38,37 @@ namespace MIPSComp {
using namespace Arm64Gen;
using namespace Arm64IRJitConstants;
+static u32 ComputeConstantAddress(const IRInst &inst, Arm64IRRegCache ®s) {
+ uint64_t base = 0;
+ if (inst.src1 != MIPS_REG_ZERO) {
+ if (!regs.IsGPRImm(inst.src1))
+ return 0;
+ base = regs.GetGPRImm(inst.src1);
+ }
+
+ int64_t imm = (int32_t)inst.constant;
+ if ((imm & 0xC0000000) == 0x80000000) {
+ imm = (uint64_t)(uint32_t)inst.constant;
+ }
+ return (u32)(base + imm);
+}
+
+static bool NeedsGenericMeHwAccess(const IRInst &inst, Arm64IRRegCache ®s, const MIPSComp::JitOptions &jo) {
+ if (!jo.isMeJit)
+ return false;
+ // Only fall back for provable HW register accesses.
+ // Non-constant bases keep the normal RAM path.
+ if (inst.src1 != MIPS_REG_ZERO && !regs.IsGPRImm(inst.src1)) {
+ return false;
+ }
+ u32 addr = ComputeConstantAddress(inst, regs);
+ bool sensitive = Memory::IsMeSensitiveHwPage(addr);
+ if (sensitive) {
+ DEBUG_LOG(Log::JIT, "ME HW access detected: addr=%08x src1=%d constant=%08x -> GENERIC", addr, inst.src1, inst.constant);
+ }
+ return sensitive;
+}
+
static int IROpToByteWidth(IROp op) {
switch (op) {
case IROp::Load8:
@@ -80,6 +111,11 @@ Arm64JitBackend::LoadStoreArg Arm64JitBackend::PrepareSrc1Address(IRInst inst) {
// If it's about to be clobbered, don't waste time pointerifying. Use displacement.
bool clobbersSrc1 = !readsFromSrc1 && regs_.IsGPRClobbered(inst.src1);
+#ifdef MASKED_PSP_MEMORY
+ // ME kseg1 addresses need a 29-bit physical mask.
+ const u32 addrMask = jo.isMeJit ? 0x1FFFFFFFU : Memory::MEMVIEW32_MASK;
+#endif
+
int64_t imm = (int32_t)inst.constant;
// It can't be this negative, must be a constant address with the top bit set.
if ((imm & 0xC0000000) == 0x80000000) {
@@ -91,7 +127,7 @@ Arm64JitBackend::LoadStoreArg Arm64JitBackend::PrepareSrc1Address(IRInst inst) {
// The constant gets applied later.
addrArg.base = MEMBASEREG;
#ifdef MASKED_PSP_MEMORY
- imm &= Memory::MEMVIEW32_MASK;
+ imm &= addrMask;
#endif
} else if (!jo.enablePointerify && readsFromSrc1) {
#ifndef MASKED_PSP_MEMORY
@@ -107,7 +143,7 @@ Arm64JitBackend::LoadStoreArg Arm64JitBackend::PrepareSrc1Address(IRInst inst) {
if (!addrArg.useRegisterOffset) {
ADDI2R(SCRATCH1, regs_.MapGPR(inst.src1), imm, SCRATCH2);
#ifdef MASKED_PSP_MEMORY
- ANDI2R(SCRATCH1, SCRATCH1, Memory::MEMVIEW32_MASK, SCRATCH2);
+ ANDI2R(SCRATCH1, SCRATCH1, addrMask, SCRATCH2);
#endif
addrArg.base = MEMBASEREG;
@@ -121,7 +157,7 @@ Arm64JitBackend::LoadStoreArg Arm64JitBackend::PrepareSrc1Address(IRInst inst) {
} else {
ADDI2R(SCRATCH1, regs_.MapGPR(inst.src1), imm, SCRATCH2);
#ifdef MASKED_PSP_MEMORY
- ANDI2R(SCRATCH1, SCRATCH1, Memory::MEMVIEW32_MASK, SCRATCH2);
+ ANDI2R(SCRATCH1, SCRATCH1, addrMask, SCRATCH2);
#endif
addrArg.base = MEMBASEREG;
@@ -136,7 +172,7 @@ Arm64JitBackend::LoadStoreArg Arm64JitBackend::PrepareSrc1Address(IRInst inst) {
#ifdef MASKED_PSP_MEMORY
// In case we have an address + offset reg.
if (imm > 0)
- imm &= Memory::MEMVIEW32_MASK;
+ imm &= addrMask;
#endif
int scale = IROpToByteWidth(inst.op);
@@ -150,10 +186,17 @@ Arm64JitBackend::LoadStoreArg Arm64JitBackend::PrepareSrc1Address(IRInst inst) {
addrArg.useUnscaled = true;
} else {
// No luck, we'll need to load into a register.
- MOVI2R(SCRATCH1, imm);
+ if (addrArg.base != MEMBASEREG) {
+ // Pointerified bases need 32-bit wrapping arithmetic here.
+ ADDI2R(SCRATCH1, DecodeReg(addrArg.base), (u64)(u32)(s32)imm, SCRATCH2);
+ addrArg.base = MEMBASEREG;
+ } else {
+ MOVI2R(SCRATCH1, imm);
+ }
addrArg.regOffset = SCRATCH1;
addrArg.useRegisterOffset = true;
- addrArg.signExtendRegOffset = true;
+ // Keep bit 31 addresses inside the 4 GB arena.
+ addrArg.signExtendRegOffset = false;
}
}
@@ -164,6 +207,8 @@ void Arm64JitBackend::CompIR_CondStore(IRInst inst) {
CONDITIONAL_DISABLE;
if (inst.op != IROp::Store32Conditional)
INVALIDOP;
+ if (NeedsGenericMeHwAccess(inst, regs_, jo))
+ DISABLE;
regs_.SpillLockGPR(IRREG_LLBIT, inst.src3, inst.src1);
LoadStoreArg addrArg = PrepareSrc1Address(inst);
@@ -197,6 +242,8 @@ void Arm64JitBackend::CompIR_CondStore(IRInst inst) {
void Arm64JitBackend::CompIR_FLoad(IRInst inst) {
CONDITIONAL_DISABLE;
+ if (NeedsGenericMeHwAccess(inst, regs_, jo))
+ DISABLE;
LoadStoreArg addrArg = PrepareSrc1Address(inst);
@@ -220,6 +267,8 @@ void Arm64JitBackend::CompIR_FLoad(IRInst inst) {
void Arm64JitBackend::CompIR_FStore(IRInst inst) {
CONDITIONAL_DISABLE;
+ if (NeedsGenericMeHwAccess(inst, regs_, jo))
+ DISABLE;
LoadStoreArg addrArg = PrepareSrc1Address(inst);
@@ -243,6 +292,8 @@ void Arm64JitBackend::CompIR_FStore(IRInst inst) {
void Arm64JitBackend::CompIR_Load(IRInst inst) {
CONDITIONAL_DISABLE;
+ if (NeedsGenericMeHwAccess(inst, regs_, jo))
+ DISABLE;
regs_.SpillLockGPR(inst.dest, inst.src1);
LoadStoreArg addrArg = PrepareSrc1Address(inst);
@@ -339,6 +390,8 @@ void Arm64JitBackend::CompIR_LoadShift(IRInst inst) {
void Arm64JitBackend::CompIR_Store(IRInst inst) {
CONDITIONAL_DISABLE;
+ if (NeedsGenericMeHwAccess(inst, regs_, jo))
+ DISABLE;
regs_.SpillLockGPR(inst.src3, inst.src1);
LoadStoreArg addrArg = PrepareSrc1Address(inst);
@@ -404,6 +457,8 @@ void Arm64JitBackend::CompIR_StoreShift(IRInst inst) {
void Arm64JitBackend::CompIR_VecLoad(IRInst inst) {
CONDITIONAL_DISABLE;
+ if (NeedsGenericMeHwAccess(inst, regs_, jo))
+ DISABLE;
LoadStoreArg addrArg = PrepareSrc1Address(inst);
@@ -427,6 +482,8 @@ void Arm64JitBackend::CompIR_VecLoad(IRInst inst) {
void Arm64JitBackend::CompIR_VecStore(IRInst inst) {
CONDITIONAL_DISABLE;
+ if (NeedsGenericMeHwAccess(inst, regs_, jo))
+ DISABLE;
LoadStoreArg addrArg = PrepareSrc1Address(inst);
diff --git a/Core/MIPS/ARM64/Arm64IRCompSystem.cpp b/Core/MIPS/ARM64/Arm64IRCompSystem.cpp
index 54a759f1bd26..e4f2263d1c48 100644
--- a/Core/MIPS/ARM64/Arm64IRCompSystem.cpp
+++ b/Core/MIPS/ARM64/Arm64IRCompSystem.cpp
@@ -383,7 +383,7 @@ void Arm64JitBackend::CompIR_ValidateAddress(IRInst inst) {
regs_.Map(inst);
ADDI2R(SCRATCH1, regs_.R(inst.src1), inst.constant, SCRATCH2);
}
- ANDI2R(SCRATCH1, SCRATCH1, 0x3FFFFFFF, SCRATCH2);
+ ANDI2R(SCRATCH1, SCRATCH1, jo.isMeJit ? 0x1FFFFFFFU : 0x3FFFFFFFU, SCRATCH2);
std::vector validJumps;
diff --git a/Core/MIPS/ARM64/Arm64IRJit.cpp b/Core/MIPS/ARM64/Arm64IRJit.cpp
index f9b206fffc5c..568dd040727e 100644
--- a/Core/MIPS/ARM64/Arm64IRJit.cpp
+++ b/Core/MIPS/ARM64/Arm64IRJit.cpp
@@ -33,6 +33,8 @@ namespace MIPSComp {
using namespace Arm64Gen;
using namespace Arm64IRJitConstants;
+Arm64MEIRJit::FastCacheEntry *Arm64MEIRJit::g_meFastCache = nullptr;
+
// Invalidations just need at most two MOVs and B.
static constexpr int MIN_BLOCK_NORMAL_LEN = 12;
// As long as we can fit a B, we should be fine.
@@ -414,6 +416,34 @@ void Arm64JitBackend::LoadStaticRegisters() {
}
}
+const u8 *Arm64MEIRJit::CompileAndLookup(u32 pc) {
+ // Fast path: direct-mapped cache lookup.
+ int idx = (pc >> kFastCacheShift) & kFastCacheMask;
+ FastCacheEntry &entry = fastCache_[idx];
+ if (entry.pc == pc && entry.nativeEntry) {
+ return entry.nativeEntry;
+ }
+
+ // Slow path: find or compile block.
+ int blockNum = blocks_.FindPreloadBlock(pc);
+ if (blockNum < 0) {
+ Compile(pc);
+ blockNum = blocks_.FindPreloadBlock(pc);
+ }
+ if (blockNum < 0) {
+ return nullptr;
+ }
+ const IRBlock *irBlock = blocks_.GetBlock(blockNum);
+ if (!irBlock || irBlock->GetNativeOffset() < 0) {
+ return nullptr;
+ }
+ const u8 *result = backend_->CodeBlock().GetBasePtr() + irBlock->GetNativeOffset();
+
+ entry.pc = pc;
+ entry.nativeEntry = result;
+ return result;
+}
+
} // namespace MIPSComp
#endif
diff --git a/Core/MIPS/ARM64/Arm64IRJit.h b/Core/MIPS/ARM64/Arm64IRJit.h
index 66053f42ae79..8cd2f1b90aac 100644
--- a/Core/MIPS/ARM64/Arm64IRJit.h
+++ b/Core/MIPS/ARM64/Arm64IRJit.h
@@ -52,6 +52,8 @@ class Arm64JitBackend : public Arm64Gen::ARM64CodeBlock, public IRNativeBackend
}
private:
+ friend class Arm64MEJitBackend;
+
void RestoreRoundingMode(bool force = false);
void ApplyRoundingMode(bool force = false);
void UpdateRoundingMode(bool force = false);
@@ -165,6 +167,67 @@ class Arm64IRJit : public IRNativeJit {
Arm64JitBackend arm64Backend_;
};
+// ME variant with a simpler dispatcher.
+class Arm64MEJitBackend : public Arm64JitBackend {
+public:
+ using Arm64JitBackend::Arm64JitBackend;
+ void GenerateFixedCode(MIPSState *mipsState) override;
+};
+
+class Arm64MEIRJit : public IRNativeJit {
+public:
+ Arm64MEIRJit(MIPSState *mipsState)
+ : IRNativeJit(mipsState), meBackend_(jo, blocks_) {
+ // Do not patch EMUHACKs into RAM. The main CPU JIT shares the same
+ // address space.
+ jo.enableBlocklink = false;
+ // Keep address masking on the MEMBASEREG path for kseg0/kseg1.
+ jo.enablePointerify = false;
+ // Let CompIR_* be conservative around sensitive MMIO accesses.
+ jo.isMeJit = true;
+ blocks_.SetPatchMemory(false);
+ Init(meBackend_);
+ memset(fastCache_, 0, sizeof(fastCache_));
+ g_meFastCache = fastCache_;
+ }
+
+ // Returns the native entry for pc, compiling on demand.
+ const u8 *CompileAndLookup(u32 pc);
+
+ // Runs native blocks until downcount < 0.
+ void EnterDispatcher() {
+ hooks_.enterDispatcher();
+ }
+
+ void ClearCache() override {
+ IRNativeJit::ClearCache();
+ memset(fastCache_, 0, sizeof(fastCache_));
+ }
+
+ void InvalidateCacheAt(u32 em_address, int length = 4) override {
+ IRNativeJit::InvalidateCacheAt(em_address, length);
+ memset(fastCache_, 0, sizeof(fastCache_));
+ }
+
+ // Fast direct-mapped cache: PC -> native entry pointer.
+ static constexpr int kFastCacheShift = 2; // Instructions are 4-byte aligned
+ static constexpr int kFastCacheSize = 4096;
+ static constexpr int kFastCacheMask = kFastCacheSize - 1;
+ struct FastCacheEntry {
+ u32 pc;
+ u32 pad; // Align nativeEntry to 8 bytes within 16-byte entry
+ const u8 *nativeEntry;
+ };
+ static_assert(sizeof(FastCacheEntry) == 16, "FastCacheEntry must be 16 bytes for asm indexing");
+
+ // Shared with generated asm.
+ static FastCacheEntry *g_meFastCache;
+
+private:
+ Arm64MEJitBackend meBackend_;
+ FastCacheEntry fastCache_[kFastCacheSize];
+};
+
} // namespace MIPSComp
#endif
diff --git a/Core/MIPS/ARM64/Arm64Jit.cpp b/Core/MIPS/ARM64/Arm64Jit.cpp
index 4b152d28c899..4e4d995f8278 100644
--- a/Core/MIPS/ARM64/Arm64Jit.cpp
+++ b/Core/MIPS/ARM64/Arm64Jit.cpp
@@ -26,6 +26,10 @@
#include "Common/CPUDetect.h"
#include "Common/StringUtils.h"
+#if PPSSPP_PLATFORM(MAC)
+#include
+#endif
+
#include "Core/Reporting.h"
#include "Core/Config.h"
#include "Core/Core.h"
@@ -301,6 +305,10 @@ void Arm64Jit::Compile(u32 em_address) {
void Arm64Jit::RunLoopUntil(u64 globalticks) {
PROFILE_THIS_SCOPE("jit");
+#if PPSSPP_PLATFORM(MAC) && PPSSPP_ARCH(ARM64)
+ // Ensure W^X is in execute mode on this thread (MAP_JIT pages default to writable).
+ pthread_jit_write_protect_np(true);
+#endif
((void (*)())enterDispatcher)();
}
diff --git a/Core/MIPS/IR/IRFrontend.cpp b/Core/MIPS/IR/IRFrontend.cpp
index 38337623f685..4ebe2e46c1e1 100644
--- a/Core/MIPS/IR/IRFrontend.cpp
+++ b/Core/MIPS/IR/IRFrontend.cpp
@@ -188,6 +188,18 @@ void IRFrontend::Comp_ReplacementFunc(MIPSOpcode op) {
void IRFrontend::Comp_Generic(MIPSOpcode op) {
FlushAll();
ir.Write(IROp::Interpret, 0, ir.AddConstant(op.encoding));
+
+ // ERET (0x42000018) and HALT (0x70000000) change control flow or stop
+ // execution entirely. They must terminate the IR block so the caller
+ // can react (e.g., redirect PC or stop the ME).
+ if (op.encoding == 0x42000018 || op.encoding == 0x70000000) {
+ ir.Write(IROp::Downcount, 0, ir.AddConstant(js.downcountAmount));
+ js.downcountAmount = 0;
+ ir.Write(IROp::ExitToPC);
+ js.compiling = false;
+ return;
+ }
+
const MIPSInfo info = MIPSGetInfo(op);
if ((info & IS_VFPU) != 0 && (info & VFPU_NO_PREFIX) == 0) {
// If it does eat them, it'll happen in MIPSCompileOp().
@@ -275,19 +287,24 @@ void IRFrontend::DoJit(u32 em_address, std::vector &instructions, u32 &m
IRWriter simplified;
IRWriter *code = &ir;
+ bool isME = (em_address >= 0x80000000 || (currentMIPS != nullptr && currentMIPS != &mipsr4k));
if (!js.hadBreakpoints) {
- std::vector passes{
- &ApplyMemoryValidation,
- &RemoveLoadStoreLeftRight,
- &OptimizeFPMoves,
- &PropagateConstants,
- &PurgeTemps,
- &ReduceVec4Flush,
- &OptimizeLoadsAfterStores,
- // &ReorderLoadStore,
- // &MergeLoadStore,
- // &ThreeOpToTwoOp,
- };
+ std::vector passes;
+ // ME blocks use a custom validation pass that skips HW register
+ // addresses (handled by the backend's NeedsGenericMeHwAccess).
+ if (!isME)
+ passes.push_back(&ApplyMemoryValidation);
+ passes.push_back(&RemoveLoadStoreLeftRight);
+ passes.push_back(&OptimizeFPMoves);
+ passes.push_back(&PropagateConstants);
+ if (isME)
+ passes.push_back(&ApplyMeMemoryValidation);
+ passes.push_back(&PurgeTemps);
+ passes.push_back(&ReduceVec4Flush);
+ passes.push_back(&OptimizeLoadsAfterStores);
+ // &ReorderLoadStore,
+ // &MergeLoadStore,
+ // &ThreeOpToTwoOp,
if (opts.optimizeForInterpreter) {
// Add special passes here.
diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp
index ac8bd0871874..423c29feee53 100644
--- a/Core/MIPS/IR/IRInterpreter.cpp
+++ b/Core/MIPS/IR/IRInterpreter.cpp
@@ -229,19 +229,19 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) {
break;
case IROp::Load8:
- mips->r[inst->dest] = Memory::ReadUnchecked_U8(mips->r[inst->src1] + inst->constant);
+ mips->r[inst->dest] = Memory::Read_U8(mips->r[inst->src1] + inst->constant);
break;
case IROp::Load8Ext:
- mips->r[inst->dest] = SignExtend8ToU32(Memory::ReadUnchecked_U8(mips->r[inst->src1] + inst->constant));
+ mips->r[inst->dest] = SignExtend8ToU32(Memory::Read_U8(mips->r[inst->src1] + inst->constant));
break;
case IROp::Load16:
- mips->r[inst->dest] = Memory::ReadUnchecked_U16(mips->r[inst->src1] + inst->constant);
+ mips->r[inst->dest] = Memory::Read_U16(mips->r[inst->src1] + inst->constant);
break;
case IROp::Load16Ext:
- mips->r[inst->dest] = SignExtend16ToU32(Memory::ReadUnchecked_U16(mips->r[inst->src1] + inst->constant));
+ mips->r[inst->dest] = SignExtend16ToU32(Memory::Read_U16(mips->r[inst->src1] + inst->constant));
break;
case IROp::Load32:
- mips->r[inst->dest] = Memory::ReadUnchecked_U32(mips->r[inst->src1] + inst->constant);
+ mips->r[inst->dest] = Memory::Read_U32(mips->r[inst->src1] + inst->constant);
break;
case IROp::Load32Left:
{
@@ -271,13 +271,13 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) {
break;
case IROp::Store8:
- Memory::WriteUnchecked_U8(mips->r[inst->src3], mips->r[inst->src1] + inst->constant);
+ Memory::Write_U8(mips->r[inst->src3], mips->r[inst->src1] + inst->constant);
break;
case IROp::Store16:
- Memory::WriteUnchecked_U16(mips->r[inst->src3], mips->r[inst->src1] + inst->constant);
+ Memory::Write_U16(mips->r[inst->src3], mips->r[inst->src1] + inst->constant);
break;
case IROp::Store32:
- Memory::WriteUnchecked_U32(mips->r[inst->src3], mips->r[inst->src1] + inst->constant);
+ Memory::Write_U32(mips->r[inst->src3], mips->r[inst->src1] + inst->constant);
break;
case IROp::Store32Left:
{
diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp
index f495d4fd6489..bc90ae933dd3 100644
--- a/Core/MIPS/IR/IRJit.cpp
+++ b/Core/MIPS/IR/IRJit.cpp
@@ -185,6 +185,11 @@ void IRJit::RunLoopUntil(u64 globalticks) {
compilerEnabled_ = false;
#endif
while (mips->downcount >= 0) {
+ if (!Memory::IsValid4AlignedAddress(mips->pc)) {
+ mips->downcount = -1;
+ mips->pc = 0;
+ break;
+ }
u32 inst = Memory::ReadUnchecked_U32(mips->pc);
u32 opcode = inst & 0xFF000000;
if (opcode == MIPS_EMUHACK_OPCODE) {
@@ -257,7 +262,7 @@ void IRBlockCache::Clear() {
arena_.shrink_to_fit();
}
-IRBlockCache::IRBlockCache(bool compileToNative) : compileToNative_(compileToNative) {}
+IRBlockCache::IRBlockCache(bool compileToNative, bool patchMemory) : compileToNative_(compileToNative), patchMemory_(patchMemory) {}
int IRBlockCache::AllocateBlock(int emAddr, u32 origSize, const std::vector &insts) {
// We have 24 bits to represent offsets with.
@@ -333,10 +338,14 @@ std::vector IRBlockCache::FindInvalidatedBlockNumbers(u32 address, u32 leng
}
void IRBlockCache::FinalizeBlock(int blockIndex) {
- // TODO: What's different about preload blocks?
IRBlock &block = blocks_[blockIndex];
int cookie = compileToNative_ ? block.GetNativeOffset() : block.GetIRArenaOffset();
- block.Finalize(cookie);
+ if (patchMemory_) {
+ block.Finalize(cookie);
+ } else {
+ // Update hash for validation without patching memory.
+ block.UpdateHash();
+ }
u32 startAddr, size;
block.GetRange(&startAddr, &size);
diff --git a/Core/MIPS/IR/IRJit.h b/Core/MIPS/IR/IRJit.h
index 2024ecb1d95e..7dcd3eb113d8 100644
--- a/Core/MIPS/IR/IRJit.h
+++ b/Core/MIPS/IR/IRJit.h
@@ -119,11 +119,13 @@ class IRBlock {
class IRBlockCache : public JitBlockCacheDebugInterface {
public:
- IRBlockCache(bool compileToNative);
+ IRBlockCache(bool compileToNative, bool patchMemory = true);
~IRBlockCache() {
Clear();
}
+ void SetPatchMemory(bool v) { patchMemory_ = v; }
+
void Clear();
std::vector FindInvalidatedBlockNumbers(u32 address, u32 length);
void FinalizeBlock(int blockNum);
@@ -199,6 +201,7 @@ class IRBlockCache : public JitBlockCacheDebugInterface {
private:
u32 AddressToPage(u32 addr) const;
bool compileToNative_;
+ bool patchMemory_;
std::vector blocks_;
std::vector arena_;
std::unordered_map> byPage_;
diff --git a/Core/MIPS/IR/IRNativeCommon.cpp b/Core/MIPS/IR/IRNativeCommon.cpp
index 3197d8cd414b..f6ed94cd728a 100644
--- a/Core/MIPS/IR/IRNativeCommon.cpp
+++ b/Core/MIPS/IR/IRNativeCommon.cpp
@@ -15,9 +15,14 @@
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
+#include "ppsspp_config.h"
+
#include
#include
#include
+#if PPSSPP_PLATFORM(MAC) && PPSSPP_ARCH(ARM64)
+#include
+#endif
#include "Common/Profiler/Profiler.h"
#include "Common/StringUtils.h"
#include "Common/TimeUtil.h"
@@ -521,6 +526,10 @@ void IRNativeJit::RunLoopUntil(u64 globalticks) {
}
PROFILE_THIS_SCOPE("jit");
+#if PPSSPP_PLATFORM(MAC) && PPSSPP_ARCH(ARM64)
+ // Ensure W^X is in execute mode on this thread (MAP_JIT pages default to writable).
+ pthread_jit_write_protect_np(true);
+#endif
hooks_.enterDispatcher();
}
diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp
index 30b96a2b3db0..acf79b5ea694 100644
--- a/Core/MIPS/IR/IRPassSimplify.cpp
+++ b/Core/MIPS/IR/IRPassSimplify.cpp
@@ -6,6 +6,7 @@
#include "Common/Data/Convert/SmallDataConvert.h"
#include "Common/Log.h"
#include "Core/Config.h"
+#include "Core/MemMap.h"
#include "Core/MIPS/MIPSVFPUUtils.h"
#include "Core/MIPS/IR/IRAnalysis.h"
#include "Core/MIPS/IR/IRInterpreter.h"
@@ -1808,6 +1809,62 @@ static IRMemoryOpInfo IROpMemoryAccessSize(IROp op) {
}
}
+bool ApplyMeMemoryValidation(const IRWriter &in, IRWriter &out, const IROptions &opts) {
+ CONDITIONAL_DISABLE;
+ if (g_Config.bFastMemory)
+ DISABLE;
+
+ // Skip validation for ME HW pages and non-constant base registers.
+ // Validate the remaining constant addresses.
+ std::map checks;
+ bool logBlocks = false;
+ for (IRInst inst : in.GetInstructions()) {
+ IRMemoryOpInfo info = IROpMemoryAccessSize(inst.op);
+ if (info.size != 0) {
+ bool skipValidation = false;
+ if (inst.src1 != MIPS_REG_ZERO) {
+ // Non-constant base register.
+ skipValidation = true;
+ } else {
+ // src1 == ZERO, address is fully constant.
+ u32 addr = inst.constant;
+ if (Memory::IsMeSensitiveHwPage(addr)) {
+ // The backend handles ME HW registers.
+ skipValidation = true;
+ }
+ }
+
+ if (!skipValidation) {
+ IROp validateOp = IROp::Nop;
+ switch (info.size) {
+ case 1: validateOp = IROp::ValidateAddress8; break;
+ case 2: validateOp = IROp::ValidateAddress16; break;
+ case 4: validateOp = IROp::ValidateAddress32; break;
+ case 16: validateOp = IROp::ValidateAddress128; break;
+ default: break;
+ }
+ if (validateOp != IROp::Nop) {
+ uint64_t key = ((uint64_t)inst.src1 << 32) | inst.constant;
+ auto it = checks.find(key);
+ if (it == checks.end() || it->second < info.size) {
+ out.Write(validateOp, 0, inst.src1, info.isWrite ? 1U : 0U, inst.constant);
+ checks[key] = info.size;
+ }
+ }
+ }
+ }
+
+ const IRMeta *m = GetIRMeta(inst.op);
+ if (m->types[0] == 'G' && (m->flags & IRFLAG_SRC3) == 0) {
+ uint64_t key = (uint64_t)inst.dest << 32;
+ checks.erase(checks.lower_bound(key), checks.upper_bound(key | 0xFFFFFFFFULL));
+ }
+
+ out.Write(inst);
+ }
+ return logBlocks;
+}
+
bool ApplyMemoryValidation(const IRWriter &in, IRWriter &out, const IROptions &opts) {
CONDITIONAL_DISABLE;
if (g_Config.bFastMemory)
diff --git a/Core/MIPS/IR/IRPassSimplify.h b/Core/MIPS/IR/IRPassSimplify.h
index 5fbd2a8fba02..fe0df535daf7 100644
--- a/Core/MIPS/IR/IRPassSimplify.h
+++ b/Core/MIPS/IR/IRPassSimplify.h
@@ -15,6 +15,7 @@ bool OptimizeFPMoves(const IRWriter &in, IRWriter &out, const IROptions &opts);
bool ReorderLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts);
bool MergeLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts);
bool ApplyMemoryValidation(const IRWriter &in, IRWriter &out, const IROptions &opts);
+bool ApplyMeMemoryValidation(const IRWriter &in, IRWriter &out, const IROptions &opts);
bool ReduceVec4Flush(const IRWriter &in, IRWriter &out, const IROptions &opts);
bool OptimizeLoadsAfterStores(const IRWriter &in, IRWriter &out, const IROptions &opts);
diff --git a/Core/MIPS/JitCommon/JitCommon.cpp b/Core/MIPS/JitCommon/JitCommon.cpp
index fd53e69ad6f2..4f9c99b0e93b 100644
--- a/Core/MIPS/JitCommon/JitCommon.cpp
+++ b/Core/MIPS/JitCommon/JitCommon.cpp
@@ -57,6 +57,7 @@
namespace MIPSComp {
JitInterface *jit;
+ JitInterface *mainCpuJit;
std::recursive_mutex jitLock;
void JitAt() {
diff --git a/Core/MIPS/JitCommon/JitCommon.h b/Core/MIPS/JitCommon/JitCommon.h
index 1bda27636735..d68762b57448 100644
--- a/Core/MIPS/JitCommon/JitCommon.h
+++ b/Core/MIPS/JitCommon/JitCommon.h
@@ -170,6 +170,7 @@ namespace MIPSComp {
u32 ResolveNotTakenTarget(const BranchInfo &branchInfo);
extern JitInterface *jit;
+ extern JitInterface *mainCpuJit; // Always points to main CPU JIT (never swapped for ME)
extern std::recursive_mutex jitLock;
void DoDummyJitState(PointerWrap &p);
diff --git a/Core/MIPS/JitCommon/JitState.h b/Core/MIPS/JitCommon/JitState.h
index d450eed5703a..494af06be359 100644
--- a/Core/MIPS/JitCommon/JitState.h
+++ b/Core/MIPS/JitCommon/JitState.h
@@ -239,5 +239,8 @@ namespace MIPSComp {
// Common
bool enableBlocklink;
+
+ // ME JIT: conservative memory access handling for HW registers.
+ bool isMeJit = false;
};
}
diff --git a/Core/MIPS/MIPS.cpp b/Core/MIPS/MIPS.cpp
index 4335c45e59c6..4cbfc4cf937b 100644
--- a/Core/MIPS/MIPS.cpp
+++ b/Core/MIPS/MIPS.cpp
@@ -19,11 +19,13 @@
#include
#include
#include
+#include
#include "Common/CommonTypes.h"
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
+#include "Core/Config.h"
#include "Core/ConfigValues.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSInt.h"
@@ -31,15 +33,25 @@
#include "Core/MIPS/MIPSDebugInterface.h"
#include "Core/MIPS/MIPSVFPUUtils.h"
#include "Core/MIPS/IR/IRJit.h"
+#include "Core/MIPS/IR/IRInterpreter.h"
#include "Core/Reporting.h"
#include "Core/Core.h"
#include "Core/System.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/CoreTiming.h"
+#include "Core/HLE/sceKernelInterrupt.h"
+#include "Core/MemMap.h"
+#if PPSSPP_ARCH(ARM64)
+#include "Core/MIPS/ARM64/Arm64IRJit.h"
+#elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
+#include "Core/MIPS/x86/X64IRJit.h"
+#endif
MIPSState mipsr4k;
+MIPSState mipsMe;
MIPSState *currentMIPS = &mipsr4k;
MIPSDebugInterface debugr4k(&mipsr4k);
+MIPSDebugInterface debugMe(&mipsMe);
MIPSDebugInterface *currentDebugMIPS = &debugr4k;
u8 voffset[128];
@@ -90,6 +102,7 @@ const float cst_constants[32] = {
MIPSState::MIPSState() {
MIPSComp::jit = nullptr;
+ MIPSComp::mainCpuJit = nullptr;
// Initialize vorder
@@ -157,14 +170,26 @@ MIPSState::MIPSState() {
}
}
+static void ME_ShutdownIR();
+static void ME_ShutdownNative();
+static void ME_ResetState();
+
MIPSState::~MIPSState() {
}
void MIPSState::Shutdown() {
+ // Only the main CPU owns the JIT. Media Engine has its own IR resources.
+ if (this != &mipsr4k) {
+ ME_ShutdownIR();
+ ME_ShutdownNative();
+ ME_ResetState();
+ return;
+ }
std::lock_guard guard(MIPSComp::jitLock);
MIPSComp::JitInterface *oldjit = MIPSComp::jit;
if (oldjit) {
MIPSComp::jit = nullptr;
+ MIPSComp::mainCpuJit = nullptr;
delete oldjit;
}
}
@@ -209,6 +234,10 @@ void MIPSState::Init() {
memset(vcmpResult, 0, sizeof(vcmpResult));
+ // Only create JIT for the main CPU, not the Media Engine.
+ if (this != &mipsr4k) {
+ return;
+ }
std::lock_guard guard(MIPSComp::jitLock);
if (PSP_CoreParameter().cpuCore == CPUCore::JIT || PSP_CoreParameter().cpuCore == CPUCore::JIT_IR) {
MIPSComp::jit = MIPSComp::CreateNativeJit(this, PSP_CoreParameter().cpuCore == CPUCore::JIT_IR);
@@ -217,6 +246,7 @@ void MIPSState::Init() {
} else {
MIPSComp::jit = nullptr;
}
+ MIPSComp::mainCpuJit = MIPSComp::jit;
}
bool MIPSState::HasDefaultPrefix() const {
@@ -236,6 +266,7 @@ void MIPSState::UpdateCore(CPUCore desired) {
if (MIPSComp::jit) {
delete MIPSComp::jit;
MIPSComp::jit = nullptr;
+ MIPSComp::mainCpuJit = nullptr;
}
}
@@ -266,6 +297,7 @@ void MIPSState::UpdateCore(CPUCore desired) {
std::lock_guard guard(MIPSComp::jitLock);
MIPSComp::jit = newjit;
+ MIPSComp::mainCpuJit = newjit;
}
void MIPSState::DoState(PointerWrap &p) {
@@ -388,3 +420,879 @@ void MIPSState::ClearJitCache() {
}
}
}
+
+// Media Engine state and scheduling.
+
+static bool meEnabled = false;
+static int meCoreSliceEvent = -1;
+
+// Flag set by Int_Halt when a HALT instruction is executed inside a JIT block.
+bool g_meHaltDetected = false;
+
+// Used for debug drift accounting only.
+static s64 meCyclesExecuted_ = 0;
+static u32 meCountAccum_ = 0; // fractional accumulator for CP0 Count
+
+// Debug-only drift tracking between SC and ME.
+#define ME_DEBUG_DRIFT 0
+#if ME_DEBUG_DRIFT
+static s64 driftMax_ = 0;
+static s64 driftSum_ = 0;
+static int driftSamples_ = 0;
+#endif
+
+static void ME_ResetState() {
+ meEnabled = false;
+ meCoreSliceEvent = -1;
+ g_meHaltDetected = false;
+ meCyclesExecuted_ = 0;
+ meCountAccum_ = 0;
+#if ME_DEBUG_DRIFT
+ driftMax_ = 0;
+ driftSum_ = 0;
+ driftSamples_ = 0;
+#endif
+}
+
+// Scheduling quantum in us while idle or spinwaiting.
+static constexpr int ME_SLICE_US = 100;
+static constexpr int ME_FREQ = 333000000;
+
+// Minimal ME syscall trampoline for the ME kernel image range.
+static void ME_HLE_DispatchSyscall(u32 pc) {
+ mipsMe.pc = mipsMe.r[31]; // jr $ra
+}
+
+// Detect a spinwait pattern: the ME is sitting on a tight loop reading a
+// shared-memory word that hasn't changed. Pattern:
+// PC+0: LW rt, offset(base) [opcode 0x23 = LW]
+// PC+4: branch back to PC+0 (BEQ rt,$zero,-8 or BNE/$zero variants)
+// If the loaded register equals the comparison value (i.e. the branch is
+// taken), this is a spinwait and we should yield.
+static bool ME_DetectSpinwait(u32 pc, u32 opWord);
+
+// Scan forward from a given PC, skipping cache/sync/nop instructions, looking
+// for a LW+branch spinwait pattern within `maxInsns` instructions.
+// Unlike ME_DetectSpinwait (which requires the branch to target the LW itself),
+// this allows the branch to target any address <= startPC (backward branch),
+// covering patterns like: sync; cache-loop; sync; lw; beqz -> outer_loop_start.
+static bool ME_DetectSpinwaitScan(u32 startPC, int maxInsns = 16) {
+ for (int i = 0; i < maxInsns; i++) {
+ u32 scanPC = startPC + i * 4;
+ if (!Memory::IsValidAddress(scanPC))
+ return false;
+ u32 op = Memory::Read_Instruction(scanPC, true).encoding;
+ u32 opcode = op >> 26;
+ // Skip NOP
+ if (op == 0)
+ continue;
+ // Skip CACHE (opcode 0x2F)
+ if (opcode == 0x2F)
+ continue;
+ // Skip SYNC
+ if (op == 0x0000000F || (op & 0xFFFFF83F) == 0x0000000F)
+ continue;
+ // Skip MOVE (addu/or rd,$zero) and ADDIU (commonly part of cache loops)
+ if (opcode == 0x00) {
+ u32 func = op & 0x3F;
+ // ADDU, OR with $zero source (= MOVE)
+ if ((func == 0x21 || func == 0x25) && ((op >> 21) & 0x1F) == 0)
+ continue;
+ }
+ if (opcode == 0x09) // ADDIU
+ continue;
+ // Skip BNE (inner cache loop branch)
+ if (opcode == 0x05) {
+ // Also skip its delay slot
+ i++;
+ continue;
+ }
+ // Found a LW - check for a relaxed spinwait pattern.
+ if (opcode == 0x23) {
+ u32 rt = (op >> 16) & 0x1F;
+ if (rt == 0)
+ return false;
+ u32 base = (op >> 21) & 0x1F;
+ s16 imm = (s16)(op & 0xFFFF);
+ u32 addr = mipsMe.r[base] + imm;
+ u32 memVal = Memory::Read_U32(addr);
+
+ u32 nextAddr = scanPC + 4;
+ if (!Memory::IsValidAddress(nextAddr))
+ return false;
+ u32 nextOp = Memory::Read_Instruction(nextAddr, true).encoding;
+ u32 nextOpcode = nextOp >> 26;
+
+ // BEQ rt,$zero,offset, waiting for nonzero.
+ if (nextOpcode == 0x04) {
+ u32 brt = (nextOp >> 21) & 0x1F;
+ u32 brs = (nextOp >> 16) & 0x1F;
+ s16 offset = (s16)(nextOp & 0xFFFF);
+ u32 target = nextAddr + 4 + (offset << 2);
+ if (target <= startPC && target >= startPC - (u32)(maxInsns * 4) && ((brt == rt && brs == 0) || (brt == 0 && brs == rt))) {
+ if (memVal == 0)
+ return true;
+ }
+ }
+ // BNE rt,$zero,offset, waiting for zero.
+ if (nextOpcode == 0x05) {
+ u32 brt = (nextOp >> 21) & 0x1F;
+ u32 brs = (nextOp >> 16) & 0x1F;
+ s16 offset = (s16)(nextOp & 0xFFFF);
+ u32 target = nextAddr + 4 + (offset << 2);
+ if (target <= startPC && target >= startPC - (u32)(maxInsns * 4) && ((brt == rt && brs == 0) || (brt == 0 && brs == rt))) {
+ if (memVal != 0)
+ return true;
+ }
+ }
+ return false;
+ }
+ // Any other instruction stops the scan.
+ return false;
+ }
+ return false;
+}
+
+static bool ME_DetectSpinwait(u32 pc, u32 opWord) {
+ // Is current instruction a LW? opcode field = bits[31:26]
+ u32 opcode = opWord >> 26;
+ if (opcode != 0x23) // LW
+ return false;
+
+ u32 rt = (opWord >> 16) & 0x1F;
+ if (rt == 0)
+ return false;
+
+ // Compute the effective address of the LW to read fresh memory value.
+ u32 base = (opWord >> 21) & 0x1F;
+ s16 imm = (s16)(opWord & 0xFFFF);
+ u32 addr = mipsMe.r[base] + imm;
+ u32 memVal = Memory::Read_U32(addr);
+
+ // Read next instruction (the branch).
+ u32 nextOp = Memory::Read_Instruction(pc + 4, true).encoding;
+ u32 nextOpcode = nextOp >> 26;
+
+ // BEQ rt, $zero, offset, branch back to pc.
+ if (nextOpcode == 0x04) { // BEQ
+ u32 brt = (nextOp >> 21) & 0x1F;
+ u32 brs = (nextOp >> 16) & 0x1F;
+ s16 offset = (s16)(nextOp & 0xFFFF);
+ u32 target = (pc + 4) + (offset << 2) + 4; // MIPS branch delay: target = PC+4 + offset*4
+ // Check: BEQ rt,$zero back to the LW (or BEQ $zero,rt)
+ if (target == pc) {
+ if ((brt == rt && brs == 0) || (brt == 0 && brs == rt)) {
+ // If the branch is still taken, this is a spinwait.
+ if (memVal == 0)
+ return true;
+ }
+ }
+ }
+
+ // BNEZ rt, offset, loop waiting for the value to become zero.
+ if (nextOpcode == 0x05) { // BNE
+ u32 brt = (nextOp >> 21) & 0x1F;
+ u32 brs = (nextOp >> 16) & 0x1F;
+ s16 offset = (s16)(nextOp & 0xFFFF);
+ u32 target = (pc + 4) + (offset << 2) + 4;
+ if (target == pc) {
+ if ((brt == rt && brs == 0) || (brt == 0 && brs == rt)) {
+ if (memVal != 0)
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+static int meSliceCount_ = 0;
+static int meInsnCount_ = 0;
+static int meNativeInterpreterCountdown_ = 0;
+static constexpr int ME_NATIVE_WARMUP_INSNS = 20000;
+
+static constexpr int ME_NATIVE_EXCEPTION_INSNS = 2048;
+
+static bool ME_ShouldInterpretAtPC(u32 pc) {
+ // Previously forced all ME code to the interpreter. Now that IR's
+ // Comp_Generic terminates blocks on ERET/HALT, the IR backend can
+ // compile ME code. MFC0/MTC0/cache fall through to IROp::Interpret
+ // which is safe for non-control-flow instructions.
+ (void)pc;
+ return false;
+}
+
+// Check if the ME should take an interrupt and deliver it if so.
+// Returns true if an interrupt was delivered (PC has been redirected to the exception vector).
+static bool ME_CheckAndDeliverInterrupt() {
+ // Update hardware interrupt lines in Cause. Per MIPS spec, Cause.IP[7:2]
+ // are read-only bits that reflect the current state of hardware lines.
+
+ u32 cause = mipsMe.cp0regs[13];
+
+ // IP7: timer interrupt when Count >= Compare.
+ if (mipsMe.cp0regs[11] != 0 && mipsMe.cp0regs[9] >= mipsMe.cp0regs[11]) {
+ cause |= 0x8000; // Cause.IP7
+ }
+
+ // IP2: External soft interrupt from SC or ME pending flag.
+ if (Memory::ME_HasPendingInterrupt()) {
+ cause |= 0x0400; // Cause.IP2
+ } else {
+ cause &= ~0x0400; // Clear IP2 when hardware line is deasserted
+ }
+
+ mipsMe.cp0regs[13] = cause;
+
+ // Any pending and enabled interrupt?
+ u32 status = mipsMe.cp0regs[12];
+ u32 pendingAndEnabled = (cause & status) & 0xFF00; // IP[7:0] & IM[7:0]
+ if (!pendingAndEnabled)
+ return false;
+
+ // Check: interrupts globally enabled (IE=bit0), not in exception (EXL=bit1).
+ if (!(status & 0x01)) // IE not set
+ return false;
+ if (status & 0x02) // EXL set, already handling an exception.
+ return false;
+
+ // Deliver the interrupt:
+ mipsMe.cp0regs[14] = mipsMe.pc; // EPC = current PC
+ mipsMe.cp0regs[12] |= 0x02; // Status.EXL = 1
+ mipsMe.pc = mipsMe.cp0regs[25]; // Jump to EBase (exception vector)
+ mipsMe.inDelaySlot = false;
+ // Clear external soft interrupt if that's what triggered us.
+ if (cause & 0x0400) {
+ Memory::ME_ClearSoftInterrupt();
+ }
+ if (meNativeInterpreterCountdown_ < ME_NATIVE_EXCEPTION_INSNS)
+ meNativeInterpreterCountdown_ = ME_NATIVE_EXCEPTION_INSNS;
+ return true;
+}
+
+static int MEInterpret_RunSlice(int budget) {
+ MIPSState *saved = currentMIPS;
+ currentMIPS = &mipsMe;
+ meSliceCount_++;
+ int startBudget = budget;
+
+ // Check for pending interrupts once at slice entry (not per-instruction).
+ ME_CheckAndDeliverInterrupt();
+
+ while (budget > 0 && meEnabled) {
+ u32 pc = mipsMe.pc;
+
+ // Bail out if PC is not valid (e.g. ran off the end of ME SRAM).
+ if (!Memory::IsValidAddress(pc)) {
+ WARN_LOG(Log::CPU, "ME interp: invalid PC %08x, stopping", pc);
+ meEnabled = false;
+ break;
+ }
+
+ meInsnCount_++;
+
+ // Check for ME kernel syscall dispatch (0x88300000-0x883FFFFF)
+ u32 phys = pc & 0x1FFFFFFF;
+ if (phys >= 0x08300000 && phys < 0x08400000) {
+ ME_HLE_DispatchSyscall(pc);
+ budget -= 10;
+ continue;
+ }
+
+ // Resolve replacements so ME never sees EMUHACK opcodes.
+ u32 opWord = Memory::Read_Instruction(pc, true).encoding;
+
+ // HALT instruction (0x70000000)
+ if (opWord == 0x70000000) {
+ meEnabled = false;
+ break;
+ }
+
+ // Spinwait detection: yield remaining budget if the ME is busy-waiting
+ // on a shared-memory flag that the main CPU hasn't set yet.
+ if (ME_DetectSpinwait(pc, opWord)) {
+ break;
+ }
+
+ bool isNop = false;
+
+ // CACHE instruction (opcode field = 0x2F)
+ if ((opWord >> 26) == 0x2F) {
+ isNop = true;
+ }
+
+ // SYNC instruction
+ if (opWord == 0x0000000F || (opWord & 0xFFFFF83F) == 0x0000000F) {
+ isNop = true;
+ }
+
+ // MTC0/MFC0 (COP0): handle CP0 register access
+ if ((opWord >> 26) == 0x10) { // COP0
+ u32 rs = (opWord >> 21) & 0x1F;
+ u32 rt = (opWord >> 16) & 0x1F;
+ u32 rd = (opWord >> 11) & 0x1F;
+ if (rs == 0x04) { // MTC0: rt -> cp0[rd]
+ mipsMe.cp0regs[rd] = mipsMe.r[rt];
+ // Per MIPS spec, writing to Compare (reg 11) clears Cause.IP7.
+ if (rd == 11) {
+ mipsMe.cp0regs[13] &= ~0x8000;
+ }
+ isNop = true;
+ } else if (rs == 0x00) { // MFC0: cp0[rd] -> rt
+ if (rt != 0)
+ mipsMe.r[rt] = mipsMe.cp0regs[rd];
+ isNop = true;
+ } else if (rs == 0x10 && (opWord & 0x3F) == 0x18) {
+ // ERET: Return from exception.
+ // encoding: 0x42000018 (COP0 CO=1, func=0x18)
+ mipsMe.pc = mipsMe.cp0regs[14]; // PC = EPC
+ mipsMe.cp0regs[12] &= ~0x02; // Clear Status.EXL
+ mipsMe.inDelaySlot = false;
+ budget--;
+ continue; // Skip normal PC advance
+ }
+ }
+
+ bool wasInDelaySlot = mipsMe.inDelaySlot;
+
+ if (isNop) {
+ mipsMe.pc += 4;
+ } else {
+ // Normal MIPS instruction - use standard interpreter
+ MIPSOpcode op(opWord);
+
+ MIPSInterpret(op);
+
+ // Fixup: DelayBranchTo/SkipLikely use mipsr4k directly instead of currentMIPS
+ if (mipsr4k.inDelaySlot && !mipsMe.inDelaySlot) {
+ mipsMe.inDelaySlot = true;
+ mipsMe.nextPC = mipsr4k.nextPC;
+ mipsr4k.inDelaySlot = false;
+ }
+ }
+
+ if (mipsMe.inDelaySlot && wasInDelaySlot) {
+ mipsMe.pc = mipsMe.nextPC;
+ mipsMe.inDelaySlot = false;
+ }
+
+ // CP0 Count register (reg 9).
+ // On real PSP, Count ticks at CPU_CLOCK/2. The bench's me_cycles_to_us()
+ // formula is: cycles * 2 / 333. For the ME timing to be consistent with
+ // the SC's CoreTiming model (which counts 1 cycle per instruction at
+ // the current clock), we scale Count so:
+ // count * 2 / 333 == insns / (currentClockMHz)
+ // count/insn = 333 / (2 * currentClockMHz)
+ // At 333 MHz: 0.5 per insn. At 222 MHz: 0.75 per insn.
+ // Use a fractional accumulator for precision.
+ {
+ meCountAccum_ += 333;
+ int clockMHz = CoreTiming::GetClockFrequencyHz() / 1000000;
+ if (clockMHz < 100) clockMHz = 333; // safety
+ u32 inc = meCountAccum_ / (2 * clockMHz);
+ meCountAccum_ %= (2 * clockMHz);
+ mipsMe.cp0regs[9] += inc;
+ }
+
+ budget--;
+ }
+
+ currentMIPS = saved;
+ return startBudget - budget;
+}
+
+// ME IR interpreter backend.
+// ME blocks compile to IR without patching RAM.
+
+static MIPSComp::IRFrontend *meFrontend_ = nullptr;
+static MIPSComp::IRBlockCache *meBlocks_ = nullptr;
+
+static void ME_InitIR() {
+ if (meFrontend_)
+ return;
+ InitIR();
+ meFrontend_ = new MIPSComp::IRFrontend(true); // defaultPrefix = true
+ meBlocks_ = new MIPSComp::IRBlockCache(false, false); // compileToNative=false, patchMemory=false
+
+ // Set up IR options for interpreter mode.
+ IROptions opts{};
+ opts.optimizeForInterpreter = true;
+ meFrontend_->SetOptions(opts);
+}
+
+static void ME_ShutdownIR() {
+ delete meBlocks_;
+ meBlocks_ = nullptr;
+ delete meFrontend_;
+ meFrontend_ = nullptr;
+}
+
+static int ME_IRRunSlice(int budget) {
+ MIPSState *saved = currentMIPS;
+ currentMIPS = &mipsMe;
+ int startBudget = budget;
+
+ // Use the plain interpreter for kernel and exception paths.
+ if (ME_ShouldInterpretAtPC(mipsMe.pc)) {
+ int executed = MEInterpret_RunSlice(budget);
+ currentMIPS = saved;
+ return executed;
+ }
+
+ ME_InitIR();
+
+ while (budget > 0 && meEnabled) {
+ // Check for pending interrupts at block boundaries.
+ // Only check when a soft interrupt might actually be pending.
+ if (Memory::ME_HasPendingInterrupt())
+ ME_CheckAndDeliverInterrupt();
+
+ u32 pc = mipsMe.pc;
+
+ // Bail out if PC is not valid (e.g. jr $ra with $ra=0).
+ if (!Memory::IsValidAddress(pc)) {
+ WARN_LOG(Log::CPU, "ME IR: invalid PC %08x, stopping", pc);
+ meEnabled = false;
+ break;
+ }
+
+ // Check for ME kernel syscall dispatch (0x88300000-0x883FFFFF)
+ u32 phys = pc & 0x1FFFFFFF;
+ if (phys >= 0x08300000 && phys < 0x08400000) {
+ ME_HLE_DispatchSyscall(pc);
+ budget -= 10;
+ continue;
+ }
+
+ // Fetch first opcode of block to check for ME-specific instructions.
+ u32 opWord = Memory::Read_Instruction(pc, true).encoding;
+
+ // HALT stops ME execution.
+ if (opWord == 0x70000000) {
+ meEnabled = false;
+ break;
+ }
+
+ // Check for spinwaits before compiling a block.
+ if (ME_DetectSpinwait(pc, opWord) || ME_DetectSpinwaitScan(pc)) {
+ break;
+ }
+
+ // Handle ERET at block boundaries.
+ if (opWord == 0x42000018) {
+ mipsMe.pc = mipsMe.cp0regs[14]; // PC = EPC
+ mipsMe.cp0regs[12] &= ~0x02; // Clear Status.EXL
+ mipsMe.inDelaySlot = false;
+ budget--;
+ continue;
+ }
+
+ // Look up an existing IR block for this address.
+ int blockNum = meBlocks_->FindPreloadBlock(pc);
+ if (blockNum < 0) {
+ // Compile a new block.
+ std::vector instructions;
+ u32 mipsBytes = 0;
+ meFrontend_->DoJit(pc, instructions, mipsBytes);
+ if (instructions.empty()) {
+ // Compilation failed, single-step instead.
+ MIPSOpcode op(opWord);
+ MIPSInterpret(op);
+ budget--;
+ continue;
+ }
+ blockNum = meBlocks_->AllocateBlock(pc, mipsBytes, instructions);
+ if (blockNum < 0) {
+ // Arena full, clear and retry.
+ meBlocks_->Clear();
+ blockNum = meBlocks_->AllocateBlock(pc, mipsBytes, instructions);
+ }
+ if (blockNum >= 0) {
+ meBlocks_->FinalizeBlock(blockNum);
+ } else {
+ // Still failed, single-step.
+ MIPSOpcode op(opWord);
+ MIPSInterpret(op);
+ budget--;
+ continue;
+ }
+ }
+
+ // Execute the IR block.
+ const MIPSComp::IRBlock *block = meBlocks_->GetBlock(blockNum);
+ const IRInst *instPtr = meBlocks_->GetBlockInstructionPtr(*block);
+
+ // The IR block's Downcount instruction decrements mips->downcount
+ // internally. Set it to our budget before execution, then read
+ // back the remaining amount to compute the actual block cost.
+ mipsMe.downcount = budget;
+
+ u32 newPC = IRInterpret(&mipsMe, instPtr);
+
+ // Fixup: MIPSInterpret (called via IROp::Interpret fallback) may
+ // modify mipsr4k instead of mipsMe for delay slot handling.
+ if (mipsr4k.inDelaySlot && !mipsMe.inDelaySlot) {
+ mipsMe.inDelaySlot = true;
+ mipsMe.nextPC = mipsr4k.nextPC;
+ mipsr4k.inDelaySlot = false;
+ }
+
+ mipsMe.pc = newPC;
+ int blockCost = budget - mipsMe.downcount;
+ if (blockCost < 1) blockCost = 1; // safety: always consume at least 1
+ budget = mipsMe.downcount;
+
+ // Update CP0 Count with the same scaling used by the interpreter.
+ {
+ meCountAccum_ += (s64)blockCost * 333;
+ int clockMHz = CoreTiming::GetClockFrequencyHz() / 1000000;
+ if (clockMHz < 100) clockMHz = 333;
+ u32 inc = (u32)(meCountAccum_ / (2 * clockMHz));
+ meCountAccum_ %= (2 * clockMHz);
+ mipsMe.cp0regs[9] += inc;
+ }
+
+ // Check if we landed on HALT after the block.
+ if (Memory::IsValidAddress(mipsMe.pc)) {
+ u32 nextOp = Memory::Read_Instruction(mipsMe.pc, true).encoding;
+ if (nextOp == 0x70000000) {
+ meEnabled = false;
+ break;
+ }
+ }
+ }
+
+ currentMIPS = saved;
+ return startBudget - budget;
+}
+// Native ME backend. ME code is compiled without patching RAM.
+
+static MIPSComp::JitInterface *meJit_ = nullptr;
+
+MIPSComp::JitInterface *ME_GetJit() {
+ return meJit_;
+}
+
+static void ME_InitNative() {
+ if (meJit_)
+ return;
+#if PPSSPP_ARCH(ARM64)
+ meJit_ = new MIPSComp::Arm64MEIRJit(&mipsMe);
+#elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
+ meJit_ = new MIPSComp::X64MEIRJit(&mipsMe);
+#else
+ // Fallback: other architectures not yet supported for ME native JIT.
+ return;
+#endif
+}
+
+static void ME_ShutdownNative() {
+ if (meJit_) {
+ delete meJit_;
+ meJit_ = nullptr;
+ }
+}
+
+// Called from generated ME dispatcher code.
+const u8 *MECompileAndLookup() {
+#if PPSSPP_ARCH(ARM64)
+ u32 pc = currentMIPS->pc;
+ u32 phys = pc & 0x1FFFFFFF;
+
+ // HLE syscall range: bail to the C++ wrapper.
+ if (phys >= 0x08300000 && phys < 0x08400000) {
+ currentMIPS->downcount = -1; // Force dispatcher exit
+ return nullptr;
+ }
+
+ // HALT instruction.
+ if (Memory::IsValidAddress(pc)) {
+ u32 opWord = Memory::Read_Instruction(pc, true).encoding;
+ if (opWord == 0x70000000) {
+ g_meHaltDetected = true;
+ currentMIPS->downcount = -1;
+ return nullptr;
+ }
+ }
+
+ auto *meNativeJit = static_cast(meJit_);
+ const u8 *result = meNativeJit->CompileAndLookup(pc);
+ return result;
+#elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
+ u32 pc = currentMIPS->pc;
+ u32 phys = pc & 0x1FFFFFFF;
+
+ if (phys >= 0x08300000 && phys < 0x08400000) {
+ currentMIPS->downcount = -1;
+ return nullptr;
+ }
+
+ if (Memory::IsValidAddress(pc)) {
+ u32 opWord = Memory::Read_Instruction(pc, true).encoding;
+ if (opWord == 0x70000000) {
+ g_meHaltDetected = true;
+ currentMIPS->downcount = -1;
+ return nullptr;
+ }
+ }
+
+ auto *meNativeJit = static_cast(meJit_);
+ return meNativeJit->CompileAndLookup(pc);
+#else
+ return nullptr;
+#endif
+}
+
+static int ME_NativeRunSlice(int budget) {
+#if PPSSPP_ARCH(ARM64)
+ ME_InitNative();
+ if (!meJit_)
+ return ME_IRRunSlice(budget);
+
+ MIPSState *saved = currentMIPS;
+ currentMIPS = &mipsMe;
+
+ int startBudget = budget;
+
+ // Pre-dispatch: deliver pending interrupts.
+ ME_CheckAndDeliverInterrupt();
+
+ if (!meEnabled) {
+ currentMIPS = saved;
+ return 0;
+ }
+
+ // Pre-dispatch: check for HALT/spinwait at current PC.
+ if (Memory::IsValidAddress(mipsMe.pc)) {
+ u32 opWord = Memory::Read_Instruction(mipsMe.pc, true).encoding;
+ if (opWord == 0x70000000) {
+ meEnabled = false;
+ currentMIPS = saved;
+ return 0;
+ }
+ if (ME_DetectSpinwait(mipsMe.pc, opWord) || ME_DetectSpinwaitScan(mipsMe.pc)) {
+ currentMIPS = saved;
+ return 0;
+ }
+ } else {
+ meEnabled = false;
+ currentMIPS = saved;
+ return 0;
+ }
+
+ // Run native blocks until downcount < 0.
+ mipsMe.downcount = budget;
+ auto *meNativeJit = static_cast(meJit_);
+
+ meNativeJit->EnterDispatcher();
+
+ // Compute consumed budget.
+ int consumed = startBudget - mipsMe.downcount;
+ if (consumed < 1) consumed = 1;
+
+ // Update CP0 Count with the same scaling used elsewhere.
+ {
+ meCountAccum_ += (s64)consumed * 333;
+ int clockMHz = CoreTiming::GetClockFrequencyHz() / 1000000;
+ if (clockMHz < 100) clockMHz = 333;
+ u32 inc = (u32)(meCountAccum_ / (2 * clockMHz));
+ meCountAccum_ %= (2 * clockMHz);
+ mipsMe.cp0regs[9] += inc;
+ }
+
+ // Check if HALT was detected during block lookup.
+ if (g_meHaltDetected) {
+ g_meHaltDetected = false;
+ meEnabled = false;
+ }
+
+ currentMIPS = saved;
+ return consumed;
+#elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
+ ME_InitNative();
+ if (!meJit_)
+ return ME_IRRunSlice(budget);
+
+ MIPSState *saved = currentMIPS;
+ currentMIPS = &mipsMe;
+
+ int startBudget = budget;
+
+ ME_CheckAndDeliverInterrupt();
+
+ if (!meEnabled) {
+ currentMIPS = saved;
+ return 0;
+ }
+
+ if (Memory::IsValidAddress(mipsMe.pc)) {
+ u32 opWord = Memory::Read_Instruction(mipsMe.pc, true).encoding;
+ if (opWord == 0x70000000) {
+ meEnabled = false;
+ currentMIPS = saved;
+ return 0;
+ }
+ if (ME_DetectSpinwait(mipsMe.pc, opWord) || ME_DetectSpinwaitScan(mipsMe.pc)) {
+ currentMIPS = saved;
+ return 0;
+ }
+ } else {
+ meEnabled = false;
+ currentMIPS = saved;
+ return 0;
+ }
+
+ mipsMe.downcount = budget;
+ auto *meNativeJit = static_cast(meJit_);
+ meNativeJit->EnterDispatcher();
+
+ int consumed = startBudget - mipsMe.downcount;
+ if (consumed < 1)
+ consumed = 1;
+
+ {
+ meCountAccum_ += (s64)consumed * 333;
+ int clockMHz = CoreTiming::GetClockFrequencyHz() / 1000000;
+ if (clockMHz < 100) clockMHz = 333;
+ u32 inc = (u32)(meCountAccum_ / (2 * clockMHz));
+ meCountAccum_ %= (2 * clockMHz);
+ mipsMe.cp0regs[9] += inc;
+ }
+
+ if (g_meHaltDetected) {
+ g_meHaltDetected = false;
+ meEnabled = false;
+ }
+
+ currentMIPS = saved;
+ return consumed;
+#else
+ return ME_IRRunSlice(budget);
+#endif
+}
+
+static void MECallback(u64 userdata, int cyclesLate) {
+ if (!meEnabled) {
+ // ME not booted. Core_EnableME() will schedule a new event
+ // when the main CPU enables the ME.
+ return;
+ }
+
+ // ME budget per slice, scaled to the current CPU clock frequency.
+ int meBudget = (int)(CoreTiming::GetClockFrequencyHz() / (1000000 / ME_SLICE_US));
+ if (meBudget < 1000) meBudget = 1000;
+
+ // Cap the interpreter budget: the plain interpreter runs at ~50ns/insn on
+ // the host, so a budget of 33300 would block the SC for ~1.6ms per slice.
+ // IR/JIT backends are fast enough to handle the full clock-scaled budget.
+ static constexpr int ME_MAX_INTERP_BUDGET = 5000;
+ if ((CPUCore)g_Config.iMECpuCore == CPUCore::INTERPRETER && meBudget > ME_MAX_INTERP_BUDGET)
+ meBudget = ME_MAX_INTERP_BUDGET;
+
+ // Deliver any pending SC->ME soft interrupt before the slice.
+ u32 softIntBefore = Memory::ME_PeekSoftInterruptRaw();
+ if (softIntBefore != 0)
+ Memory::ME_RaiseSoftInterrupt();
+
+ int executed = 0;
+
+ switch ((CPUCore)g_Config.iMECpuCore) {
+ case CPUCore::IR_INTERPRETER:
+ executed = ME_IRRunSlice(meBudget);
+ break;
+ case CPUCore::JIT:
+ case CPUCore::JIT_IR:
+ executed = ME_NativeRunSlice(meBudget);
+ break;
+ default:
+ executed = MEInterpret_RunSlice(meBudget);
+ break;
+ }
+
+ // Update ME virtual-time position.
+ meCyclesExecuted_ += executed;
+
+#if ME_DEBUG_DRIFT
+ {
+ s64 scCycles = CoreTiming::GetTicks();
+ s64 drift = scCycles - meCyclesExecuted_;
+ if (drift < 0) drift = -drift;
+ if (drift > driftMax_) driftMax_ = drift;
+ driftSum_ += drift;
+ driftSamples_++;
+ if ((driftSamples_ % 10000) == 0) {
+ int clockMHz = CoreTiming::GetClockFrequencyHz() / 1000000;
+ INFO_LOG(Log::CPU, "ME drift: max=%lld avg=%lld cycles (%d samples, %d MHz)",
+ driftMax_, driftSamples_ ? driftSum_ / driftSamples_ : 0,
+ driftSamples_, clockMHz);
+ }
+ }
+#endif
+
+ // ME->SC soft interrupt: defer delivery until after the ME slice so the
+ // main CPU state is active again.
+ u32 softIntAfter = Memory::ME_PeekSoftInterruptRaw();
+ if (Memory::ME_ConsumeCpuInterruptRequest() || (softIntBefore == 0 && softIntAfter != 0)) {
+ __TriggerInterrupt(PSP_INTR_IMMEDIATE, PSP_MECODEC_INTR, PSP_INTR_SUB_NONE);
+ Memory::ME_ClearSoftInterrupt();
+ }
+
+ // Schedule next event only if ME is still running. When the ME is in a
+ // spinwait (executed == 0), use a longer interval to reduce host overhead.
+ if (meEnabled) {
+ static constexpr int ME_BACKOFF_US = 1000; // 1ms when idle
+ int nextUs = (executed == 0) ? ME_BACKOFF_US : ME_SLICE_US;
+ CoreTiming::ScheduleEvent(usToCycles(nextUs), meCoreSliceEvent, 0);
+ }
+}
+
+void Core_EnableME() {
+ if (meEnabled) return;
+
+ // ME disabled via config (iMECpuCore == -1)
+ if (g_Config.iMECpuCore < 0) return;
+
+ mipsMe.pc = 0xBFC00000;
+ mipsMe.r[0] = 0;
+ mipsMe.r[28] = mipsr4k.r[28]; // GP: copy from main CPU so ME can access globals
+ mipsMe.r[29] = 0x80014000; // SP: top of scratchpad (kseg0 cached view of 0x00014000)
+ mipsMe.inDelaySlot = false;
+ mipsMe.nextPC = 0;
+ memset(mipsMe.cp0regs, 0, sizeof(mipsMe.cp0regs));
+ mipsMe.cp0regs[22] = 2; // Processor ID: ME = 2 (bit[1]), main CPU = 1 (bit[0]) for HW mutex
+
+ meEnabled = true;
+ meSliceCount_ = 0;
+ meInsnCount_ = 0;
+ meCyclesExecuted_ = CoreTiming::GetTicks(); // Sync ME start to current SC position
+ meCountAccum_ = 0;
+ meNativeInterpreterCountdown_ = ME_NATIVE_WARMUP_INSNS;
+
+ // Reset interrupt state for a fresh ME boot.
+ Memory::ME_ResetInterruptState();
+
+ // Make HW register page read-write so the spinlock (0xBC100048)
+ // and subsequent JIT accesses go through without faulting.
+ Memory::ME_ProtectHwPage(false);
+
+ INFO_LOG(Log::CPU, "ME: Core_EnableME called, PC=%08x, core=%d", mipsMe.pc, g_Config.iMECpuCore);
+
+ // Schedule the first ME slice event. MECallback no longer self-schedules
+ // when !meEnabled, so we must kick-start it here.
+ if (meCoreSliceEvent == -1) {
+ meCoreSliceEvent = CoreTiming::RegisterEvent("meCoreSlice", MECallback);
+ }
+ CoreTiming::ScheduleEvent(usToCycles(ME_SLICE_US), meCoreSliceEvent, 0);
+}
+
+void ME_InitPolling() {
+ if (g_Config.iMECpuCore < 0)
+ return;
+ if (meCoreSliceEvent == -1) {
+ meCoreSliceEvent = CoreTiming::RegisterEvent("meCoreSlice", MECallback);
+ }
+ // Core_EnableME() schedules the first event.
+ // when the main CPU enables the ME. This avoids wasting CoreTiming
+ // overhead on a disabled ME.
+ INFO_LOG(Log::CPU, "ME: Event registered (core=%d, slice=%dus)", g_Config.iMECpuCore, ME_SLICE_US);
+}
diff --git a/Core/MIPS/MIPS.h b/Core/MIPS/MIPS.h
index 4b53fe2e58cd..b419eadcbeea 100644
--- a/Core/MIPS/MIPS.h
+++ b/Core/MIPS/MIPS.h
@@ -269,6 +269,9 @@ class MIPSState
// Doesn't need save stating.
volatile bool insideJit = false;
volatile bool hasPendingClears = false;
+
+ // ME CP0 registers (added at end to preserve JIT offsets)
+ u32 cp0regs[32];
};
class MIPSDebugInterface;
@@ -276,6 +279,15 @@ class MIPSDebugInterface;
//The one we are compiling or running currently
extern MIPSState *currentMIPS;
extern MIPSDebugInterface *currentDebugMIPS;
+extern MIPSDebugInterface debugMe;
extern MIPSState mipsr4k;
+extern MIPSState mipsMe;
+
+// Media Engine LLE
+void Core_EnableME();
+void ME_InitPolling();
+
+// Used by the ME native JIT dispatcher to compile and look up blocks.
+const u8 *MECompileAndLookup();
extern const float cst_constants[32];
diff --git a/Core/MIPS/MIPSInt.cpp b/Core/MIPS/MIPSInt.cpp
index ba926a7e577b..0c00e978c97c 100644
--- a/Core/MIPS/MIPSInt.cpp
+++ b/Core/MIPS/MIPSInt.cpp
@@ -57,7 +57,10 @@
static inline void DelayBranchTo(u32 where)
{
if (!Memory::IsValidAddress(where) || (where & 3) != 0) {
- Core_ExecException(where, PC, ExecExceptionType::JUMP);
+ // Allow the ME to stop by returning through jr $ra with $ra == 0.
+ if (!(currentMIPS == &mipsMe && where == 0)) {
+ Core_ExecException(where, PC, ExecExceptionType::JUMP);
+ }
}
PC += 4;
mipsr4k.nextPC = where;
@@ -90,6 +93,9 @@ int MIPS_SingleStep()
return 1;
}
+// Flag set by Int_Halt inside JIT blocks; checked by ME_NativeRunSlice.
+extern bool g_meHaltDetected;
+
namespace MIPSInt
{
void Int_Cache(MIPSOpcode op)
@@ -815,11 +821,45 @@ namespace MIPSInt
PC += 4;
}
+ void Int_Halt(MIPSOpcode op)
+ {
+ // Force the native ME dispatcher to return promptly.
+ g_meHaltDetected = true;
+ // Force downcount negative so the JIT exits promptly.
+ currentMIPS->downcount = -1;
+ PC += 4;
+ }
+
+ void Int_Cop0(MIPSOpcode op)
+ {
+ // Used by the ME. The main CPU handles these through exceptions.
+ int rs = _RS;
+ int rt = _RT;
+ int rd = (op >> 11) & 0x1F;
+ if (rs == 0x04) { // MTC0: GPR[rt] -> CP0[rd]
+ currentMIPS->cp0regs[rd] = R(rt);
+ } else if (rs == 0x00) { // MFC0: CP0[rd] -> GPR[rt]
+ if (rt != 0)
+ R(rt) = currentMIPS->cp0regs[rd];
+ } else if (rs == 0x10 && (op & 0x3F) == 0x18) {
+ // ERET: Return from exception.
+ // PC = EPC, clear Status.EXL
+ currentMIPS->pc = currentMIPS->cp0regs[14]; // EPC
+ currentMIPS->cp0regs[12] &= ~0x02; // Clear EXL
+ currentMIPS->inDelaySlot = false;
+ return; // Don't advance PC
+ }
+ PC += 4;
+ }
+
void Int_Special2(MIPSOpcode op)
{
static int reported = 0;
switch (op & 0x3F)
{
+ case 0: // halt
+ Int_Halt(op);
+ return;
case 36: // mfic
// move from interrupt controller, not implemented
// See related report https://report.ppsspp.org/logs/kind/316 for possible locations.
diff --git a/Core/MIPS/MIPSInt.h b/Core/MIPS/MIPSInt.h
index 4de0bba48873..ea7e851bc8b9 100644
--- a/Core/MIPS/MIPSInt.h
+++ b/Core/MIPS/MIPSInt.h
@@ -45,6 +45,8 @@ namespace MIPSInt
void Int_FPUComp(MIPSOpcode op);
void Int_FPUBranch(MIPSOpcode op);
void Int_Emuhack(MIPSOpcode op);
+ void Int_Halt(MIPSOpcode op);
+ void Int_Cop0(MIPSOpcode op);
void Int_Special2(MIPSOpcode op);
void Int_Special3(MIPSOpcode op);
void Int_Interrupt(MIPSOpcode op);
diff --git a/Core/MIPS/MIPSTables.cpp b/Core/MIPS/MIPSTables.cpp
index 4e3e820c3077..8bfe1db118fb 100644
--- a/Core/MIPS/MIPSTables.cpp
+++ b/Core/MIPS/MIPSTables.cpp
@@ -249,7 +249,7 @@ static const MIPSInstruction tableSpecial[64] = // 000000 ..... ..... ..... ....
// Theoretically should not hit these.
static const MIPSInstruction tableSpecial2[64] = // 011100 ..... ..... ..... ..... xxxxxx
{
- INSTR("halt", JITFUNC(Comp_Generic), Dis_Generic, 0, 0),
+ INSTR("halt", JITFUNC(Comp_Generic), Dis_Generic, Int_Halt, 0),
INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
//8
INVALID_X_8,
@@ -364,11 +364,11 @@ static const MIPSInstruction tableCop2BC2[4] = // 010010 01000 ...xx ...........
static const MIPSInstruction tableCop0[32] = // 010000 xxxxx ..... ................
{
- INSTR("mfc0", JITFUNC(Comp_Generic), Dis_Generic, 0, OUT_RT), // unused
+ INSTR("mfc0", JITFUNC(Comp_Generic), Dis_Generic, Int_Cop0, OUT_RT),
INVALID,
INVALID,
INVALID,
- INSTR("mtc0", JITFUNC(Comp_Generic), Dis_Generic, 0, IN_RT), // unused
+ INSTR("mtc0", JITFUNC(Comp_Generic), Dis_Generic, Int_Cop0, IN_RT),
INVALID,
INVALID,
INVALID,
@@ -403,7 +403,7 @@ static const MIPSInstruction tableCop0CO[64] = // 010000 1.... ..... ..... .....
INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
INVALID_X_8,
//24
- INSTR("eret", JITFUNC(Comp_Generic), Dis_Generic, 0, 0),
+ INSTR("eret", JITFUNC(Comp_Generic), Dis_Generic, Int_Cop0, 0),
INSTR("iack", JITFUNC(Comp_Generic), Dis_Generic, 0, 0),
INVALID, INVALID, INVALID, INVALID, INVALID,
INSTR("deret", JITFUNC(Comp_Generic), Dis_Generic, 0, 0),
diff --git a/Core/MIPS/x86/X64IRAsm.cpp b/Core/MIPS/x86/X64IRAsm.cpp
index 5cf413693019..9cd3e4d2d165 100644
--- a/Core/MIPS/x86/X64IRAsm.cpp
+++ b/Core/MIPS/x86/X64IRAsm.cpp
@@ -21,6 +21,7 @@
#include "Common/Log.h"
#include "Core/CoreTiming.h"
#include "Core/MemMap.h"
+#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/x86/X64IRJit.h"
#include "Core/MIPS/x86/X64IRRegCache.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
@@ -315,6 +316,165 @@ void X64JitBackend::GenerateFixedCode(MIPSState *mipsState) {
EndWrite();
}
+void X64MEJitBackend::GenerateFixedCode(MIPSState *mipsState) {
+ const u8 *start = AlignCodePage();
+ if (DebugProfilerEnabled()) {
+ ProtectMemoryPages(start, GetMemoryProtectPageSize(), MEM_PROT_READ | MEM_PROT_WRITE);
+ hooks_.profilerPC = (uint32_t *)GetWritableCodePtr();
+ Write32(0);
+ hooks_.profilerStatus = (IRProfilerStatus *)GetWritableCodePtr();
+ Write32(0);
+ }
+
+ EmitFPUConstants();
+ EmitVecConstants();
+
+ const u8 *disasmStart = AlignCodePage();
+ BeginWrite(GetMemoryProtectPageSize());
+
+ jo.downcountInRegister = false;
+#if PPSSPP_ARCH(AMD64)
+ bool jitbaseInR15 = false;
+ intptr_t jitbase = (intptr_t)GetBasePtr() - MIPS_EMUHACK_OPCODE;
+ if ((jitbase < -0x80000000LL || jitbase > 0x7FFFFFFFLL) && !Accessible((const u8 *)&mipsState->f[0], (const u8 *)jitbase)) {
+ jo.reserveR15ForAsm = true;
+ jitbaseInR15 = true;
+ } else {
+ jo.downcountInRegister = true;
+ jo.reserveR15ForAsm = true;
+ }
+#endif
+
+ if (jo.useStaticAlloc && false) {
+ saveStaticRegisters_ = AlignCode16();
+ if (jo.downcountInRegister)
+ MOV(32, MDisp(CTXREG, downcountOffset), R(DOWNCOUNTREG));
+ RET();
+
+ loadStaticRegisters_ = AlignCode16();
+ if (jo.downcountInRegister)
+ MOV(32, R(DOWNCOUNTREG), MDisp(CTXREG, downcountOffset));
+ RET();
+ } else {
+ saveStaticRegisters_ = nullptr;
+ loadStaticRegisters_ = nullptr;
+ }
+
+ restoreRoundingMode_ = AlignCode16();
+ {
+ STMXCSR(MDisp(CTXREG, tempOffset));
+ AND(32, MDisp(CTXREG, tempOffset), Imm32(~(7 << 13)));
+ LDMXCSR(MDisp(CTXREG, tempOffset));
+ RET();
+ }
+
+ applyRoundingMode_ = AlignCode16();
+ {
+ MOV(32, R(SCRATCH1), MDisp(CTXREG, fcr31Offset));
+ AND(32, R(SCRATCH1), Imm32(0x01000003));
+
+ FixupBranch skip = J_CC(CC_Z);
+ STMXCSR(MDisp(CTXREG, tempOffset));
+
+ TEST(8, R(AL), Imm8(1));
+ FixupBranch skip2 = J_CC(CC_Z);
+ XOR(32, R(SCRATCH1), Imm8(2));
+ SetJumpTarget(skip2);
+
+ SHL(32, R(SCRATCH1), Imm8(13));
+ AND(32, MDisp(CTXREG, tempOffset), Imm32(~(7 << 13)));
+ OR(32, MDisp(CTXREG, tempOffset), R(SCRATCH1));
+
+ TEST(32, MDisp(CTXREG, fcr31Offset), Imm32(1 << 24));
+ FixupBranch skip3 = J_CC(CC_Z);
+ OR(32, MDisp(CTXREG, tempOffset), Imm32(1 << 15));
+ SetJumpTarget(skip3);
+
+ LDMXCSR(MDisp(CTXREG, tempOffset));
+ SetJumpTarget(skip);
+ RET();
+ }
+
+ hooks_.enterDispatcher = (IRNativeFuncNoArg)AlignCode16();
+
+ ABI_PushAllCalleeSavedRegsAndAdjustStack();
+#if PPSSPP_ARCH(AMD64)
+ MOV(64, R(MEMBASEREG), ImmPtr(Memory::base));
+ if (jitbaseInR15)
+ MOV(64, R(JITBASEREG), ImmPtr((const void *)jitbase));
+#endif
+ MOV(PTRBITS, R(CTXREG), ImmPtr(&mipsState->f[0]));
+
+ LoadStaticRegisters();
+ WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
+
+ FixupBranch skipFirstMovToPC = J();
+
+ dispatcherPCInSCRATCH1_ = GetCodePtr();
+ outerLoopPCInSCRATCH1_ = GetCodePtr();
+ outerLoop_ = GetCodePtr();
+ MovToPC(SCRATCH1);
+
+ hooks_.dispatcher = GetCodePtr();
+ SetJumpTarget(skipFirstMovToPC);
+
+ if (jo.downcountInRegister) {
+ TEST(32, R(DOWNCOUNTREG), R(DOWNCOUNTREG));
+ } else {
+ CMP(32, MDisp(CTXREG, downcountOffset), Imm8(0));
+ }
+ FixupBranch bail = J_CC(CC_S, true);
+
+ dispatcherNoCheck_ = GetCodePtr();
+ dispatcherCheckCoreState_ = dispatcherNoCheck_;
+ hooks_.dispatchFetch = GetCodePtr();
+
+ SaveStaticRegisters();
+ RestoreRoundingMode(true);
+ WriteDebugProfilerStatus(IRProfilerStatus::COMPILING);
+ ABI_CallFunction((const void *)&MECompileAndLookup);
+ WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
+ ApplyRoundingMode(true);
+ LoadStaticRegisters();
+
+ TEST(PTRBITS, R(RAX), R(RAX));
+ FixupBranch bail2 = J_CC(CC_Z, true);
+ JMPptr(R(RAX));
+
+ SetJumpTarget(bail);
+ SetJumpTarget(bail2);
+ WriteDebugProfilerStatus(IRProfilerStatus::NOT_RUNNING);
+ SaveStaticRegisters();
+ RestoreRoundingMode(true);
+ ABI_PopAllCalleeSavedRegsAndAdjustStack();
+ RET();
+
+ hooks_.crashHandler = GetCodePtr();
+ if (RipAccessible((const void *)&coreState)) {
+ MOV(32, M(&coreState), Imm32(CORE_RUNTIME_ERROR));
+ } else {
+ MOV(PTRBITS, R(RAX), ImmPtr((const void *)&coreState));
+ MOV(32, MatR(RAX), Imm32(CORE_RUNTIME_ERROR));
+ }
+ SaveStaticRegisters();
+ RestoreRoundingMode(true);
+ ABI_PopAllCalleeSavedRegsAndAdjustStack();
+ RET();
+
+ if (enableDisasm) {
+#if PPSSPP_ARCH(AMD64)
+ std::vector lines = DisassembleX86(disasmStart, (int)(GetCodePtr() - disasmStart));
+ for (const auto &s : lines) {
+ INFO_LOG(Log::JIT, "%s", s.c_str());
+ }
+#endif
+ }
+
+ AlignCodePage();
+ jitStartOffset_ = (int)(GetCodePtr() - start);
+ EndWrite();
+}
+
} // namespace MIPSComp
#endif
diff --git a/Core/MIPS/x86/X64IRCompLoadStore.cpp b/Core/MIPS/x86/X64IRCompLoadStore.cpp
index 9b3eea1341d4..31b164fe39b2 100644
--- a/Core/MIPS/x86/X64IRCompLoadStore.cpp
+++ b/Core/MIPS/x86/X64IRCompLoadStore.cpp
@@ -37,6 +37,29 @@ namespace MIPSComp {
using namespace Gen;
using namespace X64IRJitConstants;
+static u32 ComputeConstantAddress(const IRInst &inst, X64IRRegCache ®s) {
+ uint64_t base = 0;
+ if (inst.src1 != MIPS_REG_ZERO) {
+ if (!regs.IsGPRImm(inst.src1))
+ return 0;
+ base = regs.GetGPRImm(inst.src1);
+ }
+
+ int64_t imm = (int32_t)inst.constant;
+ if ((imm & 0xC0000000) == 0x80000000) {
+ imm = (uint64_t)(uint32_t)inst.constant;
+ }
+ return (u32)(base + imm);
+}
+
+static bool NeedsGenericMeHwAccess(const IRInst &inst, X64IRRegCache ®s, const MIPSComp::JitOptions &jo) {
+ if (!jo.isMeJit)
+ return false;
+ if (inst.src1 != MIPS_REG_ZERO && !regs.IsGPRImm(inst.src1))
+ return false;
+ return Memory::IsMeSensitiveHwPage(ComputeConstantAddress(inst, regs));
+}
+
Gen::OpArg X64JitBackend::PrepareSrc1Address(IRInst inst) {
const IRMeta *m = GetIRMeta(inst.op);
@@ -52,14 +75,15 @@ Gen::OpArg X64JitBackend::PrepareSrc1Address(IRInst inst) {
}
#ifdef MASKED_PSP_MEMORY
+ const u32 addrMask = jo.isMeJit ? 0x1FFFFFFFU : Memory::MEMVIEW32_MASK;
if (disp > 0)
- disp &= Memory::MEMVIEW32_MASK;
+ disp &= addrMask;
#endif
OpArg addrArg;
if (inst.src1 == MIPS_REG_ZERO) {
#ifdef MASKED_PSP_MEMORY
- disp &= Memory::MEMVIEW32_MASK;
+ disp &= addrMask;
#endif
#if PPSSPP_ARCH(AMD64)
addrArg = MDisp(MEMBASEREG, disp & 0x7FFFFFFF);
@@ -73,7 +97,7 @@ Gen::OpArg X64JitBackend::PrepareSrc1Address(IRInst inst) {
regs_.MapGPR(inst.src1);
#ifdef MASKED_PSP_MEMORY
LEA(PTRBITS, SCRATCH1, MDisp(regs_.RX(inst.src1), disp));
- AND(PTRBITS, R(SCRATCH1), Imm32(Memory::MEMVIEW32_MASK));
+ AND(PTRBITS, R(SCRATCH1), Imm32(addrMask));
addrArg = MDisp(SCRATCH1, (intptr_t)Memory::base);
#else
#if PPSSPP_ARCH(AMD64)
@@ -91,6 +115,8 @@ void X64JitBackend::CompIR_CondStore(IRInst inst) {
CONDITIONAL_DISABLE;
if (inst.op != IROp::Store32Conditional)
INVALIDOP;
+ if (NeedsGenericMeHwAccess(inst, regs_, jo))
+ DISABLE;
regs_.SpillLockGPR(IRREG_LLBIT, inst.src3, inst.src1);
OpArg addrArg = PrepareSrc1Address(inst);
@@ -118,6 +144,8 @@ void X64JitBackend::CompIR_CondStore(IRInst inst) {
void X64JitBackend::CompIR_FLoad(IRInst inst) {
CONDITIONAL_DISABLE;
+ if (NeedsGenericMeHwAccess(inst, regs_, jo))
+ DISABLE;
OpArg addrArg = PrepareSrc1Address(inst);
@@ -135,6 +163,8 @@ void X64JitBackend::CompIR_FLoad(IRInst inst) {
void X64JitBackend::CompIR_FStore(IRInst inst) {
CONDITIONAL_DISABLE;
+ if (NeedsGenericMeHwAccess(inst, regs_, jo))
+ DISABLE;
OpArg addrArg = PrepareSrc1Address(inst);
@@ -152,6 +182,8 @@ void X64JitBackend::CompIR_FStore(IRInst inst) {
void X64JitBackend::CompIR_Load(IRInst inst) {
CONDITIONAL_DISABLE;
+ if (NeedsGenericMeHwAccess(inst, regs_, jo))
+ DISABLE;
regs_.SpillLockGPR(inst.dest, inst.src1);
OpArg addrArg = PrepareSrc1Address(inst);
@@ -211,6 +243,8 @@ void X64JitBackend::CompIR_LoadShift(IRInst inst) {
void X64JitBackend::CompIR_Store(IRInst inst) {
CONDITIONAL_DISABLE;
+ if (NeedsGenericMeHwAccess(inst, regs_, jo))
+ DISABLE;
regs_.SpillLockGPR(inst.src3, inst.src1);
OpArg addrArg = PrepareSrc1Address(inst);
@@ -275,6 +309,8 @@ void X64JitBackend::CompIR_StoreShift(IRInst inst) {
void X64JitBackend::CompIR_VecLoad(IRInst inst) {
CONDITIONAL_DISABLE;
+ if (NeedsGenericMeHwAccess(inst, regs_, jo))
+ DISABLE;
OpArg addrArg = PrepareSrc1Address(inst);
@@ -292,6 +328,8 @@ void X64JitBackend::CompIR_VecLoad(IRInst inst) {
void X64JitBackend::CompIR_VecStore(IRInst inst) {
CONDITIONAL_DISABLE;
+ if (NeedsGenericMeHwAccess(inst, regs_, jo))
+ DISABLE;
OpArg addrArg = PrepareSrc1Address(inst);
diff --git a/Core/MIPS/x86/X64IRCompSystem.cpp b/Core/MIPS/x86/X64IRCompSystem.cpp
index b137ee176d11..523d4ed3a126 100644
--- a/Core/MIPS/x86/X64IRCompSystem.cpp
+++ b/Core/MIPS/x86/X64IRCompSystem.cpp
@@ -392,7 +392,7 @@ void X64JitBackend::CompIR_ValidateAddress(IRInst inst) {
regs_.Map(inst);
LEA(PTRBITS, SCRATCH1, MDisp(regs_.RX(inst.src1), inst.constant));
}
- AND(32, R(SCRATCH1), Imm32(0x3FFFFFFF));
+ AND(32, R(SCRATCH1), Imm32(jo.isMeJit ? 0x1FFFFFFF : 0x3FFFFFFF));
std::vector validJumps;
diff --git a/Core/MIPS/x86/X64IRJit.cpp b/Core/MIPS/x86/X64IRJit.cpp
index fbfcf9ff9c80..a5d76e9eca9a 100644
--- a/Core/MIPS/x86/X64IRJit.cpp
+++ b/Core/MIPS/x86/X64IRJit.cpp
@@ -401,6 +401,22 @@ void X64JitBackend::EmitConst4x32(const void **c, uint32_t v) {
Write32(v);
}
+const u8 *X64MEIRJit::CompileAndLookup(u32 pc) {
+ int blockNum = blocks_.FindPreloadBlock(pc);
+ if (blockNum < 0) {
+ Compile(pc);
+ blockNum = blocks_.FindPreloadBlock(pc);
+ }
+ if (blockNum < 0) {
+ return nullptr;
+ }
+ const IRBlock *irBlock = blocks_.GetBlock(blockNum);
+ if (!irBlock || irBlock->GetNativeOffset() < 0) {
+ return nullptr;
+ }
+ return backend_->CodeBlock().GetBasePtr() + irBlock->GetNativeOffset();
+}
+
} // namespace MIPSComp
#endif
diff --git a/Core/MIPS/x86/X64IRJit.h b/Core/MIPS/x86/X64IRJit.h
index fe44eed3adac..45bbcb313f59 100644
--- a/Core/MIPS/x86/X64IRJit.h
+++ b/Core/MIPS/x86/X64IRJit.h
@@ -61,7 +61,6 @@ class X64JitBackend : public Gen::XCodeBlock, public IRNativeBackend {
return *this;
}
-private:
void RestoreRoundingMode(bool force = false);
void ApplyRoundingMode(bool force = false);
void MovFromPC(Gen::X64Reg r);
@@ -177,6 +176,33 @@ class X64IRJit : public IRNativeJit {
X64JitBackend x64Backend_;
};
+class X64MEJitBackend : public X64JitBackend {
+public:
+ using X64JitBackend::X64JitBackend;
+ void GenerateFixedCode(MIPSState *mipsState) override;
+};
+
+class X64MEIRJit : public IRNativeJit {
+public:
+ X64MEIRJit(MIPSState *mipsState)
+ : IRNativeJit(mipsState), meBackend_(jo, blocks_) {
+ jo.enableBlocklink = false;
+ jo.enablePointerify = false;
+ jo.isMeJit = true;
+ blocks_.SetPatchMemory(false);
+ Init(meBackend_);
+ }
+
+ const u8 *CompileAndLookup(u32 pc);
+
+ void EnterDispatcher() {
+ hooks_.enterDispatcher();
+ }
+
+private:
+ X64MEJitBackend meBackend_;
+};
+
} // namespace MIPSComp
#endif
diff --git a/Core/MemFault.cpp b/Core/MemFault.cpp
index 799b7ed10406..3524921be78f 100644
--- a/Core/MemFault.cpp
+++ b/Core/MemFault.cpp
@@ -44,6 +44,10 @@
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/Debugger/SymbolMap.h"
+// ME JIT code range check for fault handling.
+namespace MIPSComp { class JitInterface; }
+MIPSComp::JitInterface *ME_GetJit();
+
// Stack walking stuff
#include "Core/MIPS/MIPSStackWalk.h"
#include "Core/MIPS/MIPSDebugInterface.h"
@@ -125,6 +129,11 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) {
// TODO: Check that codePtr is within the current JIT space.
bool inJitSpace = MIPSComp::jit && MIPSComp::jit->CodeInRange(codePtr);
+ if (!inJitSpace) {
+ // Also check ME JIT code range.
+ MIPSComp::JitInterface *meJit = ME_GetJit();
+ inJitSpace = meJit && meJit->CodeInRange(codePtr);
+ }
if (!inJitSpace) {
// This is a crash in non-jitted code. Not something we want to handle here, ignore.
// Actually, we could handle crashes from the IR interpreter here, although recovering the call stack
@@ -286,17 +295,53 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) {
g_lastMemoryExceptionType = type;
+ // ---------- ME HW register page fault (back-patching) ----------
+ // The page at 0xBC100000 is initially read-only when the ME is configured.
+ // A JIT write to 0xBC10004C (reset) or 0xBC100048 (mutex) faults here.
+ // HandleMeHwFault makes the page RW; we then emulate the write and resume.
+ bool meHwHandled = false;
+ if (success && info.isMemoryWrite) {
+ meHwHandled = Memory::HandleMeHwFault(guestAddress, true);
+ }
+
bool handled = true;
- if (success && (g_Config.bIgnoreBadMemAccess || g_ignoredAddresses.find(codePtr) != g_ignoredAddresses.end())) {
- if (!info.isMemoryWrite) {
- // It was a read. Fill the destination register with 0.
- // TODO
+ if (success && (meHwHandled || g_Config.bIgnoreBadMemAccess || g_ignoredAddresses.find(codePtr) != g_ignoredAddresses.end())) {
+
+ if (info.isMemoryWrite) {
+ // Emulate the write through checked path (handles MMIO / HW registers).
+#if PPSSPP_ARCH(ARM64)
+ uint32_t val = (uint32_t)context->CTX_REG(info.Rt);
+ if (info.size == 2) {
+ Memory::Write_U32(val, guestAddress);
+ } else if (info.size == 1) {
+ Memory::Write_U16((uint16_t)val, guestAddress);
+ } else if (info.size == 0) {
+ Memory::Write_U8((uint8_t)val, guestAddress);
+ }
+#endif
+ } else {
+ // It was a read. Emulate through checked path.
+#if PPSSPP_ARCH(ARM64)
+ uint32_t val = 0;
+ if (info.size == 2) {
+ val = Memory::Read_U32(guestAddress);
+ } else if (info.size == 1) {
+ val = Memory::Read_U16(guestAddress);
+ } else if (info.size == 0) {
+ val = Memory::Read_U8(guestAddress);
+ }
+ context->CTX_REG(info.Rt) = val;
+#endif
}
// Move on to the next instruction. Note that handling bad accesses like this is pretty slow.
context->CTX_PC += info.instructionSize;
- g_numReportedBadAccesses++;
- if (g_numReportedBadAccesses < 100) {
- ERROR_LOG(Log::MemMap, "Bad memory access detected and ignored: %08x (%p)", guestAddress, (void *)hostAddress);
+ if (meHwHandled) {
+ Memory::HandleMeHwFaultPost();
+ } else {
+ g_numReportedBadAccesses++;
+ if (g_numReportedBadAccesses < 100) {
+ ERROR_LOG(Log::MemMap, "Bad memory access detected and ignored: %08x (%p)", guestAddress, (void *)hostAddress);
+ }
}
} else {
std::string infoString = "";
diff --git a/Core/MemMap.cpp b/Core/MemMap.cpp
index a7b5caec0532..dd9858f7946f 100644
--- a/Core/MemMap.cpp
+++ b/Core/MemMap.cpp
@@ -53,6 +53,7 @@ MemArena g_arena;
u8 *m_pNullPage;
u8 *m_pPhysicalScratchPad;
u8 *m_pUncachedScratchPad;
+static u8 *m_pKernelScratchPad; // kseg0 mirror at 0x80010000 (ME stack lives here)
// 64-bit: Pointers to high-mem mirrors
// 32-bit: Same as above
u8 *m_pPhysicalRAM[3];
@@ -62,6 +63,25 @@ u8 *m_pKernelRAM[3]; // RAM mirrored up to "kernel space". Fully accessible at a
// This matches how we handle 32-bit masking.
u8 *m_pUncachedKernelRAM[3];
+// Media Engine SRAM: exception vector + context save + shared vars
+static u8 *m_pMeSram;
+static u8 *m_pMeSramCached;
+static u8 *m_pMeSramPhysical;
+
+// Media Engine eDRAM (VRAM alias at physical 0x00000000 for the ME).
+// Physical 0x00020000-0x001FFFFF, kseg0 0x80020000-0x801FFFFF.
+// kseg1 0xA0020000-0xA01FFFFF (uncached kernel).
+// Starts at 0x20000 to avoid overlapping with scratchpad (0x10000).
+static u8 *m_pMeEdramPhysical;
+static u8 *m_pMeEdramCached;
+static u8 *m_pMeEdramUncached;
+
+// Media Engine HW registers: a 4KB page at physical 0x1C100000.
+// Contains the HW mutex (offset 0x48), bus clock enable (0x50), etc.
+// Mapped so the JIT can do raw LDR without faulting.
+static u8 *m_pMeHwRegsPhysical;
+static u8 *m_pMeHwRegsUncached;
+
// VRAM is mirrored 4 times. The second and fourth mirrors are swizzled, so actually it's not correct
// to mirror them like we do here unfortunately.
// In practice, a game accessing the mirrors most likely is deswizzling the depth buffer, and things mostly work out
@@ -87,6 +107,7 @@ static MemoryView views[] = {
{&m_pNullPage, 0x00000000, 0x00010000, MV_NULL_PAGE}, // Null page, usually not enabled. Only used for working around some race condition bugs.
{&m_pPhysicalScratchPad, 0x00010000, SCRATCHPAD_SIZE, 0},
{&m_pUncachedScratchPad, 0x40010000, SCRATCHPAD_SIZE, MV_MIRROR_PREVIOUS},
+ {&m_pKernelScratchPad, 0x80010000, SCRATCHPAD_SIZE, MV_MIRROR_PREVIOUS | MV_KERNEL},
{&m_pPhysicalVRAM[0], 0x04000000, 0x00200000, 0},
{&m_pPhysicalVRAM[1], 0x04200000, 0x00200000, MV_MIRROR_PREVIOUS},
{&m_pPhysicalVRAM[2], 0x04400000, 0x00200000, MV_MIRROR_PREVIOUS},
@@ -112,6 +133,24 @@ static MemoryView views[] = {
// TODO: There are a few swizzled mirrors of VRAM, not sure about the best way to
// implement those.
+
+ // Media Engine SRAM: exception vector at 0xBFC00000 (physical 0x1FC00000)
+ {&m_pMeSramPhysical, 0x1FC00000, 0x00001000, 0},
+ {&m_pMeSramCached, 0x9FC00000, 0x00001000, MV_MIRROR_PREVIOUS | MV_KERNEL},
+ {&m_pMeSram, 0xBFC00000, 0x00001000, MV_MIRROR_PREVIOUS | MV_KERNEL},
+
+ // Media Engine eDRAM: local memory used by the ME for stack and data.
+ // Physical 0x00020000..0x001FFFFF (avoids scratchpad at 0x00010000).
+ // Cached kernel mirror (kseg0) at 0x80020000..0x801FFFFF.
+ // Uncached kernel mirror (kseg1) at 0xA0020000..0xA01FFFFF.
+ {&m_pMeEdramPhysical, 0x00020000, 0x001E0000, 0},
+ {&m_pMeEdramCached, 0x80020000, 0x001E0000, MV_MIRROR_PREVIOUS | MV_KERNEL},
+ {&m_pMeEdramUncached, 0xA0020000, 0x001E0000, MV_MIRROR_PREVIOUS | MV_KERNEL},
+
+ // Media Engine HW registers: 4KB page at physical 0x1C100000 / uncached 0xBC100000.
+ // The JIT does raw LDR/STR here; per-CPU mutex masking is applied at slice boundaries.
+ {&m_pMeHwRegsPhysical, 0x1C100000, 0x00001000, 0},
+ {&m_pMeHwRegsUncached, 0xBC100000, 0x00001000, MV_MIRROR_PREVIOUS | MV_KERNEL},
};
inline static bool CanIgnoreView(const MemoryView &view) {
@@ -439,9 +478,13 @@ static Opcode Read_Instruction(u32 address, bool resolveReplacements, Opcode ins
return inst;
}
+ // Use mainCpuJit for EMUHACK resolution: only the main CPU writes
+ // EMUHACKs, and MIPSComp::jit may point to the ME's JIT during ME slices.
+ MIPSComp::JitInterface *resolveJit = MIPSComp::mainCpuJit ? MIPSComp::mainCpuJit : MIPSComp::jit;
+
// No mutex on jit access here, but we assume the caller has locked, if necessary.
- if (MIPS_IS_RUNBLOCK(inst.encoding) && MIPSComp::jit) {
- inst = MIPSComp::jit->GetOriginalOp(inst);
+ if (MIPS_IS_RUNBLOCK(inst.encoding) && resolveJit) {
+ inst = resolveJit->GetOriginalOp(inst);
if (resolveReplacements && MIPS_IS_REPLACEMENT(inst)) {
u32 op;
if (GetReplacedOpAt(address, &op)) {
@@ -493,9 +536,10 @@ Opcode ReadUnchecked_Instruction(u32 address, bool resolveReplacements) {
Opcode Read_Opcode_JIT(u32 address)
{
Opcode inst = Opcode(Read_U32(address));
- // No mutex around jit access here, but we assume caller has if necessary.
- if (MIPS_IS_RUNBLOCK(inst.encoding) && MIPSComp::jit) {
- return MIPSComp::jit->GetOriginalOp(inst);
+ // Use mainCpuJit: only the main CPU writes EMUHACKs into memory.
+ MIPSComp::JitInterface *resolveJit = MIPSComp::mainCpuJit ? MIPSComp::mainCpuJit : MIPSComp::jit;
+ if (MIPS_IS_RUNBLOCK(inst.encoding) && resolveJit) {
+ return resolveJit->GetOriginalOp(inst);
} else {
return inst;
}
diff --git a/Core/MemMap.h b/Core/MemMap.h
index 6e9fa3b51b07..b61805cac131 100644
--- a/Core/MemMap.h
+++ b/Core/MemMap.h
@@ -114,6 +114,17 @@ ENUM_CLASS_BITOPS(MemMapSetupFlags);
bool Init(MemMapSetupFlags flags);
void Shutdown();
void DoState(PointerWrap &p);
+void InitMeEdram();
+void ShutdownMeEdram();
+void ME_ProtectHwPage(bool readOnly);
+bool HandleMeHwFault(u32 guestAddress, bool isWrite);
+void HandleMeHwFaultPost();
+bool ME_HasPendingInterrupt();
+u32 ME_PeekSoftInterruptRaw();
+bool ME_ConsumeCpuInterruptRequest();
+void ME_ClearSoftInterrupt(); // Consume the pending SC→ME interrupt (called on delivery).
+void ME_RaiseSoftInterrupt();
+void ME_ResetInterruptState();
// False when shutdown has already been called.
bool IsActive();
@@ -302,6 +313,8 @@ inline bool IsValidAddress(const u32 address) {
return true;
} else if ((address & 0x3F000000) >= 0x08000000 && (address & 0x3F000000) < 0x08000000 + g_MemorySize) {
return true;
+ } else if ((address & 0x1FFFF000) == 0x1FC00000) {
+ return true; // ME SRAM (0xBFC00000 / 0x9FC00000 / 0x1FC00000)
} else {
return false;
}
@@ -316,11 +329,18 @@ inline bool IsValid4AlignedAddress(const u32 address) {
return true;
} else if ((address & 0x3F000000) >= 0x08000000 && (address & 0x3F000000) < 0x08000000 + g_MemorySize) {
return (address & 3) == 0;
+ } else if ((address & 0x1FFFF003) == 0x1FC00000) {
+ return true; // ME SRAM
} else {
return false;
}
}
+// Returns true if address is in an ME-sensitive hardware register page
+// (system controller, ME interrupts, VME, DMACplus, etc.).
+// Implementation in MemMapFunctions.cpp.
+bool IsMeSensitiveHwPage(u32 address);
+
inline u32 MaxSizeAtAddress(const u32 address){
if ((address & 0x3E000000) == 0x08000000) {
return 0x08000000 + g_MemorySize - (address & 0x3FFFFFFF);
@@ -334,6 +354,8 @@ inline u32 MaxSizeAtAddress(const u32 address){
return 0x00014000 - (address & 0x3FFFFFFF);
} else if ((address & 0x3F000000) >= 0x08000000 && (address & 0x3F000000) < 0x08000000 + g_MemorySize) {
return 0x08000000 + g_MemorySize - (address & 0x3FFFFFFF);
+ } else if ((address & 0x1FFFF000) == 0x1FC00000) {
+ return 0x1FC01000 - (address & 0x1FFFFFFF); // ME SRAM 4KB
} else {
return 0;
}
diff --git a/Core/MemMapFunctions.cpp b/Core/MemMapFunctions.cpp
index 6f39b1b0067f..c9f06df047bd 100644
--- a/Core/MemMapFunctions.cpp
+++ b/Core/MemMapFunctions.cpp
@@ -17,6 +17,7 @@
#include "Common/CommonTypes.h"
#include "Common/LogReporting.h"
+#include "Common/MemoryUtil.h"
#include "Core/Core.h"
#include "Core/MemMap.h"
@@ -24,17 +25,469 @@
#include "Core/ConfigValues.h"
#include "Core/MIPS/MIPS.h"
+#include "Core/HLE/sceKernelInterrupt.h"
+
+#if !defined(_WIN32)
+#include
+#endif
namespace Memory {
+// ME HW register storage backed by the mapped page at base + 0xBC100000.
+// Non-mapped registers still use static variables.
+
+// ME eDRAM buffer (2MB at physical 0x00000000-0x001FFFFF, kseg0 0x80000000-0x801FFFFF)
+static u8 *meEdram = nullptr;
+static const u32 ME_EDRAM_SIZE = 0x00200000; // 2MB
+
+// Returns a pointer to the mapped HW register page (uncached view 0xBC100000).
+static inline u32 *MeHwRegPtr(u32 offset) {
+ return (u32 *)(Memory::base + 0xBC100000 + offset);
+}
+
+// Register offsets within the 4KB HW register page (base 0xBC100000):
+static constexpr u32 ME_HWREG_TACHYON = 0x40; // 0xBC100040
+static constexpr u32 ME_HWREG_SOFTINT = 0x44; // 0xBC100044, software interrupt SC->ME
+static constexpr u32 ME_HWREG_MUTEX = 0x48; // 0xBC100048
+static constexpr u32 ME_HWREG_RESET = 0x4C; // 0xBC10004C
+static constexpr u32 ME_HWREG_BUSCLK = 0x50; // 0xBC100050
+
+// ME interrupt controller (system-level) at 0xBC300000:
+// These are software-maintained, not memory-mapped via MeHwRegPtr.
+static u32 meIntrFlags_ = 0; // 0xBC300000, interrupt flags (write 1 to clear)
+static u32 meIntrEnable_ = 0; // 0xBC300008, interrupt enable mask
+
+// Pending external interrupt flag. Set when the main CPU writes to
+// 0xBC100044 (soft interrupt) while ME interrupts are enabled. Checked
+// at the top of each ME instruction in the interpreter / at block
+// boundaries in IR/JIT.
+static bool mePendingInterrupt_ = false;
+static bool meCpuInterruptPending_ = false;
+
+// ---------- DMACplus controller (0xBC800xxx) ----------
+//
+// Three DMA channels:
+// Ch0 (0xBC800180): SC->ME
+// Ch1 (0xBC8001A0): ME->SC
+// Ch2 (0xBC8001C0): SC->SC
+// LCDC registers at 0xBC800100-0xBC80010C.
+//
+// Transfers execute synchronously when the status register is written
+// with bit 0 (channel enable) set. LLI chains are followed immediately.
+
+struct DmacPlusChannel {
+ u32 src;
+ u32 dst;
+ u32 next; // pointer to next LLI descriptor (0 = end of chain)
+ u32 ctrl;
+ u32 status; // bit 0 = channel enable/active
+};
+
+static DmacPlusChannel dmacChannels_[3];
+static u32 lcdcRegs_[4]; // [0]=bufAddr, [1]=pixFmt, [2]=bufSize, [3]=stride
+
+// ---------- VME Color Space Conversion (0xBC800120–0xBC800160) ----------
+//
+// Hardware YCbCr->RGB conversion. ME eDRAM holds Y/Cb/Cr planes;
+// the CSC engine reads them, applies a 3x3 color matrix, and writes
+// 8888-format pixels to a VRAM destination buffer.
+//
+// Registers:
+// 0xBC800120 Y source (ME eDRAM physical address)
+// 0xBC800130 Cb source
+// 0xBC800134 Cr source
+// 0xBC800140 Control: blocks_h<<16 | blocks_w<<8 | flags
+// 0xBC800144 RGB destination 1 (VRAM)
+// 0xBC800148 RGB destination 2 (unused)
+// 0xBC80014C Stride/format: stride<<8 | pixfmt<<1 | separate_dst
+// 0xBC800150 Matrix row R (Cr_coeff<<20 | Cb_coeff<<10 | Y_coeff)
+// 0xBC800154 Matrix row G
+// 0xBC800158 Matrix row B
+// 0xBC800160 Start CSC (write 1)
+
+struct CscState {
+ u32 yAddr; // 0xBC800120
+ u32 cbAddr; // 0xBC800130
+ u32 crAddr; // 0xBC800134
+ u32 control; // 0xBC800140
+ u32 dstAddr1; // 0xBC800144
+ u32 dstAddr2; // 0xBC800148
+ u32 strideCtrl; // 0xBC80014C
+ u32 matrixR; // 0xBC800150
+ u32 matrixG; // 0xBC800154
+ u32 matrixB; // 0xBC800158
+};
+
+static CscState cscState_;
+
+// Implemented after GetPointerWrite() / GetMeEdramPointer().
+static u8 *DmaResolveAddress(u32 addr, bool isMeBus);
+static void DmaExecuteTransfer(u32 src, u32 dst, u32 ctrl, bool srcME, bool dstME);
+static void DmaExecuteChannel(int ch);
+static void CscExecute();
+
+void InitMeEdram() {
+ if (!meEdram) {
+ meEdram = new u8[ME_EDRAM_SIZE]();
+ }
+ // Seed the reset register with a non-zero sentinel so that the
+ // first write of 0x00 (ME boot) is detected as a transition.
+ if (Memory::base) {
+ *MeHwRegPtr(ME_HWREG_RESET) = 1;
+ // Make the HW register page read-only so JIT writes fault through
+ // HandleMeHwFault() -> WriteMeHwRegister() -> Core_EnableME().
+ if (g_Config.iMECpuCore >= 0) {
+ ME_ProtectHwPage(true);
+ }
+ }
+}
+
+void ShutdownMeEdram() {
+ delete[] meEdram;
+ meEdram = nullptr;
+}
+
+static inline bool IsMeEdramAddress(u32 address) {
+ u32 phys = address & 0x1FFFFFFF;
+ return phys < ME_EDRAM_SIZE && phys >= 0x00010000;
+}
+
+static inline u8 *GetMeEdramPointer(u32 address) {
+ u32 phys = address & 0x1FFFFFFF;
+ if (phys >= 0x00020000) {
+ // Arena-backed region: physical view at 0x00020000, cached mirror at 0x80020000.
+ return Memory::base + phys;
+ }
+ // Fallback for lower eDRAM (0x10000-0x1FFFF, not arena-backed due to scratchpad overlap).
+ return meEdram + phys;
+}
+
+static inline bool IsMeSramAddress(u32 address) {
+ return (address & 0x1FFFF000) == 0x1FC00000;
+}
+
+static inline bool IsMeHwRegister(u32 address) {
+ u32 phys = address & 0x1FFFFFFF;
+ return (phys >= 0x1C000000 && phys < 0x1D100000);
+}
+
+// Finer-grained check for specific ME hardware register pages.
+// Used by IR backends and validation passes to detect MMIO accesses
+// that must go through ReadFromHardware/WriteToHardware.
+//
+// Physical ranges (after masking with 0x1FFFFFFF):
+// 0x1C000000 - System Controller (power, clock, reset control)
+// 0x1C100000 - ME interrupt / soft-interrupt registers (0xBC100044, 0xBC100048, etc.)
+// 0x1C200000 - ME/SC communication registers
+// 0x1C300000 - Additional system control
+// 0x1CC00000 - VME (Video ME) registers (CSC, etc.)
+// 0x1D000000 - DMACplus registers
+bool IsMeSensitiveHwPage(u32 address) {
+ u32 phys = address & 0x1FFFFFFF;
+ return (phys >= 0x1C000000 && phys < 0x1C001000) ||
+ (phys >= 0x1C100000 && phys < 0x1C101000) ||
+ (phys >= 0x1C200000 && phys < 0x1C201000) ||
+ (phys >= 0x1C300000 && phys < 0x1C301000) ||
+ (phys >= 0x1CC00000 && phys < 0x1CC01000) ||
+ (phys >= 0x1D000000 && phys < 0x1D001000);
+}
+
+// The page at 0xBC100000 (physical 0x1C100000) is mapped in the arena.
+// The JIT does raw LDR/STR there, and the interpreter goes through these
+// helpers. Per-CPU mutex masking is NOT applied because the emulator is
+// single-threaded - there is no true concurrent access, so both CPUs'
+// operations are naturally serialized.
+
+// ---------- ME HW register page protection ----------
+//
+// When the ME is configured (iMECpuCore >= 0), the HW register page at
+// 0xBC100000 (and its physical mirror at 0x1C100000) is initially mapped
+// read-only. The main CPU's JIT writes to 0xBC10004C (reset register)
+// and 0xBC100048 (HW mutex) via raw STR instructions. The first such
+// write faults; HandleMeHwFault() emulates it through WriteMeHwRegister()
+// (which detects the ME boot transition) and then makes the page RW so
+// subsequent accesses are direct and zero-cost.
+
+static bool meHwPageReadOnly_ = false;
+
+// Use mprotect directly instead of ProtectMemoryPages because the latter
+// is hijacked by pthread_jit_write_protect_np on macOS ARM64 (W^X mode),
+// which only affects JIT pages, not data pages in the memory arena.
+static void MeHwMprotect(bool readOnly) {
+#if !defined(_WIN32)
+ int prot = readOnly ? PROT_READ : (PROT_READ | PROT_WRITE);
+ mprotect(Memory::base + 0xBC100000, 0x1000, prot);
+ mprotect(Memory::base + 0x1C100000, 0x1000, prot);
+#endif
+}
+
+static u32 ReadMeHwRegister(u32 address) {
+ // Normalise to uncached virtual address for the switch.
+ u32 virt = 0xBC000000 | (address & 0x01FFFFFF);
+ switch (virt) {
+ case 0xBC100040: return *MeHwRegPtr(ME_HWREG_TACHYON);
+ case 0xBC100044: return *MeHwRegPtr(ME_HWREG_SOFTINT);
+ case 0xBC100048: return *MeHwRegPtr(ME_HWREG_MUTEX);
+ case 0xBC10004C: return *MeHwRegPtr(ME_HWREG_RESET);
+ case 0xBC100050: return *MeHwRegPtr(ME_HWREG_BUSCLK);
+ case 0xBC300000: return meIntrFlags_; // ME interrupt flags
+ case 0xBC300008: return meIntrEnable_; // ME interrupt enable mask
+ case 0xBCC00010: return 0; // VME reset: 0 = reset complete
+
+ // DMACplus LCDC registers
+ case 0xBC800100: return lcdcRegs_[0];
+ case 0xBC800104: return lcdcRegs_[1];
+ case 0xBC800108: return lcdcRegs_[2];
+ case 0xBC80010C: return lcdcRegs_[3];
+
+ // CSC registers
+ case 0xBC800120: return cscState_.yAddr;
+ case 0xBC800130: return cscState_.cbAddr;
+ case 0xBC800134: return cscState_.crAddr;
+ case 0xBC800140: return cscState_.control;
+ case 0xBC800144: return cscState_.dstAddr1;
+ case 0xBC800148: return cscState_.dstAddr2;
+ case 0xBC80014C: return cscState_.strideCtrl;
+ case 0xBC800150: return cscState_.matrixR;
+ case 0xBC800154: return cscState_.matrixG;
+ case 0xBC800158: return cscState_.matrixB;
+ case 0xBC800160: return 0; // CSC status: 0 = idle
+
+ // DMACplus Channel 0 (SC->ME) 0xBC800180
+ case 0xBC800180: return dmacChannels_[0].src;
+ case 0xBC800184: return dmacChannels_[0].dst;
+ case 0xBC800188: return dmacChannels_[0].next;
+ case 0xBC80018C: return dmacChannels_[0].ctrl;
+ case 0xBC800190: return dmacChannels_[0].status;
+
+ // DMACplus Channel 1 (ME->SC) 0xBC8001A0
+ case 0xBC8001A0: return dmacChannels_[1].src;
+ case 0xBC8001A4: return dmacChannels_[1].dst;
+ case 0xBC8001A8: return dmacChannels_[1].next;
+ case 0xBC8001AC: return dmacChannels_[1].ctrl;
+ case 0xBC8001B0: return dmacChannels_[1].status;
+
+ // DMACplus Channel 2 (SC->SC) 0xBC8001C0
+ case 0xBC8001C0: return dmacChannels_[2].src;
+ case 0xBC8001C4: return dmacChannels_[2].dst;
+ case 0xBC8001C8: return dmacChannels_[2].next;
+ case 0xBC8001CC: return dmacChannels_[2].ctrl;
+ case 0xBC8001D0: return dmacChannels_[2].status;
+ default:
+ return 0;
+ }
+}
+
+static void WriteMeHwRegister(u32 address, u32 value) {
+ // Temporarily lift page protection so the write doesn't fault.
+ // This is needed for the interpreter path (which calls this directly)
+ // when the page is still read-only before ME boot.
+ bool wasReadOnly = meHwPageReadOnly_;
+ if (wasReadOnly)
+ MeHwMprotect(false);
+
+ u32 virt = 0xBC000000 | (address & 0x01FFFFFF);
+ switch (virt) {
+ case 0xBC100050: *MeHwRegPtr(ME_HWREG_BUSCLK) = value; break;
+ case 0xBC100040: *MeHwRegPtr(ME_HWREG_TACHYON) = value; break;
+ case 0xBC100044:
+ // Software interrupt register shared between SC->ME and ME->SC.
+ // If the current writer is the ME, route it to the main CPU's MECODEC
+ // interrupt instead of treating it as a self-interrupt on the ME.
+ if (currentMIPS == &mipsMe) {
+ if (value != 0) {
+ meCpuInterruptPending_ = true;
+ }
+ *MeHwRegPtr(ME_HWREG_SOFTINT) = 0;
+ break;
+ }
+
+ // SC->ME: main CPU writes here to signal ME.
+ *MeHwRegPtr(ME_HWREG_SOFTINT) = value;
+ meIntrFlags_ |= 0x80000000; // Set pending external interrupt flag
+ if (value != 0)
+ mePendingInterrupt_ = true;
+ break;
+ case 0xBC10004C: {
+ u32 prev = *MeHwRegPtr(ME_HWREG_RESET);
+ if (prev != 0 && value == 0) {
+ Core_EnableME();
+ }
+ *MeHwRegPtr(ME_HWREG_RESET) = value;
+ break;
+ }
+ case 0xBC100048:
+ *MeHwRegPtr(ME_HWREG_MUTEX) = value;
+ break;
+ case 0xBC300000:
+ // ME interrupt flags: write 1 to clear bits.
+ meIntrFlags_ &= ~value;
+ // Also clear the raw softint register if the corresponding flag was cleared.
+ if (value & 0x80000000)
+ *MeHwRegPtr(ME_HWREG_SOFTINT) = 0;
+ if (!(meIntrFlags_ & meIntrEnable_))
+ mePendingInterrupt_ = false;
+ break;
+ case 0xBC300008:
+ // ME interrupt enable mask.
+ meIntrEnable_ = value;
+ if (meIntrFlags_ & meIntrEnable_)
+ mePendingInterrupt_ = true;
+ else
+ mePendingInterrupt_ = false;
+ break;
+
+ // DMACplus LCDC registers (absorb writes)
+ case 0xBC800100: lcdcRegs_[0] = value; break;
+ case 0xBC800104: lcdcRegs_[1] = value; break;
+ case 0xBC800108: lcdcRegs_[2] = value; break;
+ case 0xBC80010C: lcdcRegs_[3] = value; break;
+
+ // CSC registers
+ case 0xBC800120: cscState_.yAddr = value; break;
+ case 0xBC800130: cscState_.cbAddr = value; break;
+ case 0xBC800134: cscState_.crAddr = value; break;
+ case 0xBC800140: cscState_.control = value; break;
+ case 0xBC800144: cscState_.dstAddr1 = value; break;
+ case 0xBC800148: cscState_.dstAddr2 = value; break;
+ case 0xBC80014C: cscState_.strideCtrl = value; break;
+ case 0xBC800150: cscState_.matrixR = value; break;
+ case 0xBC800154: cscState_.matrixG = value; break;
+ case 0xBC800158: cscState_.matrixB = value; break;
+ case 0xBC800160:
+ if (value & 1) CscExecute();
+ break;
+
+ // DMACplus Channel 0 (SC->ME) 0xBC800180
+ case 0xBC800180: dmacChannels_[0].src = value; break;
+ case 0xBC800184: dmacChannels_[0].dst = value; break;
+ case 0xBC800188: dmacChannels_[0].next = value; break;
+ case 0xBC80018C: dmacChannels_[0].ctrl = value; break;
+ case 0xBC800190:
+ dmacChannels_[0].status = value;
+ if (value & 1) DmaExecuteChannel(0);
+ break;
+
+ // DMACplus Channel 1 (ME->SC) 0xBC8001A0
+ case 0xBC8001A0: dmacChannels_[1].src = value; break;
+ case 0xBC8001A4: dmacChannels_[1].dst = value; break;
+ case 0xBC8001A8: dmacChannels_[1].next = value; break;
+ case 0xBC8001AC: dmacChannels_[1].ctrl = value; break;
+ case 0xBC8001B0:
+ dmacChannels_[1].status = value;
+ if (value & 1) DmaExecuteChannel(1);
+ break;
+
+ // DMACplus Channel 2 (SC->SC) 0xBC8001C0
+ case 0xBC8001C0: dmacChannels_[2].src = value; break;
+ case 0xBC8001C4: dmacChannels_[2].dst = value; break;
+ case 0xBC8001C8: dmacChannels_[2].next = value; break;
+ case 0xBC8001CC: dmacChannels_[2].ctrl = value; break;
+ case 0xBC8001D0:
+ dmacChannels_[2].status = value;
+ if (value & 1) DmaExecuteChannel(2);
+ break;
+
+ // VME registers (stub - absorb writes silently)
+ case 0xBCC00000: case 0xBCC00030: case 0xBCC00040: break;
+
+ default:
+ break;
+ }
+
+ // Re-protect if ME hasn't been enabled (Core_EnableME clears meHwPageReadOnly_).
+ if (wasReadOnly && meHwPageReadOnly_)
+ MeHwMprotect(true);
+}
+
+void ME_ProtectHwPage(bool readOnly) {
+ if (!Memory::base)
+ return;
+ MeHwMprotect(readOnly);
+ meHwPageReadOnly_ = readOnly;
+}
+
+bool HandleMeHwFault(u32 guestAddress, bool isWrite) {
+ if (!meHwPageReadOnly_)
+ return false;
+ u32 phys = guestAddress & 0x1FFFFFFF;
+ if (phys < 0x1C100000 || phys >= 0x1C101000)
+ return false;
+ if (!isWrite)
+ return false; // Reads shouldn't fault (page is readable).
+ // Temporarily make page writable so the emulated store (via
+ // Memory::Write_U32() -> WriteMeHwRegister() can update MeHwRegPtr().
+ // WriteMeHwRegister may call Core_EnableME() which permanently
+ // unprotects the page via ME_ProtectHwPage(false).
+ MeHwMprotect(false);
+ return true; // Tell HandleFault to emulate the write and advance PC.
+}
+
+void HandleMeHwFaultPost() {
+ // Called after HandleFault has emulated the write. If ME_ProtectHwPage(false)
+ // was NOT called (i.e. the write didn't trigger Core_EnableME), re-protect
+ // the page so the next write also faults through.
+ if (meHwPageReadOnly_) {
+ MeHwMprotect(true);
+ }
+}
+
+bool ME_HasPendingInterrupt() {
+ return mePendingInterrupt_;
+}
+
+u32 ME_PeekSoftInterruptRaw() {
+ return *MeHwRegPtr(ME_HWREG_SOFTINT);
+}
+
+bool ME_ConsumeCpuInterruptRequest() {
+ bool pending = meCpuInterruptPending_;
+ meCpuInterruptPending_ = false;
+ return pending;
+}
+
+void ME_ClearSoftInterrupt() {
+ // Called from ME_CheckAndDeliverInterrupt to consume the interrupt on delivery,
+ // because the ME interpreter's write to 0xBC300000 (the normal ACK path) bypasses
+ // WriteMeHwRegister and therefore never clears mePendingInterrupt_ or SOFTINT.
+ *MeHwRegPtr(ME_HWREG_SOFTINT) = 0;
+ meIntrFlags_ &= ~0x80000000u;
+ mePendingInterrupt_ = false;
+}
+
+void ME_RaiseSoftInterrupt() {
+ meIntrFlags_ |= 0x80000000;
+ mePendingInterrupt_ = true;
+}
+
+void ME_ResetInterruptState() {
+ meIntrFlags_ = 0;
+ meIntrEnable_ = 0;
+ mePendingInterrupt_ = false;
+ meCpuInterruptPending_ = false;
+ memset(dmacChannels_, 0, sizeof(dmacChannels_));
+ memset(lcdcRegs_, 0, sizeof(lcdcRegs_));
+ memset(&cscState_, 0, sizeof(cscState_));
+ if (Memory::base) {
+ bool wasReadOnly = meHwPageReadOnly_;
+ if (wasReadOnly)
+ MeHwMprotect(false);
+ *MeHwRegPtr(ME_HWREG_SOFTINT) = 0;
+ if (wasReadOnly)
+ MeHwMprotect(true);
+ }
+}
+
u8 *GetPointerWrite(const u32 address) {
if ((address & 0x3E000000) == 0x08000000 || // RAM
(address & 0x3F800000) == 0x04000000 || // VRAM
(address & 0xBFFFC000) == 0x00010000 || // Scratchpad
- ((address & 0x3F000000) >= 0x08000000 && (address & 0x3F000000) < 0x08000000 + g_MemorySize)) { // More RAM (remasters, etc.)
+ ((address & 0x3F000000) >= 0x08000000 && (address & 0x3F000000) < 0x08000000 + g_MemorySize) || // More RAM
+ IsMeSramAddress(address)) { // ME SRAM
return GetPointerWriteUnchecked(address);
+ } else if (IsMeEdramAddress(address) && meEdram) {
+ return GetMeEdramPointer(address);
} else {
- // Size is not known, we pass 0 to signal that.
Core_MemoryException(address, 0, currentMIPS->pc, MemoryExceptionType::WRITE_BLOCK);
return nullptr;
}
@@ -44,15 +497,131 @@ const u8 *GetPointer(const u32 address) {
if ((address & 0x3E000000) == 0x08000000 || // RAM
(address & 0x3F800000) == 0x04000000 || // VRAM
(address & 0xBFFFC000) == 0x00010000 || // Scratchpad
- ((address & 0x3F000000) >= 0x08000000 && (address & 0x3F000000) < 0x08000000 + g_MemorySize)) { // More RAM (remasters, etc.)
+ ((address & 0x3F000000) >= 0x08000000 && (address & 0x3F000000) < 0x08000000 + g_MemorySize) || // More RAM
+ IsMeSramAddress(address)) { // ME SRAM
return GetPointerUnchecked(address);
+ } else if (IsMeEdramAddress(address) && meEdram) {
+ return GetMeEdramPointer(address);
} else {
- // Size is not known, we pass 0 to signal that.
Core_MemoryException(address, 0, currentMIPS->pc, MemoryExceptionType::READ_BLOCK);
return nullptr;
}
}
+// ---------- DMACplus function implementations ----------
+
+static u8 *DmaResolveAddress(u32 addr, bool isMeBus) {
+ if (isMeBus) {
+ u32 phys = addr & 0x1FFFFFFF;
+ if (phys < 0x00200000) {
+ return GetMeEdramPointer(phys);
+ }
+ return nullptr;
+ }
+ return GetPointerWrite(addr);
+}
+
+static void DmaExecuteTransfer(u32 src, u32 dst, u32 ctrl, bool srcME, bool dstME) {
+ u32 widthShift = (ctrl >> 18) & 0x7;
+ u32 widthBytes = 1u << widthShift;
+ u32 count = ctrl & 0xFFF;
+ u32 totalBytes = widthBytes * count;
+
+ if (totalBytes == 0)
+ return;
+
+ u8 *srcPtr = DmaResolveAddress(src, srcME);
+ u8 *dstPtr = DmaResolveAddress(dst, dstME);
+
+ if (srcPtr && dstPtr) {
+ memcpy(dstPtr, srcPtr, totalBytes);
+ }
+}
+
+static void DmaExecuteChannel(int ch) {
+ DmacPlusChannel &c = dmacChannels_[ch];
+ bool srcME = (c.ctrl >> 24) & 1;
+ bool dstME = (c.ctrl >> 25) & 1;
+
+ DmaExecuteTransfer(c.src, c.dst, c.ctrl, srcME, dstME);
+
+ u32 nextAddr = c.next;
+ int safety = 1024;
+ while (nextAddr != 0 && --safety > 0) {
+ u32 lli_src = Read_U32(nextAddr + 0);
+ u32 lli_dst = Read_U32(nextAddr + 4);
+ u32 lli_next = Read_U32(nextAddr + 8);
+ u32 lli_ctrl = Read_U32(nextAddr + 12);
+
+ bool lli_srcME = (lli_ctrl >> 24) & 1;
+ bool lli_dstME = (lli_ctrl >> 25) & 1;
+ DmaExecuteTransfer(lli_src, lli_dst, lli_ctrl, lli_srcME, lli_dstME);
+
+ nextAddr = lli_next;
+ }
+
+ c.status = 0;
+}
+
+// ---------- VME CSC function implementation ----------
+
+// Decode a Q3.7 fixed-point coefficient (10-bit, signed via 2's complement).
+static inline float Q37ToFloat(u32 val) {
+ val &= 0x3FF;
+ if (val & 0x200)
+ return (float)((int)val - 1024) / 128.0f;
+ return (float)val / 128.0f;
+}
+
+static inline int Clamp255(int v) {
+ return v < 0 ? 0 : (v > 255 ? 255 : v);
+}
+
+static void CscExecute() {
+ u32 blocksW = (cscState_.control >> 8) & 0xFF;
+ u32 blocksH = (cscState_.control >> 16) & 0xFFFF;
+ u32 width = blocksW * 16;
+ u32 height = blocksH * 8;
+ u32 stride = (cscState_.strideCtrl >> 8) & 0xFFFFFF;
+
+ // Decode 3x3 colour matrix from registers (each row: Cr<<20 | Cb<<10 | Y).
+ float yR = Q37ToFloat(cscState_.matrixR);
+ float cbR = Q37ToFloat(cscState_.matrixR >> 10);
+ float crR = Q37ToFloat(cscState_.matrixR >> 20);
+ float yG = Q37ToFloat(cscState_.matrixG);
+ float cbG = Q37ToFloat(cscState_.matrixG >> 10);
+ float crG = Q37ToFloat(cscState_.matrixG >> 20);
+ float yB = Q37ToFloat(cscState_.matrixB);
+ float cbB = Q37ToFloat(cscState_.matrixB >> 10);
+ float crB = Q37ToFloat(cscState_.matrixB >> 20);
+
+ // Resolve source pointers (ME eDRAM physical addresses).
+ u8 *yPtr = GetMeEdramPointer(cscState_.yAddr & 0x1FFFFFFF);
+ u8 *cbPtr = GetMeEdramPointer(cscState_.cbAddr & 0x1FFFFFFF);
+ u8 *crPtr = GetMeEdramPointer(cscState_.crAddr & 0x1FFFFFFF);
+
+ // Resolve destination pointer (VRAM address, strip upper bits).
+ u32 *dstPtr = (u32 *)GetPointerWrite(cscState_.dstAddr1 & 0x3FFFFFFF);
+ if (!yPtr || !cbPtr || !crPtr || !dstPtr)
+ return;
+
+ // BT.601 YCbCr->RGB: subtract 16 from Y and 128 from Cb/Cr.
+ u32 halfW = width / 2;
+ for (u32 py = 0; py < height; py++) {
+ for (u32 px = 0; px < width; px++) {
+ float fY = (float)yPtr[py * width + px] - 16.0f;
+ float fCb = (float)cbPtr[(py / 2) * halfW + (px / 2)] - 128.0f;
+ float fCr = (float)crPtr[(py / 2) * halfW + (px / 2)] - 128.0f;
+
+ int R = Clamp255((int)(yR * fY + cbR * fCb + crR * fCr));
+ int G = Clamp255((int)(yG * fY + cbG * fCb + crG * fCr));
+ int B = Clamp255((int)(yB * fY + cbB * fCb + crB * fCr));
+
+ dstPtr[py * stride + px] = 0xFF000000u | ((u32)B << 16) | ((u32)G << 8) | (u32)R;
+ }
+ }
+}
+
u8 *GetPointerWriteRange(const u32 address, const u32 size) {
u8 *ptr = GetPointerWrite(address);
if (ptr) {
@@ -90,8 +659,13 @@ inline void ReadFromHardware(T &var, const u32 address) {
if ((address & 0x3E000000) == 0x08000000 || // RAM
(address & 0x3F800000) == 0x04000000 || // VRAM
(address & 0xBFFFC000) == 0x00010000 || // Scratchpad
- ((address & 0x3F000000) >= 0x08000000 && (address & 0x3F000000) < 0x08000000 + g_MemorySize)) { // More RAM (remasters, etc.)
+ ((address & 0x3F000000) >= 0x08000000 && (address & 0x3F000000) < 0x08000000 + g_MemorySize) || // More RAM
+ IsMeSramAddress(address)) { // ME SRAM
var = *((const T*)GetPointerUnchecked(address));
+ } else if (IsMeEdramAddress(address) && meEdram) {
+ var = *(const T*)GetMeEdramPointer(address);
+ } else if (IsMeHwRegister(address) && sizeof(T) == 4) {
+ var = (T)ReadMeHwRegister(address);
} else {
Core_MemoryException(address, sizeof(T), currentMIPS->pc, MemoryExceptionType::READ_WORD);
var = 0;
@@ -103,8 +677,13 @@ inline void WriteToHardware(u32 address, const T data) {
if ((address & 0x3E000000) == 0x08000000 || // RAM
(address & 0x3F800000) == 0x04000000 || // VRAM
(address & 0xBFFFC000) == 0x00010000 || // Scratchpad
- ((address & 0x3F000000) >= 0x08000000 && (address & 0x3F000000) < 0x08000000 + g_MemorySize)) { // More RAM (remasters, etc.)
+ ((address & 0x3F000000) >= 0x08000000 && (address & 0x3F000000) < 0x08000000 + g_MemorySize) || // More RAM
+ IsMeSramAddress(address)) { // ME SRAM
*(T*)GetPointerUnchecked(address) = data;
+ } else if (IsMeEdramAddress(address) && meEdram) {
+ *(T*)GetMeEdramPointer(address) = data;
+ } else if (IsMeHwRegister(address) && sizeof(T) == 4) {
+ WriteMeHwRegister(address, (u32)data);
} else {
Core_MemoryException(address, sizeof(T), currentMIPS->pc, MemoryExceptionType::WRITE_WORD);
}
diff --git a/Core/System.cpp b/Core/System.cpp
index 98818875ba4f..357df7ffd928 100644
--- a/Core/System.cpp
+++ b/Core/System.cpp
@@ -434,9 +434,15 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin
LoadSymbolsIfSupported();
mipsr4k.Reset();
+ mipsMe.Reset();
+ currentMIPS = &mipsr4k; // Restore: mipsMe.Init() overwrites currentMIPS
+
+ Memory::InitMeEdram();
CoreTiming::Init();
+ ME_InitPolling();
+
DisplayHWInit();
// Init all the HLE modules
@@ -519,6 +525,9 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin
InstallExceptionHandler(&Memory::HandleFault);
+ // ME LLE: Write the PSP fat witness word after __KernelLoadExec has finished zeroing kernel memory.
+ Memory::Write_U32(0x279c1d44, 0x08300018);
+
return true;
}
@@ -541,6 +550,8 @@ void CPU_Shutdown(bool success) {
pspFileSystem.Shutdown();
mipsr4k.Shutdown();
+ mipsMe.Shutdown();
+ Memory::ShutdownMeEdram();
Memory::Shutdown();
HLEPlugins::Shutdown();
diff --git a/UI/DeveloperToolsScreen.cpp b/UI/DeveloperToolsScreen.cpp
index ff3572f22677..54dc4ebe1222 100644
--- a/UI/DeveloperToolsScreen.cpp
+++ b/UI/DeveloperToolsScreen.cpp
@@ -158,6 +158,15 @@ void DeveloperToolsScreen::CreateGeneralTab(UI::LinearLayout *list) {
#endif
list->Add(new Choice(dev->T("JIT debug tools")))->OnClick.Handle(this, &DeveloperToolsScreen::OnJitDebugTools);
+
+ // Media Engine CPU backend selector.
+ static const char *meCores[] = { "Disabled", "Interpreter", "Dynarec/JIT", "IR Interpreter", "Native JIT (recommended)" };
+ PopupMultiChoice *meCore = list->Add(new PopupMultiChoice(&g_Config.iMECpuCore, sy->T("Media Engine Core"), meCores, -1, ARRAY_SIZE(meCores), I18NCat::SYSTEM, screenManager()));
+ meCore->HideChoice(2); // Hide "Dynarec/JIT" — not a valid ME backend
+#if !PPSSPP_ARCH(ARM64) && !PPSSPP_ARCH(X86) && !PPSSPP_ARCH(AMD64)
+ meCore->HideChoice(4); // Hide "Native JIT" on unsupported architectures
+#endif
+
list->Add(new CheckBox(&g_Config.bShowDeveloperMenu, dev->T("Show in-game developer menu")));
AddOverlayList(list, screenManager());
diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp
index 68698d7388c1..a911a90fb024 100644
--- a/UI/ImDebugger/ImDebugger.cpp
+++ b/UI/ImDebugger/ImDebugger.cpp
@@ -67,6 +67,8 @@
#include "UI/AudioCommon.h"
#include "UI/GameInfoCache.h"
+#include "Core/MIPS/MIPS.h" // for debugMe
+
extern bool g_TakeScreenshot;
static ImVec4 g_normalTextColor;
@@ -213,9 +215,10 @@ void DrawSchedulerView(ImConfig &cfg) {
ImGui::End();
}
-static void DrawGPRs(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev) {
+static void DrawGPRs(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev, const char *title = "GPRs", bool *openFlag = nullptr) {
+ if (!openFlag) openFlag = &config.gprOpen;
ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver);
- if (!ImGui::Begin("GPRs", &config.gprOpen)) {
+ if (!ImGui::Begin(title, openFlag)) {
ImGui::End();
return;
}
@@ -282,9 +285,10 @@ static void DrawGPRs(ImConfig &config, ImControl &control, const MIPSDebugInterf
ImGui::End();
}
-static void DrawFPRs(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev) {
+static void DrawFPRs(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev, const char *title = "FPRs", bool *openFlag = nullptr) {
+ if (!openFlag) openFlag = &config.fprOpen;
ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver);
- if (!ImGui::Begin("FPRs", &config.fprOpen)) {
+ if (!ImGui::Begin(title, openFlag)) {
ImGui::End();
return;
}
@@ -334,9 +338,10 @@ static void DrawFPRs(ImConfig &config, ImControl &control, const MIPSDebugInterf
ImGui::End();
}
-static void DrawVFPU(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev) {
+static void DrawVFPU(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev, const char *title = "VFPU", bool *openFlag = nullptr) {
+ if (!openFlag) openFlag = &config.vfpuOpen;
ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver);
- if (!ImGui::Begin("VFPU", &config.vfpuOpen)) {
+ if (!ImGui::Begin(title, openFlag)) {
ImGui::End();
return;
}
@@ -2405,6 +2410,11 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu
ImGui::MenuItem("Breakpoints", nullptr, &cfg_.breakpointsOpen);
ImGui::MenuItem("Watch", nullptr, &cfg_.watchOpen);
ImGui::MenuItem("JIT viewer", nullptr, &cfg_.jitViewerOpen);
+ ImGui::Separator();
+ ImGui::TextDisabled("Media Engine");
+ ImGui::MenuItem("ME Debugger", nullptr, &cfg_.meDisasmOpen);
+ ImGui::MenuItem("ME GPR regs", nullptr, &cfg_.meGprOpen);
+ ImGui::MenuItem("ME FPR regs", nullptr, &cfg_.meFprOpen);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Symbols")) {
@@ -2559,6 +2569,32 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu
DrawVFPU(cfg_, control, mipsDebug, snapshot_);
}
+ // Media Engine windows (separate from main CPU)
+ // Update ME snapshot for diff highlighting (every frame while running).
+ if (cfg_.meGprOpen || cfg_.meFprOpen || cfg_.meDisasmOpen) {
+ extern MIPSState mipsMe;
+ meSnapshot_ = meNewSnapshot_;
+ memcpy(meNewSnapshot_.gpr, mipsMe.r, sizeof(meNewSnapshot_.gpr));
+ memcpy(meNewSnapshot_.fpr, mipsMe.fs, sizeof(meNewSnapshot_.fpr));
+ meNewSnapshot_.pc = mipsMe.pc;
+ meNewSnapshot_.lo = mipsMe.lo;
+ meNewSnapshot_.hi = mipsMe.hi;
+ meNewSnapshot_.ll = mipsMe.llBit;
+ }
+
+ if (cfg_.meDisasmOpen) {
+ meDisasm_.SetIsMediaEngine(true);
+ meDisasm_.Draw(&debugMe, cfg_, control, coreState, "ME Debugger", &cfg_.meDisasmOpen);
+ }
+
+ if (cfg_.meGprOpen) {
+ DrawGPRs(cfg_, control, &debugMe, meSnapshot_, "ME GPRs", &cfg_.meGprOpen);
+ }
+
+ if (cfg_.meFprOpen) {
+ DrawFPRs(cfg_, control, &debugMe, meSnapshot_, "ME FPRs", &cfg_.meFprOpen);
+ }
+
if (cfg_.breakpointsOpen) {
DrawBreakpointsView(mipsDebug, cfg_);
}
diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h
index 63ac0efac5f9..63581d188bdb 100644
--- a/UI/ImDebugger/ImDebugger.h
+++ b/UI/ImDebugger/ImDebugger.h
@@ -122,6 +122,14 @@ struct ImConfig {
bool sasShowAllVoices = false;
+ // CPU selector for debugger: 0 = Main CPU (SC), 1 = Media Engine (ME)
+ int selectedCpu = 0;
+
+ // Media Engine debug windows (separate from main CPU windows)
+ bool meDisasmOpen = false;
+ bool meGprOpen = false;
+ bool meFprOpen = false;
+
float fbViewerZoom = 1.0f;
@@ -190,6 +198,7 @@ class ImDebugger {
RequesterToken reqToken_;
ImDisasmWindow disasm_;
+ ImDisasmWindow meDisasm_; // Media Engine disassembly
ImGeDebuggerWindow geDebugger_;
ImGeStateWindow geStateWindow_;
ImMemWindow mem_[4]; // We support 4 separate instances of the memory viewer.
@@ -205,6 +214,9 @@ class ImDebugger {
ImSnapshotState newSnapshot_;
ImSnapshotState snapshot_;
+ ImSnapshotState meNewSnapshot_;
+ ImSnapshotState meSnapshot_;
+
int lastCpuStepCount_ = -1;
int lastGpuStepCount_ = -1;
diff --git a/UI/ImDebugger/ImDisasmView.cpp b/UI/ImDebugger/ImDisasmView.cpp
index 1fd8ca1a7fd3..f0205409422f 100644
--- a/UI/ImDebugger/ImDisasmView.cpp
+++ b/UI/ImDebugger/ImDisasmView.cpp
@@ -1169,15 +1169,33 @@ u32 ImDisasmView::getInstructionSizeAt(u32 address) {
}
-void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, CoreState coreState) {
+void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, CoreState coreState, const char *title, bool *openFlag) {
+ if (!title) title = Title();
+ if (!openFlag) openFlag = &cfg.disasmOpen;
disasmView_.setDebugger(mipsDebug);
ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
- if (!ImGui::Begin(Title(), &cfg.disasmOpen)) {
+ if (!ImGui::Begin(title, openFlag)) {
ImGui::End();
return;
}
+ if (isMediaEngine_) {
+ // ME runs autonomously, no step/run/pause controls.
+ // Auto-follow: keep the view centered on ME's PC while the game runs.
+ ImGui::Checkbox("Live follow PC", &disasmView_.followPC_);
+ if (disasmView_.followPC_ && (coreState == CORE_RUNNING_CPU || coreState == CORE_RUNNING_GE)) {
+ disasmView_.GotoPC();
+ }
+ ImGui::SameLine();
+ if (ImGui::SmallButton("Goto PC")) {
+ disasmView_.GotoPC();
+ }
+ ImGui::SameLine();
+ if (ImGui::SmallButton("Goto RA")) {
+ disasmView_.GotoRA();
+ }
+ } else {
if (ImGui::IsWindowFocused()) {
// Process stepping keyboard shortcuts.
if (ImGui::IsKeyPressed(ImGuiKey_F10)) {
@@ -1262,6 +1280,7 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImContro
if (ImGui::SmallButton("Goto RA")) {
disasmView_.GotoRA();
}
+ } // end !isMediaEngine_
if (ImGui::BeginPopup("disSearch")) {
if (ImGui::IsWindowAppearing()) {
diff --git a/UI/ImDebugger/ImDisasmView.h b/UI/ImDebugger/ImDisasmView.h
index f70f7291f4a2..dcf9e32f58af 100644
--- a/UI/ImDebugger/ImDisasmView.h
+++ b/UI/ImDebugger/ImDisasmView.h
@@ -178,7 +178,7 @@ class ImDisasmView {
// Corresponds to the CDisasm dialog
class ImDisasmWindow {
public:
- void Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, CoreState coreState);
+ void Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, CoreState coreState, const char *title = nullptr, bool *openFlag = nullptr);
ImDisasmView &View() {
return disasmView_;
}
@@ -189,10 +189,13 @@ class ImDisasmWindow {
symsDirty_ = true;
}
const char *Title() const {
- return "CPU Debugger";
+ return isMediaEngine_ ? "ME Debugger" : "CPU Debugger";
}
+ void SetIsMediaEngine(bool v) { isMediaEngine_ = v; }
+ bool IsMediaEngine() const { return isMediaEngine_; }
private:
+ bool isMediaEngine_ = false;
// We just keep the state directly in the window. Can refactor later.
enum {
diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj
index dadb1715b22a..312621ce3f10 100644
--- a/UWP/CoreUWP/CoreUWP.vcxproj
+++ b/UWP/CoreUWP/CoreUWP.vcxproj
@@ -223,8 +223,11 @@
+
+
+
@@ -492,8 +495,11 @@
+
+
+
diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters
index 211b5a941091..33a513e17f53 100644
--- a/UWP/CoreUWP/CoreUWP.vcxproj.filters
+++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters
@@ -135,8 +135,11 @@
+
+
+
@@ -551,8 +554,11 @@
+
+
+
diff --git a/android/jni/Android.mk b/android/jni/Android.mk
index 00a6e145b29b..1fd96ae4eeaa 100644
--- a/android/jni/Android.mk
+++ b/android/jni/Android.mk
@@ -758,6 +758,9 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/HLE/sceNp.cpp \
$(SRC)/Core/HLE/sceNp2.cpp \
$(SRC)/Core/HLE/scePauth.cpp \
+ $(SRC)/Core/HLE/sceMeCore.cpp \
+ $(SRC)/Core/HLE/sceSysEvent.cpp \
+ $(SRC)/Core/HLE/sceSysreg.cpp \
$(SRC)/Core/FileSystems/BlobFileSystem.cpp \
$(SRC)/Core/FileSystems/BlockDevices.cpp \
$(SRC)/Core/FileSystems/ISOFileSystem.cpp \
diff --git a/headless/Headless.cpp b/headless/Headless.cpp
index 293878d5112f..3cc0270c902e 100644
--- a/headless/Headless.cpp
+++ b/headless/Headless.cpp
@@ -367,6 +367,7 @@ int main(int argc, const char* argv[])
const char *stateToLoad = 0;
GPUCore gpuCore = GPUCORE_SOFTWARE;
CPUCore cpuCore = CPUCore::JIT;
+ int meCpuCore = -1;
int debuggerPort = -1;
bool oldAtrac = false;
bool outputDebugStringLog = false;
@@ -403,6 +404,8 @@ int main(int argc, const char* argv[])
cpuCore = CPUCore::JIT_IR;
else if (!strcmp(argv[i], "--ir"))
cpuCore = CPUCore::IR_INTERPRETER;
+ else if (!strncmp(argv[i], "--me-core=", strlen("--me-core=")) && strlen(argv[i]) > strlen("--me-core="))
+ meCpuCore = atoi(argv[i] + strlen("--me-core="));
else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--compare"))
testOptions.compare = true;
else if (!strcmp(argv[i], "--bench"))
@@ -517,6 +520,8 @@ int main(int argc, const char* argv[])
// we actually set the cpu core in CoreParameter above. Probably because we end up using the JIT vs non-JIT
// vertex decoder.
g_Config.iCpuCore = 0;
+ if (meCpuCore >= 0)
+ g_Config.iMECpuCore = meCpuCore;
// NOTE: In headless mode, we never save the config. This is just for this run.
g_Config.iDumpFileTypes = 0;
diff --git a/libretro/Makefile.common b/libretro/Makefile.common
index 6bdb4effac48..998a16c110d2 100644
--- a/libretro/Makefile.common
+++ b/libretro/Makefile.common
@@ -845,6 +845,9 @@ SOURCES_CXX += \
$(COREDIR)/HLE/sceNp.cpp \
$(COREDIR)/HLE/sceNp2.cpp \
$(COREDIR)/HLE/scePauth.cpp \
+ $(COREDIR)/HLE/sceMeCore.cpp \
+ $(COREDIR)/HLE/sceSysEvent.cpp \
+ $(COREDIR)/HLE/sceSysreg.cpp \
$(COREDIR)/HLE/sceUsbGps.cpp \
$(COREDIR)/HW/BufferQueue.cpp \
$(COREDIR)/HW/Camera.cpp \