From 5f2e4bd4161262cb002a876cbff3db3426810538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 20 Oct 2026 13:14:12 +0200 Subject: [PATCH] Kernel: Implement sceKernelRegisterIntrHandler and friends sceKernelRegisterIntrHandler, sceKernelReleaseIntrHandler, sceKernelEnableIntr and sceKernelDisableIntr now keep a base handler per interrupt, and return what tests/intr/kernel/intrhandler recorded on a PSP: 80020065 for an interrupt number out of range (or no handler), 80020067 for a second handler, 80020068 for one that isn't there. Faking success for these used to stall the VSH boot, which is why they were left unresolved. It doesn't anymore: the firmware's drivers make the same 31 calls (interrupts 4, 12, 15-18, 20-24 and 31), and 6.61 reaches the XMB on the JIT and the interpreter. Nothing raises those interrupts yet, so the handlers are only kept, and saved in savestates (IntrHandler section version 2). Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/sceKernelInterrupt.cpp | 63 +++++++++++++++++++++++++++------ Core/HLE/sceKernelInterrupt.h | 12 +++++++ pspautotests | 2 +- test.py | 1 + 4 files changed, 67 insertions(+), 11 deletions(-) diff --git a/Core/HLE/sceKernelInterrupt.cpp b/Core/HLE/sceKernelInterrupt.cpp index b76c09e727..f901223fb3 100644 --- a/Core/HLE/sceKernelInterrupt.cpp +++ b/Core/HLE/sceKernelInterrupt.cpp @@ -191,6 +191,7 @@ SubIntrHandler* IntrHandler::get(int subIntrNum) void IntrHandler::clear() { subIntrHandlers.clear(); + setBaseHandler(0, 0); } int IntrHandler::queueUp(int subintr) { @@ -212,12 +213,19 @@ int IntrHandler::queueUp(int subintr) { void IntrHandler::DoState(PointerWrap &p) { - auto s = p.Section("IntrHandler", 1); + auto s = p.Section("IntrHandler", 1, 2); if (!s) return; Do(p, intrNumber); Do(p, subIntrHandlers); + if (s >= 2) { + Do(p, baseHandler_); + Do(p, baseArg_); + Do(p, baseEnabled_); + } else { + setBaseHandler(0, 0); + } if (p.mode == p.MODE_READ && (intrNumber < 0 || intrNumber >= PSP_NUMBER_INTERRUPTS)) { ERROR_LOG(Log::sceIntc, "Savestate failure: invalid interrupt handler %d", intrNumber); p.SetError(p.ERROR_FAILURE); @@ -1039,6 +1047,46 @@ void Register_InterruptManager() } +// What these return is from tests/intr/kernel/intrhandler, recorded on a PSP. Interrupts there go up to +// 67, one more than PSP_NUMBER_INTERRUPTS, which the firmware's drivers use anyway. +static int sceKernelRegisterIntrHandler(int intrNumber, u32 unknown, u32 handler, u32 handlerArg, u32 opt) { + if (intrNumber < 0 || intrNumber >= PSP_NUMBER_INTERRUPTS || handler == 0) + return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE); + IntrHandler *intr = intrHandlers[intrNumber]; + if (intr->hasBaseHandler()) + return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_FOUND_HANDLER); + intr->setBaseHandler(handler, handlerArg); + return hleLogDebug(Log::sceIntc, 0); +} + +static int sceKernelReleaseIntrHandler(int intrNumber) { + if (intrNumber < 0 || intrNumber >= PSP_NUMBER_INTERRUPTS) + return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE); + IntrHandler *intr = intrHandlers[intrNumber]; + if (!intr->hasBaseHandler()) + return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_NOTFOUND_HANDLER); + intr->setBaseHandler(0, 0); + return hleLogDebug(Log::sceIntc, 0); +} + +static int SetIntrEnabled(int intrNumber, bool enable) { + if (intrNumber < 0 || intrNumber >= PSP_NUMBER_INTERRUPTS) + return SCE_KERNEL_ERROR_ILLEGAL_INTRCODE; + IntrHandler *intr = intrHandlers[intrNumber]; + if (!intr->hasBaseHandler()) + return SCE_KERNEL_ERROR_NOTFOUND_HANDLER; + intr->enableBase(enable); + return 0; +} + +static int sceKernelEnableIntr(int intrNumber) { + return hleLogDebugOrError(Log::sceIntc, SetIntrEnabled(intrNumber, true)); +} + +static int sceKernelDisableIntr(int intrNumber) { + return hleLogDebugOrError(Log::sceIntc, SetIntrEnabled(intrNumber, false)); +} + static int sceKernelIsIntrContext() { return hleLogDebug(Log::sceKernel, __IsInInterrupt() ? 1 : 0); } @@ -1065,15 +1113,10 @@ const HLEFunction InterruptManagerForKernel[] = {0X05572A5F, &WrapV_V, "sceKernelExitGame", 'v', "" ,HLE_KERNEL_SYSCALL }, {0X4AC57943, &WrapI_I, "sceKernelRegisterExitCallback", 'i', "i" ,HLE_KERNEL_SYSCALL }, {0XFE28C6D9, &WrapI_V, "sceKernelIsIntrContext", 'i', "" ,HLE_KERNEL_SYSCALL }, - // NOT added on purpose, even though JPCSP implements all four: sceKernelRegisterIntrHandler - // (0x58DD8978), sceKernelReleaseIntrHandler (0xF987B1F0), sceKernelEnableIntr (0x4D6E7305) - // and sceKernelDisableIntr (0xD774BA45). JPCSP can honour them because it emulates the - // interrupt controller as MMIO; we dispatch the few interrupts we emulate ourselves (see - // __RegisterIntrHandler and its callers in sceGe/sceKernelAlarm/sceKernelVTimer) and have no - // way to run a guest handler for one. Stubbing them to return success is therefore a lie the - // real flash0 drivers act on - measured while booting the VSH, they make 31 such calls - // (interrupts 4, 12, 15-18, 20-24, 31), and the boot then stalls in GE list execution without - // ever starting a plugin module, where leaving them unresolved reaches the shell. + {0X58DD8978, &WrapI_IUUUU, "sceKernelRegisterIntrHandler", 'i', "ixxxx", HLE_KERNEL_SYSCALL }, + {0XF987B1F0, &WrapI_I, "sceKernelReleaseIntrHandler", 'i', "i" ,HLE_KERNEL_SYSCALL }, + {0X4D6E7305, &WrapI_I, "sceKernelEnableIntr", 'i', "i" ,HLE_KERNEL_SYSCALL }, + {0XD774BA45, &WrapI_I, "sceKernelDisableIntr", 'i', "i" ,HLE_KERNEL_SYSCALL }, }; void Register_InterruptManagerForKernel() diff --git a/Core/HLE/sceKernelInterrupt.h b/Core/HLE/sceKernelInterrupt.h index 339eae4c2b..3a5dcd0df0 100644 --- a/Core/HLE/sceKernelInterrupt.h +++ b/Core/HLE/sceKernelInterrupt.h @@ -129,12 +129,24 @@ public: SubIntrHandler *get(int subIntrNum); void clear(); + // The handler a kernel driver registered for the whole interrupt (sceKernelRegisterIntrHandler). + // Nothing raises those interrupts yet, so it's only kept. + bool hasBaseHandler() const { return baseHandler_ != 0; } + void setBaseHandler(u32 handler, u32 arg) { + baseHandler_ = handler; + baseArg_ = arg; + baseEnabled_ = false; + } + void enableBase(bool enable) { baseEnabled_ = enable; } void DoState(PointerWrap &p); private: int intrNumber; std::map subIntrHandlers; + u32 baseHandler_ = 0; + u32 baseArg_ = 0; + bool baseEnabled_ = false; int entryCycles_ = 0; int exitCycles_ = 0; }; diff --git a/pspautotests b/pspautotests index aa60f4bfec..3a6a39c58c 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit aa60f4bfecacb1abe8fcbe71e6ead1b6a3de5745 +Subproject commit 3a6a39c58cb5dc5ef59ea46f90f78172ba6c5d08 diff --git a/test.py b/test.py index 4c93dbcc48..877d5ca75a 100755 --- a/test.py +++ b/test.py @@ -321,6 +321,7 @@ tests_good = [ "hash/sha1ctx", "hle/check_not_used_uids", "intr/intr", + "intr/kernel/intrhandler", "intr/mfic", "intr/enablesub", "intr/registersub",