Skip to content

Make reset reliable (thread priorities, IOP sync, stdio after restart, SIF DMA retry) - #70

Open
kaisa847 wants to merge 8 commits into
ps2dev:masterfrom
kaisa847:fix/reset-robust
Open

kaisa847 wants to merge 8 commits into
ps2dev:masterfrom
kaisa847:fix/reset-robust

Conversation

@kaisa847

Copy link
Copy Markdown

Problem

reset did not reliably bring ps2link back after a program had run. With an SDL2 program (audio thread, pad, gsKit), reset dropped the host connection and ps2link never came back: TCP 18193 was refused, only ICMP answered, and the console needed a power cycle. A ps2link start without IPCONFIG.DAT (initial boot followed by a self-restart) also crashed on the first printf.

Each cause is fixed in its own commit:

  1. ee: run the command thread at priority 1 (was 20). It sleeps unless a command arrives.
  2. ee: pkoReset: wait until the IOP has rebooted. while (SifIopSync()); was inverted; now while (!SifIopSync());, as restartIOP() already does.
  3. ee: pkoReset: terminate all threads of the loaded program, not only the main thread. The set of ps2link's own threads is recorded before the first execee.
  4. ee: pkoReset: stop GIF and VIF1 DMA and reset GIF/VIF1. Only kuseg registers are touched, so this also works in user mode.
  5. ee: pkoReset: let stdio re-initialize after the restart. ExecPS2(&__start) keeps .data, so newlib's __cleanup mark survives while the FILE locks in .bss are cleared. The first printf then takes a NULL lock (TLB load, BadVAddr 8). libcglue clears the mark in __locks_init(), but at the offset of the full struct _reent. ps2link links newlib-nano (_REENT_SMALL, __cleanup at offset 32), so that reset misses the field. That part will also be reported to ps2sdk.
  6. iop: reset: forward the command to the EE before removing host: and tty. Removing them first broke the reset while a program was still talking to the IOP.
  7. iop: retry the SIF DMA when the queue is full (up to about 1 s) instead of dropping the command.
  8. ee: on reset, let the command thread preempt the program. ps2sdk's InitThread() puts the main thread at priority 1, and SDL2 puts its audio thread at 0. The command thread was woken by the SIF0 interrupt but never ran. The interrupt handler now demotes the program's threads and raises the command thread to 0 when it sees PKO_RPC_RESET.

Testing

Tested on real hardware: a PSX DESR-5100 (PS2-based), with ps2link at a fixed IP. Programs were started with execee, each followed by reset, and then a check that ps2link answers again.

Build Programs Result
master (22c08eb) durchsatz (ATA reads, printf), bildtest, demo3d (ps2gl) reset ok
master (22c08eb) demo2d (SDL2: audio thread, pad, gsKit) reset hangs, power cycle needed
master (22c08eb) start without IPCONFIG.DAT crash on the first printf after the self-restart
commits 1–7 (without 8) demo2d reset hangs, power cycle needed
this PR (1–8) demo2d ×2, durchsatz, bildtest, demo3d, start without IPCONFIG.DAT all ok, back after 4.0–4.1 s each time

Each test program printed an end marker with status=0 before the reset. The diagnosis used on-screen markers in pkoReset: they showed that the reset packet reached the EE in the SIF0 interrupt, but the command thread never ran (fixed by 8). Those markers are not part of this PR.

Not tested separately: whether commit 6 (order on the IOP) is still needed once commit 8 is in. The new order is safer, but the hardware test only covers the full set.

🤖 Generated with Claude Code

kaisa847 and others added 8 commits November 13, 2026 15:52
The command thread ran at priority 20. The EE kernel schedules strictly by
priority, so a thread of the loaded program that never blocks and runs at a
numerically lower priority starves it, and reset (and every other command) is
never executed. The thread sleeps unless a command arrives, so a high priority
costs the program nothing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
SifIopSync() returns 1 once the IOP has finished booting (BOOTEND).
pkoReset() looped while it returned 1, so it fell through at once and called
SifInitRpc() on an IOP that was still rebooting. Use the same loop as
restartIOP() in ps2link.c: while (!SifIopSync());

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Only the program's main thread (userThreadID) was terminated. Other threads it
created (e.g. an SDL audio thread, pad or network threads) kept running while
the IOP was reset underneath them, for example blocked in or issuing RPCs.

Remember which threads exist right before the first program is started
(ps2link's own and any kernel/patch threads) and terminate and delete every
other thread in pkoReset(). The list lives in .bss, so it is rebuilt after the
ExecPS2() restart.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A program may leave a GIF (channel 2) or VIF1 (channel 1) transfer running or
PATH3 masked. Stop both channels (with CPND held while STR is cleared), then
reset VIF1 and GIF before the IOP reset and the restart. Only kuseg registers
(0x1000xxxx) are touched, so this also works when the EE runs in user mode.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
pkoReset() restarts ps2link with ExecPS2(&__start, ...). crt0 clears .bss but
.data is kept. newlib marks stdio as initialized in _impure_data (.data,
__cleanup), while the FILE structs and their locks (__sf) live in .bss. After
the restart __sinit() therefore skips the re-initialization and the first
printf() takes a NULL lock: TLB load exception, BadVAddr 8, EPC in
__retarget_lock_acquire_recursive, called from the command thread's first
dbgprintf().

It shows up on every start when IPCONFIG.DAT is missing (the initial boot
prints "Error reading ipconfig.dat" and then restarts), and after a reset
once ps2link has printed anything.

libcglue clears the mark in __locks_init(), but through the full struct
_reent of the installed headers. ps2link links newlib-nano (NEWLIB_NANO = 1),
which is built with _REENT_SMALL, where __cleanup is at offset 32 instead of
52, so that reset hits another field. Clear the mark through the _REENT_SMALL
layout before ExecPS2().

Co-Authored-By: Claude Opus 5.5 <[email protected]>
On reset, the IOP command thread first unmounted host: and deleted the tty
driver, and only then sent PKO_RPC_RESET to the EE. With a program that keeps
talking to the IOP (an SDL2 program printing once a second while streaming
audio), the reset never ran: host: was gone (TCP 18193 refused), the program
froze and only ICMP still answered.

Send the command to the EE first, then remove host: and tty as before.
Also import DelayThread, which the next commit uses.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
sceSifSetDma() returns 0 when the SIF DMA queue is full, e.g. while a program
streams audio or pad data. pkoSendSifCmd() then dropped the command; for a
reset that leaves ps2link dead. Retry for up to about one second.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
ps2sdk's InitThread() moves a program's main thread to priority 1 and SDL2
runs its audio thread one above that, at 0. When such a thread keeps the CPU
busy, the command thread (priority 1) is woken by the SIF0 interrupt but never
runs, so pkoReset() is never called.

When the interrupt handler sees PKO_RPC_RESET, demote all threads of the
program to the lowest priority and raise the command thread to 0 before
waking it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant