Files
rebbarb/gc_bba_test/source/bba.c
T
Roflin ccf4eb4996 Add on-console GameCube test suite + devkitPPC in the devcontainer
gc_bba_test/ builds a .dol that drives the adapter over raw EXI from the
console and reports pass/fail per test on the TV, navigable with the pad.

Uses libogc's EXI API against EXI0:2 (Serial Port 1) rather than its bba
network driver: driving the register file directly gives each check its own
verdict, so a failure points at a specific register instead of at
'networking'.  Six suites - identity, registers, reset/control, EXI link
integrity (including an EXI clock-index sweep), ethernet TX/RX, SPRAM/DMA
streaming.

Encodes the hardware lessons from bring-up so the app cannot mislead:
  * the device ID is a one-shot cleared by the first NCRA reset, so it is
    reported SKIP on a re-run rather than FAIL
  * TI means the frame reached the W5100, NOT the wire - the TX test says so
  * register reads clock exactly the requested length (over-clocking
    desynchronises the next transaction)
  * TXDATA is written one byte per transaction (eff_addr auto-increments)

.devcontainer/Dockerfile gains the devkitPro gamecube-dev group.  devkitPro
publishes no .deb on GitHub, so this uses their installer script against
apt.devkitpro.org -- which is 403 from inside the running container's egress
but reachable from a host-side docker build.  Rebuild the container to get it.

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

106 lines
2.6 KiB
C

#include "bba.h"
#include <string.h>
u32 bba_speed = EXI_SPEED32MHZ; /* nominal name; ~27 MHz in reality */
/* Build the 2-byte EXI transaction header.
* byte0: [7]=write [6:0]=addr[12:6]
* byte1: [7:2]=addr[5:0] [1:0]=len-1
* Returned packed big-endian in a u16 so EXI_Imm(...,2,...) clocks byte0 first
* (the GameCube is big-endian, so the high byte goes out first). */
static u16 hdr(u16 addr, u32 len, int write)
{
u8 b0 = (write ? 0x80 : 0x00) | ((addr >> 6) & 0x7F);
u8 b1 = (u8)(((addr & 0x3F) << 2) | ((len - 1) & 0x03));
return ((u16)b0 << 8) | b1;
}
static int begin(void)
{
if (EXI_Lock(BBA_EXI_CHAN, BBA_EXI_DEV, NULL) == 0) 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, u32 len, int write)
{
u16 h = hdr(addr, len, write);
if (EXI_Imm(BBA_EXI_CHAN, &h, 2, 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 || 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. */
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[4];
if (len < 1 || len > 4) return -10;
memcpy(tmp, buf, len);
if ((rc = begin()) < 0) return rc;
if ((rc = put_hdr(addr, len, 1)) < 0) { end(); return rc; }
if (EXI_ImmEx(BBA_EXI_CHAN, tmp, len, EXI_WRITE) == 0) { end(); return -6; }
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)
{
int rc;
if (len < 1) return -10;
if ((rc = begin()) < 0) return rc;
/* Length field is capped at 2 bits; the device ignores it for addresses
* >= 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; }
memset(buf, 0, len);
if (EXI_ImmEx(BBA_EXI_CHAN, buf, len, EXI_READ) == 0) { end(); return -5; }
end();
return 0;
}
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;
}