# 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()` ```