diff --git a/gc_bba_test/source/netboot.c b/gc_bba_test/source/netboot.c index 3f970a3..cf5da8b 100644 --- a/gc_bba_test/source/netboot.c +++ b/gc_bba_test/source/netboot.c @@ -33,6 +33,8 @@ static u8 s_last_desc[4]; * supposed to put it at +0 always; hardware shows it sometimes landing at +1, * varying frame to frame, so count both rather than assume either. */ static u32 s_shift_hits[2]; +/* ACKs where TI never set -- the frame did not reach the W5100. */ +static u32 s_tx_timeouts; static u8 s_last_dump[32]; static u32 s_last_page; @@ -139,6 +141,8 @@ static int ring_next(u8 *buf, u32 cap) static int send_ack(u16 seq) { u8 f[NB_MIN]; + int i, ir = 0; + memset(f, 0, sizeof(f)); memcpy(f + 0, s_peer_mac, 6); memcpy(f + 6, s_our_mac, 6); @@ -148,7 +152,37 @@ static int send_ack(u16 seq) wr16(f + 20, seq); wr16(f + 22, 0); wr16(f + 24, 0); - return bba_tx_bytes(f, sizeof(f)); + + /* THE FULL SEQUENCE, matching t_tx() which is proven on hardware. + * bba_tx_bytes() only streams bytes into the TXDATA FIFO -- it does NOT + * set the length or fire the transmit. An earlier version of this + * function called it alone, so every ACK was written into the FIFO and + * never put on the wire: the host retried block 0 twenty times, saw no + * ACK and gave up, while the GC reported frames arriving normally + * ("22 seen, 0 bad" against the sender's 20 retries). + * + * TWD must be written BEFORE streaming: that is what queues the length + * so TXFrameDrain can consume concurrently with the EXI writes. Without + * it the drain waits for a length that only arrives at the ST trigger and + * the frame has to fit the 16-deep FIFO whole -- the truncate-at-16-bytes + * bug. The NCRA ST write afterwards is the fallback trigger. */ + bba_w8(REG_IR, 0xFF); /* clear TI/RI */ + bba_w8(REG_TWD_LO, (u8)(sizeof(f) & 0xFF)); + bba_w8(REG_TWD_HI, (u8)((sizeof(f) >> 8) & 0xFF)); + if (bba_tx_bytes(f, sizeof(f)) < 0) return -1; + bba_w8(REG_NCRA, NCRA_SR | NCRA_ST0); + + /* Wait briefly for TI. Not proof it reached the wire, but its absence + * is a clear failure, and blocking here is fine: stop-and-wait means the + * host is idle until this ACK lands anyway. */ + for (i = 0; i < 20; i++) { + ir = bba_r8(REG_IR); + if (ir >= 0 && (ir & IR_TI)) + return 0; + usleep(500); + } + s_tx_timeouts++; + return -1; } /* Destination path carried in the DONE frame, so the host decides where the @@ -331,11 +365,12 @@ int netboot_receive(void *dst, u32 maxlen, void (*status)(const char *msg)) if (idle > (got ? 5000u : 30000u)) { if (status) { snprintf(line, sizeof(line), - "%s (%u seen, %u bad, %u lapped, desc@+0=%u +1=%u)", + "%s (%u seen, %u bad, desc+0=%u +1=%u, txto=%u)", got ? "timeout mid-transfer" : "timeout: no match", - (unsigned)seen, (unsigned)bad, (unsigned)lapped, + (unsigned)seen, (unsigned)bad, (unsigned)s_shift_hits[0], - (unsigned)s_shift_hits[1]); + (unsigned)s_shift_hits[1], + (unsigned)s_tx_timeouts); status(line); } return -3;