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) <[email protected]>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-09-28 15:59:25 -06:00
1 parent acd2738b7a
commit 24b41d3f23
3 files changed
+21 -10

No files matched your search

+14 -8
View File
@@ -150,8 +150,8 @@ template <typename WaitInfoType, typename PauseType>
WaitBeginEndCallbackResult WaitBeginCallback(SceUID threadID, SceUID prevCallbackId, int waitTimer, std::vector<WaitInfoType> &waitingThreads, std::map<SceUID, PauseType> &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.
+5 -1
View File
@@ -566,7 +566,11 @@ static void __KernelMsgPipeEndCallback(SceUID threadID, SceUID prevCallbackId) {
MsgPipe *ko = uid == 0 ? NULL : kernelObjects.Get<MsgPipe>(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;
}
+2 -1
View File
@@ -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;