Honour the GC's ring geometry (BP / RHBP) instead of hardcoding it

Compat TODO item 2, the last silent-corruption risk.  RXFrameAssembler took
the RX ring as a compile-time constant (pages 1-15), so a driver configuring a
SMALLER ring was ignored and we kept writing pages it would never read, with
nothing reporting an error.  The bounds now come from bp/rhbp, wired in BBATop
from the GC's BP (0x0A/0B) and RHBP (0x1A/1B) registers, and both the page wrap
and the free-space calculation use them.

Garbage is rejected rather than obeyed: the driver's values are used only when
bp >= 1 (page 0 is the TX buffer), rhbp <= 0x0F and rhbp > bp.  Otherwise the
hardcoded layout stands and geom_honoured goes low, so a mismatch is
observable.  Without that check BP=0 or an RHBP below BP gives a zero or
negative ring size and the assembler writes outside it.

⚠️ Recorded as a known limitation: a ring must be LARGER than
_MAX_FRAME_PAGES (6) or nothing is ever stored, because a frame's length is
unknown until EOF so the worst case (1518+4 = 6 pages) is reserved up front.
Found the honest way -- the test was written with RHBP=4 first and passed
VACUOUSLY, storing nothing at all.  Making that work needs a length-aware
reservation, which is a bigger change than this one.

T7 (8-page ring honoured, frames only in pages 1-8, and at least one actually
stored) and T7b (all three garbage cases rejected, fallback still stores).
Mutation-tested: wrapping at the hardcoded page 15 writes pages 9-11 and fails
T7; removing the sanity check fails all four T7b assertions.

Item 1 (RX filtering) remains the only open gap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 14:06:04 +02:00
parent 4a455bbe88
commit 147ef8fcfc
4 changed files with 155 additions and 29 deletions
+29 -9
View File
@@ -59,17 +59,37 @@ nothing is ever lost but the ring fills with other hosts' traffic.
## 2. Ring geometry is hardcoded — silent corruption risk
**Status: OPEN.**
**Status: ✅ DONE 2026-10-10.** `RXFrameAssembler` takes the ring bounds from
`bp`/`rhbp`, wired from the GC's `BP` (0x0A/0B) and `RHBP` (0x1A/1B) registers
in `BBATop`. The wrap and the free-space calculation both use them, so a
driver's smaller ring is respected instead of ignored.
We fix TX = page 0 and RX = pages 1–15 (`_RX_PAGE_FIRST`, `_RX_PAGE_LAST`,
`_PAGES_TOTAL`). libogc2 writes `TLBP = 0x0000`, `BP = 0x0001`,
`RHBP = 0x000F`, which matches us exactly — but **we ignore those writes.**
**Garbage is rejected, not obeyed.** The bounds are used only when
`bp >= 1` (page 0 is the TX buffer), `rhbp <= 0x0F` and `rhbp > bp`;
otherwise the hardcoded pages 1–15 stand and `geom_honoured` goes low, so a
mismatch is observable rather than silent. Without that check, `BP = 0` or an
`RHBP` below `BP` gives a zero or negative ring size and the assembler writes
outside it.
A game choosing a smaller RX ring (`RHBP < 0x0F`) would read pages the
assembler is still writing, with nothing reporting an error. The minimum
honest fix is to honour `RHBP`/`BP`, or failing that to detect a value that
disagrees with our layout and surface it (a status bit, or at least the UART
console) rather than corrupt silently.
⚠️ **A ring must be LARGER than `_MAX_FRAME_PAGES` (6) or nothing is ever
stored.** A frame's length is unknown until EOF, so the assembler reserves the
worst case (1518 + 4 bytes = 6 pages) before accepting one. With a 4-page ring
`free` can never reach 6, so RBFI drops every frame — correct, conservative,
and useless. This was found by writing the test with `RHBP = 4` first, where it
passed **vacuously** because nothing was stored at all. If a real driver ever
configures a ring that small, the reservation has to become length-aware
(accept a frame whose actual length fits), which is a bigger change than this
one.
Tests: `rx_frame_assembler` **T7** (an 8-page ring is honoured, frames land
only in pages 1–8) and **T7b** (all three garbage cases rejected, and the
fallback layout still stores). Mutation-tested: wrapping at the hardcoded page
15 writes pages 9–11 and fails T7; removing the sanity check fails all four
T7b assertions.
For the record, libogc2 writes `TLBP = 0x0000`, `BP = 0x0001`,
`RHBP = 0x000F` — exactly the old hardcoded layout, which is why this never
showed up in practice.
## 3. `NCRB` (0x01) is not decoded at all