ccf4eb4996
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>
189 lines
6.2 KiB
C
189 lines
6.2 KiB
C
/* re-bba-rb on-console test suite — GameCube
|
|
*
|
|
* Put the adapter in Serial Port 1, run this from Swiss.
|
|
*
|
|
* D-pad up/down move
|
|
* A open suite / rerun the selected test
|
|
* X run the selected suite
|
|
* START run everything
|
|
* Y clear all results
|
|
* B back
|
|
*/
|
|
#include <gccore.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include "tests.h"
|
|
#include "bba.h"
|
|
|
|
static void *xfb = NULL;
|
|
static GXRModeObj *rmode = NULL;
|
|
|
|
#define C_RESET "\x1b[37;0m"
|
|
#define C_HEAD "\x1b[36;1m"
|
|
#define C_SEL "\x1b[30;47m" /* black on white */
|
|
#define C_PASS "\x1b[32;1m"
|
|
#define C_FAIL "\x1b[31;1m"
|
|
#define C_SKIP "\x1b[33;1m"
|
|
#define C_DIM "\x1b[37;0m"
|
|
|
|
static const char *st_tag(tstatus_t s)
|
|
{
|
|
switch (s) {
|
|
case T_PASS: return C_PASS "PASS" C_RESET;
|
|
case T_FAIL: return C_FAIL "FAIL" C_RESET;
|
|
case T_SKIP: return C_SKIP "SKIP" C_RESET;
|
|
default: return C_DIM " -- " C_RESET;
|
|
}
|
|
}
|
|
|
|
static void suite_tally(const suite_t *s, int *p, int *f, int *k)
|
|
{
|
|
int i; *p = *f = *k = 0;
|
|
for (i = 0; i < s->ntests; i++)
|
|
switch (s->tests[i].status) {
|
|
case T_PASS: (*p)++; break;
|
|
case T_FAIL: (*f)++; break;
|
|
case T_SKIP: (*k)++; break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
static void cls(void) { printf("\x1b[2J\x1b[1;1H"); }
|
|
|
|
static void banner(void)
|
|
{
|
|
printf(C_HEAD " re-bba-rb -- on-console test suite" C_RESET "\n");
|
|
printf(C_DIM " GameCube Serial Port 1 (EXI0:2), clock index %u\n" C_RESET,
|
|
(unsigned)bba_speed);
|
|
printf(" ------------------------------------------------------------\n");
|
|
}
|
|
|
|
static void draw_main(int sel, const char *status)
|
|
{
|
|
int i, p, f, k, tp, tf, tk, tpend;
|
|
cls();
|
|
banner();
|
|
for (i = 0; i < g_nsuites; i++) {
|
|
suite_tally(&g_suites[i], &p, &f, &k);
|
|
printf("%s %-14s" C_RESET " %s%2d" C_RESET "/%s%2d" C_RESET "/%s%2d" C_RESET
|
|
" %s%s\n",
|
|
i == sel ? C_SEL : " ", g_suites[i].name,
|
|
C_PASS, p, C_FAIL, f, C_SKIP, k,
|
|
C_DIM, g_suites[i].what);
|
|
}
|
|
tests_tally(&tp, &tf, &tk, &tpend);
|
|
printf(" ------------------------------------------------------------\n");
|
|
printf(" total " C_PASS "%d passed" C_RESET " " C_FAIL "%d failed" C_RESET
|
|
" " C_SKIP "%d skipped" C_RESET " %d not run\n", tp, tf, tk, tpend);
|
|
if (tf) {
|
|
printf(C_FAIL " failing:" C_RESET);
|
|
for (i = 0; i < g_nsuites; i++) {
|
|
int j;
|
|
for (j = 0; j < g_suites[i].ntests; j++)
|
|
if (g_suites[i].tests[j].status == T_FAIL)
|
|
printf(" %s/%s", g_suites[i].name, g_suites[i].tests[j].name);
|
|
}
|
|
printf("\n");
|
|
}
|
|
printf("\n" C_DIM " A open X run suite START run all Y clear\n" C_RESET);
|
|
if (status && *status) printf("\n %s\n", status);
|
|
}
|
|
|
|
static void draw_suite(int si, int sel, const char *status)
|
|
{
|
|
suite_t *s = &g_suites[si];
|
|
int i, p, f, k;
|
|
cls();
|
|
banner();
|
|
suite_tally(s, &p, &f, &k);
|
|
printf(C_HEAD " %s" C_RESET " -- %s\n", s->name, s->what);
|
|
printf(" " C_PASS "%d passed" C_RESET " " C_FAIL "%d failed" C_RESET
|
|
" " C_SKIP "%d skipped" C_RESET " of %d\n\n", p, f, k, s->ntests);
|
|
for (i = 0; i < s->ntests; i++) {
|
|
printf("%s %s %-16s" C_RESET "\n",
|
|
i == sel ? C_SEL : " ", st_tag(s->tests[i].status), s->tests[i].name);
|
|
printf(C_DIM " %s\n" C_RESET, s->tests[i].what);
|
|
if (s->tests[i].msg[0])
|
|
printf(" -> %s\n", s->tests[i].msg);
|
|
}
|
|
printf("\n" C_DIM " A rerun this test X run whole suite B back\n" C_RESET);
|
|
if (status && *status) printf("\n %s\n", status);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int screen = 0; /* 0 = main, 1 = suite detail */
|
|
int sel = 0, ssel = 0, cur = 0;
|
|
char status[80] = "";
|
|
|
|
VIDEO_Init();
|
|
PAD_Init();
|
|
rmode = VIDEO_GetPreferredMode(NULL);
|
|
xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
|
|
console_init(xfb, 20, 20, rmode->fbWidth, rmode->xfbHeight,
|
|
rmode->fbWidth * VI_DISPLAY_PIX_SZ);
|
|
VIDEO_Configure(rmode);
|
|
VIDEO_SetNextFramebuffer(xfb);
|
|
VIDEO_SetBlack(FALSE);
|
|
VIDEO_Flush();
|
|
VIDEO_WaitVSync();
|
|
if (rmode->viTVMode & VI_NON_INTERLACE) VIDEO_WaitVSync();
|
|
|
|
snprintf(status, sizeof(status), "ready - press START to run everything");
|
|
draw_main(sel, status);
|
|
|
|
for (;;) {
|
|
u16 down;
|
|
VIDEO_WaitVSync();
|
|
PAD_ScanPads();
|
|
down = PAD_ButtonsDown(0);
|
|
if (!down) continue;
|
|
|
|
if (down & PAD_BUTTON_START) {
|
|
snprintf(status, sizeof(status), "running all suites...");
|
|
if (screen) draw_suite(cur, ssel, status); else draw_main(sel, status);
|
|
tests_run_all();
|
|
snprintf(status, sizeof(status), "done");
|
|
screen = 0;
|
|
draw_main(sel, status);
|
|
continue;
|
|
}
|
|
|
|
if (screen == 0) {
|
|
int n = g_nsuites;
|
|
if (down & PAD_BUTTON_DOWN) sel = (sel + 1) % n;
|
|
if (down & PAD_BUTTON_UP) sel = (sel + n - 1) % n;
|
|
if (down & PAD_BUTTON_A) { screen = 1; cur = sel; ssel = 0; status[0] = 0;
|
|
draw_suite(cur, ssel, status); continue; }
|
|
if (down & PAD_BUTTON_X) { snprintf(status, sizeof(status), "running %s...",
|
|
g_suites[sel].name);
|
|
draw_main(sel, status);
|
|
tests_run_suite(&g_suites[sel]);
|
|
snprintf(status, sizeof(status), "%s done",
|
|
g_suites[sel].name); }
|
|
if (down & PAD_BUTTON_Y) { tests_reset_status();
|
|
snprintf(status, sizeof(status), "results cleared"); }
|
|
draw_main(sel, status);
|
|
} else {
|
|
suite_t *s = &g_suites[cur];
|
|
if (down & PAD_BUTTON_DOWN) ssel = (ssel + 1) % s->ntests;
|
|
if (down & PAD_BUTTON_UP) ssel = (ssel + s->ntests - 1) % s->ntests;
|
|
if (down & PAD_BUTTON_B) { screen = 0; status[0] = 0;
|
|
draw_main(sel, status); continue; }
|
|
if (down & PAD_BUTTON_A) { snprintf(status, sizeof(status), "running %s...",
|
|
s->tests[ssel].name);
|
|
draw_suite(cur, ssel, status);
|
|
tests_run_test(&s->tests[ssel]);
|
|
status[0] = 0; }
|
|
if (down & PAD_BUTTON_X) { snprintf(status, sizeof(status), "running %s...", s->name);
|
|
draw_suite(cur, ssel, status);
|
|
tests_run_suite(s);
|
|
status[0] = 0; }
|
|
draw_suite(cur, ssel, status);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|