From de725da74db5cba0f9bfd0755269e1683423d87b Mon Sep 17 00:00:00 2001 From: user Date: Wed, 15 Jul 2026 23:44:28 +0200 Subject: gbos scaffold: working cooperative microkernel (verified ABAB in emulator) - 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 --- README.md | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 README.md (limited to 'README.md') diff --git a/README.md b/README.md new file mode 100644 index 0000000..bd6c5cf --- /dev/null +++ b/README.md @@ -0,0 +1,102 @@ +# gbos + +A tiny **Unix-flavored microkernel for the Game Boy Color** (MBC5 cartridge). +Not Linux, not FUZIX — a from-scratch cooperative kernel that borrows the V7 +*process model* (separate read-only text + per-process data, a syscall trap, +round-robin scheduling) and squeezes it into the GBC's banked, MMU-less memory. + +This is a **scaffold**, but a *working* one: it assembles into a valid 32 KiB +ROM, brings up a process table, and round-robin context-switches between two +demo tasks that each `write()` a byte and `yield()`. Verified end-to-end in the +`~/dev/gbc` emulator (headless serial console) — it streams `ABABAB...`. + +## Why this shape (the hardware reality) + +- No MMU, no memory protection. "Unix" here = the API/process model, not isolation. +- CPU only ever sees **64 KiB**; RAM is banked. +- **Kernel code lives in ROM** (`$0000-$3FFF`, up to 8 MiB via MBC5) — free, read-only. +- **Program text lives in ROM banks** (`$4000-$7FFF`) — the V7 "pure text" idea. +- **Per-task RW state** (data/bss/heap/stack) lives in one **8 KiB cart-RAM bank** + (`$A000-$BFFF`, MBC5, up to 128 KiB / 16 banks). +- The context switch swaps banks, so it **must** run from **HRAM** (never banked) + and never touch a swappable stack mid-swap. + +## Memory map + +``` +$0000-$3FFF ROM0 kernel core (rst/IRQ vectors, init, sched, syscalls) - fixed +$4000-$7FFF ROMX current task TEXT (read-only program code) +$8000-$9FFF VRAM graphics (unused so far) +$A000-$BFFF SRAM current task DATA/BSS/HEAP/STACK (MBC5 RAM bank) +$C000-$CFFF WRAM0 kernel globals + kernel stack (never swapped) +$D000-$DFFF WRAMX per-process u-area (SVBK bank) (reserved, not used yet) +$FF80-$FFEE HRAM context-switch trampoline (111 bytes) +$FFEF-$FFF2 HRAM bank shadows + switch target +``` + +## Process control block (`include/gbos.inc`) + +`STATE, PID, SP, ROMB(16b text bank), RAMB(data bank), WRAMB(u-area), PARENT, EXIT` +— 10 bytes. `MAX_PROCS = 8`. + +## Syscall ABI + +User loads `C` = syscall number, args in `DE`/`B`, then `rst $30`. Return in `A`. + +| # | name | status | +|---|---------|--------| +| 0 | exit | ✅ marks zombie, reschedules | +| 3 | write | ✅ `DE`=buf `B`=len → serial console | +| 8 | getpid | ✅ | +| 11| yield | ✅ cooperative switch | +| 1,2,4,5,6,7,9,10 | fork/read/open/close/exec/wait/kill/brk | ⛔ `ENOSYS` | + +## The context switch (`src/hram.asm`) + +The crux. `hSwitchTo(DE=&incomingPCB)`: +1. push full register context onto the **outgoing** task's stack +2. save `SP` into the outgoing PCB (WRAM0, always mapped) +3. program incoming banks: **RAM bank → SVBK → ROM text bank** +4. load `SP` from the incoming PCB (its banks are now mapped) +5. pop context, `ret` into the incoming task + +Runs from HRAM so changing `SVBK`/RAM-bank never pulls the rug out from `PC` or +the stack. New tasks are bootstrapped with a fake frame (`ProcSetupStack`) so the +first switch-in `ret`s straight to their entry point. + +## Build + +```sh +make # -> gbos.gb (RGBDS: rgbasm/rgblink/rgbfix) +make clean +``` + +Console output goes to the **serial port** (`tty.asm`). Easiest way to watch it +is the project emulator's headless serial console: + +```sh +~/dev/gbc/build/gbc gbos.gb --headless --uncapped # prints ABABAB... +``` + +A real gbos would render to VRAM; serial is the zero-VRAM debug channel. + +### Bring-up bugs already found & fixed (kept as cautionary tales) + +- **Stack vs. BSS clear:** the kernel stack (`SP=$D000`) grows down into + `$CxFF`, so zeroing all of WRAM0 wiped the live return address. `ClearKernelRAM` + now clears only `$C000-$CBFF`, leaving the stack region alone. +- **Syscall ABI vs. dispatch:** `SyscallTrap` originally used `DE` to index the + jump table, clobbering the `write()` buffer pointer passed in `DE`. Dispatch + now indexes via `A`/`HL` only, preserving `DE`/`B` for the handler. + +## Roadmap + +- [ ] `fork`: copy parent's 8 KiB RAM bank → free bank, dup u-area, child returns 0 +- [ ] `exec`: point `PROC_ROMB` at a flashed program, reset RAM-bank layout +- [ ] Preemptive scheduling: real context save in `TimerISR` → `hSwitchTo` +- [ ] Use the `$D000-$DFFF` u-area (SVBK) for per-process kernel state / kstack +- [ ] `brk`/heap allocator inside the task bank (heap up, stack down, collision = ENOMEM) +- [ ] A filesystem in remaining cart SRAM/flash (minix/v7-ish), paged 8 KiB at a time +- [ ] Swap whole tasks to cart flash when RAM banks are exhausted (UZI-style) +- [ ] VRAM console + keyboard/joypad `read()` +``` -- cgit v1.3.1-sl0p