diff --git a/CLAUDE.md b/CLAUDE.md index e4c8e56..868bddd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1219,6 +1219,68 @@ parallel master is the default — see "W5100 vs W5500".) --- +## RX Ring Descriptor — the format GC software actually expects + +✅ **Corrected 2026-10-10 from libogc's own driver.** The 4 bytes at each ring +page start are a **packed 32-bit little-endian word**, not a byte-wise layout: + +| bits | field | +|---|---| +| 0–11 | `next_packet_ptr` — the PAGE the NEXT frame starts at | +| 12–23 | `packet_len` — total bytes, **including** these 4 | +| 24–31 | `status` — `0x80` and `0x04` are errors; `0x00` is good | + +``` +Byte 0: next_packet_ptr[7:0] +Byte 1: packet_len[3:0] << 4 | next_packet_ptr[11:8] +Byte 2: packet_len[11:4] +Byte 3: status +``` + +⚠️ **This was WRONG until 2026-10-10 and nothing we had built could have +caught it.** The assembler wrote `00 00 len[11:8] len[7:0]` and every reader we +had — `netboot`, `t_dma`, the testbenches — was written by us to match it, so +the whole stack was self-consistent and green while being incompatible with the +only driver that matters. The error was found by disassembling +`/opt/devkitpro/libogc/lib/cube/libbba.a` (`gcif.o`, `bba_interrupt`): + +``` +lwbrx r9,0,r9 # the 4 bytes as a LITTLE-ENDIAN word +rlwinm r4,r9,20,20,31 # packet_len = (word >> 12) & 0xFFF +addi r4,r4,-4 # payload = packet_len - 4 +cmpwi r4,1540 # ... must be <= 1540 +srwi r9,r9,24 # status = word >> 24 +and. r9,r9,r18 # r18 = 0x..84 -> errors are 0x80 | 0x04 +lwz r9,cur_descr # and the low 12 bits go STRAIGHT to RRP: + # byte 0 -> 0x0018 (RRP_LO) +rlwinm r9,r31,24,28,31 # bits 11:8 -> 0x0019 (RRP_HI) +``` + +Under the old format a 342-byte frame (`00 00 01 5A`) decodes as +`packet_len = 16` → an 12-byte frame, with `next_packet_ptr = 0` pointing at +the reserved page. The length semantics and addressing were already right +(descriptor at `page<<8`, body at `+4`, length includes the descriptor); only +the packing was wrong, and `next_packet_ptr` was never written at all. + +**Lesson worth keeping:** a green suite proves self-consistency, not +compatibility. For anything an external driver parses, check it against that +driver — the binary is on disk and `objdump` settles in minutes what guessing +could not. This is the third time a *guessed* interface cost real hardware +debugging (after the EXI header format and the Mode 2/3 mix-up). + +`rx_frame_assembler`'s testbench now decodes descriptors with +`libogc_decode()`, which reproduces the instruction sequence above, so the +tests check the driver's algorithm rather than our own packing. + +⚠️ **The status byte must be written EXPLICITLY** (`WRITE_D3`). The placeholder +issues four writes but the first lands twice, so `desc_base+3` is never +written; that was harmless while the length write covered byte 3. Left +unwritten it hands the driver whatever was in that SPRAM cell, and a stale +`0x80` or `0x04` discards the frame as an error. Simulation flags it as an +unwritten cell; real SPRAM has no such tell. + +--- + ## SPRAM Notes - iCE40UP5K has 128 KB SPRAM (SB_SPRAM256KA, 16-bit wide). diff --git a/docs/gc_bba_fpga_design.md b/docs/gc_bba_fpga_design.md index 6be88bc..31eb333 100644 --- a/docs/gc_bba_fpga_design.md +++ b/docs/gc_bba_fpga_design.md @@ -553,14 +553,28 @@ Each page is 256 bytes. A received frame may span multiple pages. **Frame descriptor (first 4 bytes of first page):** +A **packed 32-bit little-endian word**, not a byte-wise layout: + ``` -Byte 0: LRPS value (Last Received Packet Status — set to 0x00 or actual status) -Byte 1: 0x00 -Byte 2: frame_length[15:8] (big-endian, includes descriptor bytes) -Byte 3: frame_length[7:0] -Bytes 4+: raw ethernet frame data (DA, SA, EtherType, payload, FCS) +bits 0–11 : next_packet_ptr — the PAGE at which the NEXT frame starts +bits 12–23 : packet_len — total bytes, INCLUDING these 4 +bits 24–31 : status — 0x80 and 0x04 are errors; 0x00 means good + +Byte 0: next_packet_ptr[7:0] +Byte 1: packet_len[3:0] << 4 | next_packet_ptr[11:8] +Byte 2: packet_len[11:4] +Byte 3: status +Bytes 4+: raw ethernet frame data (DA, SA, EtherType, payload) ``` +⚠️ Read out of libogc's own BBA driver (`libbba.a`, `gcif.o`, +`bba_interrupt`) — `lwbrx` loads the four bytes as a little-endian word, +`rlwinm r4,r9,20,20,31` takes `packet_len = (word >> 12) & 0xFFF`, `srwi +r9,r9,24` takes the status, and the low 12 bits are written straight back to +`RRP`. This spec previously said `00 00 len_hi len_lo`, which that driver +decodes as a 12-byte frame with a next-pointer of 0 for a 342-byte frame. Do +not "simplify" it back to a plain big-endian length. + **Flow:** 1. Wait for `W5500SPIMaster` to signal frame available (`rx_sof` pulse). diff --git a/exi_bba/bba_top.py b/exi_bba/bba_top.py index 8925cde..bf98755 100644 --- a/exi_bba/bba_top.py +++ b/exi_bba/bba_top.py @@ -626,8 +626,13 @@ if __name__ == "__main__": rwp = await exi_read(ctx, 0x16, 1) # RX write pointer (page) total_len = len(RX_FRAME) + 4 + RX_NEXT_PAGE = 2 # one-page frame written at page 1 got = await exi_read(ctx, 0x0100, total_len) # descriptor + frame - want = [0x00, 0x00, (total_len >> 8) & 0xFF, total_len & 0xFF] + RX_FRAME + # The descriptor is a packed little-endian word, as libogc's driver + # reads it: next_packet_ptr[11:0] | packet_len[23:12] | status[31:24]. + # See the format note in rx_frame_assembler.py. + _desc_word = ((total_len & 0xFFF) << 12) | (RX_NEXT_PAGE & 0xFFF) + want = [(_desc_word >> (8 * i)) & 0xFF for i in range(4)] + RX_FRAME print(f"T6 RWP=0x{rwp[0]:02X} (want 0x02)") print(f"T6 SPRAM[0x100]: {[f'0x{b:02X}' for b in got]}") print(f"T6 expected : {[f'0x{b:02X}' for b in want]}") diff --git a/exi_bba/rx_frame_assembler.py b/exi_bba/rx_frame_assembler.py index c19d152..740afee 100644 --- a/exi_bba/rx_frame_assembler.py +++ b/exi_bba/rx_frame_assembler.py @@ -11,17 +11,50 @@ Page wrap: after 0x0F → 0x01 (skip 0x00). Frame descriptor (4 bytes at page start) ----------------------------------------------- -Byte 0: LRPS (last received packet status) — 0x00 -Byte 1: 0x00 -Byte 2: total_length[15:8] (big-endian; includes 4 descriptor bytes) -Byte 3: total_length[7:0] -Bytes 4+: raw ethernet frame +A PACKED 32-BIT LITTLE-ENDIAN WORD, not a byte-wise layout: + + bits 0–11 : next_packet_ptr — the PAGE at which the NEXT frame starts + bits 12–23 : packet_len — total bytes, INCLUDING these 4 + bits 24–31 : status — errors are 0x80 and 0x04; 0x00 means good + +so the four bytes at the page start are + + Byte 0: next_packet_ptr[7:0] + Byte 1: packet_len[3:0] << 4 | next_packet_ptr[11:8] + Byte 2: packet_len[11:4] + Byte 3: status + Bytes 4+: raw ethernet frame + +⚠️ This is NOT a guess and must not be "simplified" back to a plain +big-endian length. It was read out of libogc's own BBA driver +(`/opt/devkitpro/libogc/lib/cube/libbba.a`, `gcif.o`, `bba_interrupt`), which +is the driver GC software actually runs: + + lwbrx r9,0,r9 # load the 4 bytes as a LITTLE-ENDIAN word + rlwinm r4,r9,20,20,31 # packet_len = (word >> 12) & 0xFFF + addi r4,r4,-4 # payload = packet_len - 4 + cmpwi r4,1540 # ... which must be <= 1540 + srwi r9,r9,24 # status = word >> 24 + and. r9,r9,r18 # r18 = 0x...84, so errors are 0x80 | 0x04 + +and it writes the low 12 bits straight back to RRP as the next read pointer: + + lwz r9,cur_descr # byte 0 -> register 0x0018 (RRP_LO) + rlwinm r9,r31,24,28,31 # bits 11:8 -> register 0x0019 (RRP_HI) + +The length semantics and addressing already matched (descriptor at page<<8, +body at +4, length including the descriptor); the PACKING did not, and +`next_packet_ptr` was never written at all. Our previous +`00 00 len[11:8] len[7:0]` decodes under that driver as packet_len = 16 and +next_packet_ptr = 0 for a 342-byte frame — a 12-byte frame and a read pointer +into the reserved page. Write sequence -------------- 1. Issue 4 SPRAM writes of 0x00 (placeholder descriptor). 2. For each byte received from W5500, issue one SPRAM write. -3. After EOF: rewrite descriptor bytes 2 and 3 with actual length. +3. After EOF: compute the next page, then rewrite descriptor bytes 0, 1 and 2 + with the packed next_packet_ptr + packet_len. Byte 3 (status) stays 0x00. 4. Advance RWP, push to rx_wptr FIFO, pulse rx_irq. """ @@ -112,6 +145,10 @@ class RXFrameAssembler(Elaboratable): data_ctr = Signal(12) # Total length = data_ctr + 4 total_len = Signal(12) + # The page the NEXT frame will start at. Computed before the + # descriptor is written, because the descriptor carries it as + # next_packet_ptr -- libogc writes those 12 bits straight to RRP. + next_page = Signal(12) # Descriptor base (rwp*256) — saved when frame starts desc_base = Signal(16) @@ -207,7 +244,7 @@ class RXFrameAssembler(Elaboratable): # RWP advanced, but the GC's ethertype test read the wrong # offset and silently dropped every frame -- "no ACK" from the # host with no other sign of trouble. The matching - # descriptor bug (same cause, in WRITE_LEN_HI) showed up as a + # descriptor bug (same cause, in the length write) showed up as a # descriptor of 00 00 00 01. # # The first byte is written twice as a consequence of @@ -247,10 +284,10 @@ class RXFrameAssembler(Elaboratable): # Compute pages used: ceil((data_ctr + 4) / 256) # = (total_len + 255) >> 8 = total_len[11:8] + (total_len[7:0] != 0) m.d.sync += pages_used.eq(total_len[8:12] + (total_len[:8] != 0)) - m.next = "WRITE_LEN_HI" + m.next = "CALC_NEXT" - # ⚠️ Both length writes must advance on `valid & ready`, NOT on - # `ready` alone. `valid` is scheduled with m.d.sync and so is low + # ⚠️ All three descriptor writes must advance on `valid & ready`, + # NOT on `ready` alone. `valid` is scheduled with m.d.sync and so is low # for the whole first cycle in the state, while `ready` is tested # immediately -- and coming out of WAIT_LAST_WRITE `ready` is # usually already high. Testing `ready` alone therefore fired both @@ -264,32 +301,72 @@ class RXFrameAssembler(Elaboratable): # happens to hold `ready` high on the entry cycle, which is why # bring-up's 342-byte DHCP frame read a correct 00 00 01 5A and # this went unnoticed. - with m.State("WRITE_LEN_HI"): - # Overwrite descriptor byte 2 with total_len[15:8] - m.d.sync += self.eth_wr_addr.eq(desc_base + 2) - m.d.sync += self.eth_wr_data.eq(total_len[8:12]) + with m.State("CALC_NEXT"): + # The descriptor carries next_packet_ptr, so the next page must + # be known BEFORE the descriptor is written -- this state used + # to come after it (as ADVANCE_RWP). + # next_rwp = ((rwp - 1 + pages_used) % 15) + 1 + next_rwp_raw = Signal(8) + m.d.comb += next_rwp_raw.eq(rwp + pages_used) + with m.If(next_rwp_raw > _RX_PAGE_LAST): + m.d.sync += next_page.eq(next_rwp_raw - _PAGES_TOTAL) + with m.Else(): + m.d.sync += next_page.eq(next_rwp_raw) + m.next = "WRITE_D0" + + with m.State("WRITE_D0"): + # Descriptor byte 0 = next_packet_ptr[7:0] + m.d.sync += self.eth_wr_addr.eq(desc_base + 0) + m.d.sync += self.eth_wr_data.eq(next_page[0:8]) m.d.sync += self.eth_wr_valid.eq(1) with m.If(self.eth_wr_valid & self.eth_wr_ready): m.d.sync += self.eth_wr_valid.eq(0) - m.next = "WRITE_LEN_LO" + m.next = "WRITE_D1" - with m.State("WRITE_LEN_LO"): - # Overwrite descriptor byte 3 with total_len[7:0] + with m.State("WRITE_D1"): + # Descriptor byte 1 = packet_len[3:0] << 4 | next_ptr[11:8]. + # Cat puts its FIRST argument in the low bits. + m.d.sync += self.eth_wr_addr.eq(desc_base + 1) + m.d.sync += self.eth_wr_data.eq( + Cat(next_page[8:12], total_len[0:4])) + m.d.sync += self.eth_wr_valid.eq(1) + with m.If(self.eth_wr_valid & self.eth_wr_ready): + m.d.sync += self.eth_wr_valid.eq(0) + m.next = "WRITE_D2" + + with m.State("WRITE_D2"): + # Descriptor byte 2 = packet_len[11:4]. Byte 3 (status) keeps + # the placeholder 0x00, which libogc reads as "no error". + m.d.sync += self.eth_wr_addr.eq(desc_base + 2) + m.d.sync += self.eth_wr_data.eq(total_len[4:12]) + m.d.sync += self.eth_wr_valid.eq(1) + with m.If(self.eth_wr_valid & self.eth_wr_ready): + m.d.sync += self.eth_wr_valid.eq(0) + m.next = "WRITE_D3" + + with m.State("WRITE_D3"): + # Descriptor byte 3 = status = 0x00 ("no error": libogc tests + # it against 0x84). + # + # ⚠️ This MUST be written explicitly and cannot rely on the + # placeholder. WRITE_PLACEHOLDER issues four writes but the + # first lands twice (eth_wr_addr trails wr_addr by a cycle), + # so desc_base+3 is NEVER written. That was harmless while + # the length write covered byte 3; now byte 3 is the status + # and leaving it would hand libogc whatever the previous + # frame -- or power-on -- left in that SPRAM cell, and any + # frame landing on a stale 0x80 or 0x04 would be discarded as + # an error. The simulation model reports it as an unwritten + # cell; real SPRAM has no such tell. m.d.sync += self.eth_wr_addr.eq(desc_base + 3) - m.d.sync += self.eth_wr_data.eq(total_len[:8]) + m.d.sync += self.eth_wr_data.eq(0x00) m.d.sync += self.eth_wr_valid.eq(1) with m.If(self.eth_wr_valid & self.eth_wr_ready): m.d.sync += self.eth_wr_valid.eq(0) m.next = "ADVANCE_RWP" with m.State("ADVANCE_RWP"): - # next_rwp = ((rwp - 1 + pages_used) % 15) + 1 - next_rwp_raw = Signal(8) - m.d.comb += next_rwp_raw.eq(rwp + pages_used) - with m.If(next_rwp_raw > _RX_PAGE_LAST): - m.d.sync += rwp.eq(next_rwp_raw - _PAGES_TOTAL) - with m.Else(): - m.d.sync += rwp.eq(next_rwp_raw) + m.d.sync += rwp.eq(next_page) m.next = "PUSH_WPT" with m.State("PUSH_WPT"): @@ -465,6 +542,27 @@ if __name__ == "__main__": base = page << 8 return [mem.get(base + i) for i in range(4)] + def pack_desc(total, next_ptr, status=0): + """The 4 descriptor bytes libogc's driver expects.""" + word = (status << 24) | ((total & 0xFFF) << 12) | (next_ptr & 0xFFF) + return [(word >> (8 * i)) & 0xFF for i in range(4)] + + def libogc_decode(d): + """Decode exactly as libogc's bba_interrupt does, so these tests + check the driver's ALGORITHM rather than our own packing: + + lwbrx -> little-endian word from the 4 bytes + rlwinm r4,r9,20,20,31 -> packet_len = (word >> 12) & 0xFFF + srwi r9,r9,24 -> status = word >> 24 + word & 0xFFF -> next_packet_ptr + + Returns (payload_len, next_ptr, status_is_error).""" + if any(b is None for b in d): + return (None, None, None) + word = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24) + return (((word >> 12) & 0xFFF) - 4, word & 0xFFF, + bool((word >> 24) & 0x84)) + def body(page, n): base = (page << 8) + 4 return [mem.get(base + i) for i in range(n)] @@ -479,8 +577,9 @@ if __name__ == "__main__": await send_frame(f1) check("T1 rwp advance", await wait_irq(), 2) total1 = len(f1) + 4 - check("T1 descriptor", descriptor(1), - [0x00, 0x00, (total1 >> 8) & 0xFF, total1 & 0xFF]) + check("T1 descriptor", descriptor(1), pack_desc(total1, 2)) + check("T1 descriptor via libogc decode", libogc_decode(descriptor(1)), + (len(f1), 2, False)) # The body MUST start at base+4. This is the check that catches a # placeholder write being dropped: with only three placeholder bytes # written the body landed at base+3 and every frame was shifted one @@ -500,8 +599,9 @@ if __name__ == "__main__": await send_frame(f2) rwp2 = await wait_irq() total2 = len(f2) + 4 - check("T2 descriptor (len > 255)", descriptor(2), - [0x00, 0x00, (total2 >> 8) & 0xFF, total2 & 0xFF]) + check("T2 descriptor (len > 255)", descriptor(2), pack_desc(total2, 4)) + check("T2 descriptor via libogc decode", libogc_decode(descriptor(2)), + (len(f2), 4, False)) check("T2 body at base+4", body(2, len(f2)), f2) # 304 bytes spans two pages, so page 2 -> page 4. check("T2 rwp advance (2 pages)", rwp2, 4) @@ -564,8 +664,11 @@ if __name__ == "__main__": check("T5 stored despite stalls", rwp5, "stored") pg5 = (ctx.get(dut.rx_wptr_w_data) - 2) & 0xFF # page it was written to total5 = len(f5) + 4 + next5 = ctx.get(dut.rx_wptr_w_data) check("T5 descriptor under stall", descriptor(pg5), - [0x00, 0x00, (total5 >> 8) & 0xFF, total5 & 0xFF]) + pack_desc(total5, next5)) + check("T5 descriptor via libogc decode", libogc_decode(descriptor(pg5)), + (len(f5), next5, False)) check("T5 body at base+4 under stall", body(pg5, len(f5)), f5) stall[0] = 1 await tick(4) @@ -586,8 +689,9 @@ if __name__ == "__main__": await send_frame(f6) check("T6 rwp back to page 1 after NCRA reset", await wait_irq(), 2) total6 = len(f6) + 4 - check("T6 frame lands at page 1", descriptor(1), - [0x00, 0x00, (total6 >> 8) & 0xFF, total6 & 0xFF]) + check("T6 frame lands at page 1", descriptor(1), pack_desc(total6, 2)) + check("T6 descriptor via libogc decode", libogc_decode(descriptor(1)), + (len(f6), 2, False)) check("T6 body at page 1 base+4", body(1, len(f6)), f6) await tick(4) diff --git a/gc_bba_test/source/netboot.c b/gc_bba_test/source/netboot.c index 804db6c..f81b731 100644 --- a/gc_bba_test/source/netboot.c +++ b/gc_bba_test/source/netboot.c @@ -32,7 +32,7 @@ static u8 s_last_desc[4]; /* How often the descriptor was found at each page offset. The assembler is * 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]; +static u32 s_desc_ok; /* ACKs where TI never set -- the frame did not reach the W5100. */ static u32 s_tx_timeouts; static u8 s_last_dump[32]; @@ -60,7 +60,7 @@ static int ring_next(u8 *buf, u32 cap) { u8 wp[2], hdr[8]; u32 rpage, wpage, dlen, flen, addr; - u32 shift; + u32 desc, nextp; if (bba_read(REG_RWP_LO, wp, 2) < 0) return -1; rpage = s_rrp; @@ -87,25 +87,24 @@ static int ring_next(u8 *buf, u32 cap) memcpy(s_last_desc, hdr, 4); s_last_page = rpage; - /* LOCATE the descriptor rather than assume it is at +0. + /* The descriptor is a PACKED 32-BIT LITTLE-ENDIAN WORD -- the format + * libogc's own BBA driver reads, so this is what GC software expects: * - * It should always be at +0: four bytes [00 00 len_hi len_lo] followed - * immediately by the frame. On hardware it sometimes sits at +1 instead, - * and which one varies frame to frame -- 18 of 23 frames were rejected - * for an implausible length while 5 parsed fine, in the same run. That - * is a race in the assembler, not a constant offset, and the gateware bug - * behind it is not yet found. + * bits 0-11 next_packet_ptr : the PAGE the NEXT frame starts at + * bits 12-23 packet_len : total bytes, INCLUDING these 4 + * bits 24-31 status : 0x80 and 0x04 are errors * - * Taking the length from whichever offset yields a plausible one lets the - * transfer proceed either way AND measures the split, which is the - * measurement needed to find the real cause. A length is a strong check: - * it must exceed the 4 descriptor bytes and fit a legal frame. */ - for (shift = 0; shift <= 1; shift++) { - dlen = ((u32)hdr[shift + 2] << 8) | hdr[shift + 3]; - if (dlen > 4 && dlen <= 1518 + 4) break; - } - if (shift > 1) { - dlen = ((u32)hdr[2] << 8) | hdr[3]; /* report the nominal one */ + * See the note in exi_bba/rx_frame_assembler.py for the disassembly this + * was taken from. The old reader assumed [00 00 len_hi len_lo] and + * probed offsets +0 and +1 to work around the tx-FIFO-underrun bug that + * shifted descriptors; that bug is fixed (confirmed on console, +1=0) and + * the probe is gone -- under the packed format a +1 read is not a shifted + * descriptor, just garbage. */ + desc = (u32)hdr[0] | ((u32)hdr[1] << 8) | + ((u32)hdr[2] << 16) | ((u32)hdr[3] << 24); + dlen = (desc >> 12) & 0xFFF; + nextp = desc & 0xFFF; + if (((desc >> 24) & 0x84) || dlen <= 4 || dlen > 1518 + 4) { /* Only now is a hex dump worth an EXI transaction. Doing it for * EVERY frame (as an earlier version did) added a round trip to the * poll loop that is already losing the race against the writer -- @@ -113,7 +112,7 @@ static int ring_next(u8 *buf, u32 cap) bba_stream((u16)addr, s_last_dump, sizeof(s_last_dump)); return -3; } - s_shift_hits[shift]++; + s_desc_ok++; flen = dlen - 4; if (flen > cap) flen = cap; @@ -122,16 +121,18 @@ static int ring_next(u8 *buf, u32 cap) * one DMA read covers it. */ { static u8 tmp[1536 + 4]; - u32 want = shift + dlen; + u32 want = dlen; if (want > sizeof(tmp)) want = sizeof(tmp); if (bba_stream((u16)addr, tmp, want) < 0) return -1; - memcpy(buf, tmp + shift + 4, flen); + memcpy(buf, tmp + 4, flen); } - /* Advance RRP by the pages this frame occupied, wrapping 1..15. */ + /* Advance RRP to the descriptor's OWN next_packet_ptr, which is what + * libogc does, rather than recomputing the page span here. The writer + * put that value in the descriptor, so the two cannot disagree. */ { - u32 pages = (shift + dlen + 255) >> 8; - u32 next = ((rpage - 1 + pages) % 15) + 1; + u32 next = nextp; + if (next < 1 || next > 15) next = 1; if (bba_w8(REG_RRP_LO, (u8)next) < 0) return -1; s_rrp = next; } @@ -381,11 +382,10 @@ 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, desc+0=%u +1=%u, txto=%u)", + "%s (%u seen, %u bad, desc ok=%u, txto=%u)", got ? "timeout mid-transfer" : "timeout: no match", (unsigned)seen, (unsigned)bad, - (unsigned)s_shift_hits[0], - (unsigned)s_shift_hits[1], + (unsigned)s_desc_ok, (unsigned)s_tx_timeouts); status(line); } @@ -433,9 +433,8 @@ int netboot_receive(void *dst, u32 maxlen, void (*status)(const char *msg)) * W5100) versus sent-but-lost. The host reports the * retries; only the GC knows which kind they were. */ snprintf(line, sizeof(line), - "desc@+0=%u +1=%u bad=%u lapped=%u txto=%u", - (unsigned)s_shift_hits[0], - (unsigned)s_shift_hits[1], + "desc ok=%u bad=%u lapped=%u txto=%u", + (unsigned)s_desc_ok, (unsigned)bad, (unsigned)lapped, (unsigned)s_tx_timeouts); status(line); diff --git a/gc_bba_test/source/tests.c b/gc_bba_test/source/tests.c index b8c1af4..8f88be1 100644 --- a/gc_bba_test/source/tests.c +++ b/gc_bba_test/source/tests.c @@ -394,7 +394,7 @@ static int t_rx(char *m, size_t n) static int t_dma(char *m, size_t n) { u8 buf[64], rp[2], wp[2]; - u32 dlen, page, addr; + u32 dlen, page, addr, desc, nextp; if (bba_read(REG_RRP_LO, rp, 2) < 0) { snprintf(m, n, "RRP read failed"); return 1; } if (bba_read(REG_RWP_LO, wp, 2) < 0) { snprintf(m, n, "RWP read failed"); return 1; } page = ((u32)rp[1] << 8) | rp[0]; @@ -417,11 +417,18 @@ static int t_dma(char *m, size_t n) snprintf(m, n, "stream read at 0x%04X failed", (unsigned)addr); return 1; } - /* 4-byte ring descriptor; its length field INCLUDES the descriptor. */ - dlen = ((u32)buf[2] << 8) | buf[3]; - if (dlen > 4 && dlen <= 1518) { - snprintf(m, n, "p%u len 0x%04X = %u bytes, etype %02X%02X", - (unsigned)page, (unsigned)dlen, (unsigned)(dlen - 4), + /* The 4-byte ring descriptor is a PACKED 32-BIT LITTLE-ENDIAN WORD, the + * format libogc's own driver reads: + * bits 0-11 next_packet_ptr (page), 12-23 packet_len (INCLUDING these + * 4 bytes), 24-31 status (0x80 and 0x04 are errors). + * Decoded here exactly as bba_interrupt does. */ + desc = (u32)buf[0] | ((u32)buf[1] << 8) | + ((u32)buf[2] << 16) | ((u32)buf[3] << 24); + dlen = (desc >> 12) & 0xFFF; + nextp = desc & 0xFFF; + if (((desc >> 24) & 0x84) == 0 && dlen > 4 && dlen <= 1518 + 4) { + snprintf(m, n, "p%u len %u B, next p%u, etype %02X%02X", + (unsigned)page, (unsigned)(dlen - 4), (unsigned)nextp, buf[16], buf[17]); return 0; } @@ -442,10 +449,13 @@ static int t_dma(char *m, size_t n) map[pg - 1] = '?'; continue; } - dl = ((u32)d[2] << 8) | d[3]; - if (d[0] == 0 && d[1] == 0 && dl == 0) map[pg - 1] = '-'; - else if (dl > 4 && dl <= 1518) map[pg - 1] = 'o'; - else map[pg - 1] = 'x'; + u32 w = (u32)d[0] | ((u32)d[1] << 8) | + ((u32)d[2] << 16) | ((u32)d[3] << 24); + dl = (w >> 12) & 0xFFF; + if (w == 0) map[pg - 1] = '-'; + else if (((w >> 24) & 0x84) == 0 && + dl > 4 && dl <= 1518 + 4) map[pg - 1] = 'o'; + else map[pg - 1] = 'x'; } map[15] = 0; snprintf(m, n, "p%u len 0x%04X raw %02X %02X %02X %02X RWP=%u map %s",