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:
@@ -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).
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user