FlashOS

Setup

Host toolchain, SD-card layout, serial console, QEMU, and the test runner.

README · Documentation · Setup · Port · Versioning · Changelog · License

English · Deutsch

--- This page covers the host toolchain, the SD-card layout the Raspberry Pi 4 expects, the serial console, QEMU, and the test runner. Reference: [BCM2711 ARM Peripherals (RPi 4)](https://pip-assets.raspberrypi.com/categories/545-raspberry-pi-4-model-b/documents/RP-008248-DS-1-bcm2711-peripherals.pdf?disposition=inline). ## Contents 1. [Host toolchain](#1-host-toolchain) 2. [Building](#2-building) 3. [Running under QEMU](#3-running-under-qemu) 4. [SD-card layout](#4-sd-card-layout) 5. [Serial console](#5-serial-console) 6. [Helper shell functions](#6-helper-shell-functions) 7. [Host-side unit tests](#7-host-side-unit-tests) ## 1. Host toolchain | Tool | Minimum version | Purpose | | :------------------------- | :-------------- | :---------------------------------------- | | Zig | 0.16.0 | Compile Zig + assembly, run `build.zig` | | `flashc` | pinned | Transpile Flash (`.flash`) sources to Zig | | `aarch64-elf-objcopy` | 2.40+ | ELF → raw binary | | `aarch64-elf-nm` | 2.40+ | Symbol extraction for `populate-syms` | | `qemu-system-aarch64` | 11.0.0+ | Run the kernel under QEMU | | `screen` (or equivalent) | – | Serial console for the Pi | On macOS: ```bash brew install zig aarch64-elf-binutils qemu ``` ### Flash compiler (`flashc`) FlashOS's source modules are written in [Flash](https://github.com/ajhahnde/Flash) and transpiled to Zig at build time. `build.zig` resolves the `flashc` binary at `~/Flash/zig-out/bin/flashc-stage1` by default; override the path with `-Dflashc=`. Flash publishes no prebuilt binaries, so build the pinned self-hosted compiler from source — run this from the FlashOS checkout so the pin is read from `flash-toolchain.lock`: ```bash git clone https://github.com/ajhahnde/Flash.git ~/Flash git -C ~/Flash checkout "$(grep -oE '[0-9a-f]{40}' flash-toolchain.lock)" ( cd ~/Flash && zig build stage1 ) # → ~/Flash/zig-out/bin/flashc-stage1 ``` `zig build stage1` — not the bare `zig build`, which emits only the stage0 bootstrap seed `flashc` — produces `flashc-stage1`, the revision pinned in `flash-toolchain.lock`. Rebuild it only when that pin moves. ## 2. Building Every build transpiles the `.flash` source modules with `flashc`, so build it first (see §1). ```bash zig build # default: kernel8.img + armstub8.bin → zig-out/ ``` ```bash ./build.sh # full two-pass build with optional deploy ``` `build.sh` invokes `zig build`, `zig build populate-syms`, then `zig build` again, diff-checks that the symbol layout converged, and optionally runs `zig build deploy`. ## 3. Running under QEMU Two QEMU machines are wired up; pick by `-Dboard=`: ```bash zig build -Dboard=rpi4b run # Pi 4 model (raspi4b) zig build -Dboard=virt run-virt # generic ARMv8 (virt) ``` For a self-validating run that exits 0 when the boot reaches the interactive `fsh` prompt (the third `type 'help' for commands` homescreen marker — see below) with no `[FAIL]` / `ERROR CAUGHT` and the expected free-page checkpoints, and 1 on a failure or watchdog timeout (no manual QEMU supervision): ```bash zig build -Dboard=virt test-virt zig build -Dboard=rpi4b test-rpi4b # (matches run) ``` To verify the Pi byte-identity baseline before flashing the SD card (stashes `src/symbol_area.S`, cleans, rebuilds, diffs against `scripts/pi_baseline.sha256`): ```bash scripts/verify_pi_baseline.sh ``` `run` invokes `qemu-system-aarch64 -M raspi4b -serial null -serial stdio -kernel zig-out/kernel8.img` — the Mini-UART (UART1) is routed onto host stdio so the kernel's output and the test harness's `[TEST]/[PASS]/[FAIL]` lines appear directly on the controlling terminal. `run-virt` uses `-M virt,gic-version=3 -cpu cortex-a72 -m 1G -nographic`, with the PL011 routed onto host stdio. A green run on either board lands `30/30 passed`, 34 per-scenario free-page checkpoints (`0xbbff2` on rpi4b, `0x3be46` on virt) plus the matching boot baseline (`0xbc000` / `0x3be54`), and 0 `ERROR CAUGHT`. The boot then hands off to `/bin/login` → `/bin/fsh`; with the login lifecycle fsh's homescreen marker (`type 'help' for commands`) appears three times (two scripted `[TEST] login` sessions + the real boot login), and the CI watchdog (`scripts/run_qemu_test.sh`) counts exactly that. The free-page invariants are documented in [Documentation §8](DOCUMENTATION.md#free-page-invariants). QEMU is the authoritative inner-loop signal. The boot path matches real hardware byte-for-byte, modulo timing. ## 4. SD-card layout The Raspberry Pi 4 boots from a FAT32-formatted card whose root must contain at least: ```text config.txt # ships in this repo kernel8.img # built by `zig build` armstub8.bin # built by `zig build` bcm2711-rpi-4-b.dtb # bundled in this repo start4.elf # bundled in this repo fixup4.dat # bundled in this repo overlays/miniuart-bt.dtbo ``` The firmware blobs are bundled in this repo under `firmware/` (`bcm2711-rpi-4-b.dtb`, `start4.elf`, `fixup4.dat`, `overlays/miniuart-bt.dtbo`), taken from the official [raspberrypi/firmware](https://github.com/raspberrypi/firmware/tree/master/boot) project and kept here for convenience and license/credit clarity. The deploy step points at that directory by default: ```bash SD_BOOT=/Volumes/BOOT FIRMWARE=firmware zig build deploy ``` The deploy step reads two environment variables: | Variable | Default | Purpose | | :----------- | :---------------- | :------------------------------------------------ | | `SD_BOOT` | `/Volumes/BOOT` | SD-card mount point on macOS | | `FIRMWARE` | `firmware` | Directory holding the bundled RPi firmware files | ## 5. Serial console The kernel has three console/debug channels on the Pi: - **Mini-UART (UART1)** on GPIO 14 / 15 — main console (and fallback when USB is not enumerated). - **PL011 (UART4)** on GPIO 8 / 9 — dedicated trace channel. - **USB-C gadget console** — the interactive `fsh` console over the Pi's USB-C port; no adapter or jumper wires (see below). GPIO 14/15 is shared with the firmware on purpose. `config.txt` enables `uart_2ndstage=1` and `dtoverlay=miniuart-bt`, which routes the firmware's PL011_0 to GPIO 14/15 so the `MESS:…` lines from `start4.elf` are visible on the same cable. Once the kernel runs, `mini_uart_init` (`src/board/rpi4b/uart.flash`) reconfigures the pins to alt5 (mini-UART) — last-write on the GPIO function selector wins, so the firmware-side PL011_0 routing is silently replaced. This is a sequential handoff, not a conflict. ### UART1 pinout (RPi 4 → USB-TTL adapter) | RPi pin | Function | USB-TTL pin | | :------ | :------------ | :---------- | | Pin 6 | GND | GND | | Pin 8 | TXD (GPIO 14) | RXD | | Pin 10 | RXD (GPIO 15) | TXD | Do **not** connect VCC if the Pi is powered independently. ### Connecting on macOS The PL2303G chip is supported natively. Find the device node and open a session at 115200 baud: ```bash ls /dev/cu.usbserial-* ``` ```bash screen /dev/cu.usbserial-XXXX 115200 ``` Exit `screen` with `Ctrl-A`, then `K`, confirmed with `y`. To kill a detached `picapture` session from a second terminal, run `piquit` (see §6). ### USB-C console (single C-to-C cable) The Pi's own USB-C port doubles as the console. The kernel brings the BCM2711's DWC2 OTG controller up as a **CDC-ACM USB device** (`src/board/rpi4b/usb.flash`), so one USB-C ↔ USB-C cable to the Mac carries both **power and the interactive `fsh` console**. macOS binds its built-in `AppleUSBCDCACM` driver — nothing to install. ```bash ls /dev/cu.usbmodem* # node appears once the gadget enumerates ``` ```bash screen /dev/cu.usbmodem00011 115200 ``` Once enumerated, user/`fsh` output (the `# ` / `$ ` prompt, command output) switches from the Mini-UART to the USB console automatically; kernel `[Debug]` prints and the USB driver's own bring-up trace stay on the Mini-UART. If the gadget never enumerates (no host attached, or under QEMU, which does not emulate the DWC2 device path), the console falls back to the Mini-UART and the GPIO flow above works unchanged. The baud rate is cosmetic — there is no physical UART behind the USB device; any rate works. Keystrokes typed into `screen` reach `fsh` over USB bulk-OUT; replug / re-enumeration hardening is a known work item, so if the console wedges after replugging the cable, power-cycle the Pi. ## 6. Helper shell functions The repo ships [`flashos.env.zsh`](flashos.env.zsh) with a handful of helpers, exposed as two verb dispatchers — `pi ` (serial console) and `run ` (build, emulate, or attach) — plus `build` and `flashos`. Source it from `~/.zshrc` (`source ~/FlashOS/flashos.env.zsh`) to make them available in every shell. The legacy flat names (`picapture`, `piconnect`, `piquit`, `pilist`) remain as thin aliases for the corresponding `pi` verbs. - **`picapture [usb|mu]`** — runs the canonical boot-capture flow, logging the session to `boot.log` in the repo root (regardless of the current directory; covered by the repo `.gitignore`). - `usb` (default): waits for the CDC gadget to enumerate on `/dev/cu.usbmodem*` (plugging in the C-to-C cable powers the Pi, so the node's appearance is itself the first boot signal), then probes the console once per second until the boot marker `type 'help' for commands` appears (fsh reached its interactive REPL). - `mu`: captures the Mini-UART trace adapter (`/dev/cu.usbserial-*`) until `type 'help' for commands` (the boot reached the shell on the MU fallback — no USB host attached) or `ERROR CAUGHT` appears. Power-cycle the Pi when prompted. - Kernel faults only ever print on the MU adapter — use `mu` mode (trace adapter + external power) for fault diagnosis. - **`piconnect [usb|mu]`** — opens an interactive `screen` session on the Pi console at 115200 baud. With no argument it auto-picks the USB CDC console (`fsh`) when present, else the MU trace adapter; `usb` / `mu` force a specific channel. - **`piquit`** — terminates the detached `pi_capture` screen session started by `picapture`. Use from a second terminal. - **`pilist`** — lists attached console devices: the USB CDC console (`/dev/cu.usbmodem*`) and any USB-serial adapters (`/dev/cu.usbserial-*`, MU trace). - **`pi log`** — pages the most recent `boot.log` capture. - **`pi tail [N]`** — live-tails `boot.log` (last `N` lines, default 40), following across the next capture's log rotation. - **`build`** — runs `./build.sh` from the repo root (works from any directory): clean, link pass 1, `populate-syms`, link pass 2, diff-check the symbol layout, optionally `deploy`. `BOARD=virt build` selects the virt board (deploy is skipped); `NM=llvm-nm build` overrides the symbol-dump binary. - **`run `** — builds and runs a board, runs the boot watchdog, or attaches to hardware. `run qemu` (alias `auto`) builds and launches the rpi4b model in QEMU; `run virt` does the same for the virt board; `run test` runs the host unit tests (`run test --NAME` filters by name); `run hw` attaches to the Pi over serial (`--trace` selects the MU adapter). - **`run watchdog [virt|rpi4b]`** — runs the unattended boot watchdog with the required `-Dci-login-seed=true` and `-Dboot-selftest=true` flags applied automatically; defaults to the virt board (`rpi4b` is a slower TCG run). - **`flashos`** — lists the shell helpers defined in [`flashos.env.zsh`](flashos.env.zsh) and the available `zig build` steps — a quick inventory of targets. The MU trace adapter is auto-detected from `/dev/cu.usbserial-*` and the USB CDC console from `/dev/cu.usbmodem*`; override with `PI_SERIAL_DEVICE=/dev/cu.usbserial-XXXX` / `PI_USB_CONSOLE_DEVICE=/dev/cu.usbmodemXXXX` if multiple devices are connected. The `picapture` timeouts default to 120 s (overall) and 30 s (prompt probe); override with `PI_CAPTURE_TIMEOUT` / `PI_PROBE_TIMEOUT`. ### Auto-source on `cd` (optional) To load `flashos.env.zsh` automatically whenever the shell enters `~/FlashOS`, append a `chpwd` hook to `~/.zshrc`. The command below is idempotent: ```bash grep -q '_FLASHOS_LOADED' ~/.zshrc || cat >> ~/.zshrc <<'EOF' # --- FlashOS auto-source on cd --- autoload -Uz add-zsh-hook load_flashos_env() { if [[ "$PWD" == "$HOME/FlashOS"* && -z "$_FLASHOS_LOADED" ]]; then [[ -f "$HOME/FlashOS/flashos.env.zsh" ]] && source "$HOME/FlashOS/flashos.env.zsh" && typeset -g _FLASHOS_LOADED=1 fi } add-zsh-hook chpwd load_flashos_env load_flashos_env EOF ``` Open a new shell or run `source ~/.zshrc` to activate. Assumes the repo lives at `~/FlashOS`. ## 7. Host-side unit tests ```bash zig build test ``` Runs the host-side unit tests against pure-logic kernel modules. Each module that has tests is its own test root, linked against `tests/host_stubs.zig` (stubs for assembly-only externs). The current suite covers 41 modules (468 host tests); it finishes in well under a second and is the fastest signal that core kernel logic still holds. --- [← Prev: Documentation](DOCUMENTATION.md) · [Next: Port →](PORT.md)