Skip to content

PSP Threading Semantics

Jason edited this page Aug 30, 2026 · 1 revision

PSP Threading Semantics

This page is the Nakagawa-specific public summary of PSP CreateThread and StartThread research. It is title-neutral and separates measured behavior, public declarations, open implementation comparators, Nakagawa source facts, and inference.

Status: the CreateThread and StartThread hardware-oracle specifications below are planned and not run. Emulator agreement is useful comparator evidence, not silicon proof.

Evidence classes

Class Meaning
HARDWARE_MEASURED Observed on a real PSP within the stated, bounded scope.
PUBLIC_HEADER_FACT Declared by a public SDK/header or generated public reference.
OPEN_FIRMWARE_IMPLEMENTATION Behavior visible in an open emulator or firmware-like implementation.
EMULATOR_CONSENSUS PPSSPP and JPCSP model the same behavior; still comparator evidence.
IMPLEMENTATION_DISAGREEMENT Public models differ.
SOURCE_VERIFIED_NAKAGAWA Confirmed in the reviewed Nakagawa source tree.
INFERENCE Reasoned interpretation not directly measured or declared.
HARDWARE_UNKNOWN Available evidence does not establish real-PSP behavior.

A successful route, local test, or emulator match must not be promoted to hardware validation.

Established bounded facts

  • The measured PSP user-call ABI places arguments 5 through 8 in $t0 through $t3: HARDWARE_MEASURED within that user-call scope.
  • A real-firmware sceKernelCreateThread measurement established its fifth argument in $t0: HARDWARE_MEASURED. Arguments 6 through 8 were not separately measured in that run.
  • Nakagawa stack_arg() extracts arguments 5 through 8 from $t0 through $t3 and later arguments from the caller stack: SOURCE_VERIFIED_NAKAGAWA.
  • An ordinary sceKernelWaitEventFlag wait does not dispatch a callback; a subsequent sceKernelCheckCallback() does: HARDWARE_MEASURED for that sequence.
  • At the measured non-delete exit seam, positive 0x77 exits as 0x77, while error-shaped 0x800201ac exits as 0x800200d2: HARDWARE_MEASURED for that exact seam only.

CreateThread

Attributes

The public header declares the following values:

Attribute Value Class Open 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. Those values are EMULATOR_CONSENSUS in this note, 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 is:

typedef struct {
    SceSize size;
    SceUID stackMpid;
} SceKernelThreadOptParam;

The public constants identify KERNEL=1 and USER=2 partitions. 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 explicit uncertainty marker, so a genuine block UID is retained only as a competing hypothesis. The final firmware meaning of stackMpid is HARDWARE_UNKNOWN.

PPSSPP and JPCSP expose SceKernelThreadInfo.stack as a low allocation/base address. That is EMULATOR_CONSENSUS, not confirmed PSP semantics. Establish address orientation before any bounded in-stack sample. Do not probe an adjacent byte or read outside the allocation.

Planned CreateThread oracle

The specification marker is CREATE_THREAD_HARDWARE_ORACLE_SPEC_READY followed by HARDWARE_NOT_RUN. The 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.

Cell Option input Purpose
C01 NULL No-option control.
C02 Non-NULL, size=4 Prefix-sized option.
C03 Non-NULL, size=8, stackMpid=USER (2) Primary partition-ID hypothesis.
C04 Non-NULL, size=8, invalid partition 7 Invalid partition discriminator.
C05 Non-NULL, size=8, stackMpid=KERNEL (1) Kernel partition discriminator.
C06 Non-NULL, size=8, genuine block UID Competing PSPSDK hypothesis, not the primary interpretation.

Remaining cells cover declared attributes, one unknown-bit mutation, bounded priority edges, stack-size controls, and malformed option inputs. Each record retains the raw return value, whether a UID was produced, and observable thread-info state.

Constraints:

  • Use partition IDs as the primary stackMpid inputs; keep the block-UID hypothesis separate.
  • Establish stack orientation before sampling; never use an adjacent/outside-allocation probe.
  • Test LOW_STACK with simultaneous paired allocations, not sequential create/delete address reuse.
  • For NO_FILLSTACK, collect a bounded in-stack sample or a count/checksum over an explicitly defined region, not a single byte.
  • Keep malformed pointers and malformed sizes in a dedicated phase; do not call them safe.

