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 \