Batch TXDATA writes; record netboot working and the +1 offset as open

Self-update is proven end to end on console: 300076 bytes in 294 blocks,
verify OK, /boot.dol boots, and new builds are now delivered this way instead
of by SD card.

bba_tx_bytes() wrote TXDATA byte by byte with bba_w8(), i.e. a full
EXI_Lock / EXI_Select / header / EXI_Deselect / EXI_Unlock PER BYTE -- about
sixty transactions to send one 60-byte ACK.  Hardware showed txto=0, so every
ACK did reach the W5100 with TI set; the ~1/3 of blocks the host retried were
not transmit failures but ACKs that missed the host's timeout.  The data phase
of a register-space write runs until CS, so there was never a reason to pay
the per-byte cost.  It now streams in 64-byte chunks, one transaction each
(64 is bba_write()'s buffer limit, not a protocol limit).  t_tx() benefits
equally.

CLAUDE.md records the facility, including that it CANNOT be driven from the
devcontainer -- raw ethernet is link-local and the container has a veth, so
the sender must run on a host holding the physical NIC.

Also records the +1 descriptor offset as OPEN, with what has been ruled out:
not overflow (lapped=0 under RBFI), not the two dropped-write handshake bugs
(fixed and mutation-tested), not write-port contention (ETH has absolute
priority so the port is never stalled; T5 models a stall anyway and the layout
holds), and not the late-response leak (guarded; ratio unchanged at
desc@+0=100 +1=343).

And names the test nobody has written: a bba_top-level EXI DMA read from the
ring WHILE the ethernet side writes into it.  ETH preempts EXI reads, so only
that overlap can delay a response past its transaction.  Write it before
guessing again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 19:13:28 +02:00
parent 799692a5f4
commit a2e8e5ceaf
2 changed files with 69 additions and 3 deletions
+19 -3
View File
@@ -123,10 +123,26 @@ int bba_stream(u16 addr, void *buf, u32 len)
int bba_tx_bytes(const u8 *buf, u32 len)
{
u32 i;
for (i = 0; i < len; i++) {
int rc = bba_w8(REG_TXDATA, buf[i]);
u32 off = 0;
/* Stream in 64-byte chunks, ONE EXI transaction each.
*
* This used to write byte by byte with bba_w8(), which means a full
* EXI_Lock / EXI_Select / header / EXI_Deselect / EXI_Unlock per BYTE --
* about sixty transactions to send one 60-byte ACK. That latency is why
* roughly a third of netboot blocks were retried: the ACK was always
* transmitted (txto=0 on hardware, so TI always set) but did not get back
* to the host inside its timeout. The data phase of a register-space
* write runs until CS, so there is no reason to pay the per-byte cost.
*
* 64 is bba_write()'s internal buffer limit, not a protocol limit. */
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;
}