Files
rebbarb/docs/bba_compat_todo.md
T
Roflin 4a455bbe88 Decode NCRB and the registers with non-zero hardware defaults
Compat TODO items 3 and 4.  Every register libogc2 writes now stores writes and
reads back, with the documented hardware reset defaults: NCRB (0x01), BP
(0x0A/0B), TLBP (0x0C/0D), RXINTT (0x14/15), RHBP (0x1A/1B), NWAYC (0x30,
default 0x84), GCA (0x32), MISC (0x3D, default 0x3C), MISC2 (0x50).  BP and
RHBP reset to OUR layout (page 1, page 15) so a driver reading them back
without writing sees something consistent with where the ring actually is.

Previously all of these read 0x00 and dropped writes, so a driver doing
read-modify-write or sanity-checking a default saw zero -- and libogc2 does
read NWAYC before writing it.

Deliberately still reading 0x00: LTPS, LRPS, TWP, TRP, TXFIFOCNT.  Those are
STATUS registers, not configuration, so storing what was written to them would
be a lie rather than a default.  Drivers read LRPS/LTPS only on error bits and
0x00 reads as "no error", which is truthful for a device that does not detect
them.

NCRB, BP and RHBP are also exported as BBARegisterFile.ncrb/.bp/.rhbp for the
RX-filtering work (item 1) and the ring-geometry check (item 2).  Nothing
consumes them yet -- the decode lands first so the behaviour that uses it is a
separate, testable change.

T15 checks the defaults, replays libogc2's twelve init writes in its order and
reads every one back, and asserts the NCRB export is 0x52 with PR=0, PM=0,
AB=1 -- i.e. that the real BBA delivers own-MAC + broadcast only, which is the
fact item 1 has to act on.  Mutation-tested: dropping NWAYC's 0x84 default
fails it, as does dropping NCRB writes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-10 14:02:28 +02:00

165 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: ✅ DECODED 2026-10-10** — stored, readable, and exported as
`BBARegisterFile.ncrb`. Nothing consumes the bits yet; that is item 1.
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.
`bba_register_file` **T15** asserts the export is `0x52` with `PR=0`, `PM=0`,
`AB=1` — i.e. the real BBA would deliver own-MAC + broadcast only — which is
the fact item 1 has to act on.
## 4. Registers that read `0x00` but have non-zero hardware defaults
**Status: ✅ DONE 2026-10-10** for every register libogc2 writes. They now
store writes and read back, with the documented hardware defaults at reset.
`bba_register_file` **T15** checks the defaults, replays libogc2's twelve
init writes and reads every one back; mutation-tested (dropping `NWAYC`'s
`0x84` default fails it, as does dropping `NCRB` writes).
Still reading `0x00`, deliberately: `LTPS` (0x04), `LRPS` (0x05), `TWP`
(0x0E/0F), `TRP` (0x12/13) and `TXFIFOCNT` (0x3E/3F). These are **status**
registers, not configuration — a driver reads them to learn what the device
did, so storing what was written to them would be a lie rather than a default.
Drivers read `LRPS`/`LTPS` only on error bits, and `0x00` reads as "no error",
which is the truthful answer for a device that does not detect those errors.
`TWP`/`TRP`/`TXFIFOCNT` would need the TX engine to actually report its page
pointers and FIFO depth; worth doing if a driver is ever seen to poll them.
The original problem for reference — a driver doing read-modify-write, or
sanity-checking a default, saw 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.