From 24b41d3f23697c7338191205441f917d88df838e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 28 Sep 2026 15:10:50 -0600 Subject: [PATCH] Kernel waits: Fix VPL/FPL and msgpipe waits around callbacks, report timeout left Verified against pspautotests threads/callbacks/waittypes: - __KernelThreadingInit() cleared the wait type callback table after __KernelMemoryInit() had registered VPL and FPL in it, so a VPL or FPL wait interrupted by a callback was never paused or resumed, and could hang forever. - A msgpipe deleted during a callback left its waiter waiting, instead of waking it with WAIT_DELETE. - A wait that got its object during a callback reported no time left; put the timer back before trying to unlock, so the unlock writes what remains. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/KernelWaitHelpers.h | 22 ++++++++++++++-------- Core/HLE/sceKernelMsgPipe.cpp | 6 +++++- Core/HLE/sceKernelThread.cpp | 3 ++- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Core/HLE/KernelWaitHelpers.h b/Core/HLE/KernelWaitHelpers.h index 9aed035147..196ee10060 100644 --- a/Core/HLE/KernelWaitHelpers.h +++ b/Core/HLE/KernelWaitHelpers.h @@ -150,8 +150,8 @@ template WaitBeginEndCallbackResult WaitBeginCallback(SceUID threadID, SceUID prevCallbackId, int waitTimer, std::vector &waitingThreads, std::map &pausedWaits, bool doTimeout = true) { SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId; - // This means two callbacks in a row. PSP crashes if the same callback waits inside itself (may need more testing.) - // TODO: Handle this better? + // Shouldn't happen: each nesting level pauses under its own key, and on hardware a callback can + // nest only one level (a CB wait that would go deeper never returns.) if (pausedWaits.find(pauseKey) != pausedWaits.end()) { return WAIT_CB_SUCCESS; } @@ -222,6 +222,14 @@ WaitBeginEndCallbackResult WaitEndCallback(SceUID threadID, SceUID prevCallbackI // TODO: Don't wake up if __KernelCurHasReadyCallbacks()? + // The timeout kept running during the callback. Put the timer back first, so that an unlock + // reports the time that's left. + s64 cyclesLeft = waitDeadline - CoreTiming::GetTicks(currentMIPS); + const bool hasTimer = timeoutPtr != 0 && waitTimer != -1 && waitDeadline != 0; + if (hasTimer) { + CoreTiming::ScheduleEvent(cyclesLeft < 0 ? 0 : cyclesLeft, waitTimer, threadID); + } + bool wokeThreads; // Attempt to unlock. if (TryUnlock(ko, waitData, error, 0, wokeThreads)) { @@ -229,20 +237,18 @@ WaitBeginEndCallbackResult WaitEndCallback(SceUID threadID, SceUID prevCallbackI } // We only check if it timed out if it couldn't unlock. - s64 cyclesLeft = waitDeadline - CoreTiming::GetTicks(currentMIPS); if (cyclesLeft < 0 && waitDeadline != 0) { + if (hasTimer) { + CoreTiming::UnscheduleEvent(waitTimer, threadID); + } if (timeoutPtr != 0 && waitTimer != -1) { Memory::WriteOrException_U32(0, timeoutPtr); } __KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_TIMEOUT); return WAIT_CB_TIMED_OUT; - } else { - if (timeoutPtr != 0 && waitTimer != -1) { - CoreTiming::ScheduleEvent(cyclesLeft, waitTimer, __KernelGetCurThread()); - } - return WAIT_CB_RESUMED_WAIT; } + return WAIT_CB_RESUMED_WAIT; } // Meant to be called in a registered end callback function for a wait type. diff --git a/Core/HLE/sceKernelMsgPipe.cpp b/Core/HLE/sceKernelMsgPipe.cpp index 47110b2752..82a2f2108d 100644 --- a/Core/HLE/sceKernelMsgPipe.cpp +++ b/Core/HLE/sceKernelMsgPipe.cpp @@ -566,7 +566,11 @@ static void __KernelMsgPipeEndCallback(SceUID threadID, SceUID prevCallbackId) { MsgPipe *ko = uid == 0 ? NULL : kernelObjects.Get(uid, error); if (ko == NULL) { - ERROR_LOG_REPORT(Log::sceKernel, "__KernelMsgPipeEndCallback: Invalid object"); + // Deleted during the callback. + u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error); + if (timeoutPtr != 0 && waitTimer != -1) + Memory::WriteOrException_U32(0, timeoutPtr); + __KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_DELETE); return; } diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index 0a7636494b..895774e174 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -782,7 +782,8 @@ void __KernelThreadingInit() { u32 blockSize = sizeof(idleThreadCode) + ARRAY_SIZE(threadHacks) * 2 * 4; // The thread code above plus 8 bytes per "hack" dispatchEnabled = true; - memset(waitTypeFuncs, 0, sizeof(waitTypeFuncs)); + // Don't clear waitTypeFuncs here: __KernelMemoryInit() registers VPL and FPL before this runs. + // Every entry is set again by its module's init anyway. __SetCurrentThread(NULL, 0, NULL); g_inCbCount = 0;