0aa1fa884a
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>
196 lines
5.9 KiB
C
196 lines
5.9 KiB
C
#include "bba.h"
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <ogc/lwp_watchdog.h>
|
|
|
|
u32 bba_speed = EXI_SPEED32MHZ; /* nominal name; ~27 MHz in reality */
|
|
static int begin(void);
|
|
static void end(void);
|
|
|
|
u32 bba_gap_us = 16; /* see bba.h -- 0 reproduces the raw failure */
|
|
|
|
/* Busy-wait, not usleep(): we are holding the EXI lock with CS asserted, so
|
|
* yielding here is both unnecessary and imprecise at these timescales. */
|
|
static void gap(void)
|
|
{
|
|
u64 t0;
|
|
if (!bba_gap_us) return;
|
|
t0 = gettime();
|
|
while (ticks_to_microsecs(diff_ticks(t0, gettime())) < bba_gap_us) ;
|
|
}
|
|
|
|
/* Real GC<->BBA protocol (docs/exi_protocol_real.md), verified against libogc
|
|
* and Swiss. Register space is a 4-byte header followed by a data phase that
|
|
* runs until CS -- there is no length field.
|
|
* read [0x80, addr>>8, addr&0xFF, 0x00] + N bytes
|
|
* write [0xC0, addr>>8, addr&0xFF, 0x00] + N bytes
|
|
* The trailing 0x00 is the device's prefetch window: the address completes a
|
|
* whole byte before the data phase so the register file can fetch in time. */
|
|
int begin_raw(void) { return begin(); }
|
|
void end_raw(void) { end(); }
|
|
|
|
static int begin(void)
|
|
{
|
|
int tries;
|
|
/* EXI_Lock fails outright (rather than blocking) when another subsystem
|
|
* holds the channel -- Swiss or a libogc driver may have it briefly.
|
|
* Retry for ~100 ms before giving up so a transient collision does not
|
|
* surface as a test failure. */
|
|
for (tries = 0; tries < 100; tries++) {
|
|
if (EXI_Lock(BBA_EXI_CHAN, BBA_EXI_DEV, NULL) > 0) break;
|
|
usleep(1000);
|
|
}
|
|
if (tries == 100) return -1;
|
|
if (EXI_Select(BBA_EXI_CHAN, BBA_EXI_DEV, bba_speed) <= 0) {
|
|
EXI_Unlock(BBA_EXI_CHAN);
|
|
return -2;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void end(void)
|
|
{
|
|
EXI_Deselect(BBA_EXI_CHAN);
|
|
EXI_Unlock(BBA_EXI_CHAN);
|
|
}
|
|
|
|
static int put_hdr(u16 addr, int write)
|
|
{
|
|
u32 h = ((u32)(write ? 0xC0 : 0x80) << 24) | ((u32)addr << 8);
|
|
if (EXI_Imm(BBA_EXI_CHAN, &h, 4, EXI_WRITE, NULL) <= 0) return -3;
|
|
if (EXI_Sync(BBA_EXI_CHAN) <= 0) return -4;
|
|
return 0;
|
|
}
|
|
|
|
int bba_read(u16 addr, void *buf, u32 len)
|
|
{
|
|
int rc;
|
|
if (len < 1) return -10;
|
|
if ((rc = begin()) < 0) return rc;
|
|
if ((rc = put_hdr(addr, 0)) < 0) { end(); return rc; }
|
|
gap();
|
|
memset(buf, 0, len);
|
|
if (EXI_ImmEx(BBA_EXI_CHAN, buf, len, EXI_READ) <= 0) { end(); return -5; }
|
|
end();
|
|
return 0;
|
|
}
|
|
|
|
int bba_write(u16 addr, const void *buf, u32 len)
|
|
{
|
|
int rc;
|
|
u8 tmp[64];
|
|
if (len < 1 || len > sizeof(tmp)) return -10;
|
|
memcpy(tmp, buf, len);
|
|
if ((rc = begin()) < 0) return rc;
|
|
if ((rc = put_hdr(addr, 1)) < 0) { end(); return rc; }
|
|
if (EXI_ImmEx(BBA_EXI_CHAN, tmp, len, EXI_WRITE) <= 0) { end(); return -6; }
|
|
end();
|
|
return 0;
|
|
}
|
|
|
|
int bba_devid(u8 id[4])
|
|
{
|
|
int rc;
|
|
u16 h = 0x0000; /* command space, reg 0, read */
|
|
if ((rc = begin()) < 0) return rc;
|
|
if (EXI_Imm(BBA_EXI_CHAN, &h, 2, EXI_WRITE, NULL) <= 0) { end(); return -3; }
|
|
if (EXI_Sync(BBA_EXI_CHAN) <= 0) { end(); return -4; }
|
|
gap();
|
|
memset(id, 0, 4);
|
|
if (EXI_ImmEx(BBA_EXI_CHAN, id, 4, EXI_READ) <= 0) { end(); return -5; }
|
|
end();
|
|
return 0;
|
|
}
|
|
|
|
/* COMMAND-space access: a 2-BYTE header then a data phase -- three bytes
|
|
* total for a single-byte write, NOT a 4-byte transfer. This is the shape
|
|
* libogc's own driver uses (gcif.o, __bba_init):
|
|
*
|
|
* EXI_Imm (len=2, write) -> [0x40|reg, 0x00] write, or [reg, 0x00] read
|
|
* EXI_Sync
|
|
* EXI_ImmEx(len=N, write/read)
|
|
*
|
|
* The device-ID query is the same form with reg 0 and a 4-byte read, which is
|
|
* why bba_devid above looks like this too. */
|
|
int bba_cmd_write(u8 creg, u8 val)
|
|
{
|
|
int rc;
|
|
u16 h = (u16)(((0x40 | (creg & 0x3F)) << 8));
|
|
if ((rc = begin()) < 0) return rc;
|
|
if (EXI_Imm(BBA_EXI_CHAN, &h, 2, EXI_WRITE, NULL) <= 0) { end(); return -3; }
|
|
if (EXI_Sync(BBA_EXI_CHAN) <= 0) { end(); return -4; }
|
|
gap();
|
|
if (EXI_ImmEx(BBA_EXI_CHAN, &val, 1, EXI_WRITE) <= 0) { end(); return -5; }
|
|
end();
|
|
return 0;
|
|
}
|
|
|
|
int bba_cmd_read(u8 creg)
|
|
{
|
|
int rc;
|
|
u8 v = 0;
|
|
u16 h = (u16)(((creg & 0x3F) << 8));
|
|
if ((rc = begin()) < 0) return rc;
|
|
if (EXI_Imm(BBA_EXI_CHAN, &h, 2, EXI_WRITE, NULL) <= 0) { end(); return -3; }
|
|
if (EXI_Sync(BBA_EXI_CHAN) <= 0) { end(); return -4; }
|
|
gap();
|
|
if (EXI_ImmEx(BBA_EXI_CHAN, &v, 1, EXI_READ) <= 0) { end(); return -5; }
|
|
end();
|
|
return (int)v;
|
|
}
|
|
|
|
int bba_r8(u16 addr)
|
|
{
|
|
u8 v;
|
|
int rc = bba_read(addr, &v, 1);
|
|
return rc < 0 ? rc : (int)v;
|
|
}
|
|
|
|
int bba_w8(u16 addr, u8 val)
|
|
{
|
|
return bba_write(addr, &val, 1);
|
|
}
|
|
|
|
int bba_stream(u16 addr, void *buf, u32 len)
|
|
{
|
|
/* Identical to bba_read now: the real protocol always streams until CS,
|
|
* so there is no separate "DMA" header form. */
|
|
return bba_read(addr, buf, len);
|
|
}
|
|
|
|
int bba_tx_bytes(const u8 *buf, u32 len)
|
|
{
|
|
u32 off = 0;
|
|
|
|
/* Stream in 64-byte chunks, ONE EXI transaction each.
|
|
*
|
|
* Writing byte by byte with bba_w8() costs a full EXI_Lock / EXI_Select /
|
|
* header / EXI_Deselect / EXI_Unlock PER BYTE -- about sixty transactions
|
|
* for one 60-byte ACK. That latency is why ~1/3 of netboot blocks were
|
|
* retried: the ACK always reached the W5100 (txto=0, TI always set) but
|
|
* missed the host's timeout.
|
|
*
|
|
* ⚠️ BUT THE CHUNK SIZE IS BOUNDED BY tx_bytes, WHICH IS 16 DEEP.
|
|
* BBARegisterFile silently drops bytes when tx_bytes.w_rdy is low,
|
|
* because SPI cannot back-pressure. Within one transaction the bytes
|
|
* arrive back-to-back at EXI speed -- far faster than TXFrameDrain can
|
|
* start consuming -- so a 64-byte chunk overran the FIFO, the frame went
|
|
* out truncated, and the host saw no valid ACK at all ("no ACK after 20
|
|
* tries"). Byte-by-byte worked only because the per-byte EXI overhead
|
|
* happened to give the drain time.
|
|
*
|
|
* tx_bytes is now 2048 deep -- a whole maximum-size frame -- so the old
|
|
* 8-byte cap is gone and 64 (bba_write()'s buffer limit) sends a 60-byte
|
|
* ACK in a SINGLE transaction. The depth is what makes this safe: SPI
|
|
* cannot back-pressure, so the FIFO must absorb the entire burst. */
|
|
while (off < len) {
|
|
u32 n = len - off;
|
|
int rc;
|
|
if (n > 64) n = 64;
|
|
rc = bba_write(REG_TXDATA, buf + off, n);
|
|
if (rc < 0) return rc;
|
|
off += n;
|
|
}
|
|
return 0;
|
|
}
|