/* 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 * Z self-update over the BBA, then relaunch (see netboot.c) */ #include #include #include #include #include #include #include "tests.h" #include "bba.h" #include "netboot.h" static void *xfb = NULL; static void *s_stage = NULL; /* netboot staging region (top of MEM1) */ static u32 s_stage_max = 0; 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 Z net-update\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); } /* Progress callback: netboot runs with the console as the only output, so * print straight to it rather than buffering -- a stalled transfer should show * where it stalled. */ static void nb_status(const char *msg) { printf(" %s\n", msg); } /* Hold the screen until a button is pressed. Without this the caller's * draw_main() redraws immediately and wipes every diagnostic line netboot * just printed -- which is exactly why the first two failures were reported * as "a short flash" with no detail. */ static void nb_pause(const char *why) { printf("\n" C_DIM " %s -- press any button to return to the menu\n" C_RESET, why); for (;;) { VIDEO_WaitVSync(); PAD_ScanPads(); if (PAD_ButtonsDown(0)) return; } } static void do_netboot(char *status, size_t nstatus) { int len, rc; printf("\n" C_HEAD " Self-update over the BBA" C_RESET "\n\n"); printf(" On the host: sudo ./scripts/send-dol.py \n\n"); /* Deliberately terse: the console holds ~25 lines and the diagnostics * printed during a failed transfer are what matter. Explanatory text * here pushed them off the top of the screen, which cost two blind * round trips. */ printf(C_DIM " temp+verify+rotate; keeps .prev.dol; boots /boot.dol\n" C_RESET); printf(" Blocks until done or it times out (30 s idle, 5 s mid-xfer).\n" " Stops with the summary before booting Swiss.\n\n"); if (!s_stage) { snprintf(status, nstatus, "net-update: staging region unavailable"); return; } len = netboot_receive(s_stage, s_stage_max, nb_status); if (len <= 0) { snprintf(status, nstatus, "net-update failed (%d)", len); nb_pause("transfer failed"); return; } rc = netboot_sd_write(netboot_dest_path(), s_stage, (u32)len, nb_status); if (rc < 0) { nb_pause("SD write failed"); /* Nothing has been launched and nothing is half-written that we * know of, so staying in the menu is the safe outcome. */ snprintf(status, nstatus, "SD write failed (%d) - existing build untouched", rc); return; } /* Stop here so the transfer summary can actually be READ. Booting * straight into Swiss scrolled it away, which meant the one set of * numbers that says WHY blocks needed retrying -- txto, the descriptor * split, bad/lapped counts -- was never visible. */ nb_pause("update written to the SD card"); printf("\n booting /boot.dol ...\n"); VIDEO_WaitVSync(); VIDEO_WaitVSync(); /* Reuse the staging region: the new build is already on the card and * verified, so the copy in RAM is finished with. */ rc = netboot_boot_file("/boot.dol", s_stage, s_stage_max, nb_status); nb_pause("boot refused"); /* Only reached on refusal. The new build IS on the card either way, * so this is recoverable by rebooting into Swiss by hand. */ snprintf(status, nstatus, "update saved, but /boot.dol would not boot (%d)", rc); } int main(void) { int screen = 0; /* 0 = main, 1 = suite detail */ int sel = 0, ssel = 0, cur = 0; char status[80] = ""; /* Reserve the netboot staging region FIRST, before libogc allocates the * framebuffer or anything else out of the arena. A staged image must * not live in ordinary heap memory: DOL sections load from 0x80003100 * upward and would overwrite it partway through the copy. */ s_stage = netboot_reserve(&s_stage_max); 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_TRIGGER_Z) { /* Self-update: pull a new .dol over the BBA and launch it. * Staged in a malloc'd buffer rather than written in place -- * netboot_launch() copies sections to their load addresses, * which overlap the memory this program is running from. */ do_netboot(status, sizeof(status)); screen = 0; draw_main(sel, status); 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; }