From a2e8e5ceafca902b172e6ff5ade93325872c3511 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Fri, 9 Oct 2026 19:13:28 +0200 Subject: [PATCH] Batch TXDATA writes; record netboot working and the +1 offset as open Self-update is proven end to end on console: 300076 bytes in 294 blocks, verify OK, /boot.dol boots, and new builds are now delivered this way instead of by SD card. bba_tx_bytes() wrote TXDATA byte by byte with bba_w8(), i.e. a full EXI_Lock / EXI_Select / header / EXI_Deselect / EXI_Unlock PER BYTE -- about sixty transactions to send one 60-byte ACK. Hardware showed txto=0, so every ACK did reach the W5100 with TI set; the ~1/3 of blocks the host retried were not transmit failures but ACKs that missed the host's timeout. The data phase of a register-space write runs until CS, so there was never a reason to pay the per-byte cost. It now streams in 64-byte chunks, one transaction each (64 is bba_write()'s buffer limit, not a protocol limit). t_tx() benefits equally. CLAUDE.md records the facility, including that it CANNOT be driven from the devcontainer -- raw ethernet is link-local and the container has a veth, so the sender must run on a host holding the physical NIC. Also records the +1 descriptor offset as OPEN, with what has been ruled out: not overflow (lapped=0 under RBFI), not the two dropped-write handshake bugs (fixed and mutation-tested), not write-port contention (ETH has absolute priority so the port is never stalled; T5 models a stall anyway and the layout holds), and not the late-response leak (guarded; ratio unchanged at desc@+0=100 +1=343). And names the test nobody has written: a bba_top-level EXI DMA read from the ring WHILE the ethernet side writes into it. ETH preempts EXI reads, so only that overlap can delay a response past its transaction. Write it before guessing again. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 50 ++++++++++++++++++++++++++++++++++++++++ gc_bba_test/source/bba.c | 22 +++++++++++++++--- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ecfbb2c..f07c105 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -226,6 +226,56 @@ RX + 2 KB TX buffer at reset (`RMSR/TMSR=0x55`), so socket-3 frames are stored in the chip independently — no extra FPGA buffer needed. Register addresses are still datasheet-from-memory — **confirm at hardware bring-up**. +### ✅ Self-update over the BBA works on hardware (2026-10-09) + +Press **Z** in `gc_bba_test` and run `sudo ./scripts/send-dol.py +` on a Linux host on the same segment. The build is received over the +adapter, written to the SD card, **read back and verified**, rotated in (the +previous build is kept as `.prev.dol`), and then the app boots +**`/boot.dol`** — a known-good Swiss. Nothing received is ever launched +directly, so a broken test build costs nothing. Proven end to end on console: +300076 bytes in 294 blocks, verify OK, Swiss boots, and new builds are now +delivered this way instead of by SD card. + +Details and the recovery ladder are in `gc_bba_test/README.md`. Key points: +- **Raw ethertype `0x88B5`, not IP** — no ARP, no IP stack, no libogc network + driver competing for the EXI channel. It reuses only `bba_tx_bytes()` and the + SPRAM ring, so each update also soak-tests the BBA data path. +- **It cannot be driven from the devcontainer.** Raw ethernet is link-local, so + the sender must run on a host with the physical NIC (the container has a + veth and the frames go nowhere). +- **Stop-and-wait is load-bearing** — see the RBFI note; one frame in flight + means the ring is never contended. +- `bba_tx_bytes()` streams in 64-byte chunks, ONE EXI transaction each. Writing + byte-by-byte cost a full EXI_Lock/Select/Deselect/Unlock per byte — ~60 + transactions per ACK — and that latency, not any transmit failure, is what + made ~⅓ of blocks retry (`txto=0` on hardware: TI always set). + +### ⚠️ OPEN: ring descriptor sometimes lands at page offset +1 + +On console the SPRAM ring descriptor is at page+0 for ~23% of frames and +page+1 for ~77% (`desc@+0=100 +1=343` in one run), **varying frame to frame**. +`netboot`'s reader probes both offsets and takes whichever yields a plausible +length, so it is worked around, not fixed. + +What has been ruled out: +- **Not ring overflow** — `lapped=0` with RBFI active. +- **Not the two dropped-write handshake bugs** in `RXFrameAssembler` (fixed, + mutation-tested; T2 would catch a recurrence). +- **Not write-port contention** — `eth_wr_ready` is asserted combinationally + whenever `eth_wr_valid` is high (ETH has absolute priority in + `SPRAMArbiter`), so the write port is never stalled. T5 models a stall + anyway and the layout is unchanged. +- **Not the late-SPRAM-response leak** — guarding on `outstanding != 0` and + clearing `sp_pending` in `HEADER0` fixed a real latent bug but left the + ratio unchanged. + +⚠️ **THE MISSING TEST:** a `bba_top`-level test that performs an EXI DMA read +from the SPRAM ring **while** the ethernet side writes frames into it. ETH +preempts EXI reads, so only under that overlap can a read response be delayed +past the end of its transaction. No simulation does this, which is why the +offset is still unexplained. Write that before guessing again. + ### ⚠️ Simulation rate — always re-run at the hardware rate `bba_top`'s testbench clocks EXI at `27 MHz / EXI_HALF`. The default diff --git a/gc_bba_test/source/bba.c b/gc_bba_test/source/bba.c index b9bd313..8640913 100644 --- a/gc_bba_test/source/bba.c +++ b/gc_bba_test/source/bba.c @@ -123,10 +123,26 @@ int bba_stream(u16 addr, void *buf, u32 len) int bba_tx_bytes(const u8 *buf, u32 len) { - u32 i; - for (i = 0; i < len; i++) { - int rc = bba_w8(REG_TXDATA, buf[i]); + u32 off = 0; + + /* Stream in 64-byte chunks, ONE EXI transaction each. + * + * This used to write byte by byte with bba_w8(), which means a full + * EXI_Lock / EXI_Select / header / EXI_Deselect / EXI_Unlock per BYTE -- + * about sixty transactions to send one 60-byte ACK. That latency is why + * roughly a third of netboot blocks were retried: the ACK was always + * transmitted (txto=0 on hardware, so TI always set) but did not get back + * to the host inside its timeout. The data phase of a register-space + * write runs until CS, so there is no reason to pay the per-byte cost. + * + * 64 is bba_write()'s internal buffer limit, not a protocol limit. */ + while (off < len) { + u32 n = len - off; + int rc; + if (n > 64) n = 64; + rc = bba_write(REG_TXDATA, buf + off, n); if (rc < 0) return rc; + off += n; } return 0; }