From d8725392a537ad19a88572436c1df6647f24c950 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Sat, 3 Oct 2026 11:24:47 +0200 Subject: [PATCH] gc_bba_test: fix inverted EXI error checks; builds clean libogc's EXI functions return '1 on success, <=0 on error', but every call site tested '== 0'. A -1 error return therefore read as success, so a failed EXI_Lock or EXI_Select would fall through and the subsequent transfer would produce garbage reported as a misleading test failure rather than an EXI error. All six call sites now test <= 0. Also retries EXI_Lock for ~100 ms: it fails outright rather than blocking when another subsystem holds the channel, and Swiss or a libogc driver may hold it briefly -- without the retry a transient collision surfaces as a test failure. Builds clean with devkitPPC 16.1.0 / GCC 16, no warnings: 172 KB .dol. Co-Authored-By: Claude Opus 5 --- .devcontainer/Dockerfile | 37 +++++++++++++++++++++++-------------- .gitignore | 6 ++++++ gc_bba_test/README.md | 7 +++++++ gc_bba_test/source/bba.c | 24 +++++++++++++++++------- gc_bba_test/source/tests.c | 1 + 5 files changed, 54 insertions(+), 21 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 6b899cf..54d4977 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,3 +1,8 @@ +# devkitPro's official image, used only as a source for the GameCube toolchain +# (see the devkitPPC section below). `:latest` tracks devkitPro releases; pin a +# dated tag (e.g. :20250527) if a toolchain update ever breaks the build. +FROM devkitpro/devkitppc:latest AS devkitppc + FROM debian:sid-slim RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -5,6 +10,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ git \ git-lfs \ + make \ sudo \ python3 \ python3-venv \ @@ -52,21 +58,24 @@ RUN pip install --no-cache-dir pyftdi pyserial # ── GameCube homebrew toolchain (devkitPPC + libogc) ──────────────────────── # Needed to build the on-console BBA test app in `gc_bba_test/` into a .dol. -# devkitPro does NOT publish a .deb on GitHub -- the Debian package only exists -# in their own apt repo -- so this uses their maintained installer script, which -# adds the repo, imports the key and installs dkp-pacman. `gamecube-dev` is a -# package GROUP (devkitPPC, libogc, libfat, tools incl. elf2dol). # -# NOTE this pulls from apt.devkitpro.org / pkg.devkitpro.org. Those hosts are -# blocked (HTTP 403) from inside the running container's network egress, but a -# host-side `docker build` reaches them fine -- which is why this lives in the -# Dockerfile rather than being installed at runtime. Adds roughly 1.5-2 GB. -RUN curl -fsSL https://apt.devkitpro.org/install-devkitpro-pacman -o /tmp/install-devkitpro-pacman \ - && chmod +x /tmp/install-devkitpro-pacman \ - && yes | /tmp/install-devkitpro-pacman \ - && dkp-pacman -S --noconfirm gamecube-dev \ - && dkp-pacman -Scc --noconfirm \ - && rm -f /tmp/install-devkitpro-pacman +# Copied out of devkitPro's official `devkitpro/devkitppc` image rather than +# installed with their `install-devkitpro-pacman` script: apt.devkitpro.org and +# pkg.devkitpro.org sit behind Cloudflare, which answers curl/wget/scripted +# fetches with HTTP 403 (browser user-agents get 200) -- from the host's +# `docker build` too, not just from inside the container. The Docker image is +# devkitPro's sanctioned route for CI/containers and needs no network access to +# their hosts. The binaries only link against libc, so they run fine on sid. +# +# Only the GameCube/PPC parts are copied (~540 MB); devkitARM, wut and the +# Wii/Wii U portlibs from the source image are skipped. +COPY --from=devkitppc /opt/devkitpro/devkitPPC /opt/devkitpro/devkitPPC +COPY --from=devkitppc /opt/devkitpro/libogc /opt/devkitpro/libogc +COPY --from=devkitppc /opt/devkitpro/tools /opt/devkitpro/tools +COPY --from=devkitppc /opt/devkitpro/cmake /opt/devkitpro/cmake +COPY --from=devkitppc /opt/devkitpro/licenses /opt/devkitpro/licenses +COPY --from=devkitppc /opt/devkitpro/portlibs/ppc /opt/devkitpro/portlibs/ppc +COPY --from=devkitppc /opt/devkitpro/portlibs/gamecube /opt/devkitpro/portlibs/gamecube ENV DEVKITPRO=/opt/devkitpro ENV DEVKITPPC=/opt/devkitpro/devkitPPC diff --git a/.gitignore b/.gitignore index b8ab273..ef1f636 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,9 @@ hardware/re-bba-rb/re-bba-rb-eeprom.bin *.swp .history/ build_w5100test/ + +# GameCube test app build products +gc_bba_test/build/ +gc_bba_test/*.elf +gc_bba_test/*.dol +gc_bba_test/*.map diff --git a/gc_bba_test/README.md b/gc_bba_test/README.md index 9dca3f0..4fb9280 100644 --- a/gc_bba_test/README.md +++ b/gc_bba_test/README.md @@ -65,3 +65,10 @@ fault. `bba_read()` clocks exactly `len`; don't "round up" for convenience. **TXDATA takes one byte per transaction.** The register file's effective address auto-increments per data byte, so a 4-byte write to 0x48 would spill into 0x49–0x4B. `bba_tx_bytes()` does it correctly. + +## Build status + +Builds clean (no warnings) with devkitPPC 16.1.0 / GCC 16 in the devcontainer: +`gc_bba_test.dol`, ~172 KB, entry `0x80003100`. + +`.gitignore` excludes the build products — run `make` to produce the `.dol`. diff --git a/gc_bba_test/source/bba.c b/gc_bba_test/source/bba.c index de518ba..0c3eff7 100644 --- a/gc_bba_test/source/bba.c +++ b/gc_bba_test/source/bba.c @@ -1,5 +1,6 @@ #include "bba.h" #include +#include u32 bba_speed = EXI_SPEED32MHZ; /* nominal name; ~27 MHz in reality */ @@ -17,8 +18,17 @@ static u16 hdr(u16 addr, u32 len, int write) 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) { + 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; } @@ -34,8 +44,8 @@ static void end(void) 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; + 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; } @@ -49,7 +59,7 @@ int bba_read(u16 addr, void *buf, u32 len) * 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; } + if (EXI_ImmEx(BBA_EXI_CHAN, buf, len, EXI_READ) <= 0) { end(); return -5; } end(); return 0; } @@ -62,7 +72,7 @@ int bba_write(u16 addr, const void *buf, u32 len) 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; } + if (EXI_ImmEx(BBA_EXI_CHAN, tmp, len, EXI_WRITE) <= 0) { end(); return -6; } end(); return 0; } @@ -89,7 +99,7 @@ int bba_stream(u16 addr, void *buf, u32 len) * 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; } + if (EXI_ImmEx(BBA_EXI_CHAN, buf, len, EXI_READ) <= 0) { end(); return -5; } end(); return 0; } diff --git a/gc_bba_test/source/tests.c b/gc_bba_test/source/tests.c index 00a9a53..e4f6cca 100644 --- a/gc_bba_test/source/tests.c +++ b/gc_bba_test/source/tests.c @@ -2,6 +2,7 @@ #include "bba.h" #include #include +#include #include int g_ncra_reset_done = 0;