Fix the RX ring descriptor to the format libogc actually parses

The assembler wrote 00 00 len[11:8] len[7:0] with bytes 0 and 1 zero.  The real
format, read out of libogc's own BBA driver, is a PACKED 32-BIT LITTLE-ENDIAN
word: next_packet_ptr in bits 0-11, packet_len in bits 12-23, status in bits
24-31.  Under that driver our descriptor for a 342-byte frame decoded as a
12-byte frame with a next-pointer of 0 into the reserved page, so Swiss and
anything else built on libogc would have initialised perfectly and then
misparsed every received frame.

Found by disassembling /opt/devkitpro/libogc/lib/cube/libbba.a (gcif.o,
bba_interrupt) rather than by guessing:

  lwbrx   r9,0,r9              # 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
  srwi    r9,r9,24             # status = word >> 24
  and.    r9,r9,r18            # r18 = 0x..84 -> errors are 0x80 | 0x04

and the low 12 bits are written straight back to RRP (0x0018/0x0019), which is
why next_packet_ptr has to be there at all -- it is the driver's read pointer.

The length semantics and addressing were already correct (descriptor at
page<<8, body at +4, length including the descriptor); only the packing was
wrong and next_packet_ptr was never written.

RXFrameAssembler now computes the next page BEFORE writing the descriptor
(CALC_NEXT, formerly ADVANCE_RWP, moved ahead of the backfill) and writes bytes
0-3 as WRITE_D0..WRITE_D3.

WRITE_D3 fixes a latent bug this exposed: WRITE_PLACEHOLDER issues four writes
but the first lands twice, so desc_base+3 was NEVER written.  That was harmless
while the length write covered byte 3.  As the status byte it would have been
whatever the previous frame left in that cell, and a stale 0x80 or 0x04 makes
the driver discard the frame.

Readers updated to decode identically: netboot (which also now takes RRP from
the descriptor's own next_packet_ptr, as libogc does, instead of recomputing
the page span) and t_dma, including its page map.  netboot's +0/+1 offset probe
is removed -- the underrun that shifted descriptors is fixed and confirmed on
console, and under a packed format a +1 read is not a shifted descriptor but
garbage.

The assembler's testbench now decodes with libogc_decode(), reproducing the
instruction sequence above, so the tests check the DRIVER'S algorithm rather
than our own packing.  Every module testbench and bba_top at EXI_HALF=1/2/8
pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 10:27:45 +02:00
parent 5ee1b9d07b
commit d83f7beef8
6 changed files with 273 additions and 79 deletions
+62
View File
@@ -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).
+19 -5
View File
@@ -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).
+6 -1
View File
@@ -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]}")
+136 -32
View File
@@ -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)
+30 -31
View File
@@ -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);
+20 -10
View File
@@ -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",