Fix command-space writes, and replay libogc's real init sequence
Second protocol mismatch found the same way as the descriptor: by disassembling libogc's own driver instead of trusting our notes. COMMAND SPACE IS A 2-BYTE HEADER + DATA PHASE, not a single 4-byte transfer. CLAUDE.md said [0x40|reg, 00, 00, data] and the decoder followed it, waiting for a fourth byte and consuming the DATA byte as header byte 2 -- so every command-space WRITE was silently dropped. libogc's __bba_init writes command register 2 (its interrupt mask) at both the start and the end of init, so none of that took effect. What it actually sends: EXI_Imm (len=2, write) -> 42 00 the whole header, 0x4000 | (reg << 8) EXI_Sync EXI_ImmEx(len=1, write) -> 00 the data Three bytes between CS fall and CS rise. The device-ID read survived the wrong decode only because SPIMode3Slave answers it on a fast path independent of this FSM -- which is precisely why no test caught it: the only command-space traffic under test bypassed the decoder being tested. CHDR_2/CHDR_3 are replaced by CMD_WRITE, which takes the data byte straight after header byte 1 and auto-increments the command register so a multi-byte command write behaves like the register-space path. cmd_wreg latches the target because the write applies a cycle later, by which time cmd_reg has moved on. bba_register_file T10 writes command register 2 and reads it back, and is mutation-tested: restoring the 4-byte decode fails it. Also replaces the GUESSED init sequence in CLAUDE.md with the real transaction list. The old one was wrong in three ways that matter: libogc READS the MAC out of PAR0-5 rather than writing one (so our reset value is what the console uses), IMR is 0xDF not 0x86, and it writes RWP (0x0016/17) and RHBP (0x001A/1B) which the register file does not accept. The latter is harmless today -- RHBP is 0x000F, matching our hardcoded 15-page ring -- but a driver configuring a smaller ring would be ignored while the assembler kept writing outside it. New gc_bba_test suite 7 "libogc init" replays the sequence on hardware: cmd-space write (2-byte header, read back), MAC readable (one 6-byte PAR read, Nintendo OUI), and init replay (all 30 writes in order, then NWAYS/HIPR/NCRA.SR must still read correctly -- the old guessed header format turned a NWAYS read into an NCRA reset, so a misdecoded write is a real risk). bba.c gains bba_cmd_write/bba_cmd_read in libogc's shape. Every module testbench and bba_top at EXI_HALF=1/2/8 pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1057,13 +1057,33 @@ There are TWO address spaces, selected by bit 7 of the first byte, and **no
|
||||
length field**:
|
||||
|
||||
```
|
||||
COMMAND space (6-bit reg) one 4-byte READWRITE transfer
|
||||
write: [0x40|reg, 00, 00, data]
|
||||
read : [reg, 00, 00, xx ] <- response is the 4th byte
|
||||
COMMAND space (6-bit reg) 2-byte header, then a SEPARATE data phase
|
||||
write: [0x40|reg, 0x00] then N bytes written (libogc writes 1)
|
||||
read : [reg, 0x00] then N bytes read (device ID reads 4)
|
||||
REGISTER space (16-bit reg) 4-byte header, then a SEPARATE data phase
|
||||
read : [0x80, reg>>8, reg&0xFF, 0x00] then N bytes read (Swiss uses DMA)
|
||||
write: [0xC0, reg>>8, reg&0xFF, 0x00] then N bytes written
|
||||
```
|
||||
|
||||
⚠️ **Command space is a 2-byte header + data phase — corrected 2026-10-10.**
|
||||
This file previously said "one 4-byte READWRITE transfer,
|
||||
`[0x40|reg, 00, 00, data]`", and the decoder followed it: it waited for a
|
||||
fourth byte, consumed the DATA byte as header byte 2, and **silently dropped
|
||||
every command-space write.** libogc's `__bba_init` writes command register 2
|
||||
(its interrupt mask) at both the start and the end of init, so none of that
|
||||
would have taken effect. The real shape, from the disassembly:
|
||||
|
||||
```
|
||||
EXI_Imm (len=2, write) -> 42 00 the WHOLE header
|
||||
EXI_Sync
|
||||
EXI_ImmEx(len=1, write) -> 00 the data
|
||||
```
|
||||
|
||||
Three bytes between CS fall and CS rise, not four. `0x4200 = 0x4000 |
|
||||
(reg << 8)`. The device-ID read survived the wrong decode only because
|
||||
`SPIMode3Slave` answers it on a fast path independent of the FSM — which is
|
||||
exactly why no test caught it. `bba_register_file` **T10** now writes command
|
||||
register 2 and reads it back, and is mutation-tested against the 4-byte decode.
|
||||
NCRA, IR, NWAYS, RWP, RRP, PAR… all live in the REGISTER space
|
||||
(`bba_in8`/`bba_out8`/`bba_ins`/`bba_outs`). Only the device-ID query uses a
|
||||
short header — the single case where the two formats coincide.
|
||||
@@ -1292,21 +1312,60 @@ unwritten cell; real SPRAM has no such tell.
|
||||
|
||||
---
|
||||
|
||||
## GC Initialisation Sequence (Swiss/BBA driver)
|
||||
## GC Initialisation Sequence — read out of libogc, 2026-10-10
|
||||
|
||||
```
|
||||
1. Write 0x0000 × 2, read 4 B → must get 0x04020200 (device ID)
|
||||
2. Write NCRA = 0x01 (reset, self-clears; resets W5500 + SPRAM ptrs)
|
||||
3. Poll NCRA bit 0 until 0 (wait reset complete)
|
||||
4. Write PAR0–5 (MAC address)
|
||||
5. Write MAR0–7 = 0xFF (promiscuous multicast)
|
||||
6. Write ANALOG = 0xD6 (enable PHY — no FPGA effect, just store)
|
||||
7. Write NWAYC (autoneg config — store only)
|
||||
8. Write IMR = 0x86 (enable RBFI | TI | RI interrupts)
|
||||
9. Write GCA (AUTOPUB bit)
|
||||
10. Write NCRA SR bit = 0x08 (start receive)
|
||||
11. Poll NWAYS until link up → return hardcoded 0x17 immediately
|
||||
```
|
||||
⚠️ **The sequence below replaces a GUESSED one.** The old version said the
|
||||
driver *writes* PAR0–5, writes `MAR0–7 = 0xFF`, `ANALOG = 0xD6`, `NWAYC`,
|
||||
`GCA` and `IMR = 0x86`. Disassembling libogc's `__bba_init`
|
||||
(`/opt/devkitpro/libogc/lib/cube/libbba.a`, `gcif.o`) shows otherwise. This is
|
||||
the actual transaction list, in order:
|
||||
|
||||
| # | transaction | note |
|
||||
|---|---|---|
|
||||
| 0 | **cmd** write reg 2 = `0x00` | interrupt mask (IRMASKALL) |
|
||||
| 1 | reg `0x0060` = `0x00` | |
|
||||
| 2 | **cmd** read reg `0x0F` | |
|
||||
| 3 | reg `0x0000` = `0x01` | **NCRA reset** |
|
||||
| 4 | reg `0x0000` = `0x00` | |
|
||||
| 5 | **cmd** read reg `0x01` | |
|
||||
| 6,7 | **cmd** writes reg `0x04`, `0x05` | |
|
||||
| 8–15 | reg `0x0058`–`0x005F` = `80 00 03 83 32 FE 1F 1F` | PHY/analog block |
|
||||
| 16,17 | reg `0x0001` = `0x52`, `0x0060` = `0x74` | |
|
||||
| 18,19 | reg `0x0014` = `0x00`, `0x0015` = `0x06` | |
|
||||
| 20 | reg `0x0050` = `0x80` | |
|
||||
| 21–24 | reg `0x000C/0D` = `0x00`, `0x000A` = `0x01`, `0x000B` = `0x00` | |
|
||||
| 25,26 | reg `0x001A/1B` = `0x000F` | **RHBP** — ring high boundary = page 15 |
|
||||
| 27,28 | reg `0x0016/17` = `0x0001` | **writes RWP** |
|
||||
| 29,30 | reg `0x0018/19` = `0x0001` | **writes RRP** |
|
||||
| 31 | reg `0x0032` = `0x08` | |
|
||||
| 32 | reg `0x0000` = `0x08` | **NCRA SR — start receive** |
|
||||
| 33–36 | reg `0x0030` read, then writes `0x04`, `0x0C` | |
|
||||
| 37 | reg `0x0020`, **6-byte READ** | **reads PAR0–5 — the MAC** |
|
||||
| 38 | reg `0x0009` = `0xFF` | IR: clear all |
|
||||
| 39 | reg `0x0008` = `0xDF` | **IMR — not `0x86`** |
|
||||
| 40 | **cmd** write reg 2 = `0xF8` | IRMASKNONE (enable) |
|
||||
|
||||
Three corrections that matter:
|
||||
- **The driver READS the MAC from PAR0–5; it does not write one.** So the
|
||||
PAR0–5 reset value is what the console will use — keep a valid Nintendo OUI
|
||||
there (`00:09:BF:00:00:01`). It is a single 6-byte register-space read.
|
||||
- **`IMR` is `0xDF`**, and `IR` is cleared with `0xFF` first.
|
||||
- **It writes `RWP` (0x0016/17) and `RHBP` (0x001A/1B)**, which the register
|
||||
file does not accept. Harmless today — `RHBP` is `0x000F`, matching our
|
||||
hardcoded 15-page ring, and the NCRA reset already puts `RWP` at page 1 —
|
||||
but it is a real divergence: a driver that configured a SMALLER ring would
|
||||
be ignored, and our assembler would write outside it.
|
||||
- Registers `0x0058`–`0x005F`, `0x0060`, `0x0001`, `0x0014/15`, `0x0050`,
|
||||
`0x000A`–`0x000D`, `0x0030`, `0x0032` are not decoded. Writes fall through
|
||||
the `Switch` with no `Case`, so they are ignored rather than aliased —
|
||||
which is the safe failure, but worth knowing when a driver expects one back.
|
||||
|
||||
`gc_bba_test` suite 7 (**libogc init**) replays this list on hardware and
|
||||
checks the shapes: `cmd-space write` (2-byte header + data phase, read back),
|
||||
`MAC readable` (one 6-byte PAR read, Nintendo OUI), and `init replay` (all 30
|
||||
register writes in order, then NWAYS/HIPR/NCRA.SR must still be correct — the
|
||||
old guessed header format turned a NWAYS read into an NCRA reset, so a
|
||||
misdecoded write is a real risk, not a theoretical one).
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user