From 8af7643efae0d8061b2eb6d90e21a8f0f8b41206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 20 Oct 2026 19:23:42 +0200 Subject: [PATCH] ME: sceSysreg_driver's ME reset and clock calls, MMIO copies in sysclib The calls homebrew starts the ME with (1.50's NIDs, and 3.71's for the ME's reset and bus clock) set or clear a bit of the reset or bus clock register the way the firmware's driver does, returning whether it was set. Only those NIDs, so a later firmware's lowio.prx keeps its own exports under --vsh. Kernel modules copy their ME code to the boot page with SysclibForKernel's memcpy, which now goes through MMIO for hardware addresses like the replaced memcpy already did (MMIO::Copy, shared). pspsdk's mebasic, MElib's test, PSP Bunny, snes9xTYL and ProTrekkr's test now start their ME. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/CMakeLists.txt | 2 + Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 ++ Core/HLE/HLETables.cpp | 2 + Core/HLE/ReplaceTables.cpp | 33 +--------- Core/HLE/sceKernelInterrupt.cpp | 13 ++-- Core/HLE/sceSysreg.cpp | 96 +++++++++++++++++++++++++++++ Core/HLE/sceSysreg.h | 20 ++++++ Core/HW/MMIO.cpp | 24 ++++++++ Core/HW/MMIO.h | 4 ++ UWP/CoreUWP/CoreUWP.vcxproj | 2 + UWP/CoreUWP/CoreUWP.vcxproj.filters | 2 + android/jni/Android.mk | 1 + libretro/Makefile.common | 1 + 14 files changed, 174 insertions(+), 34 deletions(-) create mode 100644 Core/HLE/sceSysreg.cpp create mode 100644 Core/HLE/sceSysreg.h diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index 2770aee2dd..556d938586 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -387,6 +387,8 @@ add_library(Core STATIC HLE/PSPThreadContext.h HLE/KUBridge.h HLE/KUBridge.cpp + HLE/sceSysreg.h + HLE/sceSysreg.cpp HLE/Plugins.h HLE/Plugins.cpp HLE/ThreadQueueList.h diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 2348b5a217..fff3741a09 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -474,6 +474,7 @@ + @@ -848,6 +849,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 70a1be4ee9..f3fd104aae 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -673,6 +673,9 @@ HLE\Libraries + + HLE\Libraries + FileSystems @@ -2010,6 +2013,9 @@ HLE\Libraries + + HLE\Libraries + FileSystems diff --git a/Core/HLE/HLETables.cpp b/Core/HLE/HLETables.cpp index 38ef766c76..36b8033ccc 100644 --- a/Core/HLE/HLETables.cpp +++ b/Core/HLE/HLETables.cpp @@ -92,6 +92,7 @@ // #include "sceNp2.h" #include "sceNet_lib.h" #include "sceVideocodec.h" +#include "sceSysreg.h" static const HLEFunction FakeSysCalls[] = { {NID_THREADRETURN, __KernelReturnFromThread, "__KernelReturnFromThread", 'x', ""}, @@ -356,6 +357,7 @@ void RegisterAllModules() { Register_sceMePower_driver(); Register_SystemCtrlForUser(); Register_ExceptionManagerForKernel(); + Register_sceSysreg_driver(); // Not ready to enable this due to apparent softlocks in Patapon 3. // Register_sceNpMatching2(); diff --git a/Core/HLE/ReplaceTables.cpp b/Core/HLE/ReplaceTables.cpp index 7b05326d93..15c9167073 100644 --- a/Core/HLE/ReplaceTables.cpp +++ b/Core/HLE/ReplaceTables.cpp @@ -120,33 +120,6 @@ static int Replace_ceilf(MIPSState *mips) { // Should probably do JIT versions of this, possibly ones that only delegate // large copies to a C function. -// Kernel code copies Media Engine code to the boot page at 0xBFC00000, which the main CPU reaches -// through MMIO. Byte by byte, forward like memmove when the destination is lower. Returns false if -// neither side is MMIO. -static bool CopyThroughMMIO(MIPSState *mips, u32 destPtr, u32 srcPtr, u32 bytes) { - if (!Memory::IsMMIOAccess(destPtr) && !Memory::IsMMIOAccess(srcPtr)) { - return false; - } - const bool backward = destPtr > srcPtr && destPtr < srcPtr + bytes; - for (u32 i = 0; i < bytes; ++i) { - const u32 offset = backward ? bytes - 1 - i : i; - const u32 src = srcPtr + offset; - const u32 dest = destPtr + offset; - u8 value = 0; - if (Memory::IsMMIOAccess(src)) { - value = (u8)MMIO::Read(mips, src, 1); - } else if (Memory::IsValidAddress(src)) { - value = Memory::ReadUnchecked_U8(src); - } - if (Memory::IsMMIOAccess(dest)) { - MMIO::Write(mips, dest, 1, value); - } else if (Memory::IsValidAddress(dest)) { - Memory::WriteUnchecked_U8(value, dest); - } - } - return true; -} - static int Replace_memcpy(MIPSState *mips) { u32 destPtr = PARAM(0); u32 srcPtr = PARAM(1); @@ -163,7 +136,7 @@ static int Replace_memcpy(MIPSState *mips) { } } gpu->NotifyVideoCopy(destPtr, srcPtr, bytes); - if (!skip && bytes != 0 && !CopyThroughMMIO(mips, destPtr, srcPtr, bytes)) { + if (!skip && bytes != 0 && !MMIO::Copy(mips, destPtr, srcPtr, bytes)) { u8 *dst = Memory::GetPointerWriteRangeOrException(destPtr, bytes); const u8 *src = Memory::GetPointerRangeOrException(srcPtr, bytes); @@ -293,7 +266,7 @@ static int Replace_memcpy16(MIPSState *mips) { } } gpu->NotifyVideoCopy(destPtr, srcPtr, bytes); - if (!skip && bytes != 0 && !CopyThroughMMIO(mips, destPtr, srcPtr, bytes)) { + if (!skip && bytes != 0 && !MMIO::Copy(mips, destPtr, srcPtr, bytes)) { u8 *dst = Memory::GetPointerWriteRangeOrException(destPtr, bytes); const u8 *src = Memory::GetPointerRangeOrException(srcPtr, bytes); if (dst && src) { @@ -360,7 +333,7 @@ static int Replace_memmove(MIPSState *mips) { } } gpu->NotifyVideoCopy(destPtr, srcPtr, bytes); - if (!skip && bytes != 0 && !CopyThroughMMIO(mips, destPtr, srcPtr, bytes)) { + if (!skip && bytes != 0 && !MMIO::Copy(mips, destPtr, srcPtr, bytes)) { u8 *dst = Memory::GetPointerWriteRangeOrException(destPtr, bytes); const u8 *src = Memory::GetPointerRangeOrException(srcPtr, bytes); if (dst && src) { diff --git a/Core/HLE/sceKernelInterrupt.cpp b/Core/HLE/sceKernelInterrupt.cpp index 2d85e89970..95db13592f 100644 --- a/Core/HLE/sceKernelInterrupt.cpp +++ b/Core/HLE/sceKernelInterrupt.cpp @@ -31,6 +31,7 @@ #include "Core/HLE/ErrorCodes.h" #include "Core/MIPS/MIPS.h" #include "Core/MIPS/MeCore.h" +#include "Core/HW/MMIO.h" #include "Core/Debugger/MemBlockInfo.h" #include "Core/HLE/sceKernel.h" @@ -812,8 +813,10 @@ const HLEFunction Kernel_Library[] = {0X4AC57943, &WrapI_I, "sceKernelRegisterExitCallback", 'i', "i" }, }; -static u32 sysclib_memcpy(u32 dst, u32 src, u32 size) { - if (Memory::IsValidRange(dst, size) && Memory::IsValidRange(src, size)) { +static u32 sysclib_memcpy(MIPSState *mips, u32 dst, u32 src, u32 size) { + if (MMIO::Copy(mips, dst, src, size)) { + // To or from hardware registers. + } else if (Memory::IsValidRange(dst, size) && Memory::IsValidRange(src, size)) { memcpy(Memory::GetPointerWriteUnchecked(dst), Memory::GetPointerUnchecked(src), size); } if (MemBlockInfoDetailed(size)) { @@ -957,8 +960,10 @@ static int sysclib_strncmp(u32 s1, u32 s2, u32 size) { return hleLogError(Log::sceKernel, 0, "Bad addresses"); } -static u32 sysclib_memmove(u32 dst, u32 src, u32 size) { - if (Memory::IsValidRange(dst, size) && Memory::IsValidRange(src, size)) { +static u32 sysclib_memmove(MIPSState *mips, u32 dst, u32 src, u32 size) { + if (MMIO::Copy(mips, dst, src, size)) { + // To or from hardware registers. + } else if (Memory::IsValidRange(dst, size) && Memory::IsValidRange(src, size)) { memmove(Memory::GetPointerWriteUnchecked(dst), Memory::GetPointerUnchecked(src), size); } if (MemBlockInfoDetailed(size)) { diff --git a/Core/HLE/sceSysreg.cpp b/Core/HLE/sceSysreg.cpp new file mode 100644 index 0000000000..ba639bcdb1 --- /dev/null +++ b/Core/HLE/sceSysreg.cpp @@ -0,0 +1,96 @@ +// 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/. + +// The parts of the firmware's system register driver homebrew uses to start the Media Engine. Each +// sets or clears a bit of a register the way the driver does (lowio.prx 6.61 at 080001b8 and +// 080004b8, sysreg.prx 1.50 the same), returning whether it was set. Only the NIDs of the firmware +// versions homebrew was built for (1.50, and 3.71 and later's for the ME calls), so the VSH's own +// later lowio.prx keeps its exports. + +#include "Common/CommonTypes.h" +#include "Core/HLE/HLE.h" +#include "Core/HLE/FunctionWrappers.h" +#include "Core/HLE/sceSysreg.h" +#include "Core/HW/MMIO.h" +#include "Core/MIPS/MIPS.h" + +enum : u32 { + SYSREG_RESET = 0xBC10004C, + SYSREG_BUS_CLOCK = 0xBC100050, + + RESET_ME = 0x04, + RESET_VME = 0x10, + RESET_AVC = 0x20, + BUS_CLOCK_ME = 0x01, +}; + +static int SetSysregBit(MIPSState *mips, u32 reg, u32 bit, bool set) { + const u32 old = MMIO::Read(mips, reg, 4); + MMIO::Write(mips, reg, 4, set ? (old | bit) : (old & ~bit)); + return (old & bit) ? 1 : 0; +} + +static int sceSysregMeResetEnable(MIPSState *mips) { + return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_RESET, RESET_ME, true)); +} + +static int sceSysregMeResetDisable(MIPSState *mips) { + return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_RESET, RESET_ME, false)); +} + +static int sceSysregMeBusClockEnable(MIPSState *mips) { + return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_BUS_CLOCK, BUS_CLOCK_ME, true)); +} + +static int sceSysregMeBusClockDisable(MIPSState *mips) { + return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_BUS_CLOCK, BUS_CLOCK_ME, false)); +} + +static int sceSysregVmeResetEnable(MIPSState *mips) { + return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_RESET, RESET_VME, true)); +} + +static int sceSysregVmeResetDisable(MIPSState *mips) { + return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_RESET, RESET_VME, false)); +} + +static int sceSysregAvcResetEnable(MIPSState *mips) { + return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_RESET, RESET_AVC, true)); +} + +static int sceSysregAvcResetDisable(MIPSState *mips) { + return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_RESET, RESET_AVC, false)); +} + +const HLEFunction sceSysreg_driver[] = { + {0xDE59DACB, &WrapI_V, "sceSysregMeResetEnable", 'i', "", HLE_KERNEL_SYSCALL }, + {0x2DB0EB28, &WrapI_V, "sceSysregMeResetDisable", 'i', "", HLE_KERNEL_SYSCALL }, + {0x44F6CDA7, &WrapI_V, "sceSysregMeBusClockEnable", 'i', "", HLE_KERNEL_SYSCALL }, + {0x158AD4FC, &WrapI_V, "sceSysregMeBusClockDisable", 'i', "", HLE_KERNEL_SYSCALL }, + {0xD20581EA, &WrapI_V, "sceSysregVmeResetEnable", 'i', "", HLE_KERNEL_SYSCALL }, + {0x7558064A, &WrapI_V, "sceSysregVmeResetDisable", 'i', "", HLE_KERNEL_SYSCALL }, + {0x9BB70D34, &WrapI_V, "sceSysregAvcResetEnable", 'i', "", HLE_KERNEL_SYSCALL }, + {0xFD6C562B, &WrapI_V, "sceSysregAvcResetDisable", 'i', "", HLE_KERNEL_SYSCALL }, + // 3.71 and later. + {0xA9997109, &WrapI_V, "sceSysregMeResetEnable371", 'i', "", HLE_KERNEL_SYSCALL }, + {0x76220E94, &WrapI_V, "sceSysregMeResetDisable371", 'i', "", HLE_KERNEL_SYSCALL }, + {0x3199CF1C, &WrapI_V, "sceSysregMeBusClockEnable371", 'i', "", HLE_KERNEL_SYSCALL }, +}; + +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 0000000000..053de1e933 --- /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/HW/MMIO.cpp b/Core/HW/MMIO.cpp index cad553324f..4e976d3e87 100644 --- a/Core/HW/MMIO.cpp +++ b/Core/HW/MMIO.cpp @@ -108,6 +108,30 @@ void Shutdown() { g_devices.clear(); } +bool Copy(MIPSState *mips, u32 destPtr, u32 srcPtr, u32 bytes) { + if (!Memory::IsMMIOAccess(destPtr) && !Memory::IsMMIOAccess(srcPtr)) { + return false; + } + const bool backward = destPtr > srcPtr && destPtr < srcPtr + bytes; + for (u32 i = 0; i < bytes; ++i) { + const u32 offset = backward ? bytes - 1 - i : i; + const u32 src = srcPtr + offset; + const u32 dest = destPtr + offset; + u8 value = 0; + if (Memory::IsMMIOAccess(src)) { + value = (u8)Read(mips, src, 1); + } else if (Memory::IsValidAddress(src)) { + value = Memory::ReadUnchecked_U8(src); + } + if (Memory::IsMMIOAccess(dest)) { + Write(mips, dest, 1, value); + } else if (Memory::IsValidAddress(dest)) { + Memory::WriteUnchecked_U8(value, dest); + } + } + return true; +} + void DoState(PointerWrap &p) { auto s = p.Section("MMIO", 1, 2); if (!s) diff --git a/Core/HW/MMIO.h b/Core/HW/MMIO.h index 950f9bc3b5..97e1d5f8a4 100644 --- a/Core/HW/MMIO.h +++ b/Core/HW/MMIO.h @@ -65,5 +65,9 @@ void DoState(PointerWrap &p); // or 4. An access no device handles is logged, and reads as UNHANDLED_READ. u32 Read(MIPSState *mips, u32 address, int size); void Write(MIPSState *mips, u32 address, int size, u32 value); +// A memory copy where either end may be hardware registers: kernel code copies Media Engine code +// to the boot page at 0xBFC00000, which the main CPU reaches through MMIO. Byte by byte, and +// backward where memmove would be. Returns false, having done nothing, if neither end is MMIO. +bool Copy(MIPSState *mips, u32 destPtr, u32 srcPtr, u32 bytes); } // namespace MMIO diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index a631a7493e..ea06a733ed 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -179,6 +179,7 @@ + @@ -478,6 +479,7 @@ + diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index 530f196a30..8f36ee64f4 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -87,6 +87,7 @@ + @@ -504,6 +505,7 @@ + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 4554ccc1f4..5a7db3140c 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -748,6 +748,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/HLE/ReplaceTables.cpp \ $(SRC)/Core/HLE/HLE.cpp \ $(SRC)/Core/HLE/KUBridge.cpp \ + $(SRC)/Core/HLE/sceSysreg.cpp \ $(SRC)/Core/HLE/NetInetConstants.cpp \ $(SRC)/Core/HLE/SocketManager.cpp \ $(SRC)/Core/HLE/Plugins.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 4ef0d59edd..a65a8f875c 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -755,6 +755,7 @@ SOURCES_CXX += \ $(COREDIR)/Font/PGF.cpp \ $(COREDIR)/HLE/HLE.cpp \ $(COREDIR)/HLE/KUBridge.cpp \ + $(COREDIR)/HLE/sceSysreg.cpp \ $(COREDIR)/HLE/NetInetConstants.cpp \ $(COREDIR)/HLE/SocketManager.cpp \ $(COREDIR)/HLE/Plugins.cpp \