# re-bba-rb — on-console test suite (GameCube) A `.dol` you run from Swiss that drives the adapter in Serial Port 1 and reports pass/fail per test on the TV. ## Build Needs devkitPro's `gamecube-dev` group (devkitPPC + libogc). The devcontainer installs it — if `DEVKITPPC` is unset, **rebuild the container**, since the toolchain is added in `.devcontainer/Dockerfile`. ```bash make # -> gc_bba_test.dol ``` Copy the `.dol` to your SD card (SD Gecko / SD2SP2) and launch it from Swiss. ## Controls | Button | | |---|---| | D-pad up/down | move | | A | open a suite / rerun the selected test | | X | run the selected suite | | START | run everything | | Y | clear all results | | B | back | ## What it tests | Suite | Proves | |---|---| | Identity | device enumerates: `04 02 02 00`, HIPR, NWAYS | | Registers | PAR0–5, IMR writable and read back; IR is write-1-clear | | Reset/control | NCRA RESET self-clears, SR holds | | EXI integrity | 200 reads with no bit errors; which EXI clock indices work | | Ethernet | MAC readback, 60-byte ARP transmit (TI), frame receive (RWP/RI) | | SPRAM/DMA | ring descriptor is well-formed; reads ≥ 0x100 stream until CS | ## Things that will bite you **The device ID is a ONE-SHOT.** Address 0 returns `04 02 02 00` only until the first NCRA reset clears `id_phase` in the gateware; after that it reads back NCRA. That is correct — it matches the GC's own init order — but it means the Identity test can only pass once per *console power cycle*. Run it before the Reset suite. On a re-run the app reports SKIP with an explanation rather than a misleading failure. **TI is not proof of transmission.** It only means `TXFrameDrain` handed the frame to the W5100. A frame can set TI and never reach the wire. The TX test says so; to confirm the wire, capture on a PC or watch the RX test pick up a reply. **The RX test needs traffic.** It waits ~2 s for any frame. On an isolated link with nothing talking it will fail legitimately — give the PC an address on the same subnet (the ARP the TX test sends is aimed at `10.0.0.50`, so `sudo ip addr add 10.0.0.50/24 dev ` makes the PC answer it and both tests pass together). **Never over-clock a register read.** Asking for N bytes and clocking more leaves stale prefetched bytes queued and desynchronises the *next* transaction — a deterministic every-Nth-read corruption that looks like a signal-integrity fault. `bba_read()` clocks exactly `len`; don't "round up" for convenience. **TXDATA takes one byte per transaction.** The register file's effective address auto-increments per data byte, so a 4-byte write to 0x48 would spill into 0x49–0x4B. `bba_tx_bytes()` does it correctly. ## Build status Builds clean (no warnings) with devkitPPC 16.1.0 / GCC 16 in the devcontainer: `gc_bba_test.dol`, ~172 KB, entry `0x80003100`. `.gitignore` excludes the build products — run `make` to produce the `.dol`. ## Self-update over the BBA (Z button) Press **Z**, then on the host: ```bash sudo ./scripts/send-dol.py gc_bba_test/gc_bba_test.dol ``` The flow is deliberately **receive → write to SD → boot Swiss**: 1. The new build arrives over the adapter into a staging buffer. 2. It is written to the SD card (default `/gc_bba_test.dol`, override with `--dest`) and **read back and compared**. 3. The app then boots **`/boot.dol`** — your known-good Swiss. **The received image is never launched.** The only thing this ever jumps into is Swiss, which was already known good. So a broken test build costs nothing: Swiss comes up, and you load whatever you like from there, including the previous build. That is what makes the update recoverable without a power cycle or digging the SD card out. If the SD write or the verify fails, nothing is launched at all — you stay in the menu with a working app. `sudo` is needed for `CAP_NET_RAW`. The GC's MAC is discovered from its first ACK, so neither side needs an address configured. ### Why it is not IP Frames carry a private ethertype (`0x88B5`, IEEE 802 local experimental 1) and a 12-byte header: no ARP, no IP stack, no checksums, and no libogc network driver competing for the EXI channel. It reuses exactly the two paths the test suite already proves on hardware — `bba_tx_bytes()` for TX and the SPRAM ring at RRP for RX — so each update doubles as a few hundred round trips of soak testing through that path. ### Stop-and-wait is load-bearing The gateware does not consume RRP, so `RXFrameAssembler` can overwrite ring pages the GC has not read yet — there is no overflow protection. One block in flight means the ring never holds more than one unread frame. Do **not** turn this into a sliding window without adding RBFI-style protection first. ### SD interfaces `netboot_sd_write()` auto-detects, trying SD2SP2, then SD Gecko slot B, then slot A. Note the SD Gecko in **slot A shares EXI channel 0 with the BBA** (channel 0, device 2); libogc arbitrates, but SD2SP2 sits on its own channel and avoids the question. ### Why the copier lives in assembly at the top of MEM1 A DOL's sections load from 0x80003100 upward — exactly where this program's own `.text` sits. A copier compiled as an ordinary C function would **overwrite its own instructions partway through the copy** and hang. So: - `netboot_reserve()` carves the top of MEM1 and hands it to `SYS_SetArena1Hi()`, so neither libogc nor `malloc` will touch it. - `netboot_stub.S` is copied into that region and executed *there*. It must stay strictly position independent: no globals, no literal pool, no TOC references, no calls — otherwise it reads from the address it was *linked* at instead of the one it is *running* at. - `netboot_launch()` checks every section (and BSS) against the reserved region and **refuses** if any would land on it. ### Failure behaviour - 30 s with no frames, or 5 s mid-transfer, aborts back to the menu. - A duplicate block is re-ACKed rather than ignored, so a lost ACK recovers. - `netboot_receive()` re-asserts the NCRA SR bit first: an earlier test may have left receive disabled, and it would otherwise wait forever for frames the gateware is not storing. - A failed SD write or verify launches nothing. - The residual risk is confined to booting `/boot.dol`. If that is a healthy Swiss, this is the safe part; if it is missing or corrupt, you get a message and stay in the menu — the new build is already on the card either way.