From ed5c05fb28ccea13dd0c352f4260827cb903c70c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 13 Aug 2026 22:42:51 +0200 Subject: [PATCH] VSH boot: load real kd/ kernel driver modules Extends LoadAndStartVshKernelModules() to load the 11 real kd/*.prx kernel drivers for --vsh (dmacman, systimer, memlmd_01g, loadexec_01g, lowio, idstorage, syscon, rtc, wlan, wlanfirm_01g, utility), ahead of the existing 4 VSH-specific modules. Only active when g_runningVSH, no effect on normal game boot. Improve implementations of sceKernelSm1ReferOperations and sceKernelIsIntrContext. Add some more MMIO stubs (GPIO, SYSCON). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9 (cherry picked from commit 7f3168b7df85e47438900016c9ee7d7ef01a0a28) --- Core/CMakeLists.txt | 2 + Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 +++ Core/HLE/HLETables.cpp | 18 +++++++- Core/HLE/sceKernelInterrupt.cpp | 5 +++ Core/HLE/sceKernelModule.cpp | 21 +++++++++ Core/HW/GpioMMIO.cpp | 58 ++++++++++++++++++++++++ Core/HW/GpioMMIO.h | 69 +++++++++++++++++++++++++++++ Core/MIPS/Interpreter.cpp | 14 ++++++ UWP/CoreUWP/CoreUWP.vcxproj | 2 + UWP/CoreUWP/CoreUWP.vcxproj.filters | 2 + android/jni/Android.mk | 1 + libretro/Makefile.common | 1 + 13 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 Core/HW/GpioMMIO.cpp create mode 100644 Core/HW/GpioMMIO.h diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index 7eafa34695..316cec432c 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -611,6 +611,8 @@ add_library(Core STATIC HW/Camera.h HW/Display.cpp HW/Display.h + HW/GpioMMIO.cpp + HW/GpioMMIO.h HW/MediaEngine.cpp HW/MediaEngine.h HW/MpegDemux.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 322278e8fc..574123cdad 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -492,6 +492,7 @@ + @@ -1042,6 +1043,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index da1dbed56d..7b7bf9c5cf 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1207,6 +1207,9 @@ HW + + HW + HLE\Libraries @@ -2169,6 +2172,9 @@ HW + + HW + HLE\Libraries diff --git a/Core/HLE/HLETables.cpp b/Core/HLE/HLETables.cpp index 679b8ff2cd..03b4318dfe 100644 --- a/Core/HLE/HLETables.cpp +++ b/Core/HLE/HLETables.cpp @@ -164,6 +164,21 @@ static const HLEFunction LoadCoreForKernel[] = { {0XB95FA50D, nullptr, "LoadCoreForKernel_B95FA50D", '?', "" }, }; + +// sceKernelSm1ReferOperations() returns a pointer to a driver-registered "SM1 operations" +// table (set up via the sibling sceKernelSm1RegisterOperations(), also unimplemented here), +// or NULL if nothing has registered one. Real callers (e.g. kd/lowio.prx) only check the +// return value against exactly zero before dereferencing it as a struct/vtable pointer - +// leaving this as a generic `nullptr` HLE table entry meant it fell through to the "function +// unimplemented" fallback, which returns SCE_KERNEL_ERROR_LIBRARY_NOT_YET_LINKED +// (0x8002013A) in v0 instead of 0. That error code isn't zero, so the caller's null check +// didn't catch it, and it went on to call through a function pointer read from an offset +// within that "structure" - really an unrelated error code - causing a wild read/jump. Since +// nothing currently registers real SM1 operations, returning NULL here is correct. +static u32 sceKernelSm1ReferOperations() { + return hleLogDebug(Log::sceKernel, 0); +} + static const HLEFunction KDebugForKernel[] = { {0XE7A3874D, nullptr, "sceKernelRegisterAssertHandler", '?', "" }, {0X2FF4E9F9, nullptr, "sceKernelAssert", '?', "" }, @@ -182,7 +197,7 @@ static const HLEFunction KDebugForKernel[] = { {0X5282DD5E, nullptr, "sceKernelDipswSet", '?', "" }, {0X9F8703E4, nullptr, "sceKernelDipswCpTime", '?', "" }, {0X333DCEC7, nullptr, "sceKernelSm1RegisterOperations", '?', "" }, - {0XE892D9A1, nullptr, "sceKernelSm1ReferOperations", '?', "" }, + {0XE892D9A1, &WrapU_V, "sceKernelSm1ReferOperations", 'x', "" }, {0XA126F497, nullptr, "KDebugForKernel_A126F497", '?', "" }, {0XB7251823, nullptr, "sceKernelAcceptMbogoSig", '?', "" }, }; @@ -322,4 +337,3 @@ void RegisterAllModules() { // Not ready to enable this due to apparent softlocks in Patapon 3. // Register_sceNpMatching2(); } - diff --git a/Core/HLE/sceKernelInterrupt.cpp b/Core/HLE/sceKernelInterrupt.cpp index 0f5f707c3c..31e4f00263 100644 --- a/Core/HLE/sceKernelInterrupt.cpp +++ b/Core/HLE/sceKernelInterrupt.cpp @@ -1048,6 +1048,10 @@ void Register_InterruptManager() } +static int sceKernelIsIntrContext() { + return hleLogDebug(Log::sceKernel, __IsInInterrupt() ? 1 : 0); +} + const HLEFunction InterruptManagerForKernel[] = { {0x092968F4, &WrapI_V, "sceKernelCpuSuspendIntr", 'i', "" ,HLE_KERNEL_SYSCALL }, @@ -1068,6 +1072,7 @@ 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 }, + {0XFE28C6D9, &WrapI_V, "sceKernelIsIntrContext", 'i', "" ,HLE_KERNEL_SYSCALL }, }; void Register_InterruptManagerForKernel() diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index a880d4cb5f..27f149c80a 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -1126,8 +1126,29 @@ static bool ShouldHLEModuleForLoad(std::string_view modname, bool *wasDisabledMa // started as part of the kernel's own boot sequence, before any user module runs (matches // JPCSP's moduleFileNamesToBeLoaded/moduleFileNamesVshOnly). We don't emulate that sequence, // so approximate it here: load and start them ourselves right before starting the VSH itself. +// +// The first 11 paths mirror JPCSP's own HLEModuleManager module list for a --vsh boot (see +// docs/VSHBootInvestigation.md, Attempt 17) - confirmed via JPCSP's own log that it *actually +// loads and interprets these as real PRX code* (not a Java-side HLE shortcut): it uses the +// exact same generic hleKernelLoadAndStartModule() path used for any real module, and loading +// lowio.prx/wlan.prx/memlmd_01g.prx specifically flips JPCSP into full LLE CPU emulation +// (RuntimeContextLLE) for the rest of the boot. PPSSPP has no equivalent LLE-mode switch - +// these just load and run through the normal interpreter/JIT like any other PRX, same as the +// existing 4 VSH-specific modules below. Order matches JPCSP's load order exactly, in case +// later modules depend on earlier ones having already initialized. static void LoadAndStartVshKernelModules() { static const char *const vshKernelModulePaths[] = { + "flash0:/kd/dmacman.prx", + "flash0:/kd/systimer.prx", + "flash0:/kd/memlmd_01g.prx", + "flash0:/kd/loadexec_01g.prx", + "flash0:/kd/lowio.prx", + "flash0:/kd/idstorage.prx", + "flash0:/kd/syscon.prx", + "flash0:/kd/rtc.prx", + "flash0:/kd/wlan.prx", + "flash0:/kd/wlanfirm_01g.prx", + "flash0:/kd/utility.prx", "flash0:/kd/vshbridge.prx", "flash0:/vsh/module/paf.prx", "flash0:/vsh/module/common_gui.prx", diff --git a/Core/HW/GpioMMIO.cpp b/Core/HW/GpioMMIO.cpp new file mode 100644 index 0000000000..94f987c8be --- /dev/null +++ b/Core/HW/GpioMMIO.cpp @@ -0,0 +1,58 @@ +// 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/HW/GpioMMIO.h" + +namespace GpioMMIO { + +namespace { + +u32 g_regs[SIZE / 4]; + +} // namespace + +u32 Read32(u32 address) { + u32 offset = address - BASE_ADDRESS; + return g_regs[offset / 4]; +} + +void Write32(u32 address, u32 value) { + u32 offset = address - BASE_ADDRESS; + g_regs[offset / 4] = value; +} + +} // namespace GpioMMIO + +namespace SysconSerialMMIO { + +namespace { + +u32 g_regs[SIZE / 4]; + +} // namespace + +u32 Read32(u32 address) { + u32 offset = address - BASE_ADDRESS; + return g_regs[offset / 4]; +} + +void Write32(u32 address, u32 value) { + u32 offset = address - BASE_ADDRESS; + g_regs[offset / 4] = value; +} + +} // namespace SysconSerialMMIO diff --git a/Core/HW/GpioMMIO.h b/Core/HW/GpioMMIO.h new file mode 100644 index 0000000000..fdf4271366 --- /dev/null +++ b/Core/HW/GpioMMIO.h @@ -0,0 +1,69 @@ +// 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/. + +// MMIO model of two small PSP hardware register blocks used by kernel-mode code that pokes +// them directly - grouped in one file since uofw documents both together (src/debug/syscon.c +// touches both while implementing the syscon comms protocol), and both are currently just +// plain read/write-back storage defaulting to zero, not real modeled hardware behavior. +// +// GpioMMIO: the GPIO controller (flash0:/kd/lowio.prx's sceGpio_driver pokes this). Base +// address and a couple of register offsets (0x000 port output, 0x040 port direction) +// confirmed against uofw's src/kd/lowio/gpio.c. Everything else - notably offset 0x048, which +// lowio.prx's own initialization polls waiting for bits 0-1 to clear (an interrupt-pending/ +// busy-style status register, exact semantics not reverse-engineered) - defaults to zero, +// which is enough to satisfy that poll rather than spinning forever against the generic +// unknown-MMIO poison value (whose bit pattern happens to fail this kind of "wait for a +// status bit to clear" check). See docs/VSHBootInvestigation.md, Attempt 17. +// +// SysconSerialMMIO: the syscon co-processor's serial/UART-style comms interface (offset 0x008 +// TX/RX data, 0x00C TX/RX status - bit 2 of status is "RX data available" per uofw). Real +// flash0:/kd/syscon.prx polls status waiting for bits to clear the same way lowio.prx does for +// GPIO; zero-by-default satisfies it. No actual serial protocol is modeled - real Syscon +// communication (battery/power/RTC-alarm state, etc.) would need much more than this to work +// correctly, this only prevents boot from hanging on the initial handshake. + +#pragma once + +#include "Common/CommonTypes.h" + +namespace GpioMMIO { + +constexpr u32 BASE_ADDRESS = 0xBE240000; +constexpr u32 SIZE = 0x100; + +inline bool IsGpioAddress(u32 address) { + return address >= BASE_ADDRESS && address < BASE_ADDRESS + SIZE; +} + +u32 Read32(u32 address); +void Write32(u32 address, u32 value); + +} // namespace GpioMMIO + +namespace SysconSerialMMIO { + +constexpr u32 BASE_ADDRESS = 0xBE580000; +constexpr u32 SIZE = 0x100; + +inline bool IsSysconSerialAddress(u32 address) { + return address >= BASE_ADDRESS && address < BASE_ADDRESS + SIZE; +} + +u32 Read32(u32 address); +void Write32(u32 address, u32 value); + +} // namespace SysconSerialMMIO diff --git a/Core/MIPS/Interpreter.cpp b/Core/MIPS/Interpreter.cpp index 5b1d941407..784e9e2f39 100644 --- a/Core/MIPS/Interpreter.cpp +++ b/Core/MIPS/Interpreter.cpp @@ -35,6 +35,7 @@ #include "Core/HLE/HLE.h" #include "Core/HLE/HLETables.h" #include "Core/HLE/ReplaceTables.h" +#include "Core/HW/GpioMMIO.h" #define R(i) (mips->r[i]) #define F(i) (mips->f[i]) @@ -122,6 +123,12 @@ static u32 ReadMMIO_U32(MIPSState *mips, u32 addr) { Core_MemoryException(addr, 4, mips->pc, MemoryExceptionType::READ_WORD, "Kernel mode only"); return (u8)UNKNOWN_MMIO_POISON; } + if (GpioMMIO::IsGpioAddress(addr)) { + return GpioMMIO::Read32(addr); + } + if (SysconSerialMMIO::IsSysconSerialAddress(addr)) { + return SysconSerialMMIO::Read32(addr); + } WARN_LOG(Log::CPU, "MMIO Read32 at %08x", addr); return UNKNOWN_MMIO_POISON; } @@ -145,6 +152,13 @@ void WriteMMIO_U16(MIPSState *mips, u32 addr, u16 value) { void WriteMMIO_U32(MIPSState *mips, u32 addr, u32 value) { if (!Memory::IsKernelCodeAddress(mips->pc)) { Core_MemoryException(addr, 4, mips->pc, MemoryExceptionType::WRITE_WORD, "Kernel mode only"); + } + if (GpioMMIO::IsGpioAddress(addr)) { + GpioMMIO::Write32(addr, value); + return; + } + if (SysconSerialMMIO::IsSysconSerialAddress(addr)) { + SysconSerialMMIO::Write32(addr, value); return; } WARN_LOG(Log::CPU, "MMIO Write32 at %08x = %08x", addr, value); diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index e4b1fd7c91..0bf4da7707 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -254,6 +254,7 @@ + @@ -531,6 +532,7 @@ + diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index 1cc8c3d205..7b40a69b42 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -162,6 +162,7 @@ + @@ -588,6 +589,7 @@ + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 8bd3e6952d..6467be4647 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -609,6 +609,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/HW/BufferQueue.cpp \ $(SRC)/Core/HW/Camera.cpp \ $(SRC)/Core/HW/Display.cpp \ + $(SRC)/Core/HW/GpioMMIO.cpp \ $(SRC)/Core/HW/MemoryStick.cpp \ $(SRC)/Core/HW/MpegDemux.cpp.arm \ $(SRC)/Core/HW/MediaEngine.cpp.arm \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index c88a3d0235..69f4c31415 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -841,6 +841,7 @@ SOURCES_CXX += \ $(COREDIR)/HW/BufferQueue.cpp \ $(COREDIR)/HW/Camera.cpp \ $(COREDIR)/HW/Display.cpp \ + $(COREDIR)/HW/GpioMMIO.cpp \ $(COREDIR)/HW/SimpleAudioDec.cpp \ $(COREDIR)/HW/Atrac3Standalone.cpp \ $(COREDIR)/HW/AsyncIOManager.cpp \