Files
rebbarb/gc_bba_test/source/tests.c
T
Roflin d83f7beef8 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>
2026-10-10 10:27:45 +02:00

574 lines
21 KiB
C

#include "tests.h"
#include "bba.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ogc/lwp_watchdog.h>
int g_ncra_reset_done = 0;
static void msleep(u32 ms) { usleep(ms * 1000); }
/* ───────────────────────── suite 1: identity ───────────────────────── */
/* Is the EXI link demonstrably healthy apart from address 0?
*
* Needed because `id_phase` is a ONE-SHOT in the FPGA, cleared by the first
* NCRA reset, and g_ncra_reset_done only tracks resets THIS RUN of the test
* app issued. That flag is software state standing in for hardware state, and
* the two come apart as soon as a build is delivered over the wire: netboot
* replaces the DOL and restarts the app with the flag clear, while the FPGA
* keeps running with id_phase already spent by the previous run. The ID tests
* then query address 0, get NCRA back -- correct behaviour -- and report a
* FAILURE for it. Reflashing the FPGA is the only thing that restores it.
*
* HIPR and NWAYS are hardwired constants in the register file and do not
* depend on id_phase, so if BOTH read correctly the EXI path is fine and a
* wrong answer at address 0 can only be a spent id_phase. If they are wrong
* too, something real is broken and the test must still fail. */
static int iface_alive(void)
{
return bba_r8(REG_HIPR) == HIPR_PRESENT &&
bba_r8(REG_NWAYS) == NWAYS_LINKUP;
}
static int t_devid(char *m, size_t n)
{
u8 b[4];
int rc;
if (g_ncra_reset_done) {
snprintf(m, n, "skipped: id_phase is a one-shot; reflash/reset the FPGA");
return 2;
}
if ((rc = bba_devid(b)) < 0) { snprintf(m, n, "EXI error %d", rc); return 1; }
if (b[0]==0x04 && b[1]==0x02 && b[2]==0x02 && b[3]==0x00) {
snprintf(m, n, "%02X %02X %02X %02X (want 04 02 02 00)",
b[0], b[1], b[2], b[3]);
return 0;
}
if (iface_alive()) {
snprintf(m, n, "%02X %02X %02X %02X: id_phase spent, reflash FPGA",
b[0], b[1], b[2], b[3]);
return 2;
}
snprintf(m, n, "%02X %02X %02X %02X (want 04 02 02 00)", b[0], b[1], b[2], b[3]);
return 1;
}
static int t_hipr(char *m, size_t n)
{
int v = bba_r8(REG_HIPR);
if (v < 0) { snprintf(m, n, "EXI error %d", v); return 1; }
snprintf(m, n, "0x%02X (want 0x%02X)", v, HIPR_PRESENT);
return v == HIPR_PRESENT ? 0 : 1;
}
static int t_nways(char *m, size_t n)
{
int v = bba_r8(REG_NWAYS);
if (v < 0) { snprintf(m, n, "EXI error %d", v); return 1; }
snprintf(m, n, "0x%02X (want 0x%02X = 100M full duplex)", v, NWAYS_LINKUP);
return v == NWAYS_LINKUP ? 0 : 1;
}
/* ───────────────────────── suite 2: registers ──────────────────────── */
static int rw_check(u16 addr, const u8 *pat, u32 len, const u8 *restore,
char *m, size_t n, const char *what)
{
u8 got[4];
int rc;
if ((rc = bba_write(addr, pat, len)) < 0) { snprintf(m, n, "write failed %d", rc); return 1; }
if ((rc = bba_read(addr, got, len)) < 0) { snprintf(m, n, "read failed %d", rc); return 1; }
if (restore) bba_write(addr, restore, len);
if (memcmp(got, pat, len) != 0) {
snprintf(m, n, "%s readback %02X %02X %02X %02X", what,
got[0], len>1?got[1]:0, len>2?got[2]:0, len>3?got[3]:0);
return 1;
}
snprintf(m, n, "%s wrote+read back %u bytes OK", what, (unsigned)len);
return 0;
}
static int t_par_lo(char *m, size_t n)
{
static const u8 pat[4] = {0xDE,0xAD,0xBE,0xEF};
static const u8 def[4] = {0x00,0x09,0xBF,0x00};
return rw_check(REG_PAR0, pat, 4, def, m, n, "PAR0-3");
}
static int t_par_hi(char *m, size_t n)
{
static const u8 pat[2] = {0x5A,0xA5};
static const u8 def[2] = {0x00,0x01};
return rw_check(REG_PAR4, pat, 2, def, m, n, "PAR4-5");
}
static int t_imr(char *m, size_t n)
{
int v;
if (bba_w8(REG_IMR, 0x86) < 0) { snprintf(m, n, "write failed"); return 1; }
v = bba_r8(REG_IMR);
snprintf(m, n, "0x%02X (want 0x86)", v);
return v == 0x86 ? 0 : 1;
}
static int t_ir_w1c(char *m, size_t n)
{
int before, after;
before = bba_r8(REG_IR);
if (before < 0) { snprintf(m, n, "read failed"); return 1; }
if (bba_w8(REG_IR, 0xFF) < 0) { snprintf(m, n, "write failed"); return 1; }
after = bba_r8(REG_IR);
snprintf(m, n, "0x%02X -> write 0xFF -> 0x%02X (want 0x00)", before, after);
return after == 0x00 ? 0 : 1;
}
/* ─────────────────────── suite 3: reset & control ──────────────────── */
static int t_ncra_reset(char *m, size_t n)
{
int i, v;
if (bba_w8(REG_NCRA, NCRA_RESET) < 0) { snprintf(m, n, "write failed"); return 1; }
g_ncra_reset_done = 1;
for (i = 0; i < 50; i++) {
v = bba_r8(REG_NCRA);
if (v < 0) { snprintf(m, n, "read failed"); return 1; }
if (!(v & NCRA_RESET)) {
snprintf(m, n, "self-cleared after %d poll(s)", i);
/* the W5100 re-inits behind this: RSTn pulse + 83 ms settle x2 */
msleep(400);
return 0;
}
msleep(5);
}
snprintf(m, n, "RESET bit never cleared (NCRA=0x%02X)", v);
return 1;
}
static int t_ncra_sr(char *m, size_t n)
{
int v;
if (bba_w8(REG_NCRA, NCRA_SR) < 0) { snprintf(m, n, "write failed"); return 1; }
v = bba_r8(REG_NCRA);
snprintf(m, n, "0x%02X (want 0x%02X, start-receive set)", v, NCRA_SR);
return v == NCRA_SR ? 0 : 1;
}
/* ──────────────────── suite 4: EXI link integrity ──────────────────── */
static int t_repeat(char *m, size_t n)
{
int i, bad = 0;
for (i = 0; i < 200; i++)
if (bba_r8(REG_NWAYS) != NWAYS_LINKUP) bad++;
snprintf(m, n, "%d/200 reads wrong at EXI speed index %u", bad, (unsigned)bba_speed);
return bad == 0 ? 0 : 1;
}
static int t_speed_sweep(char *m, size_t n)
{
u32 saved = bba_speed, s;
u32 best = 0;
int any = 0;
char buf[40];
int off = 0;
buf[0] = 0;
for (s = 0; s <= EXI_SPEED32MHZ; s++) {
int i, bad = 0;
bba_speed = s;
for (i = 0; i < 40; i++)
if (bba_r8(REG_NWAYS) != NWAYS_LINKUP) bad++;
off += snprintf(buf + off, sizeof(buf) - off, "%s%u", s ? "," : "",
(unsigned)(bad ? 0 : 1));
if (!bad) { best = s; any = 1; }
}
bba_speed = saved;
/* `best` of 0 is ambiguous (index 0 works vs nothing works), so report
* the per-index map and say NONE explicitly. */
if (!any)
snprintf(m, n, "ok=[%s] NONE work - not an oversampling issue", buf);
else
snprintf(m, n, "ok=[%s] highest good idx=%u (5=27MHz)", buf,
(unsigned)best);
return (any && best >= EXI_SPEED32MHZ) ? 0 : 1;
}
static int t_gap_sweep(char *m, size_t n)
{
static const u32 gaps[] = {0, 1, 2, 4, 8, 16, 32};
u32 saved = bba_gap_us;
int i, g, best = -1;
for (g = 0; g < (int)(sizeof(gaps)/sizeof(gaps[0])); g++) {
int bad = 0;
bba_gap_us = gaps[g];
for (i = 0; i < 30; i++)
if (bba_r8(REG_NWAYS) != NWAYS_LINKUP) bad++;
if (!bad) { best = (int)gaps[g]; break; }
}
bba_gap_us = saved;
if (best < 0) {
snprintf(m, n, "no gap up to 32us gave clean reads at speed idx %u",
(unsigned)bba_speed);
return 1;
}
snprintf(m, n, "min clean header->data gap = %d us at speed idx %u",
best, (unsigned)bba_speed);
return 0;
}
static int t_raw_sweep(char *m, size_t n)
{
/* The SAME 8-slot read at EVERY speed index, reported as the first two
* bytes per index. This is the measurement that distinguishes the two
* remaining explanations for a one-bit MISO lag:
* - a shift that APPEARS or GROWS with clock rate is a timing problem;
* - a shift that is constant across indices is structural.
* Run it against a PHASE_METER bitstream and every byte should read 80
* (aligned) / 40 (one bit late) / 20 (two bits late), with the register
* file and both byte FIFOs out of the path entirely. */
u32 saved = bba_speed, s;
int off = 0, bad = 0;
m[0] = 0;
for (s = 0; s <= EXI_SPEED32MHZ; s++) {
u8 b[8];
bba_speed = s;
if (bba_read(REG_NWAYS, b, sizeof(b)) < 0) {
off += snprintf(m + off, n - off, "%u:ERR ", (unsigned)s);
bad++;
continue;
}
off += snprintf(m + off, n - off, "%u:%02X%02X ",
(unsigned)s, b[0], b[1]);
if (b[0] != NWAYS_LINKUP)
bad++;
}
bba_speed = saved;
return bad ? 1 : 0;
}
static int t_raw_reg(char *m, size_t n)
{
/* Clock EIGHT data bytes for a one-byte register read and show them all.
* A correct device puts NWAYS (0x17) in slot 0. If it shows up later the
* reply is merely LATE and the slot index gives the latency directly; if
* it never appears the decode is wrong, not slow. */
u8 b[8];
int i, at = -1;
if (bba_read(REG_NWAYS, b, sizeof(b)) < 0) { snprintf(m, n, "EXI error"); return 1; }
for (i = 0; i < 8; i++) if (b[i] == NWAYS_LINKUP) { at = i; break; }
snprintf(m, n, "%02X %02X %02X %02X %02X %02X %02X %02X | 0x17 %s%d",
b[0],b[1],b[2],b[3],b[4],b[5],b[6],b[7],
at < 0 ? "absent" : "at slot ", at < 0 ? 0 : at);
return at == 0 ? 0 : 1;
}
static int t_raw_id(char *m, size_t n)
{
/* Same for the device ID: 2-byte header, then clock 8 instead of 4. */
u8 b[8];
int i, at = -1, rc;
u16 h = 0x0000;
/* Fast path for a reset this run issued. Note this flag can be STALE
* (see iface_alive): it is clear after a netbooted update even though the
* FPGA's id_phase is long gone, which is why the result is also checked
* against iface_alive() below rather than trusted to this. */
if (g_ncra_reset_done) {
snprintf(m, n, "skipped: id_phase is a one-shot; reflash/reset the FPGA");
return 2;
}
if ((rc = begin_raw()) < 0) { snprintf(m, n, "EXI error %d", rc); return 1; }
EXI_Imm(BBA_EXI_CHAN, &h, 2, EXI_WRITE, NULL);
EXI_Sync(BBA_EXI_CHAN);
memset(b, 0, sizeof(b));
EXI_ImmEx(BBA_EXI_CHAN, b, sizeof(b), EXI_READ);
end_raw();
for (i = 0; i < 8; i++) if (b[i] == 0x04) { at = i; break; }
snprintf(m, n, "%02X %02X %02X %02X %02X %02X %02X %02X | 0x04 %s%d",
b[0],b[1],b[2],b[3],b[4],b[5],b[6],b[7],
at < 0 ? "absent" : "at slot ", at < 0 ? 0 : at);
if (at == 0)
return 0;
/* Same discrimination as t_devid: a spent id_phase is not a fault. */
return iface_alive() ? 2 : 1;
}
/* ───────────────────────── suite 5: ethernet ───────────────────────── */
static u8 g_mac[6] = {0x00,0x09,0xBF,0x00,0x00,0x01};
static const u8 SRC_IP[4] = {10,0,0,99};
static const u8 DST_IP[4] = {10,0,0,50};
static int t_mac_read(char *m, size_t n)
{
u8 a[4], b[2];
if (bba_read(REG_PAR0, a, 4) < 0 || bba_read(REG_PAR4, b, 2) < 0) {
snprintf(m, n, "PAR read failed"); return 1;
}
memcpy(g_mac, a, 4); memcpy(g_mac + 4, b, 2);
snprintf(m, n, "%02X:%02X:%02X:%02X:%02X:%02X",
g_mac[0],g_mac[1],g_mac[2],g_mac[3],g_mac[4],g_mac[5]);
return (g_mac[0]==0x00 && g_mac[1]==0x09 && g_mac[2]==0xBF) ? 0 : 1;
}
static u32 build_arp(u8 *f)
{
memset(f, 0, 60);
memset(f, 0xFF, 6); /* dst: broadcast */
memcpy(f + 6, g_mac, 6); /* src MAC */
f[12] = 0x08; f[13] = 0x06; /* ethertype ARP */
f[14] = 0x00; f[15] = 0x01; /* HTYPE ethernet */
f[16] = 0x08; f[17] = 0x00; /* PTYPE IPv4 */
f[18] = 6; f[19] = 4; /* HLEN / PLEN */
f[20] = 0x00; f[21] = 0x01; /* OPER request */
memcpy(f + 22, g_mac, 6); /* SHA */
memcpy(f + 28, SRC_IP, 4); /* SPA */
memcpy(f + 38, DST_IP, 4); /* TPA (THA stays zero) */
return 60;
}
static int t_tx(char *m, size_t n)
{
u8 f[60];
u32 len = build_arp(f);
int i, ir = 0;
bba_w8(REG_IR, 0xFF); /* clear IR */
bba_w8(REG_TWD_LO, (u8)(len & 0xFF)); /* frame length */
bba_w8(REG_TWD_HI, (u8)((len >> 8) & 0xFF));
if (bba_tx_bytes(f, len) < 0) { snprintf(m, n, "TXDATA stream failed"); return 1; }
bba_w8(REG_NCRA, NCRA_SR | NCRA_ST0); /* fire */
for (i = 0; i < 60; i++) {
ir = bba_r8(REG_IR);
if (ir >= 0 && (ir & IR_TI)) {
snprintf(m, n, "%u-byte ARP sent, TI set after %d poll(s)", (unsigned)len, i);
return 0;
}
msleep(10);
}
/* TI only means TXFrameDrain handed the frame to the W5100 -- it is NOT
* proof the chip put it on the wire. Absence of TI is still a clear
* failure though. */
snprintf(m, n, "TI never set (IR=0x%02X) - W5100 may be wedged", ir);
return 1;
}
static int t_rx(char *m, size_t n)
{
u8 r0[2], r1[2];
int i, ir;
if (bba_read(REG_RWP_LO, r0, 2) < 0) { snprintf(m, n, "RWP read failed"); return 1; }
/* Catch RRP up to RWP before waiting, i.e. declare everything already in
* the ring consumed -- which is what a real driver does once it has read
* the frames out, and what netboot does at start-up.
*
* ⚠️ Without this the test fails on a HEALTHY adapter. This app never
* writes RRP, so ambient broadcast traffic fills the 15-page ring, `free`
* falls below _MAX_FRAME_PAGES (6) and RBFI then drops EVERY frame --
* correctly, to avoid lapping. RWP stops advancing permanently and this
* test reports "no frame in 2 s (RWP still 0x000F)" for the rest of the
* session. Page 15 is the giveaway: free = (1 + 15 - 15) mod 15 = 1.
*
* Freeing the ring here also leaves exactly the newly received frame
* unconsumed at RRP, which is where the SPRAM/DMA suite reads. */
if (bba_w8(REG_RRP_LO, r0[0]) < 0) {
snprintf(m, n, "RRP write failed"); return 1;
}
for (i = 0; i < 200; i++) { /* up to ~2 s */
if (bba_read(REG_RWP_LO, r1, 2) < 0) { snprintf(m, n, "RWP read failed"); return 1; }
ir = bba_r8(REG_IR);
if (memcmp(r0, r1, 2) != 0 || (ir >= 0 && (ir & IR_RI))) {
snprintf(m, n, "RWP 0x%02X%02X -> 0x%02X%02X, RI=%d",
r0[1], r0[0], r1[1], r1[0], (ir & IR_RI) ? 1 : 0);
return 0;
}
msleep(10);
}
snprintf(m, n, "no frame in 2 s (RWP still 0x%02X%02X)", r0[1], r0[0]);
return 1;
}
/* ─────────────────────── suite 6: SPRAM / DMA ──────────────────────── */
static int t_dma(char *m, size_t n)
{
u8 buf[64], rp[2], wp[2];
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];
/* RWP/RRP are PAGE pointers into the 15-page ring, so the next
* unconsumed frame's descriptor is at RRP*256 -- NOT at SPRAM_BASE.
* Reading 0x100 unconditionally only happens to work while nothing has
* been consumed and the ring has not wrapped, which is why this test
* reported "ring empty?" on a board that had just passed the RX test. */
if (rp[0] == wp[0] && rp[1] == wp[1]) {
snprintf(m, n, "ring empty: RRP==RWP==%u (send it a frame first)",
(unsigned)page);
return 2;
}
if (page < 1 || page > 15) {
snprintf(m, n, "RRP page %u outside ring pages 1-15", (unsigned)page);
return 1;
}
addr = page << 8;
if (bba_stream((u16)addr, buf, sizeof(buf)) < 0) {
snprintf(m, n, "stream read at 0x%04X failed", (unsigned)addr);
return 1;
}
/* 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;
}
/* Diagnostic: the length alone does not say WHERE the frames are. Walk
* all 15 ring pages and classify each descriptor, so one run shows
* whether the ring is empty, written at a different page than RRP says,
* or holding genuinely malformed descriptors:
* '-' all-zero (never written) 'o' plausible frame length
* 'x' non-zero but implausible '?' read failed
* Plus the raw 4 descriptor bytes at the RRP page. */
{
char map[16];
u32 pg;
u8 d[4];
for (pg = 1; pg <= 15; pg++) {
u32 dl;
if (bba_stream((u16)(pg << 8), d, 4) < 0) {
map[pg - 1] = '?';
continue;
}
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",
(unsigned)page, (unsigned)dlen,
buf[0], buf[1], buf[2], buf[3],
(unsigned)(((u32)wp[1] << 8) | wp[0]), map);
}
return 1;
}
static int t_dma_stream_len(char *m, size_t n)
{
u8 a[16], b[48];
if (bba_stream(SPRAM_BASE, a, sizeof(a)) < 0 ||
bba_stream(SPRAM_BASE, b, sizeof(b)) < 0) {
snprintf(m, n, "stream read failed"); return 1;
}
/* Reads >= 0x100 stream until CS, so the same address must give the same
* leading bytes regardless of how many we clock. */
if (memcmp(a, b, sizeof(a)) != 0) {
snprintf(m, n, "16B and 48B reads disagree - streaming is length-sensitive");
return 1;
}
snprintf(m, n, "16B and 48B reads agree; streams until CS as designed");
return 0;
}
/* ───────────────────────────── registry ────────────────────────────── */
static test_t s_identity[] = {
{"Device ID", "EXI link + enumeration: addr 0 returns 04 02 02 00", t_devid, T_PENDING, {0}},
{"HIPR", "BBA-present sentinel reads 0x01", t_hipr, T_PENDING, {0}},
{"NWAYS", "link-status sentinel reads 0x17", t_nways, T_PENDING, {0}},
};
static test_t s_regs[] = {
{"PAR0-3 w/r", "MAC low bytes are writable and read back", t_par_lo, T_PENDING, {0}},
{"PAR4-5 w/r", "MAC high bytes are writable and read back", t_par_hi, T_PENDING, {0}},
{"IMR", "interrupt mask is writable (GC init writes 0x86)", t_imr, T_PENDING, {0}},
{"IR write-1-clr","interrupt flags clear on write-1", t_ir_w1c, T_PENDING, {0}},
};
static test_t s_reset[] = {
{"NCRA reset", "RESET bit self-clears; W5100 re-inits behind it", t_ncra_reset, T_PENDING, {0}},
{"NCRA SR", "start-receive bit holds", t_ncra_sr, T_PENDING, {0}},
};
static test_t s_exi[] = {
{"200x read", "no bit errors over 200 register reads", t_repeat, T_PENDING, {0}},
{"Speed sweep", "which EXI clock indices are reliable (0..5)", t_speed_sweep, T_PENDING, {0}},
{"Gap sweep", "min header->data idle gap the device needs", t_gap_sweep, T_PENDING, {0}},
{"Raw reg dump", "8 slots of a NWAYS read - WHERE does 0x17 land?", t_raw_reg, T_PENDING, {0}},
{"Raw ID dump", "8 slots of the device-ID query - where is 0x04?", t_raw_id, T_PENDING, {0}},
{"Per-speed dump","first 2 slots at EVERY speed - does the shift move?",t_raw_sweep, T_PENDING, {0}},
};
static test_t s_eth[] = {
{"MAC readback", "PAR0-5 hold a Nintendo-OUI MAC", t_mac_read, T_PENDING, {0}},
{"TX ARP", "60-byte frame reaches the W5100 and TI fires", t_tx, T_PENDING, {0}},
{"RX frame", "a frame arrives and RWP/RI advance", t_rx, T_PENDING, {0}},
};
static test_t s_spram[] = {
{"DMA descriptor","ring descriptor at 0x100 is well-formed", t_dma, T_PENDING, {0}},
{"Stream length", "reads >= 0x100 stream until CS, length-independent",t_dma_stream_len, T_PENDING, {0}},
};
suite_t g_suites[] = {
{"Identity", "device enumerates and reports itself", s_identity, 3},
{"Registers", "register file is readable and writable", s_regs, 4},
{"Reset/control", "NCRA reset and control bits behave", s_reset, 2},
{"EXI integrity", "the EXI link is error-free, and how fast", s_exi, 6},
{"Ethernet", "frames get to and from the W5100", s_eth, 3},
{"SPRAM/DMA", "bulk reads stream correctly from the ring", s_spram, 2},
};
const int g_nsuites = sizeof(g_suites) / sizeof(g_suites[0]);
void tests_run_test(test_t *t)
{
int rc;
t->msg[0] = 0;
rc = t->run(t->msg, sizeof(t->msg));
t->status = (rc == 0) ? T_PASS : (rc == 2 ? T_SKIP : T_FAIL);
}
void tests_run_suite(suite_t *s)
{
int i;
for (i = 0; i < s->ntests; i++) tests_run_test(&s->tests[i]);
}
void tests_run_all(void)
{
int i;
for (i = 0; i < g_nsuites; i++) tests_run_suite(&g_suites[i]);
}
void tests_reset_status(void)
{
int i, j;
for (i = 0; i < g_nsuites; i++)
for (j = 0; j < g_suites[i].ntests; j++) {
g_suites[i].tests[j].status = T_PENDING;
g_suites[i].tests[j].msg[0] = 0;
}
}
void tests_tally(int *pass, int *fail, int *skip, int *pending)
{
int i, j;
*pass = *fail = *skip = *pending = 0;
for (i = 0; i < g_nsuites; i++)
for (j = 0; j < g_suites[i].ntests; j++)
switch (g_suites[i].tests[j].status) {
case T_PASS: (*pass)++; break;
case T_FAIL: (*fail)++; break;
case T_SKIP: (*skip)++; break;
default: (*pending)++; break;
}
}