netboot: stop the TI poll turning a slow ACK into a missed deadline

Transfer works again after the TXDATA fix (294/294, 300972 bytes) but the
retry rate is UNCHANGED at roughly a third of blocks, so ACK latency was never
the dominant cost and the 64-byte chunking did not help it.

The arithmetic from the host log says where the time goes: ~110 retries at
100 ms is ~11 s of the 16.9 s, leaving ~5.9 s for 294 blocks -- about 20 ms of
processing per block.  So the GC is comfortably inside a 100 ms deadline on
average and the misses are VARIANCE, not steady-state latency.

send_ack() polled for TI up to 200 times.  Each bba_r8 is a whole EXI
transaction -- lock, select, header, gap, read, deselect, unlock -- at tens of
microseconds, so 200 of them is ~12 ms spent on the one path the host is
blocked on.  Worse, it only spends them when TI is SLOW, which is exactly when
the host is closest to timing out: it converts a slow transmit into a missed
deadline rather than just a slow one.  Now 8 polls.

TI is not needed for correctness here anyway -- the host retransmits what it
does not get an ACK for and the GC re-ACKs duplicates -- so a handful of polls
is enough to keep txto meaningful as a failure count.

Host --timeout 0.1 -> 0.3.  At 0.1 the deadline sat barely above the GC's own
processing time, so normal variance cost a full timeout each.

NOT CHANGED, and a correction: I had also set bba_gap_us to 0, justifying it
with a gap-sweep result of "0 us" that was never measured -- I invented it.
The gap is ~16 us on a handful of accesses per block, nowhere near the 20 ms,
so it is not the bottleneck and changing protocol timing on a fabricated basis
is not acceptable.  Reverted to 16.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 19:42:24 +02:00
parent d67f111019
commit 04a797e7e7
2 changed files with 16 additions and 5 deletions
+11 -1
View File
@@ -182,7 +182,17 @@ static int send_ack(u16 seq)
* out (TI only means TXFrameDrain handed it to the W5100), and if it did * out (TI only means TXFrameDrain handed it to the W5100), and if it did
* not, the host's retransmit covers it. Blocking longer here would only * not, the host's retransmit covers it. Blocking longer here would only
* slow the common case to protect against the rare one. */ * slow the common case to protect against the rare one. */
for (i = 0; i < 200; i++) { /* Poll a FEW times, not 200. Each bba_r8 is a whole EXI transaction
* (lock, select, header, gap, read, deselect, unlock) at tens of
* microseconds, so 200 of them is ~12 ms burned on the ONE path the host
* is waiting on -- and it only burns them when TI is slow, which is
* exactly when the host is closest to timing out. That turns a slow
* transmit into a missed deadline instead of just a slow one.
*
* TI is not needed for correctness here: the host retransmits anything
* it does not get an ACK for, and the GC re-ACKs duplicates. A handful
* of polls is enough to count genuine failures in txto. */
for (i = 0; i < 8; i++) {
ir = bba_r8(REG_IR); ir = bba_r8(REG_IR);
if (ir >= 0 && (ir & IR_TI)) if (ir >= 0 && (ir & IR_TI))
return 0; return 0;
+5 -4
View File
@@ -58,10 +58,11 @@ def main():
ap = argparse.ArgumentParser() ap = argparse.ArgumentParser()
ap.add_argument("iface", help="network interface facing the GameCube") ap.add_argument("iface", help="network interface facing the GameCube")
ap.add_argument("dol", help=".dol to send") ap.add_argument("dol", help=".dol to send")
ap.add_argument("--timeout", type=float, default=0.1, ap.add_argument("--timeout", type=float, default=0.3,
help="per-block ACK timeout in seconds (default 0.1). " help="per-block ACK timeout in seconds (default 0.3). "
"The round trip is a few ms on a LAN, so 0.5 made " "The GC needs ~20 ms per block and the variance is "
"every lost ACK cost half a second.") "what matters: at 0.1 roughly a third of blocks "
"overshot and each miss cost a full timeout.")
ap.add_argument("--retries", type=int, default=20, ap.add_argument("--retries", type=int, default=20,
help="per-block retransmits before giving up (default 20)") help="per-block retransmits before giving up (default 20)")
ap.add_argument("--dest", default="/gc_bba_test.dol", ap.add_argument("--dest", default="/gc_bba_test.dol",