From 0987fdb1697be715059131cf638a79797db80456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 29 Oct 2026 08:58:32 +0100 Subject: [PATCH] VTimers: Don't run a pending timer that was cancelled, stopped or given a new handler The interrupt ran it anyway, and after sceKernelCancelVTimerHandler or sceKernelSetVTimerHandler with no handler that meant jumping to address 0. This happens when one timer's handler cancels another timer that came due together with it, or when a timer comes due while interrupts are suspended. On the PSP none of these run (threads/vtimers/pending/cancelpending). The pending entry becomes an invalid id, which the interrupt pops and skips, as with deleted timers. Co-Authored-By: Claude Opus 5.5 --- Core/HLE/sceKernelVTimer.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Core/HLE/sceKernelVTimer.cpp b/Core/HLE/sceKernelVTimer.cpp index c60525cea0..d4d7f316ec 100644 --- a/Core/HLE/sceKernelVTimer.cpp +++ b/Core/HLE/sceKernelVTimer.cpp @@ -84,6 +84,17 @@ static u64 __getVTimerCurrentTime(VTimer* vt) { return vt->nvt.current + __getVTimerRunningTime(vt); } +// A timer that's cancelled, stopped or given a new handler while its interrupt is pending doesn't run +// (threads/vtimers/pending/cancelpending). The entry stays, as an id that's never valid, for the +// interrupt to pop: see sceKernelDeleteVTimer. +static void __KernelForgetPendingVTimer(SceUID id) { + for (SceUID &pending : vtimers) { + if (pending == id) { + pending = 0; + } + } +} + static int __KernelCancelVTimer(SceUID id) { u32 error; VTimer *vt = kernelObjects.Get(id, error); @@ -92,6 +103,7 @@ static int __KernelCancelVTimer(SceUID id) { } CoreTiming::UnscheduleEvent(vtimerTimer, id); + __KernelForgetPendingVTimer(id); vt->nvt.handlerAddr = 0; return 0; } @@ -146,8 +158,9 @@ public: VTimer *vtimer = kernelObjects.Get(vtimerID, error); if (error) { - // Deleted while its interrupt was pending. handleResult won't run, so it's popped here. - WARN_LOG(Log::sceKernel, "Ignoring deleted vtimer %08x", vtimerID); + // Deleted (or cancelled, stopped or given a new handler) while its interrupt was pending. + // handleResult won't run, so it's popped here. + WARN_LOG(Log::sceKernel, "Ignoring deleted or cancelled vtimer %08x", vtimerID); vtimers.pop_front(); return false; } @@ -384,6 +397,7 @@ static void __stopVTimer(VTimer *vt) { vt->nvt.current = __getVTimerCurrentTime(vt); vt->nvt.active = 0; vt->nvt.base = 0; + __KernelForgetPendingVTimer(vt->GetUID()); } u32 sceKernelStopVTimer(SceUID uid) { @@ -426,6 +440,7 @@ u32 sceKernelSetVTimerHandler(SceUID uid, u32 scheduleAddr, u32 handlerFuncAddr, schedule = Memory::ReadUnchecked_U64(scheduleAddr); } + __KernelForgetPendingVTimer(uid); vt->nvt.handlerAddr = handlerFuncAddr; if (handlerFuncAddr) { vt->nvt.commonAddr = commonAddr; @@ -449,6 +464,7 @@ u32 sceKernelSetVTimerHandlerWide(SceUID uid, u64 schedule, u32 handlerFuncAddr, return hleLogError(Log::sceKernel, error, "bad timer ID"); } + __KernelForgetPendingVTimer(uid); vt->nvt.handlerAddr = handlerFuncAddr; if (handlerFuncAddr) { vt->nvt.commonAddr = commonAddr;