aboutsummaryrefslogtreecommitdiffstats
path: root/src/sched.asm (follow)
Commit message (Collapse)AuthorAge
* sched: SchedYield must preserve HL - link-less boots warm-rebooted every ~11suser2026-09-22
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* kernel: CGB double-speed mode - 2x everything (1.99x measured)user2026-07-18
| | | | | | | | | | | | | | KEY1 prepare + STOP at KernelInit (skipped on warm reboot if already fast). The PPU/LCD keep their normal rate; the timer and DIV double with the CPU, compensated at the two consumers: - TimerISR now fires at 128 Hz; a toggle byte keeps wTicks at 64 Hz (uptime semantics unchanged) - sys_sleep halves the DIV-delta accumulator's high byte (256 DIV ticks = 1/128 s in double speed) so gsleep/msleep stay real-time Measured: count 60 (scroll-heavy) 3.77s -> 1.89s wall; wTicks 64 Hz over 4s; ping's 800ms msleep pacing unchanged (2.77s for 4 echoes). sl0pboy already emulated KEY1/STOP + per-speed timer/PPU rates.
* kernel: a real timer source (64 Hz tick IRQ) + uptime(1)user2026-07-18
| | | | | | | | | | | | | | | | | | | | | | | | | | The kernel had no clock: TimerISR was a reti stub, IEF_TIMER masked, and IME was never enabled - the vectors were decorative. sys_sleep just polls DIV deltas per-process; nothing counted globally. Now: TAC runs the hardware timer at 16384 Hz with TMA=0, so TIMA overflows at exactly 64 Hz; TimerISR increments a monotonic 32-bit wTicks (wraps after ~2.1 years). Scheduling stays cooperative - the ISR is transparent. Enabling IME in a kernel written for zero interrupts needs care wherever SP points into memory whose bank is being switched (an IRQ pushes onto SP): - read_block/write_block map the disk bank over the $A000 window that holds the caller's stack -> di/ei around the transfer (~2ms, well under the 15.6ms tick period, so no tick is ever lost) - hSwitchTo switches SVBK + cart-RAM banks under the outgoing stack -> di on entry, ei once the incoming stack is mapped - fork already runs on KSTACK_TOP2 (fixed WRAM) - safe as-is - term_putc's SVBK switch only remaps $Dxxx, stacks live in $Axxx/$Cxxx SYS_UPTIME (36) copies the counter (4B LE, di/ei so the read can't tear) to a user buffer; libc gticks(); usr/uptime.c formats 'up [Nd] H:MM:SS'. uptime avoids SDCC long div/shift entirely: sm83.lib modules link into their own areas that land in the $A000 RAM window (latent build.sh trap, documented there) - bytewise >>6 plus bounded subtraction loops instead.
* kernel: complete the process lifecycle (exit/wait/reap + bank free list)user2026-07-16
| | | | | | | | | | | | | | - exit(): zombie + status, reparent orphans to init, wake blocked parent, schedule away forever (resources reclaimed by the reaper) - wait(): scan for a zombie child; reap (free cart-RAM bank + PCB slot) and return pid/code; block PS_BLOCKED + yield if children still live; $FF if none - cart-RAM banks are now a free list (wBankUsed bitmap): AllocRamBank/FreeRamBank - scheduler: FindNextReady returns carry when nothing runnable; yield idles instead of blindly picking slot 0 - helpers: FindPcbByPid, ReparentToInit - demo: init forks 3 workers -> exec -> exit(pid) -> wait/reap => wwwR2R3R4! verified bank recycling with 6 workers over 3 banks (w2w3w4w5w6w7) - README: document the lifecycle + syscall status
* gbos scaffold: working cooperative microkernel (verified ABAB in emulator)user2026-07-15
- fix ClearKernelRAM clobbering the kernel stack (only clear $C000-$CBFF) - fix SyscallTrap clobbering DE (buffer arg) during table dispatch - README: run via ~/dev/gbc --headless; note bring-up bugs