Add DoCString() for standalone fixed-size strings, and
TerminateLoadedCString() for names inside structs that are loaded whole.
These all get used as C strings (GetName(), logs, the debugger), and are
always terminated when created. Module names are copied from the ELF with
strncpy, so terminate those on creation too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
waitType, thread priorities, MipsCall::numArgs, action type IDs and pending
interrupt numbers all index host arrays (some of them function pointer
tables) without further checks.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
sceKernelStartModule, sceKernelStopModule and the self-stop calls now
check their option first: with SDK 2.08 or later its size must be 0x14,
and attribute bits outside 0x00F06000 are an error.
The option's attribute is added to the module's thread attribute rather
than ignored (or, when stopping, replacing it), and the default attribute
is 0 rather than the module's own attribute. A nonzero mpidstack puts the
stack in that partition, which has to be user-accessible for a user
module and kernel-only for a kernel module.
A failed thread creation is now returned instead of asserting.
Hardware test: modules/startoptions.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Semaphores, event flags, mutexes, lwmutexes, mbx, msgpipes, fpl, vpl,
tlspl and WaitThreadEnd each had their own CoreTiming event, handler
registration and savestate entry for wait timeouts, and their own function
to schedule one. Now one event (WaitThreadEnd's, renamed) times out all of
them, keyed by thread, and dispatches on the thread's wait type to a
timeoutFunc registered alongside the begin/end callback functions.
__KernelWaitCurThreadWithTimeout() starts such a wait, and the HLEKernel
helpers have overloads that use the shared event.
Old savestates still load: each object's section reads its old event id
and points it at the shared handler, so a timeout pending in the state
goes off as before. Checked with a state saved mid-wait by the previous
build, and with four games.
The one behaviour change: tlspl timeouts now follow the same hardware
rule as the others, where they used the raw timeout.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Timed on a PSP, each way of handing the CPU to another thread (the call
and the switch together):
hardware before now
rotate to an equal thread 7 14 7
signal, better thread runs 10 17 10
it waits again, back to caller 10 19 12
wakeup, better thread runs 8 13 6
it sleeps again, back to caller 7 12 6
start a better thread, entry 30 28 30
thread ends, back to its waiter 21 13 20
notify, better thread's callback 14 13 14
A switch between two threads now costs 1150 cycles instead of 2700.
Starting a better thread costs 2000 cycles more, ending a thread 3300,
and setting up a callback 1800.
Also splits a wait timeout's ~30us into the deadline being taken 12us
into the call and the timeout going off 18us after it. That only changes
the time left written back, which threads/semaphores/wait and
threads/fpl/cancel pin between them. intr/vblank is re-recorded so it
no longer depends on the phase of the frame.
threads/callbacks/combos now passes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On hardware, notifying a callback of a thread in a CB wait takes it out of
the wait at once, even though the callback only runs when the thread would
get the CPU. A semaphore signalled in between doesn't end the wait: the
callback runs first, then the wait resumes and takes it (pspautotests
threads/callbacks/combos). We left the thread on the wait list until the
callback started, so the signal ended the wait and the callback didn't
run.
The notify now pauses the wait, as starting a callback used to. If the
callbacks are canceled before the thread's turn comes, the wait just
resumes (Thread savestate section version 7).
threads/callbacks/combos goes in the to-do list: a callback returning to
the thread that notified it still takes ~13us where hardware takes ~9,
part of the context switch cost.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every wait with a timeout behaves the same on hardware (pspautotests
threads/scheduling/waittimeouts). The deadline is taken, and the alarm set
up a moment later. If the deadline has passed by then, the wait fails with
WAIT_TIMEOUT at once, without yielding or writing the timeout back. That's
usual for 0us, half the time for 1us, and rare after; AllocateVpl does more
first. Otherwise it ends max(t, 205us) + ~35us after the call. Each object
had its own guess (24/245, 25/250, 20/250 and so on), and only MsgPipe had
the immediate case.
__KernelWaitTimesOutAtOnce() and __KernelWaitTimeoutUs() now do it for
semaphores, event flags, mutexes, lwmutexes, mbx, msgpipes, fpl, vpl and
WaitThreadEnd. The latency past the deadline isn't counted in the time
left written back.
Outcomes that hardware decides by the clock's phase (these, and
sceKernelDelayThread returning at once) go with the likelier one. Ones
between 50% and certain are instead spread evenly over calls, so a polling
loop can't lock into never yielding (sceKernelThread section version 7).
This replaces the pseudo-random choice for delays.
Also adds threads/scheduling/readyqueue, which already passes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A delay's deadline is now + usec, and the clock is read again when the
alarm is set. If the deadline has passed by then, the call returns 0 at
once without giving up the CPU. On hardware that makes
sceKernelDelayThread(0) return at once about 60% of the time. On a thread's
first wait after it starts, a delay of 1 does so about two times in three
as well (pspautotests threads/scheduling/delayzero). We always waited at
least 210us.
The choice is pseudo-random off the tick count, not the tick phase, since
our cycle counts are regular enough for a polling loop to lock into never
yielding. Threads remember whether they've waited since starting (Thread
savestate section version 6).
Also moves threads/vpl/create into the passing tests: re-recorded on 6.61,
it agrees with what we do for partitions 8 and 9.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On hardware the kernel fills a new thread's stack with interrupts on, so
a better thread that wakes during it runs before the call returns, the
time it takes doesn't count towards the call, and worse threads get
nothing (pspautotests threads/scheduling/preemptsyscall). PPSSPP ate the
whole cost at once and only rescheduled at the end.
__KernelBusyDelayResult() models such a syscall: the caller waits, an
idle thread stands in for it while nothing better wants the CPU, and its
remaining cycles only count down while that's the case. When done it
goes back ahead of threads of its own priority, having never given up
the CPU. sceKernelCreateThread uses it for the stack fill, unless a
thread event handler is about to run.
Booting 75 games against master shows no difference.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From pspautotests threads/scheduling/costs: sceKernelCreateThread takes
about 150us plus roughly a cycle per byte of stack, which the kernel fills
with 0xFF (1.3ms for 256KB), and sceKernelDeleteThread 50-100us whatever
the stack size. Brings threads/scheduling/scheduling a good deal closer;
what's left needs a thread that wakes during a long syscall to preempt it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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.