Support BOTH command-space forms: Swiss's 4-byte and libogc's 2-byte

Reading Swiss's own driver (cube/patches/bba/bba.c) shows the previous commit
fixed libogc and BROKE Swiss.  The two disagree, and both are real:

  Swiss   bba_cmd_out8: exi_imm_read_write(0x40<<24 | reg<<24 | val, 4)
          -> ONE 4-byte transfer [0x40|reg, 00, 00, val], and bba_cmd_in8
             returns the LOW byte, so a read response must be in BYTE 3.
  libogc2 bba_cmd_outsnosel: req = (reg<<8)|0x4000;
          EXI_ImmEx(&req, 2, WRITE); EXI_ImmEx(val, len, WRITE)
          -> THREE bytes, value in byte 2, and a read takes byte 2.
  stock libogc: identical to libogc2 (read out of libbba.a).

They share bytes 0 and 1 and differ only in how many bytes follow, so one
decoder serves both: take the LAST data byte before CS rise as the write value
(Swiss sends [00, val], libogc sends [val] -- last is val either way), and push
a read response TWICE so bytes 2 and 3 both carry it.  Swiss clocks 4 bytes and
pops both; libogc clocks 3 and leaves one, which the next transaction's header
pops drain -- a 2-byte header gives exactly one drain slot, so the leftover is
bounded at one and cannot accumulate.

Critically this means NO auto-increment of cmd_reg across the data phase: that
applied Swiss's padding 00 to the register and val to reg+1, which is precisely
how the libogc-only version broke Swiss.

T12 covers the Swiss form and asserts bytes 2 AND 3; T10 keeps the libogc form.
Both mutation-tested: re-adding the auto-increment fails T12 with 0x00 in both
slots, and pushing the response once fails T12's byte 3.  gc_bba_test gains
"cmd-space Swiss" alongside "cmd-space write", with bba_cmd_write4/read4.

Also corrects two things in CLAUDE.md now checkable against three drivers:
- NO driver writes TWD -- not Swiss, not libogc, not libogc2.  The "GC sets
  TWD before streaming" note was self-confirming: t_tx writes TWD because we
  wrote t_tx.  The counted TXDATA length is the primary path, not a fallback.
- Swiss's exi_dma_write rounds the length UP to 32 bytes (OSRoundUp32B), so
  TXDATA gets up to 31 trailing bytes past the frame and a counted length sends
  that much extra.  Real hardware must do the same since Swiss works on it;
  trailing padding past the IP total length is ignored.  A 1514-byte frame
  rounds to 1536 though, over the 1518 maximum.

Both drivers trigger TX with NCRA ST1, which the existing any-ST test accepts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 11:01:33 +02:00
parent 71ec6a2b37
commit b3685237b2
5 changed files with 223 additions and 52 deletions
+29
View File
@@ -139,6 +139,35 @@ int bba_cmd_read(u8 creg)
return (int)v;
}
/* SWISS's command-space form: ONE 4-byte transfer, value in byte 3, and a
* read takes byte 3 (the low byte of the 32-bit result). Swiss's
* bba_cmd_out8/in8 use exi_imm_read_write(..., 4).
*
* This DIFFERS from bba_cmd_write/bba_cmd_read above, which use libogc's
* 2-byte-header form. Both are real drivers and the device must serve both,
* so both shapes are exercised. */
int bba_cmd_write4(u8 creg, u8 val)
{
int rc;
u32 w = ((u32)(0x40 | (creg & 0x3F)) << 24) | val;
if ((rc = begin_raw()) < 0) return rc;
if (EXI_Imm(BBA_EXI_CHAN, &w, 4, EXI_WRITE, NULL) <= 0) { end_raw(); return -3; }
if (EXI_Sync(BBA_EXI_CHAN) <= 0) { end_raw(); return -4; }
end_raw();
return 0;
}
int bba_cmd_read4(u8 creg)
{
int rc;
u32 w = ((u32)(creg & 0x3F) << 24);
if ((rc = begin_raw()) < 0) return rc;
if (EXI_Imm(BBA_EXI_CHAN, &w, 4, EXI_READWRITE, NULL) <= 0) { end_raw(); return -3; }
if (EXI_Sync(BBA_EXI_CHAN) <= 0) { end_raw(); return -4; }
end_raw();
return (int)(w & 0xFF); /* Swiss reads the LOW byte = byte 3 */
}
int bba_r8(u16 addr)
{
u8 v;