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 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 11:24:47 +02:00
parent ccf4eb4996
commit d8725392a5
5 changed files with 54 additions and 21 deletions
+23 -14
View File
@@ -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 FROM debian:sid-slim
RUN apt-get update && apt-get install -y --no-install-recommends \ 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 \ curl \
git \ git \
git-lfs \ git-lfs \
make \
sudo \ sudo \
python3 \ python3 \
python3-venv \ python3-venv \
@@ -52,21 +58,24 @@ RUN pip install --no-cache-dir pyftdi pyserial
# ── GameCube homebrew toolchain (devkitPPC + libogc) ──────────────────────── # ── GameCube homebrew toolchain (devkitPPC + libogc) ────────────────────────
# Needed to build the on-console BBA test app in `gc_bba_test/` into a .dol. # 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 # Copied out of devkitPro's official `devkitpro/devkitppc` image rather than
# blocked (HTTP 403) from inside the running container's network egress, but a # installed with their `install-devkitpro-pacman` script: apt.devkitpro.org and
# host-side `docker build` reaches them fine -- which is why this lives in the # pkg.devkitpro.org sit behind Cloudflare, which answers curl/wget/scripted
# Dockerfile rather than being installed at runtime. Adds roughly 1.5-2 GB. # fetches with HTTP 403 (browser user-agents get 200) -- from the host's
RUN curl -fsSL https://apt.devkitpro.org/install-devkitpro-pacman -o /tmp/install-devkitpro-pacman \ # `docker build` too, not just from inside the container. The Docker image is
&& chmod +x /tmp/install-devkitpro-pacman \ # devkitPro's sanctioned route for CI/containers and needs no network access to
&& yes | /tmp/install-devkitpro-pacman \ # their hosts. The binaries only link against libc, so they run fine on sid.
&& dkp-pacman -S --noconfirm gamecube-dev \ #
&& dkp-pacman -Scc --noconfirm \ # Only the GameCube/PPC parts are copied (~540 MB); devkitARM, wut and the
&& rm -f /tmp/install-devkitpro-pacman # 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 DEVKITPRO=/opt/devkitpro
ENV DEVKITPPC=/opt/devkitpro/devkitPPC ENV DEVKITPPC=/opt/devkitpro/devkitPPC
+6
View File
@@ -33,3 +33,9 @@ hardware/re-bba-rb/re-bba-rb-eeprom.bin
*.swp *.swp
.history/ .history/
build_w5100test/ build_w5100test/
# GameCube test app build products
gc_bba_test/build/
gc_bba_test/*.elf
gc_bba_test/*.dol
gc_bba_test/*.map
+7
View File
@@ -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 **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 address auto-increments per data byte, so a 4-byte write to 0x48 would spill
into 0x49–0x4B. `bba_tx_bytes()` does it correctly. 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`.
+17 -7
View File
@@ -1,5 +1,6 @@
#include "bba.h" #include "bba.h"
#include <string.h> #include <string.h>
#include <unistd.h>
u32 bba_speed = EXI_SPEED32MHZ; /* nominal name; ~27 MHz in reality */ 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) static int begin(void)
{ {
if (EXI_Lock(BBA_EXI_CHAN, BBA_EXI_DEV, NULL) == 0) return -1; int tries;
if (EXI_Select(BBA_EXI_CHAN, BBA_EXI_DEV, bba_speed) == 0) { /* 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); EXI_Unlock(BBA_EXI_CHAN);
return -2; return -2;
} }
@@ -34,8 +44,8 @@ static void end(void)
static int put_hdr(u16 addr, u32 len, int write) static int put_hdr(u16 addr, u32 len, int write)
{ {
u16 h = hdr(addr, len, write); u16 h = hdr(addr, len, write);
if (EXI_Imm(BBA_EXI_CHAN, &h, 2, EXI_WRITE, NULL) == 0) return -3; 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_Sync(BBA_EXI_CHAN) <= 0) return -4;
return 0; 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 * header and prefetches the response into its TX FIFO. Separate EXI_Imm
* calls give us that gap for free. */ * calls give us that gap for free. */
memset(buf, 0, len); 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(); end();
return 0; return 0;
} }
@@ -62,7 +72,7 @@ int bba_write(u16 addr, const void *buf, u32 len)
memcpy(tmp, buf, len); memcpy(tmp, buf, len);
if ((rc = begin()) < 0) return rc; if ((rc = begin()) < 0) return rc;
if ((rc = put_hdr(addr, len, 1)) < 0) { end(); 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(); end();
return 0; return 0;
} }
@@ -89,7 +99,7 @@ int bba_stream(u16 addr, void *buf, u32 len)
* bytes as we actually want. */ * bytes as we actually want. */
if ((rc = put_hdr(addr, 4, 0)) < 0) { end(); return rc; } if ((rc = put_hdr(addr, 4, 0)) < 0) { end(); return rc; }
memset(buf, 0, len); 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(); end();
return 0; return 0;
} }
+1
View File
@@ -2,6 +2,7 @@
#include "bba.h" #include "bba.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <unistd.h>
#include <ogc/lwp_watchdog.h> #include <ogc/lwp_watchdog.h>
int g_ncra_reset_done = 0; int g_ncra_reset_done = 0;