Files
rebbarb/gc_bba_test/source/bba.c
T
Roflin 6f151cd1ff gc_bba_test: add raw slot dumps to locate the response
Reading one byte and getting 0x00 cannot distinguish 'decode is wrong' from
'reply is late' -- a one-byte window has nowhere for a late answer to show up.
These two probes clock EIGHT data bytes and print every slot, so the position
of 0x17 (NWAYS) or 0x04 (device ID) gives the latency directly, the same way
bit-shift analysis turned 00 00 00 10 into 'correct, 22 bits late'.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-03 12:46:46 +02:00

133 lines
3.5 KiB
C

#include "bba.h"
#include <string.h>
#include <unistd.h>
#include <ogc/lwp_watchdog.h>
u32 bba_speed = EXI_SPEED32MHZ; /* nominal name; ~27 MHz in reality */
static int begin(void);
static void end(void);
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) ;
}
/* Real GC<->BBA protocol (docs/exi_protocol_real.md), verified against libogc
* and Swiss. Register space is a 4-byte header followed by a data phase that
* runs until CS -- there is no length field.
* read [0x80, addr>>8, addr&0xFF, 0x00] + N bytes
* write [0xC0, addr>>8, addr&0xFF, 0x00] + N bytes
* The trailing 0x00 is the device's prefetch window: the address completes a
* whole byte before the data phase so the register file can fetch in time. */
int begin_raw(void) { return begin(); }
void end_raw(void) { end(); }
static int begin(void)
{
int tries;
/* EXI_Lock fails outright (rather than blocking) when another subsystem
* holds the channel -- Swiss or a libogc driver may have it briefly.
* Retry for ~100 ms before giving up so a transient collision does not
* surface as a test failure. */
for (tries = 0; tries < 100; tries++) {
if (EXI_Lock(BBA_EXI_CHAN, BBA_EXI_DEV, NULL) > 0) break;
usleep(1000);
}
if (tries == 100) return -1;
if (EXI_Select(BBA_EXI_CHAN, BBA_EXI_DEV, bba_speed) <= 0) {
EXI_Unlock(BBA_EXI_CHAN);
return -2;
}
return 0;
}
static void end(void)
{
EXI_Deselect(BBA_EXI_CHAN);
EXI_Unlock(BBA_EXI_CHAN);
}
static int put_hdr(u16 addr, int write)
{
u32 h = ((u32)(write ? 0xC0 : 0x80) << 24) | ((u32)addr << 8);
if (EXI_Imm(BBA_EXI_CHAN, &h, 4, EXI_WRITE, NULL) <= 0) return -3;
if (EXI_Sync(BBA_EXI_CHAN) <= 0) return -4;
return 0;
}
int bba_read(u16 addr, void *buf, u32 len)
{
int rc;
if (len < 1) return -10;
if ((rc = begin()) < 0) return rc;
if ((rc = put_hdr(addr, 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();
return 0;
}
int bba_write(u16 addr, const void *buf, u32 len)
{
int rc;
u8 tmp[64];
if (len < 1 || len > sizeof(tmp)) return -10;
memcpy(tmp, buf, len);
if ((rc = begin()) < 0) return rc;
if ((rc = put_hdr(addr, 1)) < 0) { end(); return rc; }
if (EXI_ImmEx(BBA_EXI_CHAN, tmp, len, EXI_WRITE) <= 0) { end(); return -6; }
end();
return 0;
}
int bba_devid(u8 id[4])
{
int rc;
u16 h = 0x0000; /* command space, reg 0, read */
if ((rc = begin()) < 0) return rc;
if (EXI_Imm(BBA_EXI_CHAN, &h, 2, EXI_WRITE, NULL) <= 0) { end(); return -3; }
if (EXI_Sync(BBA_EXI_CHAN) <= 0) { end(); return -4; }
gap();
memset(id, 0, 4);
if (EXI_ImmEx(BBA_EXI_CHAN, id, 4, EXI_READ) <= 0) { end(); return -5; }
end();
return 0;
}
int bba_r8(u16 addr)
{
u8 v;
int rc = bba_read(addr, &v, 1);
return rc < 0 ? rc : (int)v;
}
int bba_w8(u16 addr, u8 val)
{
return bba_write(addr, &val, 1);
}
int bba_stream(u16 addr, void *buf, u32 len)
{
/* Identical to bba_read now: the real protocol always streams until CS,
* so there is no separate "DMA" header form. */
return bba_read(addr, buf, len);
}
int bba_tx_bytes(const u8 *buf, u32 len)
{
u32 i;
for (i = 0; i < len; i++) {
int rc = bba_w8(REG_TXDATA, buf[i]);
if (rc < 0) return rc;
}
return 0;
}