When the new thread outranks the caller, the firmware switches to it
directly, even if a thread of still better priority is ready but hasn't
been dispatched (one that a sceKernelTerminateThread woke, say). Verified
against the new pspautotests threads/threads/termsuspended.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Replaces the global in-callback counter with each thread's own mipscall
chain, so several threads can be inside callbacks at once, and other
threads' callbacks (better priority ones right away) run while one is.
Verified against pspautotests threads/callbacks/otherthread, recursion
and intrnotify:
- A callback nests only one level: a CB wait that would go deeper never
returns on hardware, so the callback is left pending instead.
- A non-CB wait inside a callback no longer runs callbacks because of
the CB wait the callback interrupted.
- Callbacks for a waiting thread are only taken when it beats both the
running thread and every ready one. After an interrupt (which runs on
the idle thread) that's the thread about to resume, not idle.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
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]>
Verified against pspautotests threads/callbacks/delivery:
- Notifying the callback of a better priority thread in a CB wait runs
it right away. Callbacks of other waiting threads stay pending until
those threads would get to run, rather than being taken at any
reschedule, so they can still be counted or canceled.
- sceKernelCancelCallback clears the notify count, not just the arg.
threads/callbacks/cancel, count and umd/wait/wait now pass.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Verified against new pspautotests threads/callbacks/afterwait and nested:
- A thread inside a callback runs its own pending callbacks (even the
same one again) nested, when it enters a CB wait. Waits paused by a
nested callback are keyed by the outer callback's id.
- sceKernelCheckCallback inside a callback returns ILLEGAL_CONTEXT
without running anything.
- sceKernelSleepThreadCB with a queued wakeup runs pending callbacks
before consuming it.
- A thread whose wait ended during a callback keeps the CPU, instead of
queueing behind threads of the same priority.
threads/callbacks/notify now passes too.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
A thread whose wait ends without a context switch (for example when a
callback run from the wait satisfies it) keeps its old waitType. If it
later ran a callback, from sceUmdWaitDriveStatCB with the drive already
ready for instance, the stale wait's begin/end hooks ran, couldn't find
the paused wait, and resumed the thread with SCE_KERNEL_ERROR_WAIT_DELETE,
overwriting the HLE call's return value.
Should fix "sceUmdWaitDriveStatCB: error 0x800201b5" dialog in
Maru Goukaku TOEIC Test Portable (#7576).
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Delete the old HLE mips call actions instead of leaking them or keeping
stale ones, fail the load on an unknown action type instead of crashing,
and derive the exit-callback-pending flag from the loaded state.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
KernelHeap was missing from CreateByIDType, and an unknown type returned
without an error, desyncing the rest of the load. States from before exit
callbacks kept the boot-time action slot, which sceMpeg's restore took over.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
PPSSPP decided whether a caller was privileged with hleIsKernelMode(), which reports whether the
syscall being executed is itself a kernel-only export. That's a different question from the one
the hardware answers: on a PSP the privilege belongs to the calling module, and a kernel module
reaches sceKernelCreateTlspl through the ordinary ThreadManForUser NID like anything else. So a
kernel module asking for partition 1, 3 or 4 got ILLEGAL_PERM where a real PSP hands it over,
which the new threads/tls/kernel/partition test shows directly.
BlockAllocatorFromID now also accepts a caller whose thread belongs to a kernel module, via a new
__KernelCurThreadIsKernelMode(). It checks the thread's own attribute first and then the owning
module, because a kernel module's main thread isn't necessarily flagged kernel - the attribute
comes from PSP_MAIN_THREAD_ATTR, which needn't set it. That mirrors how sceKernelCreateThread
already works out allowKernel.
This only ever widens access, and only for threads belonging to kernel modules, so games are
unaffected - they run in user modules and see exactly what they saw before.
nt.runForClocks was zeroed when a thread was created and copied out by
sceKernelReferThreadStatus, but nothing ever added to it, so every thread
reported having run for zero time forever.
Crazy Taxi: Fare Wars uses it as a liveness check. Its music state machine
samples the mp3 thread's run time once every 60 frames and compares it with
the previous two samples; when it doesn't move it concludes playback is
wedged, sets the stop bit, and the thread tears itself down and exits. The
game restarts it, and about a second later decides it's wedged again - custom
soundtracks restarted roughly once a second forever, whatever the file.
Bill the time since the previous switch to the outgoing thread, which is
exactly the thread that was running for it. The field is already part of the
serialized thread struct, so savestates don't change format; the timestamp
itself is re-based on load rather than saved, and only on load - saving runs
a measure pass and a write pass, and re-basing in those would discard the
time the running thread had accumulated since the last switch, letting a save
change what the game can observe.
Risk: this runs on every context switch, the hottest path in the scheduler.
It adds one CoreTiming read and a 64-bit add. Games that poll thread run
times will now see them move, which is correct but is new behavior.
Co-Authored-By: Claude Opus 5 <[email protected]>
CoreTiming::UnscheduleEvent returns the scheduled time minus the current time,
which is negative when the event is overdue but hasn't been processed yet - the
exact situation when a wait is satisfied right around its own timeout. Only the
semaphore clamped it; everywhere else we wrote (u32)cyclesToUs(negative) into
the game's timeout variable, i.e. a huge bogus "remaining time".
Pulled the shared shape into HLEKernel::WriteRemainingTimeout so it can't drift
apart again - event flags, mbx, fpl, vpl, msgpipe, mutex, lwmutex and semaphore
all go through it now. The two thread-end sites keep their own copy since they
unschedule even when the game passed no timeout pointer, and sceUsb just gets
the clamp.
This is stuff encountered in the VSH boot research.
sceRtc_driver, scePower_driver, sceImpose_driver, ThreadManForKernel funcs,
sceRtcGetAlarmTick, sceHprm_driver/sceHprmReadLatch
sceVshBridge_Driver imports sceKernelResumeDispatchThread, SuspendDispatchThread,
and NotifyCallback from ThreadManForKernel, but they were only registered under
ThreadManForUser. Added sceKernelGetUserLevel and sceKernelIsUserModeThread (new).
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
These KernelObject subclasses (and their Native* status structs) were
private implementation details of their respective .cpp files. Move them
into the matching .h instead, so external code - specifically the upcoming
WebSocket kernel-object introspection endpoints - can read a live object's
state directly via kernelObjects.Get<T>()/Iterate<T>(), the same way
PSPModule/PSPThread already can. Read-only by convention: nothing outside
each file should call DoState() or otherwise mutate these; the fields are
public here for that file's own pre-existing use, not an invitation to
write from elsewhere.
To avoid pulling each type's full dependency set (Memory::, BlockAllocator,
CoreTiming, HLEKernel::...) into headers many other files include, non-trivial
method bodies (DoState, and MsgPipe's buffer/wait-list management) are
declared in the header but still defined out-of-line in the .cpp, same as
before - only genuinely trivial one-liners went inline.
KernelObjectPool also gains IterateAll(), a type-agnostic sibling of the
existing Iterate<T>() - walks every live kernel object regardless of type,
for a coarse "what's alive right now" overview.
No behavior change - this is a pure visibility/declaration-vs-definition
move, not new functionality. That lands in a follow-up commit.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
PGF::ReadPtr walked four length-prefixed tables and computed table sizes
before any bounds check, and used signed 32-bit size math that could
overflow, allowing a crafted font to read past the input buffer.
- Validate the total size of all tables up front using 64-bit math.
- Check the rev3 extra header fits before reading it.
- Cap charPointerLength/charMapLength/shadowMapLength to avoid absurd
allocations.
- Bounds-check glyph data offsets before reading each glyph.
Also throw in a warning fix
Some games survive with a loaded sceAtrac, and start talking to
sceAudioCodec instead, the underlying library, though unsuccessfully
since it's not properly implemented yet.