d8725392a5
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>
116 lines
3.0 KiB
C
116 lines
3.0 KiB
C
#include "bba.h"
|
|
#include <string.h>
|
|
#include <unistd.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)
|
|
{
|
|
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;
|
|
}
|
|
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;
|
|
}
|