ThreadMan and Scheduler Model for Runtime Authors
Type: REFERENCE Status: MAINTAINED Scope: PSP thread creation, start semantics, lifecycle, registers, waits, callbacks, and scheduler-visible state
ThreadMan is a semantic subsystem, not a thin wrapper around host OS threads. A useful runtime model must explain guest-visible identity, priority, lifecycle, wait ownership, timeout, wakeup, callback-aware waits, argument placement, initial machine state, and the distinction between a route progressing and a PSP contract being complete. This page is title-neutral; project-specific source ownership is summarized in the Nakagawa Wiki threading page.
Evidence boundary
Every substantive statement belongs to an evidence class:
- HARDWARE_MEASURED: observed on a real PSP within a stated, bounded scope.
- PUBLIC_HEADER_FACT: declared by a public SDK/header or generated public reference.
- OPEN_FIRMWARE_IMPLEMENTATION: visible in an open emulator or firmware-like implementation.
- EMULATOR_CONSENSUS: PPSSPP and JPCSP model the same behavior; this remains comparator evidence.
- IMPLEMENTATION_DISAGREEMENT: public models differ.
- SOURCE_VERIFIED_NAKAGAWA: confirmed in the reviewed Nakagawa source tree.
- INFERENCE: a reasoned interpretation that is not directly measured or declared.
- HARDWARE_UNKNOWN: available evidence does not establish real-PSP behavior.
A local test, successful route, or emulator match is not hardware validation. The CreateThread and StartThread hardware-oracle specifications described here are planned and not run.
How We Know
- Public contract: PSPSDK pspthreadman.h and the generated ThreadMan reference.
- Open comparators: PPSSPP ThreadMan and JPCSP ThreadManForUser. Their agreement is useful for identifying a semantic question, not proof of silicon behavior.
- Nakagawa source: HLE ABI and route ownership and scheduler model.
- Hardware context: the accepted WaitThreadEnd status finding and source-owned PSP test fixtures. Existing findings remain bounded to their measured seam and firmware/device scope.
ABI and CreateThread
For the measured PSP user-call ABI, arguments 5 through 8 arrive in $t0 through $t3: HARDWARE_MEASURED within that user-call scope. A real-firmware sceKernelCreateThread measurement established its fifth argument in $t0; arguments 6 through 8 were not separately measured in that run. Nakagawa’s stack_arg() implements this extraction: SOURCE_VERIFIED_NAKAGAWA.
The public header declares these attributes:
| Attribute | Value | Evidence and remaining question |
|---|---|---|
PSP_THREAD_ATTR_VFPU |
0x00004000 |
PUBLIC_HEADER_FACT; hardware context consequences are unmeasured here. |
PSP_THREAD_ATTR_USER |
0x80000000 |
PUBLIC_HEADER_FACT; legal mask and normalization remain unknown. |
PSP_THREAD_ATTR_USBWLAN |
0xa0000000 |
PUBLIC_HEADER_FACT; acceptance and normalization remain unknown. |
PSP_THREAD_ATTR_VSH |
0xc0000000 |
PUBLIC_HEADER_FACT; acceptance and normalization remain unknown. |
PSP_THREAD_ATTR_SCRATCH_SRAM |
0x00008000 |
PUBLIC_HEADER_FACT; legality for the tested caller remains unknown. |
PSP_THREAD_ATTR_NO_FILLSTACK |
0x00100000 |
PUBLIC_HEADER_FACT; requires a bounded in-allocation observation. |
PSP_THREAD_ATTR_CLEAR_STACK |
0x00200000 |
PUBLIC_HEADER_FACT; pattern and precedence require measurement. |
PPSSPP and JPCSP additionally model KERNEL=0x00001000 and LOW_STACK=0x00400000: EMULATOR_CONSENSUS, not public-header facts. Legal user masks, KERNEL from a user caller, SCRATCH_SRAM legality, USBWLAN/VSH normalization, unknown bits, and priority bounds remain IMPLEMENTATION_DISAGREEMENT or HARDWARE_UNKNOWN.
Option and stack identity
The public option type contains SceSize size and SceUID stackMpid. JPCSP treats stackMpid as a partition identifier for allocation; PPSSPP notices the field but gives it no allocation effect. The PSPSDK block-UID wording carries an uncertainty marker, so a genuine block UID remains only a competing hypothesis. The final firmware meaning is HARDWARE_UNKNOWN.
PPSSPP and JPCSP expose SceKernelThreadInfo.stack as a low allocation/base address: EMULATOR_CONSENSUS, not confirmed PSP semantics. Establish address orientation before taking any bounded in-stack sample. Do not probe an adjacent byte or read outside the allocation.
StartThread argument and initial state
PPSSPP and JPCSP agree that a nonzero argument block is copied to the child stack, with the argument byte count in $a0 and the child-stack copy address in $a1: EMULATOR_CONSENSUS. They disagree for a non-NULL pointer with argSize=0: PPSSPP models $a0=0, $a1=0, while JPCSP models $a0=0 with $a1 holding a child-stack address. That discriminator is IMPLEMENTATION_DISAGREEMENT and HARDWARE_UNKNOWN.
Both models reserve a modeled kernel area, align an argument area, and use a modeled 0x40-byte frame. Exact PSP spacing is HARDWARE_UNKNOWN. Initial GP differs: PPSSPP derives module metadata, while JPCSP uses the creator GP. Initial RA differs: PPSSPP uses a stack return stub, while JPCSP uses an internal HLE exit handler. Cross-module GP and initial RA are high-value hardware discriminators. 0xDEADBEEF and 0x7F800001 are emulator initialization/debug sentinels, not hardware initial values.
Lifecycle, waits, callbacks, and scheduling
The models cover a dormant thread starting, an active-thread start error, restarting an exited thread, deleting a never-started dormant thread, waiting on a never-started dormant thread, and rejecting an invalid ID. Exact PSP statuses and lifecycle ordering remain HARDWARE_UNKNOWN until measured.
Both models reschedule immediately when a started child has a strictly better priority. Equal-priority and worse-priority ordering differs, and physical ordering remains unknown. A source-owned oracle should mark BEFORE_START, CHILD_ENTRY, and AFTER_START with one monotonic global counter. It should not add sleeps, arbitrary delays, priority changes, or hardcoded timing constants.
An ordinary sceKernelWaitEventFlag wait does not dispatch a callback; a subsequent sceKernelCheckCallback() does: HARDWARE_MEASURED for that sequence. At a measured non-delete thread-exit seam, positive 0x77 exits as 0x77, while error-shaped 0x800201ac exits as 0x800200d2: HARDWARE_MEASURED for that exact seam only. Neither observation defines every callback entry or thread-exit path.
Planned hardware-oracle work
The CreateThread specification marker is CREATE_THREAD_HARDWARE_ORACLE_SPEC_READY followed by HARDWARE_NOT_RUN. Its minimum plan has at least 16 records: 13 discriminators and 3 controls, with at least 2 launches selected from lower-risk phases. Lower-risk is only a planning label; no probe is guaranteed harmless. The first option cells are NULL, size=4, size=8 with USER partition 2, invalid partition 7, size=8 with KERNEL partition 1, and a genuine block UID as a competing hypothesis. The plan also covers declared attributes, an unknown-bit mutation, bounded priority edges, stack-size controls, and malformed options.
The StartThread specification marker is START_THREAD_HARDWARE_ORACLE_SPEC_READY followed by HARDWARE_NOT_RUN. Its minimum plan has 17 records, including at least 3 lower-risk launches as a planning subset. It covers no-argument and copied-argument starts, the zero-size/non-NULL disagreement, NULL and misaligned pointers, initial SP/GP/RA and raw registers, starting twice, restarting an exited thread, delete/wait before start, invalid IDs, returns of 0/positive/negative values, and priority markers. Huge sizes and malformed pointers are deferred until bounded normal cases establish the observation path.
All records must preserve raw return values and distinguish a returned UID from a rejected call. The stack orientation must be established before in-stack sampling. LOW_STACK requires simultaneous paired allocations rather than sequential create/delete address reuse. NO_FILLSTACK requires a bounded in-stack sample or count/checksum over a defined region, not a single byte. No planned record is evidence until an authorized real-device run produces it.
Do Not Infer
- Host OS scheduling is not PSP priority or wait semantics.
- PPSSPP/JPCSP consensus is not hardware proof.
- A route that advances is not proof of correct callback, timeout, wake, register, or lifecycle semantics.
- A current Nakagawa implementation detail is not universal PSP behavior.
- A block UID is not the established meaning of
stackMpid. - An adjacent/outside-allocation probe does not establish stack orientation.
- Planned or lower-risk oracle cases are not evidence, and no probe is guaranteed harmless.
- Private provenance paths, retail inputs, traces, and run metadata are not part of this public page.
Open questions
High-value source-owned matrices include attribute legality and normalization, option allocation semantics, stack orientation, initial SP/GP/RA, zero-size argument handling, lifecycle raw codes, equal/worse-priority ordering, callback arrival during waits, and hardware attribute consequences. Keep unknown cells explicit in the Open Research Questions registry.