diff options
| author | user <user@clank> | 2026-09-22 19:20:53 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-22 19:20:53 +0200 |
| commit | 5f872435e61fc489606b13ba33ba6f258af574b5 (patch) | |
| tree | cdc0933f6b1b55e2cc8ebc5c37837aa702c3632b | |
| parent | usr: httpd - the Game Boy serves its filesystem over HTTP (diff) | |
| download | gbos-5f872435e61fc489606b13ba33ba6f258af574b5.tar.gz gbos-5f872435e61fc489606b13ba33ba6f258af574b5.tar.xz gbos-5f872435e61fc489606b13ba33ba6f258af574b5.zip | |
sched: SchedYield must preserve HL - link-less boots warm-rebooted every ~11s
Reported by an outside agent (BUG-schedyield-clobbers-hl.md) while putting
gbos in a browser, where there is no link port at all. Verified here, fixed,
and re-verified.
SchedYield had two exits. The switch path preserved registers because
hSwitchTo saves and restores the full context. The "nobody else is runnable"
path was a bare `ret c` straight out of FindNextReady, which leaves HL
pointing into wProcTable (PcbPtr puts it there). Two callers - net_op_recv's
timeout loop and tcp_connect's - keep a pointer in HL across that call:
ld hl, wNetTO
inc [hl]
jr nz, .wait
call SchedYield ; HL now = &wProcTable, not &wNetTO
inc hl
inc [hl] ; stray increment into a PCB
With no DHCP server the boot-time `dhcp` spins in that loop for ~10s, and
with the shell blocked on tty input nothing else is PS_READY - so *every*
yield took the no-switch path and corrupted a byte, until a saved return
address rolled from $00xx to $01xx and RET landed on the cart entry $0100.
A clean-looking warm reboot, every ~11s, forever. It never happens with
tools/gbhub running, which is why it survived this long.
Fixed in the scheduler rather than at the two call sites, so the contract
holds for every present and future caller: SchedYield now preserves AF/BC/DE
and HL on both paths. On the switch path the pushes sit on the task's own
stack and are popped when it is rescheduled.
Evidence, with the link port unconnected (no --serial-sock; note --headless
bypasses the emulator's breakpoints, so these run on the display path):
before: breakpoint $0100 hit 5x in 40s, first at cycles=46,286,496
(matching the report), deltas ~45.5M cycles apart; HL at the
`inc hl` after the call read $C000 instead of $C9D1
after: 0 hits in 60s, HL reads $C9D1 every time, and a 60s screen watch
shows no spontaneous reboot (it reproduced at 9.9s before)
No regression: 34/34 across the tool, httpd and client suites.
| -rw-r--r-- | src/sched.asm | 27 |
1 files changed, 25 insertions, 2 deletions
diff --git a/src/sched.asm b/src/sched.asm index b4b5a30..d68dc93 100644 --- a/src/sched.asm +++ b/src/sched.asm @@ -29,11 +29,34 @@ SchedRunFirst:: ; ----------------------------------------------------------------------------- ; SchedYield - cooperative switch to the next runnable task. Returns to the ; caller when this task is eventually rescheduled. +; +; PRESERVES ALL REGISTERS, on both paths. The switch path always did, because +; hSwitchTo saves and restores the full context - but the "nobody else is +; runnable" path used to `ret c` straight out of FindNextReady, which leaves +; HL pointing into wProcTable (via PcbPtr). A caller holding a pointer in HL +; across the call then walked the process table instead of its own variable. +; +; That is what warm-rebooted a link-less gbos every ~11s: with no DHCP server, +; the boot-time `dhcp` spun in net_op_recv's timeout loop and, with the shell +; blocked on tty input, every yield took the no-switch path - so `inc hl / +; inc [hl]` incremented PCB bytes instead of wNetTO until a saved return +; address rolled to $0100 and the cart restarted. It only ever bit with the +; network absent, which is why it hid for so long. ; ----------------------------------------------------------------------------- SchedYield:: + push af + push bc + push de + push hl call FindNextReady ; DE = &next PCB, CF if none runnable - ret c ; nobody else ready: keep running current - call hSwitchTo + jr c, .none ; nobody else ready: keep running current + call hSwitchTo ; (returns here when we are rescheduled; the + ; pushes above sit on our own stack) +.none + pop hl + pop de + pop bc + pop af ret ; ----------------------------------------------------------------------------- |
