Implement the command registers libogc2 actually touches

Previously every command register except 0x02/0x03 read 0x00 and dropped
writes.  Real software will hit them, so they are now implemented, enumerated
from libogc2's gcif.c rather than guessed at random (Swiss's patch driver
touches only 0x02 and 0x03):

  0x01  read            revision ID -> priv->revid
  0x02  write           interrupt mask (IRMASKALL / IRMASKNONE)
  0x03  read / w1c      interrupt status
  0x04  write 2 bytes   priv->devid  = 0xD107
  0x05  write           priv->acstart = 0x4E
  0x08  read 4 bytes    challenge      | device-initiated path,
  0x09  write 4 bytes   response       | gated on status bit 0x10
  0x0B  read            challenge/response status
  0x0F  read (slow)     in __bba_reset(), result DISCARDED

0x01 returns 0x00 and is read-only.  That is a real-hardware value, not a
guess: the BBA never received a functional firmware revision that incremented
it.  libogc2's priv->revid = 0xf0 is its PRE-INIT PLACEHOLDER, overwritten by
whatever this read returns, so it must not be mistaken for the device value.
revid/devid/acstart feed only bba_calc_response(), whose path is
device-initiated, so they are functionally inert for us.

0x04 and up are plain read/write storage, which IS a guess: both drivers treat
them as write-only and never read them back, so read-what-you-wrote is the
least surprising behaviour and lets our tests confirm the write landed.
Ignoring them (the old behaviour) is strictly worse -- the write vanished.
Registers above 0x0F still read 0x00 so a stray high register cannot alias onto
a real one.

Noted for later: bba_cmd_in8_slow(0x0F) sits in __bba_reset() between two 10 ms
delays, holds CS for 200 us and discards the result.  A read whose value is
thrown away but whose timing is deliberate looks like a reset strobe; we treat
it as an ordinary register, and the NCRA reset follows immediately in that
sequence, so ignoring the side effect costs nothing today.

T14 covers the whole table and is mutation-tested: making 0x01 plain storage
fails it, and dropping the 0x04+ writes fails it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 11:24:23 +02:00
parent 49d7e8fcff
commit 7116bc1ecc
2 changed files with 113 additions and 5 deletions
+44 -4
View File
@@ -1404,10 +1404,50 @@ Tests: `bba_register_file` **T13** (bit 7 clear when idle, set after an RI with
`IMR` enabling it, `INT_N` asserted) and **T13b** (`IRMASKALL` drops `INT_N`
but must NOT clear the pending status). Both mutation-tested.
Other command registers both drivers touch, none of them decoded: `0x01`
(revision ID — libogc2 stores it as `revid`), `0x04`/`0x05` (libogc2 writes its
`devid` 2 bytes + `acstart`), `0x08`/`0x09`/`0x0B` (challenge/response — see
the note saying not to implement it). Reads return `0x00`.
### Command register map — implemented 2026-10-10
Enumerated from libogc2's `gcif.c` rather than guessed at random. Swiss's patch
driver touches only `0x02` and `0x03`; everything else here is libogc2.
| reg | access | who / what | our behaviour |
|---|---|---|---|
| `0x01` | read | revision ID → `priv->revid` | **`0x00`**, read-only |
| `0x02` | write | interrupt mask (`IRMASKALL` `0x00` / `IRMASKNONE` `0xF8`) | gates `INT_N` |
| `0x03` | read, write-1-clear | interrupt status | bit 7 = live MAC level |
| `0x04` | write, **2 bytes** | `priv->devid` = `0xD107` | stored (last byte) |
| `0x05` | write | `priv->acstart` = `0x4E` | stored |
| `0x08` | read, **4 bytes** | challenge | stored/readable |
| `0x09` | write, **4 bytes** | response | stored |
| `0x0B` | read | challenge/response status | stored/readable |
| `0x0F` | read (slow, 200 µs) | in `__bba_reset()`, result **DISCARDED** | stored/readable |
| `> 0x0F` | — | nothing | reads `0x00` |
**`0x01` = `0x00` is a real-hardware value, not a guess:** the BBA never
received a functional firmware revision that incremented it. ⚠️ Do NOT take
libogc2's `priv->revid = 0xf0` as the device value — that is its pre-init
placeholder, overwritten by whatever this read returns. `revid`, `devid` and
`acstart` feed exactly one thing, `bba_calc_response()`, and that path is
device-initiated (status bit `0x10`), so for us they are functionally inert.
Everything from `0x04` up is **plain read/write storage — a GUESS.** Both
drivers treat these as write-only and never read them back, so
read-what-you-wrote is the least surprising behaviour and lets our own tests
confirm the write landed. The alternative (ignore them, as before) is strictly
worse: software that writes them was silently dropped.
⚠️ `0x0F` deserves a note: `bba_cmd_in8_slow(0x0F)` sits in `__bba_reset()`
between two 10 ms delays, reads one byte with CS held for 200 µs, and
**discards the result**. A read whose value is thrown away but whose timing is
deliberate looks like a hardware reset strobe. We treat it as an ordinary
register; if a real-hardware reset ever turns out to depend on it, this is the
place to add the side effect. The NCRA reset follows immediately in that
sequence anyway, which is why ignoring it costs nothing today.
Test: `bba_register_file` **T14** covers the whole table — revision ID value
and read-only-ness, write/read-back on `0x04`/`0x05`/`0x09`/`0x0B`/`0x0F`, and
`0x20` still reading `0x00` so a stray high register cannot alias onto a real
one. Mutation-tested: making `0x01` plain storage fails it, and dropping the
`0x04+` writes (the old behaviour) fails it.
---