diff --git a/gc_bba_test/source/bba.c b/gc_bba_test/source/bba.c index 0c3eff7..28972b9 100644 --- a/gc_bba_test/source/bba.c +++ b/gc_bba_test/source/bba.c @@ -1,8 +1,20 @@ #include "bba.h" #include #include +#include u32 bba_speed = EXI_SPEED32MHZ; /* nominal name; ~27 MHz in reality */ +u32 bba_gap_us = 16; /* see bba.h -- 0 reproduces the raw failure */ + +/* Busy-wait, not usleep(): we are holding the EXI lock with CS asserted, so + * yielding here is both unnecessary and imprecise at these timescales. */ +static void gap(void) +{ + u64 t0; + if (!bba_gap_us) return; + t0 = gettime(); + while (ticks_to_microsecs(diff_ticks(t0, gettime())) < bba_gap_us) ; +} /* Build the 2-byte EXI transaction header. * byte0: [7]=write [6:0]=addr[12:6] @@ -55,9 +67,10 @@ int bba_read(u16 addr, void *buf, u32 len) if (len < 1 || len > 4) return -10; if ((rc = begin()) < 0) return rc; if ((rc = put_hdr(addr, len, 0)) < 0) { end(); return rc; } - /* The real GC driver pauses the clock here while the device decodes the - * header and prefetches the response into its TX FIFO. Separate EXI_Imm - * calls give us that gap for free. */ + /* Hold the clock idle while the device decodes the header and prefetches + * the response into its TX FIFO. libogc's inter-call overhead alone is + * NOT enough at full EXI speed -- see bba_gap_us. */ + gap(); memset(buf, 0, len); if (EXI_ImmEx(BBA_EXI_CHAN, buf, len, EXI_READ) <= 0) { end(); return -5; } end(); @@ -98,6 +111,7 @@ int bba_stream(u16 addr, void *buf, u32 len) * >= 0x0100 and streams until CS rises, so ask for 4 and clock as many * bytes as we actually want. */ if ((rc = put_hdr(addr, 4, 0)) < 0) { end(); return rc; } + gap(); memset(buf, 0, len); if (EXI_ImmEx(BBA_EXI_CHAN, buf, len, EXI_READ) <= 0) { end(); return -5; } end(); diff --git a/gc_bba_test/source/bba.h b/gc_bba_test/source/bba.h index fdd310a..187677c 100644 --- a/gc_bba_test/source/bba.h +++ b/gc_bba_test/source/bba.h @@ -47,6 +47,16 @@ * oversamples it 2x at 54 MHz. Settable so the suite can sweep it. */ extern u32 bba_speed; +/* Microseconds to hold the clock idle between the header write and the data + * phase, with CS still asserted. The register file decodes the header and + * PROACTIVELY pushes the response into its TX FIFO during this gap; until the + * first byte reaches the FIFO head the device drives MISO low. Measured on + * hardware 2026-10-03: at EXI index 5 the reply arrived 22 bit-times (~815 ns) + * late with no explicit gap, so a 4-byte read returned 00 00 00 10 -- the + * correct stream, just shifted out of the window. 0 = whatever gap libogc's + * inter-call overhead happens to give. */ +extern u32 bba_gap_us; + /* All return 0 on success, negative on an EXI-layer failure. */ /* Immediate read of `len` (1..4) bytes from `addr`. diff --git a/gc_bba_test/source/tests.c b/gc_bba_test/source/tests.c index e4f6cca..4ddfebd 100644 --- a/gc_bba_test/source/tests.c +++ b/gc_bba_test/source/tests.c @@ -156,6 +156,29 @@ static int t_speed_sweep(char *m, size_t n) return 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; +} + /* ───────────────────────── suite 5: ethernet ───────────────────────── */ static u8 g_mac[6] = {0x00,0x09,0xBF,0x00,0x00,0x01}; @@ -293,6 +316,7 @@ static test_t s_reset[] = { 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}}, }; static test_t s_eth[] = { {"MAC readback", "PAR0-5 hold a Nintendo-OUI MAC", t_mac_read, T_PENDING, {0}}, @@ -308,7 +332,7 @@ 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, 2}, + {"EXI integrity", "the EXI link is error-free, and how fast", s_exi, 3}, {"Ethernet", "frames get to and from the W5100", s_eth, 3}, {"SPRAM/DMA", "bulk reads stream correctly from the ring", s_spram, 2}, };