StartThread

Argument block

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:

Input PPSSPP model JPCSP model Class
argSize > 0, valid pointer $a0 is the byte count and $a1 points to the child-stack copy. Same broad model. EMULATOR_CONSENSUS
argSize=0, argp != NULL $a0=0 and $a1=0. $a0=0 and $a1 is a child-stack address. IMPLEMENTATION_DISAGREEMENT
argSize=0, argp=NULL Model-specific zero-argument path. Model-specific zero-argument path. HARDWARE_UNKNOWN

The oracle distinguishes NULL, non-NULL, and misaligned pointers and retains raw failure codes. Huge sizes and malformed pointers are deferred until bounded normal cases establish the observation path.

Initial machine state

State Comparator models Class and boundary
Stack placement Both model a reserved kernel area, aligned argument area, and 0x40-byte frame. EMULATOR_CONSENSUS for the broad model; exact PSP spacing is HARDWARE_UNKNOWN.
Initial SP Both derive SP from stack base and size with modeled reservation/alignment. EMULATOR_CONSENSUS; current formula is not firmware truth.
Initial GP PPSSPP derives module metadata; JPCSP uses the creator GP. IMPLEMENTATION_DISAGREEMENT; cross-module start is a high-value discriminator.
Initial RA PPSSPP uses a stack return stub; JPCSP uses an internal HLE exit handler. IMPLEMENTATION_DISAGREEMENT; real-PSP RA is HARDWARE_UNKNOWN.
Other registers 0xDEADBEEF and 0x7F800001 are emulator debug/initialization sentinels. OPEN_FIRMWARE_IMPLEMENTATION; not hardware initial values.

Lifecycle and scheduling

The models cover these cases, but exact PSP statuses and ordering remain oracle questions:

  • A dormant thread can start.
  • Starting an active thread returns an error.
  • An exited thread can be restarted from its entry point.
  • A never-started dormant thread can be deleted in the model.
  • Waiting on a never-started dormant thread is modeled.
  • An invalid thread ID is rejected.

Both models reschedule immediately when the child has a strictly better priority. Equal-priority and worse-priority ordering differs, and physical ordering remains unknown. The source-owned marker sequence is BEFORE_START, CHILD_ENTRY, AFTER_START, using a monotonic global counter and no sleeps, arbitrary delays, or hardcoded timing.

The planned specification marker is START_THREAD_HARDWARE_ORACLE_SPEC_READY followed by HARDWARE_NOT_RUN. It contains 17 records, including at least 3 lower-risk launches as a planning subset, covering:

  1. no-argument start;
  2. copied arguments;
  3. zero-size with a non-NULL pointer;
  4. NULL and misaligned pointer controls;
  5. initial SP, GP, and RA;
  6. raw registers at child entry;
  7. starting twice;
  8. restarting an exited thread;
  9. delete before start;
  10. wait before start;
  11. invalid IDs;
  12. returns of 0, a positive value, and a negative/error-shaped value;
  13. priority scheduling markers.

Return values and register captures must preserve raw observations. No current emulator sentinel or Nakagawa implementation detail is a universal PSP requirement.

VFPU boundary

VFPU attributes and context behavior have emulator models, but hardware consequences are unmeasured here. This page records the boundary and does not expand the separate VFPU work.

Nakagawa implementation boundary

At the reviewed source boundary:

  • src/rt/hle.c owns guest ABI extraction and routes CreateThread/StartThread into the scheduler.
  • src/rt/sched.c owns the current dormant-thread creation, stack allocation, argument-copy, re-seeding, ready-state, and preemption model.
  • docs/HARDWARE_ORACLE.md defines the source-owned probe and publication limits.

These are SOURCE_VERIFIED_NAKAGAWA facts, not firmware-equivalence claims. The remaining compatibility debt is attribute legality/normalization, stackMpid allocation meaning, stack orientation, initial SP/GP/RA/registers, zero-size argument handling, lifecycle return codes, priority ordering, and hardware attribute consequences.

Public sources

Do not publish private provenance paths, retail inputs, traces, run metadata, or claims that the planned oracle has already run.

Nakagawa Recomp

Architecture

Development

Project

Research site

Clone this wiki locally