| Commit message (Collapse) | Author | Age |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
- 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
|
|
|
- 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
|