netboot: survive stray frames, and dump the page instead of guessing

Pressing Z failed instantly, BEFORE the sender was started -- so the frames
in the ring were stray traffic from the host (mDNS, IPv6 RAs, ARP), and a
single descriptor we could not parse aborted the whole transfer.  On a live
network that is guaranteed to happen.

A bad descriptor is now non-fatal: log the first few, resync RRP to the
gateware's RWP, and keep waiting.  Capped at 200 so a genuinely broken ring
still terminates rather than spinning.  This also removes the dependence on
the ring being quiet when Z is pressed, which was never a reasonable
assumption.

The descriptor 00 00 00 01 persists after fixing both dropped-write bugs, and
grep confirms no bare `ready` tests remain, so it is NOT that.  Rather than
theorise a fourth time, the error path now dumps 32 bytes from the head of the
offending page.  That distinguishes the two remaining possibilities outright:
an ethernet header at offset 4 means the descriptor length is simply wrong,
while a header at 3 or 5 -- or at 0 -- means we are reading the wrong place.

Also dumps the first frame whose ethertype does not match, with the ethertype
read at offset 12, because a body written at the wrong offset would show the
ethertype at 11 or 13.  And the timeout now reports how many frames were seen
and how many descriptors were rejected, so "no match" is distinguishable from
"nothing arrived".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 12:07:13 +02:00
parent 86cd5d1abd
commit 0477e07b84
+71 -3
View File
@@ -29,6 +29,7 @@ static u8 s_peer_mac[6];
/* Last descriptor ring_next() looked at, kept for the error path: when a
* length is rejected, the four raw bytes are the whole diagnosis. */
static u8 s_last_desc[4];
static u8 s_last_dump[32];
static u32 s_last_page;
static u16 rd16(const u8 *p) { return ((u16)p[0] << 8) | p[1]; }
@@ -54,6 +55,12 @@ static int ring_next(u8 *buf, u32 cap)
if (bba_stream((u16)addr, hdr, 4) < 0) return -1;
memcpy(s_last_desc, hdr, 4);
s_last_page = rpage;
/* Keep the head of the page for the error path. Guessing at a 4-byte
* descriptor has already cost several round trips; 32 bytes shows
* whether an ethernet header is present and AT WHAT OFFSET, which
* settles "bad length" versus "reading the wrong place" outright. */
if (bba_stream((u16)addr, s_last_dump, sizeof(s_last_dump)) < 0)
return -1;
dlen = ((u32)hdr[2] << 8) | hdr[3];
if (dlen <= 4 || dlen > 1518 + 4) return -3; /* descriptor implausible */
flen = dlen - 4;
@@ -104,7 +111,7 @@ int netboot_receive(void *dst, u32 maxlen, void (*status)(const char *msg))
{
static u8 frame[1536];
u8 *out = (u8 *)dst;
u32 got = 0, nblocks = 0;
u32 got = 0, nblocks = 0, bad = 0, seen = 0;
u16 expect = 0;
char line[80];
u64 t0;
@@ -177,6 +184,46 @@ int netboot_receive(void *dst, u32 maxlen, void (*status)(const char *msg))
for (;;) {
int n = ring_next(frame, sizeof(frame));
if (n == -3) {
/* A descriptor we cannot make sense of must NOT kill the
* transfer. The ring collects EVERYTHING on the wire --
* mDNS, IPv6 RAs, ARP from the host -- so pressing Z on a
* live network hits stray frames long before the sender
* starts. Log the first few, resync, and carry on. */
bad++;
if (bad <= 3 && status) {
int i;
snprintf(line, sizeof(line),
"skip bad descriptor at page %u: %02X %02X %02X %02X",
(unsigned)s_last_page, s_last_desc[0],
s_last_desc[1], s_last_desc[2], s_last_desc[3]);
status(line);
for (i = 0; i < 32; i += 16) {
snprintf(line, sizeof(line),
" +%02X %02X %02X %02X %02X %02X %02X %02X %02X "
"%02X %02X %02X %02X %02X %02X %02X %02X", i,
s_last_dump[i+0], s_last_dump[i+1], s_last_dump[i+2],
s_last_dump[i+3], s_last_dump[i+4], s_last_dump[i+5],
s_last_dump[i+6], s_last_dump[i+7], s_last_dump[i+8],
s_last_dump[i+9], s_last_dump[i+10], s_last_dump[i+11],
s_last_dump[i+12], s_last_dump[i+13], s_last_dump[i+14],
s_last_dump[i+15]);
status(line);
}
}
if (bad > 200) {
if (status) status("too many bad descriptors - giving up");
return -2;
}
/* Resync to the gateware's own pointer and keep waiting. */
{
u8 wp[2];
if (bba_read(REG_RWP_LO, wp, 2) < 0) return -1;
if (wp[0] >= 1 && wp[0] <= 15)
bba_w8(REG_RRP_LO, wp[0]);
}
continue;
}
if (n < 0) {
/* Report the pointers, not just the code: -1 is an EXI access
* failure, -2 an RRP outside the ring, -3 a descriptor whose
@@ -206,13 +253,34 @@ int netboot_receive(void *dst, u32 maxlen, void (*status)(const char *msg))
* console power cycle. */
u32 idle = (u32)ticks_to_millisecs(diff_ticks(t0, gettime()));
if (idle > (got ? 5000u : 30000u)) {
if (status) status(got ? "timeout mid-transfer" : "timeout: no frames");
if (status) {
snprintf(line, sizeof(line),
"%s (%u frames seen, %u bad descriptors)",
got ? "timeout mid-transfer" : "timeout: no match",
(unsigned)seen, (unsigned)bad);
status(line);
}
return -3;
}
continue;
}
seen++;
if ((u32)n < NB_HDR) continue;
if (rd16(frame + 12) != NB_ETHERTYPE) continue;
if (rd16(frame + 12) != NB_ETHERTYPE) {
/* Dump the first non-matching frame. If the sender is running
* and nothing matches, the bytes show whether the body is
* offset -- the ethertype would appear at 11 or 13 instead of
* 12. */
if (seen == 1 && status) {
snprintf(line, sizeof(line),
"frame 1 (%d B) etype=%04X at 12; "
"%02X %02X %02X %02X %02X %02X %02X %02X",
n, rd16(frame + 12), frame[0], frame[1], frame[2],
frame[3], frame[4], frame[5], frame[6], frame[7]);
status(line);
}
continue;
}
if (frame[14] != 'G' || frame[15] != 'C' ||
frame[16] != 'U' || frame[17] != 'P') continue;