netboot: detect being lapped by the ring writer; quiet-link guidance
The alignment bug is FIXED -- hardware now reports "frame 1 (342 B) etype=0800 at 12: FF FF FF FF FF FF 00 2B", i.e. broadcast dst at 0, host MAC at 6, ethertype at 12. Perfectly aligned, correctly received, correctly skipped as not ours. So 00 00 00 01 was never a dropped write. It is frame data being read as a descriptor, because the ring HAS NO OVERFLOW PROTECTION: nothing in the gateware consumes RRP, so RXFrameAssembler keeps writing and laps the reader (the real BBA has RBFI for exactly this). The poll loop costs ~5 EXI transactions per frame (~200 us) while a 100 Mbit link delivers a 342-byte frame every ~30 us, and the ring is 15 pages -- roughly 3 to 11 frames. Under continuous traffic pages are reused before they are read. That is also why it "worked once out of three tries": it is a race against the host's background traffic. ring_next() now returns -4 when the writer is more than two thirds of the ring ahead, and the caller resyncs and continues instead of parsing bytes that belong to a different frame. The timeout reports seen / bad-descriptor / lapped counts so the three cases stay distinguishable. Detection is not prevention, so the app and the README both now say to quiet the link first with gc-testlink.sh, which sets a static address and takes the interface out of NetworkManager -- stopping the DHCP client whose 342-byte broadcasts are the main offender. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,12 @@ static void wr16(u8 *p, u16 v) { p[0] = (u8)(v >> 8); p[1] = (u8)v; }
|
||||
* The ring is pages 1-15 with a 4-byte descriptor at each page start whose
|
||||
* length field INCLUDES the descriptor. RRP is ours to advance; the
|
||||
* gateware does not act on it. */
|
||||
/* Pages the writer is ahead of us, over the 15-page ring. */
|
||||
static u32 ring_lag(u32 rpage, u32 wpage)
|
||||
{
|
||||
return (wpage >= rpage) ? (wpage - rpage) : (wpage + 15u - rpage);
|
||||
}
|
||||
|
||||
static int ring_next(u8 *buf, u32 cap)
|
||||
{
|
||||
u8 rp[2], wp[2], hdr[4];
|
||||
@@ -51,6 +57,20 @@ static int ring_next(u8 *buf, u32 cap)
|
||||
if (rpage == wpage) return 0; /* empty */
|
||||
if (rpage < 1 || rpage > 15) return -2; /* pointer corrupt */
|
||||
|
||||
/* THE RING HAS NO OVERFLOW PROTECTION. Nothing in the gateware consumes
|
||||
* RRP, so RXFrameAssembler keeps writing and laps us: the real BBA has
|
||||
* RBFI for this and we do not. Reading from a page the writer has
|
||||
* already reused yields a "descriptor" that is really frame data -- which
|
||||
* is where 00 00 00 01 came from, not a dropped write.
|
||||
*
|
||||
* Our poll loop costs ~5 EXI transactions per frame (~200 us) while a
|
||||
* 100 Mbit link delivers a 342-byte frame every ~30 us, and the ring is
|
||||
* only 15 pages, so under continuous traffic we lose that race. Bail out
|
||||
* once the writer is more than two thirds of the ring ahead: the data we
|
||||
* would read is not trustworthy, and resyncing is strictly better than
|
||||
* parsing someone else's bytes. */
|
||||
if (ring_lag(rpage, wpage) > 10) return -4;
|
||||
|
||||
addr = rpage << 8;
|
||||
if (bba_stream((u16)addr, hdr, 4) < 0) return -1;
|
||||
memcpy(s_last_desc, hdr, 4);
|
||||
@@ -111,7 +131,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, bad = 0, seen = 0;
|
||||
u32 got = 0, nblocks = 0, bad = 0, seen = 0, lapped = 0;
|
||||
u16 expect = 0;
|
||||
char line[80];
|
||||
u64 t0;
|
||||
@@ -179,11 +199,29 @@ int netboot_receive(void *dst, u32 maxlen, void (*status)(const char *msg))
|
||||
* one the adapter is actually filtering on. */
|
||||
if (bba_read(REG_PAR0, s_our_mac, 6) < 0) return -1;
|
||||
|
||||
if (status) status("waiting for host (send-dol.py)...");
|
||||
if (status) {
|
||||
status("waiting for host (send-dol.py)...");
|
||||
status("NOTE: quiet the link first, or other traffic laps the ring:");
|
||||
status(" sudo ./scripts/gc-testlink.sh up <iface>");
|
||||
}
|
||||
t0 = gettime();
|
||||
|
||||
for (;;) {
|
||||
int n = ring_next(frame, sizeof(frame));
|
||||
if (n == -4) {
|
||||
/* Lapped. Skip to the writer and carry on; with stop-and-wait
|
||||
* the host simply retransmits the block we just threw away. */
|
||||
lapped++;
|
||||
if (lapped <= 3 && status)
|
||||
status("ring lapped (too much other traffic) - resyncing");
|
||||
{
|
||||
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 == -3) {
|
||||
/* A descriptor we cannot make sense of must NOT kill the
|
||||
* transfer. The ring collects EVERYTHING on the wire --
|
||||
@@ -255,9 +293,9 @@ 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 frames seen, %u bad descriptors)",
|
||||
"%s (%u seen, %u bad desc, %u lapped)",
|
||||
got ? "timeout mid-transfer" : "timeout: no match",
|
||||
(unsigned)seen, (unsigned)bad);
|
||||
(unsigned)seen, (unsigned)bad, (unsigned)lapped);
|
||||
status(line);
|
||||
}
|
||||
return -3;
|
||||
|
||||
Reference in New Issue
Block a user