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:
@@ -389,6 +389,109 @@ static int t_rx(char *m, size_t n)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ─────────────────── suite 7: libogc init sequence ─────────────────── */
|
||||
/* These replay what libogc's own BBA driver does, in its real transaction
|
||||
* shapes, rather than what our docs said it does. The whole sequence was
|
||||
* read out of /opt/devkitpro/libogc/lib/cube/libbba.a (gcif.o, __bba_init) --
|
||||
* see CLAUDE.md. Two of the mismatches it exposed were silent:
|
||||
* - command space is a 2-byte header + data phase, not a 4-byte transfer,
|
||||
* so every command-space write was being dropped; and
|
||||
* - libogc READS the MAC out of PAR0-5 rather than writing it. */
|
||||
|
||||
static int t_cmd_write(char *m, size_t n)
|
||||
{
|
||||
int rc, rb;
|
||||
/* Command register 2 is libogc's interrupt mask: it writes 0x00
|
||||
* (IRMASKALL) at the start of init and 0xF8 (IRMASKNONE) at the end. */
|
||||
if ((rc = bba_cmd_write(0x02, 0xF8)) < 0) {
|
||||
snprintf(m, n, "cmd write failed: EXI error %d", rc); return 1;
|
||||
}
|
||||
if ((rb = bba_cmd_read(0x02)) < 0) {
|
||||
snprintf(m, n, "cmd read failed: EXI error %d", rb); return 1;
|
||||
}
|
||||
if (rb != 0xF8) {
|
||||
snprintf(m, n, "wrote 0xF8, read back 0x%02X "
|
||||
"(2-byte cmd header not honoured?)", rb);
|
||||
return 1;
|
||||
}
|
||||
/* Put it back the way libogc leaves it at the start of init. */
|
||||
bba_cmd_write(0x02, 0x00);
|
||||
rb = bba_cmd_read(0x02);
|
||||
snprintf(m, n, "0xF8 then 0x00 read back (0x%02X)", rb);
|
||||
return rb == 0x00 ? 0 : 1;
|
||||
}
|
||||
|
||||
static int t_mac_is_readable(char *m, size_t n)
|
||||
{
|
||||
u8 mac[6];
|
||||
/* libogc does ONE 6-byte register read of 0x0020 and uses whatever it
|
||||
* finds as the console's MAC -- it does not write one. So PAR0-5 must
|
||||
* hold a sane address out of reset, and a 6-byte read must work. */
|
||||
if (bba_read(REG_PAR0, mac, 6) < 0) {
|
||||
snprintf(m, n, "6-byte PAR read failed"); return 1;
|
||||
}
|
||||
snprintf(m, n, "%02X:%02X:%02X:%02X:%02X:%02X%s",
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
|
||||
(mac[0] == 0x00 && mac[1] == 0x09 && mac[2] == 0xBF)
|
||||
? " (Nintendo OUI)" : " - NOT a Nintendo OUI");
|
||||
if (mac[0] == 0xFF || (mac[0] | mac[1] | mac[2] | mac[3] | mac[4] | mac[5]) == 0)
|
||||
return 1; /* all-ones or all-zero is unusable */
|
||||
return (mac[0] == 0x00 && mac[1] == 0x09 && mac[2] == 0xBF) ? 0 : 1;
|
||||
}
|
||||
|
||||
static int t_init_sequence(char *m, size_t n)
|
||||
{
|
||||
/* The register writes libogc issues, in order, with the values it uses.
|
||||
* Most are store-only (PHY/analog) and we ignore them; what matters is
|
||||
* that none of them is misdecoded into something harmful -- the old
|
||||
* guessed header format turned a NWAYS read into an NCRA reset, so this
|
||||
* is a real risk, not a theoretical one. */
|
||||
static const struct { u16 a; u8 v; } seq[] = {
|
||||
{0x0060, 0x00},
|
||||
{0x0000, 0x01}, /* NCRA reset */
|
||||
{0x0000, 0x00},
|
||||
{0x0058, 0x80}, {0x0059, 0x00}, {0x005A, 0x03}, {0x005B, 0x83},
|
||||
{0x005C, 0x32}, {0x005D, 0xFE}, {0x005E, 0x1F}, {0x005F, 0x1F},
|
||||
{0x0001, 0x52}, {0x0060, 0x74},
|
||||
{0x0014, 0x00}, {0x0015, 0x06}, {0x0050, 0x80},
|
||||
{0x000C, 0x00}, {0x000D, 0x00}, {0x000A, 0x01}, {0x000B, 0x00},
|
||||
{0x001A, 0x0F}, {0x001B, 0x00}, /* RHBP = page 15 */
|
||||
{0x0016, 0x01}, {0x0017, 0x00}, /* RWP = page 1 */
|
||||
{0x0018, 0x01}, {0x0019, 0x00}, /* RRP = page 1 */
|
||||
{0x0032, 0x08},
|
||||
{0x0000, 0x08}, /* NCRA SR: start receive */
|
||||
{0x0009, 0xFF}, /* IR: clear all */
|
||||
{0x0008, 0xDF}, /* IMR (NOT 0x86) */
|
||||
};
|
||||
unsigned i;
|
||||
int v;
|
||||
for (i = 0; i < sizeof(seq) / sizeof(seq[0]); i++) {
|
||||
if (bba_w8(seq[i].a, seq[i].v) < 0) {
|
||||
snprintf(m, n, "write %u (0x%04X=0x%02X) failed",
|
||||
i, seq[i].a, seq[i].v);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
g_ncra_reset_done = 1;
|
||||
/* The sentinels must still read correctly afterwards -- if any write in
|
||||
* that list had been misdecoded, the most likely casualty is the state
|
||||
* these two report. */
|
||||
if ((v = bba_r8(REG_NWAYS)) != NWAYS_LINKUP) {
|
||||
snprintf(m, n, "NWAYS 0x%02X after init (want 0x%02X)",
|
||||
v, NWAYS_LINKUP);
|
||||
return 1;
|
||||
}
|
||||
if ((v = bba_r8(REG_HIPR)) != HIPR_PRESENT) {
|
||||
snprintf(m, n, "HIPR 0x%02X after init (want 0x%02X)",
|
||||
v, HIPR_PRESENT);
|
||||
return 1;
|
||||
}
|
||||
v = bba_r8(REG_NCRA);
|
||||
snprintf(m, n, "%u writes replayed; NCRA=0x%02X NWAYS/HIPR ok",
|
||||
(unsigned)(sizeof(seq) / sizeof(seq[0])), v);
|
||||
return (v & 0x08) ? 0 : 1; /* SR must still be set */
|
||||
}
|
||||
|
||||
/* ─────────────────────── suite 6: SPRAM / DMA ──────────────────────── */
|
||||
|
||||
static int t_dma(char *m, size_t n)
|
||||
@@ -518,6 +621,12 @@ static test_t s_spram[] = {
|
||||
{"Stream length", "reads >= 0x100 stream until CS, length-independent",t_dma_stream_len, T_PENDING, {0}},
|
||||
};
|
||||
|
||||
static test_t s_init[] = {
|
||||
{"cmd-space write","2-byte header + data phase, as libogc sends it", t_cmd_write, T_PENDING, {0}},
|
||||
{"MAC readable", "libogc READS the MAC from PAR0-5; it must be sane", t_mac_is_readable, T_PENDING, {0}},
|
||||
{"init replay", "libogc's 30 init writes, in order, do no harm", t_init_sequence, T_PENDING, {0}},
|
||||
};
|
||||
|
||||
suite_t g_suites[] = {
|
||||
{"Identity", "device enumerates and reports itself", s_identity, 3},
|
||||
{"Registers", "register file is readable and writable", s_regs, 4},
|
||||
@@ -525,6 +634,7 @@ suite_t g_suites[] = {
|
||||
{"EXI integrity", "the EXI link is error-free, and how fast", s_exi, 6},
|
||||
{"Ethernet", "frames get to and from the W5100", s_eth, 3},
|
||||
{"SPRAM/DMA", "bulk reads stream correctly from the ring", s_spram, 2},
|
||||
{"libogc init", "the real driver's init sequence and shapes", s_init, 3},
|
||||
};
|
||||
const int g_nsuites = sizeof(g_suites) / sizeof(g_suites[0]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user