From e10849f73d06ec899879eb21f59b0f5e7a53baf3 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Sat, 10 Oct 2026 11:40:55 +0200 Subject: [PATCH] docs: track the BBA compatibility gaps as TODOs Records the differences between this implementation and what real GC software expects, with the evidence and the driver source for each, so they are worked through rather than rediscovered. Open items: no hardware RX filtering, hardcoded ring geometry, NCRB undecoded, and registers that read 0x00 where real hardware has non-zero defaults. Also records what is deliberately NOT to be implemented (the device-initiated challenge/response) and that two of our registers (TWD, HIPR) do not exist on real hardware. Notes explicitly that games use the official Nintendo SDK driver, which none of the three readable drivers is, so "no open driver does this" is weaker evidence than "the hardware has no such register". Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 19 ++++++ docs/bba_compat_todo.md | 142 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 docs/bba_compat_todo.md diff --git a/CLAUDE.md b/CLAUDE.md index 1e7d388..654c794 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1334,6 +1334,25 @@ parallel master is the default — see "W5100 vs W5500".) --- +## ⚠️ Open compatibility gaps — see `docs/bba_compat_todo.md` + +Known differences between this implementation and what real GC software +expects, with the evidence for each, are tracked in +**`docs/bba_compat_todo.md`**. Summary of what is still OPEN: + +1. **No hardware RX filtering** — we take every frame on the segment; the real + BBA delivers own-MAC + broadcast. Causes the ring pressure already seen on + hardware. Needs a decision, not just code. +2. **Ring geometry hardcoded** — `RHBP`/`BP`/`TLBP` writes ignored; a game + using a smaller RX ring corrupts silently. +3. **`NCRB` (0x01) not decoded** — filtering and interrupt-coalescing bits. +4. **Registers reading `0x00` that have non-zero hardware defaults** — + `NWAYC` `0x84`, `MISC` `0x3C`, and the rest of the map. + +Keep that file updated in the same commit whenever one is closed. + +--- + ## ⚠️ Which driver actually talks to us — and the TWO interrupt levels **Swiss has several ethernet back-ends, and `cube/patches/bba/` holds one file diff --git a/docs/bba_compat_todo.md b/docs/bba_compat_todo.md new file mode 100644 index 0000000..a0a4636 --- /dev/null +++ b/docs/bba_compat_todo.md @@ -0,0 +1,142 @@ +# BBA compatibility TODO + +Gaps between this implementation and what real GC software expects, found by +reading the three drivers that actually drive a BBA: + +- **Swiss (in-game patches)** — `cube/patches/bba/bba.c` in + [emukidid/swiss-gc](https://github.com/emukidid/swiss-gc). Note that + directory holds one file per adapter (`bba.c` is the real DOL-015; + `enc28j60.c`, `w5500.c`, `w6100.c`, `w6300.c` are other hardware) — check + which file you are in. +- **libogc2** — `lwip/arch/gc/netif/gcif.c` in + [extremscorner/libogc2](https://github.com/extremscorner/libogc2). This is + what Swiss's own menu networking uses, via `if_configex()`. +- **stock devkitPro libogc** — same shapes as libogc2; read out of + `/opt/devkitpro/libogc/lib/cube/libbba.a` (`gcif.o`) with `objdump`. + +⚠️ **Games use the official Nintendo SDK driver, which none of us can read.** +Everything below is derived from the three open drivers plus the register map +they document. A game may exercise a mechanism no open driver touches, so +"no open driver does this" is weaker evidence than "the hardware has no such +register". + +The register map referenced throughout is the one in libogc2's `gcif.c` +(`BBA_*` defines), which is the official one. + +--- + +## 1. No hardware RX filtering — **the consequential one** + +**Status: OPEN. Needs a decision, not just code.** + +The real BBA filters received frames. libogc2 configures +`NCRB = AB|CA|2_PACKETS_PER_INT` = `0x52`, i.e. `PR=0` (not promiscuous), +`PM=0` (no multicast), `AB=1` (accept broadcast) — so the device delivers +**own-MAC + broadcast only**. + +We run the W5100 in MACRAW with the **MF (MAC filter) bit clear** +(`_S0_MR_MACRAW = 0x04`, `w5100_parallel_master.py`), so **every frame on the +segment enters the SPRAM ring.** + +Observed consequence, already seen on hardware: the test app's ring filled to +page 15 from what looked like "ambient broadcast traffic" but was in fact all +unicast traffic on the segment too. Ring pressure makes RBFI drop frames we +DO want, and a game's lean driver may not expect foreign unicast at all. + +⚠️ **Setting MF is not a one-line fix.** MF filters against the W5100's +`SHAR`, and we write `SHAR` only during the init sequence. libogc2 *reads* the +MAC out of PAR0–5 and never writes it, so SHAR never diverges there — but a +game that WRITES PAR0–5 would leave SHAR stale and MF would then drop +everything. Doing it properly needs: + +1. decode `NCRB` (see item 3) so `PR`/`PM` can steer the filter; +2. refresh `SHAR` when PAR0–5 changes, not only at init; +3. re-open socket 0 when the filter mode changes (`Sn_MR` is sampled at OPEN). + +**The decision:** filter correctly (matches real hardware, fixes the ring +pressure) at the cost of that machinery, or stay a permissive superset where +nothing is ever lost but the ring fills with other hosts' traffic. + +## 2. Ring geometry is hardcoded — silent corruption risk + +**Status: OPEN.** + +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.** + +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. + +## 3. `NCRB` (0x01) is not decoded at all + +**Status: OPEN.** Bits, from libogc2: + +| bit | name | meaning | +|---|---|---| +| 0 | `PR` | promiscuous mode | +| 1 | `CA` | capture effect mode | +| 2 | `PM` | pass multicast | +| 3 | `PB` | pass bad frame | +| 4 | `AB` | accept broadcast | +| 6–7 | `RXINTC` | RX interrupt counter: 1 / 2 / … packets per interrupt | + +We interrupt per frame where libogc2 asks for every 2 packets, which is benign +(more interrupts than requested). `PR`/`PM`/`AB` feed item 1. + +## 4. Registers that read `0x00` but have non-zero hardware defaults + +**Status: OPEN.** A driver doing read-modify-write, or sanity-checking a +default, sees zero. libogc2 *does* read `NWAYC` (0x30) before writing it. + +| reg | name | hardware default | +|---|---|---| +| 0x01 | `NCRB` | — | +| 0x04 | `LTPS` | last transmitted packet status, RO | +| 0x05 | `LRPS` | last received packet status, RO | +| 0x0A/0B | `BP` | boundary page pointer | +| 0x0C/0D | `TLBP` | TX low boundary page | +| 0x0E/0F | `TWP` | TX buffer write page | +| 0x12/13 | `TRP` | TX buffer read page | +| 0x14/15 | `RXINTT` | RX interrupt timer | +| 0x1A/1B | `RHBP` | RX high boundary page | +| 0x30 | `NWAYC` | **0x84** | +| 0x32 | `GCA` | 0x00 | +| 0x3D | `MISC` | **0x3C** | +| 0x3E/3F | `TXFIFOCNT` | TX FIFO counter, RO | +| 0x50 | `MISC2` | 0x00 | +| 0x58–0x5F | SI regs | libogc2 writes `80 00 03 83 32 FE 1F 1F` | +| 0x60 | `SI_ACTRL2` | libogc2 writes `0x00` then `0x74` | + +## 5. Two of our registers do not exist on real hardware + +**Status: KNOWN, low risk, do not "fix" blindly.** + +`0x34`/`0x35` (`TWD_LO`/`TWD_HI`, our TX length latch) and `0x3A` (`HIPR`, +returning `0x01`) are **not in the official register map** — `0x3A` is +unassigned there and `MISC` is `0x3D`. They are our own inventions, and it is +now confirmed that **no driver writes TWD** (the TX length is counted at +TXDATA instead — see CLAUDE.md). They are harmless unless a game writes those +addresses for some other purpose and trips our TX-length latch. + +Removing them would break `gc_bba_test`, which does write TWD, so keep them +but do not add more inventions. + +## 6. Lower risk / no action + +- **Challenge/response** (command regs 0x08/0x09/0x0B) is **device-initiated** + — gated on command status bit `0x10`, which we never raise, so the path + cannot be entered. Do NOT implement `bba_calc_response` emulation. +- **No EEPROM**: the MAC is a fixed reset value in PAR0–5. Since libogc2 reads + the MAC from there rather than writing it, that value is what the console + uses. Games keying anything off the MAC see ours. +- **`RXINTT`** (RX interrupt timer) is interrupt coalescing; ignoring it means + we interrupt earlier than asked, which is safe. +- **Command register `0x0F`** may be a reset strobe rather than a register: + `bba_cmd_in8_slow(0x0F)` holds CS for 200 µs between two 10 ms delays and + discards the result. Implemented as storage; the NCRA reset follows + immediately in that sequence so ignoring any side effect costs nothing + today.