#include "tests.h" #include "bba.h" #include #include #include #include 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; 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; } /* 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), 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; } 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'; } 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; } }