#include "dolphin/os.h"
#pragma scheduling off
vu32 __EXIRegs[16] : 0xCC006800;
static const char* __EXIVersion =
"<< Dolphin SDK - EXI\trelease build: Sep 5 2002 05:33:04 (0x2301) >>";
#define MAX_DEV 3
#define MAX_CHAN 3
#define REG_MAX 5
#define REG(chan, idx) (__EXIRegs[((chan)*REG_MAX) + (idx)])
#define STATE_IDLE 0x00
#define STATE_DMA 0x01
#define STATE_IMM 0x02
#define STATE_BUSY (STATE_DMA | STATE_IMM)
#define STATE_SELECTED 0x04
#define STATE_ATTACHED 0x08
#define STATE_LOCKED 0x10
#define EXI_0CR(tstart, dma, rw, tlen) \
((((u32)(tstart)) << 0) | (((u32)(dma)) << 1) | (((u32)(rw)) << 2) | (((u32)(tlen)) << 4))
#define CPR_CS(x) ((1u << (x)) << 7)
#define CPR_CLK(x) ((x) << 4)
typedef struct EXIControl {
EXICallback exiCallback;
EXICallback tcCallback;
EXICallback extCallback;
vu32 state;
int immLen;
u8* immBuf;
u32 dev;
u32 id;
s32 idTime;
int items;
struct {
u32 dev;
EXICallback callback;
} queue[MAX_DEV];
} EXIControl;
static EXIControl Ecb[MAX_CHAN];
s32 __EXIProbeStartTime[2] : (OS_BASE_CACHED | 0x30C0);
static void SetExiInterruptMask(s32 chan, EXIControl* exi) {
EXIControl* exi2;
exi2 = &Ecb[2];
switch (chan) {
case 0:
if ((exi->exiCallback == 0 && exi2->exiCallback == 0) || (exi->state & STATE_LOCKED)) {
__OSMaskInterrupts(OS_INTERRUPTMASK_EXI_0_EXI | OS_INTERRUPTMASK_EXI_2_EXI);
} else {
__OSUnmaskInterrupts(OS_INTERRUPTMASK_EXI_0_EXI | OS_INTERRUPTMASK_EXI_2_EXI);
}
break;
case 1:
if (exi->exiCallback == 0 || (exi->state & STATE_LOCKED)) {
__OSMaskInterrupts(OS_INTERRUPTMASK_EXI_1_EXI);
} else {
__OSUnmaskInterrupts(OS_INTERRUPTMASK_EXI_1_EXI);
}
break;
case 2:
if (__OSGetInterruptHandler(__OS_INTERRUPT_PI_DEBUG) == 0 || (exi->state & STATE_LOCKED)) {
__OSMaskInterrupts(OS_INTERRUPTMASK_PI_DEBUG);
} else {
__OSUnmaskInterrupts(OS_INTERRUPTMASK_PI_DEBUG);
}
break;
}
}
static void CompleteTransfer(s32 chan) {
EXIControl* exi = &Ecb[chan];
u8* buf;
u32 data;
int i;
int len;
if (exi->state & STATE_BUSY) {
if ((exi->state & STATE_IMM) && (len = exi->immLen)) {
buf = exi->immBuf;
data = REG(chan, 4);
for (i = 0; i < len; i++) {
*buf++ = (u8)((data >> ((3 - i) * 8)) & 0xff);
}
}
exi->state &= ~STATE_BUSY;
}
}
BOOL EXIImm(s32 chan, void* buf, s32 len, u32 type, EXICallback callback) {
EXIControl* exi = &Ecb[chan];
BOOL enabled;
enabled = OSDisableInterrupts();
if ((exi->state & STATE_BUSY) || !(exi->state & STATE_SELECTED)) {
OSRestoreInterrupts(enabled);
return FALSE;
}
exi->tcCallback = callback;
if (exi->tcCallback) {
EXIClearInterrupts(chan, FALSE, TRUE, FALSE);
__OSUnmaskInterrupts(OS_INTERRUPTMASK_EXI_0_TC >> (3 * chan));
}
exi->state |= STATE_IMM;
if (type != EXI_READ) {
u32 data;
int i;
data = 0;
for (i = 0; i < len; i++) {
data |= ((u8*)buf)[i] << ((3 - i) * 8);
}
REG(chan, 4) = data;
}
exi->immBuf = buf;
exi->immLen = (type != EXI_WRITE) ? len : 0;
REG(chan, 3) = EXI_0CR(1, 0, type, len - 1);
OSRestoreInterrupts(enabled);
return TRUE;
}
BOOL EXIImmEx(s32 chan, void* buf, s32 len, u32 mode) {
s32 xLen;
while (len) {
xLen = (len < 4) ? len : 4;
if (!EXIImm(chan, buf, xLen, mode, NULL)) {
return FALSE;
}
if (!EXISync(chan)) {
return FALSE;
}
(u8*)buf += xLen;
len -= xLen;
}
return TRUE;
}
BOOL EXIDma(s32 chan, void* buf, s32 len, u32 type, EXICallback callback) {
EXIControl* exi = &Ecb[chan];
BOOL enabled;
enabled = OSDisableInterrupts();
if ((exi->state & STATE_BUSY) || !(exi->state & STATE_SELECTED)) {
OSRestoreInterrupts(enabled);
return FALSE;
}
exi->tcCallback = callback;
if (exi->tcCallback) {
EXIClearInterrupts(chan, FALSE, TRUE, FALSE);
__OSUnmaskInterrupts(OS_INTERRUPTMASK_EXI_0_TC >> (3 * chan));
}
exi->state |= STATE_DMA;
REG(chan, 1) = (u32)buf & 0x3ffffe0;
REG(chan, 2) = (u32)len;
REG(chan, 3) = EXI_0CR(1, 1, type, 0);
OSRestoreInterrupts(enabled);
return TRUE;
}
extern u32 __OSGetDIConfig(void);
vu16 __OSDeviceCode : (OS_BASE_CACHED | 0x30E6);
BOOL EXISync(s32 chan) {
EXIControl* exi = &Ecb[chan];
BOOL rc = FALSE;
BOOL enabled;
while (exi->state & STATE_SELECTED) {
if (((REG(chan, 3) & 1) >> 0) == 0) {
enabled = OSDisableInterrupts();
if (exi->state & STATE_SELECTED) {
CompleteTransfer(chan);
if (__OSGetDIConfig() != 0xff || exi->immLen != 4 ||
(REG(chan, 0) & 0x00000070) != (EXI_FREQ_1M << 4) ||
(REG(chan, 4) != EXI_USB_ADAPTER && REG(chan, 4) != EXI_IS_VIEWER &&
REG(chan, 4) != 0x04220001) ||
__OSDeviceCode == 0x8200) {
rc = TRUE;
}
}
OSRestoreInterrupts(enabled);
break;
}
}
return rc;
}
u32 EXIClearInterrupts(s32 chan, BOOL exi, BOOL tc, BOOL ext) {
u32 cpr;
u32 prev;
prev = cpr = REG(chan, 0);
cpr &= 0x7f5;
if (exi)
cpr |= 2;
if (tc)
cpr |= 8;
if (ext)
cpr |= 0x800;
REG(chan, 0) = cpr;
return prev;
}
EXICallback EXISetExiCallback(s32 chan, EXICallback exiCallback) {
EXIControl* exi = &Ecb[chan];
EXICallback prev;
BOOL enabled;
enabled = OSDisableInterrupts();
prev = exi->exiCallback;
exi->exiCallback = exiCallback;
if (chan != 2) {
SetExiInterruptMask(chan, exi);
} else {
SetExiInterruptMask(0, &Ecb[0]);
}
OSRestoreInterrupts(enabled);
return prev;
}
void EXIProbeReset(void) {
__EXIProbeStartTime[0] = __EXIProbeStartTime[1] = 0;
Ecb[0].idTime = Ecb[1].idTime = 0;
__EXIProbe(0);
__EXIProbe(1);
}
static BOOL __EXIProbe(s32 chan) {
EXIControl* exi = &Ecb[chan];
BOOL enabled;
BOOL rc;
u32 cpr;
s32 t;
if (chan == 2) {
return TRUE;
}
rc = TRUE;
enabled = OSDisableInterrupts();
cpr = REG(chan, 0);
if (!(exi->state & EXI_STATE_ATTACHED)) {
if (cpr & 0x00000800) {
EXIClearInterrupts(chan, FALSE, FALSE, TRUE);
__EXIProbeStartTime[chan] = exi->idTime = 0;
}
if (cpr & 0x00001000) {
t = (s32)(OSTicksToMilliseconds(OSGetTime()) / 100) + 1;
if (__EXIProbeStartTime[chan] == 0) {
__EXIProbeStartTime[chan] = t;
}
if (t - __EXIProbeStartTime[chan] < 300 / 100) {
rc = FALSE;
}
} else {
__EXIProbeStartTime[chan] = exi->idTime = 0;
rc = FALSE;
}
} else if (!(cpr & 0x00001000) || (cpr & 0x00000800)) {
__EXIProbeStartTime[chan] = exi->idTime = 0;
rc = FALSE;
}
OSRestoreInterrupts(enabled);
return rc;
}
BOOL EXIProbe(s32 chan) {
EXIControl* exi = &Ecb[chan];
BOOL rc;
u32 id;
rc = __EXIProbe(chan);
if (rc && exi->idTime == 0) {
rc = EXIGetID(chan, 0, &id) ? TRUE : FALSE;
}
return rc;
}
s32 EXIProbeEx(s32 chan) {
if (EXIProbe(chan)) {
return 1;
} else if (__EXIProbeStartTime[chan] != 0) {
return 0;
} else {
return -1;
}
}
static BOOL __EXIAttach(s32 chan, EXICallback extCallback) {
EXIControl* exi = &Ecb[chan];
BOOL enabled;
enabled = OSDisableInterrupts();
if ((exi->state & EXI_STATE_ATTACHED) || __EXIProbe(chan) == FALSE) {
OSRestoreInterrupts(enabled);
return FALSE;
}
EXIClearInterrupts(chan, TRUE, FALSE, FALSE);
exi->extCallback = extCallback;
__OSUnmaskInterrupts(OS_INTERRUPTMASK_EXI_0_EXT >> (3 * chan));
exi->state |= STATE_ATTACHED;
OSRestoreInterrupts(enabled);
return TRUE;
}
BOOL EXIAttach(s32 chan, EXICallback extCallback) {
EXIControl* exi = &Ecb[chan];
BOOL enabled;
BOOL rc;
EXIProbe(chan);
enabled = OSDisableInterrupts();
if (exi->idTime == 0) {
OSRestoreInterrupts(enabled);
return FALSE;
}
rc = __EXIAttach(chan, extCallback);
OSRestoreInterrupts(enabled);
return rc;
}
BOOL EXIDetach(s32 chan) {
EXIControl* exi = &Ecb[chan];
BOOL enabled;
enabled = OSDisableInterrupts();
if (!(exi->state & STATE_ATTACHED)) {
OSRestoreInterrupts(enabled);
return TRUE;
}
if ((exi->state & STATE_LOCKED) && exi->dev == 0) {
OSRestoreInterrupts(enabled);
return FALSE;
}
exi->state &= ~STATE_ATTACHED;
__OSMaskInterrupts((OS_INTERRUPTMASK_EXI_0_EXT | OS_INTERRUPTMASK_EXI_0_EXI) >> (3 * chan));
OSRestoreInterrupts(enabled);
return TRUE;
}
BOOL EXISelect(s32 chan, u32 dev, u32 freq) {
EXIControl* exi = &Ecb[chan];
u32 cpr;
BOOL enabled;
enabled = OSDisableInterrupts();
if ((exi->state & STATE_SELECTED) ||
chan != 2 && (dev == 0 && !(exi->state & STATE_ATTACHED) && !__EXIProbe(chan) ||
!(exi->state & STATE_LOCKED) || (exi->dev != dev))) {
OSRestoreInterrupts(enabled);
return FALSE;
}
exi->state |= STATE_SELECTED;
cpr = REG(chan, 0);
cpr &= 0x405;
cpr |= CPR_CS(dev) | CPR_CLK(freq);
REG(chan, 0) = cpr;
if (exi->state & STATE_ATTACHED) {
switch (chan) {
case 0:
__OSMaskInterrupts(OS_INTERRUPTMASK_EXI_0_EXT);
break;
case 1:
__OSMaskInterrupts(OS_INTERRUPTMASK_EXI_1_EXT);
break;
}
}
OSRestoreInterrupts(enabled);
return TRUE;
}
BOOL EXIDeselect(s32 chan) {
EXIControl* exi = &Ecb[chan];
u32 cpr;
BOOL enabled;
enabled = OSDisableInterrupts();
if (!(exi->state & STATE_SELECTED)) {
OSRestoreInterrupts(enabled);
return FALSE;
}
exi->state &= ~STATE_SELECTED;
cpr = REG(chan, 0);
REG(chan, 0) = cpr & 0x405;
if (exi->state & STATE_ATTACHED) {
switch (chan) {
case 0:
__OSUnmaskInterrupts(OS_INTERRUPTMASK_EXI_0_EXT);
break;
case 1:
__OSUnmaskInterrupts(OS_INTERRUPTMASK_EXI_1_EXT);
break;
}
}
OSRestoreInterrupts(enabled);
if (chan != 2 && (cpr & CPR_CS(0))) {
return __EXIProbe(chan) ? TRUE : FALSE;
}
return TRUE;
}
static void EXIIntrruptHandler(__OSInterrupt interrupt, OSContext* context) {
s32 chan;
EXIControl* exi;
EXICallback callback;
chan = (interrupt - __OS_INTERRUPT_EXI_0_EXI) / 3;
exi = &Ecb[chan];
EXIClearInterrupts(chan, TRUE, FALSE, FALSE);
callback = exi->exiCallback;
if (callback) {
OSContext exceptionContext;
OSClearContext(&exceptionContext);
OSSetCurrentContext(&exceptionContext);
callback(chan, context);
OSClearContext(&exceptionContext);
OSSetCurrentContext(context);
}
}
static void TCIntrruptHandler(__OSInterrupt interrupt, OSContext* context) {
OSContext exceptionContext;
s32 chan;
EXIControl* exi;
EXICallback callback;
chan = (interrupt - __OS_INTERRUPT_EXI_0_TC) / 3;
exi = &Ecb[chan];
__OSMaskInterrupts(OS_INTERRUPTMASK(interrupt));
EXIClearInterrupts(chan, FALSE, TRUE, FALSE);
callback = exi->tcCallback;
if (callback) {
exi->tcCallback = 0;
CompleteTransfer(chan);
OSClearContext(&exceptionContext);
OSSetCurrentContext(&exceptionContext);
callback(chan, context);
OSClearContext(&exceptionContext);
OSSetCurrentContext(context);
}
}
static void EXTIntrruptHandler(__OSInterrupt interrupt, OSContext* context) {
s32 chan;
EXIControl* exi;
EXICallback callback;
chan = (interrupt - __OS_INTERRUPT_EXI_0_EXT) / 3;
__OSMaskInterrupts((OS_INTERRUPTMASK_EXI_0_EXT | OS_INTERRUPTMASK_EXI_0_EXI) >> (3 * chan));
exi = &Ecb[chan];
callback = exi->extCallback;
exi->state &= ~STATE_ATTACHED;
if (callback) {
OSContext exceptionContext;
OSClearContext(&exceptionContext);
OSSetCurrentContext(&exceptionContext);
exi->extCallback = 0;
callback(chan, context);
OSClearContext(&exceptionContext);
OSSetCurrentContext(context);
}
}
void EXIInit(void) {
OSRegisterVersion(__EXIVersion);
__OSMaskInterrupts(OS_INTERRUPTMASK_EXI_0_EXI | OS_INTERRUPTMASK_EXI_0_TC |
OS_INTERRUPTMASK_EXI_0_EXT | OS_INTERRUPTMASK_EXI_1_EXI |
OS_INTERRUPTMASK_EXI_1_TC | OS_INTERRUPTMASK_EXI_1_EXT |
OS_INTERRUPTMASK_EXI_2_EXI | OS_INTERRUPTMASK_EXI_2_TC);
REG(0, 0) = 0;
REG(1, 0) = 0;
REG(2, 0) = 0;
REG(0, 0) = 0x00002000;
__OSSetInterruptHandler(__OS_INTERRUPT_EXI_0_EXI, EXIIntrruptHandler);
__OSSetInterruptHandler(__OS_INTERRUPT_EXI_0_TC, TCIntrruptHandler);
__OSSetInterruptHandler(__OS_INTERRUPT_EXI_0_EXT, EXTIntrruptHandler);
__OSSetInterruptHandler(__OS_INTERRUPT_EXI_1_EXI, EXIIntrruptHandler);
__OSSetInterruptHandler(__OS_INTERRUPT_EXI_1_TC, TCIntrruptHandler);
__OSSetInterruptHandler(__OS_INTERRUPT_EXI_1_EXT, EXTIntrruptHandler);
__OSSetInterruptHandler(__OS_INTERRUPT_EXI_2_EXI, EXIIntrruptHandler);
__OSSetInterruptHandler(__OS_INTERRUPT_EXI_2_TC, TCIntrruptHandler);
if ((OSGetConsoleType() & 0x10000000) != 0) {
__EXIProbeStartTime[0] = __EXIProbeStartTime[1] = 0;
Ecb[0].idTime = Ecb[1].idTime = 0;
__EXIProbe(0);
__EXIProbe(1);
}
}
BOOL EXILock(s32 chan, u32 dev, EXICallback unlockedCallback) {
EXIControl* exi = &Ecb[chan];
BOOL enabled;
int i;
enabled = OSDisableInterrupts();
if (exi->state & STATE_LOCKED) {
if (unlockedCallback) {
for (i = 0; i < exi->items; i++) {
if (exi->queue[i].dev == dev) {
OSRestoreInterrupts(enabled);
return FALSE;
}
}
exi->queue[exi->items].callback = unlockedCallback;
exi->queue[exi->items].dev = dev;
exi->items++;
}
OSRestoreInterrupts(enabled);
return FALSE;
}
exi->state |= STATE_LOCKED;
exi->dev = dev;
SetExiInterruptMask(chan, exi);
OSRestoreInterrupts(enabled);
return TRUE;
}
BOOL EXIUnlock(s32 chan) {
EXIControl* exi = &Ecb[chan];
BOOL enabled;
EXICallback unlockedCallback;
enabled = OSDisableInterrupts();
if (!(exi->state & STATE_LOCKED)) {
OSRestoreInterrupts(enabled);
return FALSE;
}
exi->state &= ~STATE_LOCKED;
SetExiInterruptMask(chan, exi);
if (0 < exi->items) {
unlockedCallback = exi->queue[0].callback;
if (0 < --exi->items) {
memmove(&exi->queue[0], &exi->queue[1], sizeof(exi->queue[0]) * exi->items);
}
unlockedCallback(chan, 0);
}
OSRestoreInterrupts(enabled);
return TRUE;
}
u32 EXIGetState(s32 chan) {
EXIControl* exi = &Ecb[chan];
return (u32)exi->state;
}
static void UnlockedHandler(s32 chan, OSContext* context) {
u32 id;
EXIGetID(chan, 0, &id);
}
s32 EXIGetID(s32 chan, u32 dev, u32* id) {
EXIControl* exi = &Ecb[chan];
BOOL err;
u32 cmd;
s32 startTime;
BOOL enabled;
if (chan < 2 && dev == 0) {
if (!__EXIProbe(chan)) {
return 0;
}
if (exi->idTime == __EXIProbeStartTime[chan]) {
*id = exi->id;
return exi->idTime;
}
if (!__EXIAttach(chan, NULL)) {
return 0;
}
startTime = __EXIProbeStartTime[chan];
}
err = !EXILock(chan, dev, (chan < 2 && dev == 0) ? UnlockedHandler : NULL);
if (!err) {
err = !EXISelect(chan, dev, EXI_FREQ_1M);
if (!err) {
cmd = 0;
err |= !EXIImm(chan, &cmd, 2, EXI_WRITE, NULL);
err |= !EXISync(chan);
err |= !EXIImm(chan, id, 4, EXI_READ, NULL);
err |= !EXISync(chan);
err |= !EXIDeselect(chan);
}
EXIUnlock(chan);
}
if (chan < 2 && dev == 0) {
EXIDetach(chan);
enabled = OSDisableInterrupts();
err |= (startTime != __EXIProbeStartTime[chan]);
if (!err) {
exi->id = *id;
exi->idTime = startTime;
}
OSRestoreInterrupts(enabled);
return err ? 0 : exi->idTime;
}
return err ? 0 : !0;
}
char* EXIGetTypeString(u32 type) {
switch (type) {
case EXI_MEMORY_CARD_59:
return "Memory Card 59";
case EXI_MEMORY_CARD_123:
return "Memory Card 123";
case EXI_MEMORY_CARD_251:
return "Memory Card 251";
case EXI_MEMORY_CARD_507:
return "Memory Card 507";
case EXI_USB_ADAPTER:
return "USB Adapter";
case 0x80000000 | EXI_MEMORY_CARD_59:
case 0x80000000 | EXI_MEMORY_CARD_123:
case 0x80000000 | EXI_MEMORY_CARD_251:
case 0x80000000 | EXI_MEMORY_CARD_507:
return "Net Card";
case EXI_ETHER_VIEWER:
return "Artist Ether";
case EXI_STREAM_HANGER:
return "Stream Hanger";
case EXI_IS_VIEWER:
return "IS Viewer";
}
}
#pragma scheduling reset