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 /src/programs.asm | |
| 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.
Diffstat (limited to 'src/programs.asm')
0 files changed, 0 insertions, 0 deletions
