Merge branch 'feature/new-sio'

This commit is contained in:
Vicki Pfau 2024-09-29 19:59:41 -07:00
commit d57bb82d22
25 changed files with 1798 additions and 1160 deletions

View File

@ -46,6 +46,7 @@ Misc:
- GBA: Improve detection of valid ELF ROMs
- GBA Audio: Remove broken XQ audio pending rewrite
- GBA Memory: Improve VRAM access stall cycle estimation
- GBA SIO: Rewrite lockstep driver for improved stability
- GBA Video: Add special circlular window handling in OpenGL renderer
- Libretro: Add Super Game Boy Color support (closes mgba.io/i/3188)
- mGUI: Enable auto-softpatching (closes mgba.io/i/2899)

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2013-2016 Jeffrey Pfau
/* Copyright (c) 2013-2024 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -53,6 +53,25 @@ static inline void mLockstepUnlock(struct mLockstep* lockstep) {
}
}
struct mLockstepUser {
void (*sleep)(struct mLockstepUser*);
void (*wake)(struct mLockstepUser*);
int (*requestedId)(struct mLockstepUser*);
void (*playerIdChanged)(struct mLockstepUser*, int id);
};
#ifndef DISABLE_THREADING
struct mCoreThread;
struct mLockstepThreadUser {
struct mLockstepUser d;
struct mCoreThread* thread;
};
void mLockstepThreadUserInit(struct mLockstepThreadUser* lockstep, struct mCoreThread* thread);
#endif
CXX_GUARD_END
#endif

View File

@ -81,7 +81,7 @@ extern MGBA_EXPORT const int GBA_LUX_LEVELS[10];
enum {
mPERIPH_GBA_LUMINANCE = 0x1000,
mPERIPH_GBA_BATTLECHIP_GATE,
mPERIPH_GBA_LINK_PORT,
};
struct GBACartridgeOverride {
@ -110,13 +110,22 @@ struct GBASIODriver {
bool (*init)(struct GBASIODriver* driver);
void (*deinit)(struct GBASIODriver* driver);
bool (*load)(struct GBASIODriver* driver);
bool (*unload)(struct GBASIODriver* driver);
uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value);
void (*reset)(struct GBASIODriver* driver);
uint32_t (*driverId)(const struct GBASIODriver* renderer);
bool (*loadState)(struct GBASIODriver* renderer, const void* state, size_t size);
void (*saveState)(struct GBASIODriver* renderer, void** state, size_t* size);
void (*setMode)(struct GBASIODriver* driver, enum GBASIOMode mode);
bool (*handlesMode)(struct GBASIODriver* driver, enum GBASIOMode mode);
int (*connectedDevices)(struct GBASIODriver* driver);
int (*deviceId)(struct GBASIODriver* driver);
uint16_t (*writeSIOCNT)(struct GBASIODriver* driver, uint16_t value);
uint16_t (*writeRCNT)(struct GBASIODriver* driver, uint16_t value);
bool (*start)(struct GBASIODriver* driver);
void (*finishMultiplayer)(struct GBASIODriver* driver, uint16_t data[4]);
uint8_t (*finishNormal8)(struct GBASIODriver* driver);
uint32_t (*finishNormal32)(struct GBASIODriver* driver);
};
void GBASIOJOYCreate(struct GBASIODriver* sio);
enum GBASIOBattleChipGateFlavor {
GBA_FLAVOR_BATTLECHIP_GATE = 4,
GBA_FLAVOR_PROGRESS_GATE = 5,
@ -126,7 +135,6 @@ enum GBASIOBattleChipGateFlavor {
struct GBASIOBattlechipGate {
struct GBASIODriver d;
struct mTimingEvent event;
uint16_t chipId;
uint16_t data[2];
int state;

View File

@ -191,7 +191,7 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
* | bits 2 - 3: GB Player inputs posted
* | bits 4 - 8: GB Player transmit position
* | bits 9 - 23: Reserved
* 0x002C4 - 0x002C7: Game Boy Player next event
* 0x002C4 - 0x002C7: SIO next event
* 0x002C8 - 0x002CB: Current DMA transfer word
* 0x002CC - 0x002CF: Last DMA transfer PC
* 0x002D0 - 0x002DF: Matrix memory command buffer
@ -287,6 +287,7 @@ DECL_BITS(GBASerializedMiscFlags, KeyIRQKeys, 4, 11);
enum {
GBA_SUBSYSTEM_VIDEO_RENDERER = 0,
GBA_SUBSYSTEM_SIO_DRIVER = 1,
GBA_SUBSYSTEM_MAX,
};
@ -370,7 +371,7 @@ struct GBASerializedState {
uint8_t lightSample;
GBASerializedHWFlags2 flags2;
GBASerializedHWFlags3 flags3;
uint32_t gbpNextEvent;
uint32_t sioNextEvent;
} hw;
uint32_t dmaTransferRegister;

View File

@ -16,8 +16,6 @@ CXX_GUARD_START
#define MAX_GBAS 4
extern const int GBASIOCyclesPerTransfer[4][MAX_GBAS];
mLOG_DECLARE_CATEGORY(GBA_SIO);
enum {
@ -54,37 +52,45 @@ DECL_BITS(GBASIOMultiplayer, Id, 4, 2);
DECL_BIT(GBASIOMultiplayer, Error, 6);
DECL_BIT(GBASIOMultiplayer, Busy, 7);
DECL_BIT(GBASIOMultiplayer, Irq, 14);
struct GBASIODriverSet {
struct GBASIODriver* normal;
struct GBASIODriver* multiplayer;
struct GBASIODriver* joybus;
};
DECL_BITFIELD(GBASIORegisterRCNT, uint16_t);
DECL_BIT(GBASIORegisterRCNT, Sc, 0);
DECL_BIT(GBASIORegisterRCNT, Sd, 1);
DECL_BIT(GBASIORegisterRCNT, Si, 2);
DECL_BIT(GBASIORegisterRCNT, So, 3);
DECL_BIT(GBASIORegisterRCNT, ScDirection, 4);
DECL_BIT(GBASIORegisterRCNT, SdDirection, 5);
DECL_BIT(GBASIORegisterRCNT, SiDirection, 6);
DECL_BIT(GBASIORegisterRCNT, SoDirection, 7);
struct GBASIO {
struct GBA* p;
enum GBASIOMode mode;
struct GBASIODriverSet drivers;
struct GBASIODriver* activeDriver;
struct GBASIODriver* driver;
uint16_t rcnt;
uint16_t siocnt;
struct GBASIOPlayer gbp;
struct mTimingEvent completeEvent;
};
void GBASIOInit(struct GBASIO* sio);
void GBASIODeinit(struct GBASIO* sio);
void GBASIOReset(struct GBASIO* sio);
void GBASIOSetDriverSet(struct GBASIO* sio, struct GBASIODriverSet* drivers);
void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver, enum GBASIOMode mode);
void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver);
void GBASIOWriteRCNT(struct GBASIO* sio, uint16_t value);
void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value);
uint16_t GBASIOWriteRegister(struct GBASIO* sio, uint32_t address, uint16_t value);
int32_t GBASIOTransferCycles(enum GBASIOMode mode, uint16_t siocnt, int connected);
void GBASIOMultiplayerFinishTransfer(struct GBASIO* sio, uint16_t data[4], uint32_t cyclesLate);
void GBASIONormal8FinishTransfer(struct GBASIO* sio, uint8_t data, uint32_t cyclesLate);
void GBASIONormal32FinishTransfer(struct GBASIO* sio, uint32_t data, uint32_t cyclesLate);
int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data);
CXX_GUARD_END

View File

@ -21,7 +21,6 @@ struct GBASIOPlayer {
struct GBA* p;
unsigned inputsPosted;
int txPosition;
struct mTimingEvent event;
struct GBASIOPlayerKeyCallback callback;
bool oldOpposingDirections;
struct mKeyCallback* oldCallback;

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2013-2015 Jeffrey Pfau
/* Copyright (c) 2013-2024 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -13,40 +13,82 @@ CXX_GUARD_START
#include <mgba/core/lockstep.h>
#include <mgba/core/timing.h>
#include <mgba/internal/gba/sio.h>
#include <mgba-util/circle-buffer.h>
#include <mgba-util/table.h>
#include <mgba-util/threading.h>
struct GBASIOLockstep {
struct mLockstep d;
struct GBASIOLockstepNode* players[MAX_GBAS];
int attachedMulti;
int attachedNormal;
#define MAX_LOCKSTEP_EVENTS 8
uint16_t multiRecv[MAX_GBAS];
uint32_t normalRecv[MAX_GBAS];
enum GBASIOLockstepEventType {
SIO_EV_ATTACH,
SIO_EV_DETACH,
SIO_EV_HARD_SYNC,
SIO_EV_MODE_SET,
SIO_EV_TRANSFER_START,
};
struct GBASIOLockstepNode {
struct GBASIODriver d;
struct GBASIOLockstep* p;
struct mTimingEvent event;
struct GBASIOLockstepCoordinator {
struct Table players;
Mutex mutex;
volatile int32_t nextEvent;
int32_t eventDiff;
bool normalSO;
int id;
unsigned nextId;
unsigned attachedPlayers[MAX_GBAS];
int nAttached;
uint32_t waiting;
bool transferActive;
enum GBASIOMode transferMode;
int32_t cycle;
int32_t nextHardSync;
uint16_t multiData[4];
uint32_t normalData[4];
};
struct GBASIOLockstepEvent {
enum GBASIOLockstepEventType type;
int32_t timestamp;
struct GBASIOLockstepEvent* next;
int playerId;
union {
enum GBASIOMode mode;
bool transferFinished;
#ifndef NDEBUG
int transferId;
enum mLockstepPhase phase;
#endif
int32_t finishCycle;
};
};
void GBASIOLockstepInit(struct GBASIOLockstep*);
struct GBASIOLockstepPlayer {
struct GBASIOLockstepDriver* driver;
int playerId;
enum GBASIOMode mode;
enum GBASIOMode otherModes[MAX_GBAS];
bool asleep;
int32_t cycleOffset;
struct GBASIOLockstepEvent* queue;
bool dataReceived;
void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode*);
struct GBASIOLockstepEvent buffer[MAX_LOCKSTEP_EVENTS];
struct GBASIOLockstepEvent* freeList;
};
bool GBASIOLockstepAttachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*);
void GBASIOLockstepDetachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*);
struct GBASIOLockstepDriver {
struct GBASIODriver d;
struct GBASIOLockstepCoordinator* coordinator;
struct mTimingEvent event;
unsigned lockstepId;
struct mLockstepUser* user;
};
void GBASIOLockstepCoordinatorInit(struct GBASIOLockstepCoordinator*);
void GBASIOLockstepCoordinatorDeinit(struct GBASIOLockstepCoordinator*);
void GBASIOLockstepCoordinatorAttach(struct GBASIOLockstepCoordinator*, struct GBASIOLockstepDriver*);
void GBASIOLockstepCoordinatorDetach(struct GBASIOLockstepCoordinator*, struct GBASIOLockstepDriver*);
size_t GBASIOLockstepCoordinatorAttached(struct GBASIOLockstepCoordinator*);
void GBASIOLockstepDriverCreate(struct GBASIOLockstepDriver*, struct mLockstepUser*);
CXX_GUARD_END

View File

@ -1,10 +1,14 @@
/* Copyright (c) 2013-2016 Jeffrey Pfau
/* Copyright (c) 2013-2024 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/core/lockstep.h>
#ifndef DISABLE_THREADING
#include <mgba/core/thread.h>
#endif
void mLockstepInit(struct mLockstep* lockstep) {
lockstep->attached = 0;
lockstep->transferActive = 0;
@ -19,4 +23,21 @@ void mLockstepDeinit(struct mLockstep* lockstep) {
UNUSED(lockstep);
}
// TODO: Migrate nodes
#ifndef DISABLE_THREADING
static void mLockstepThreadUserSleep(struct mLockstepUser* user) {
struct mLockstepThreadUser* lockstep = (struct mLockstepThreadUser*) user;
mCoreThreadWaitFromThread(lockstep->thread);
}
static void mLockstepThreadUserWake(struct mLockstepUser* user) {
struct mLockstepThreadUser* lockstep = (struct mLockstepThreadUser*) user;
mCoreThreadStopWaiting(lockstep->thread);
}
void mLockstepThreadUserInit(struct mLockstepThreadUser* lockstep, struct mCoreThread* thread) {
memset(lockstep, 0, sizeof(*lockstep));
lockstep->d.sleep = mLockstepThreadUserSleep;
lockstep->d.wake = mLockstepThreadUserWake;
lockstep->thread = thread;
}
#endif

View File

@ -31,7 +31,6 @@ set(SOURCE_FILES
sharkport.c
sio.c
sio/gbp.c
sio/joybus.c
timer.c
video.c)

View File

@ -486,10 +486,10 @@ void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASeria
flags2 = GBASerializedHWFlags2SetTiltState(flags2, hw->tiltState);
flags2 = GBASerializedHWFlags1SetLightCounter(flags2, hw->lightCounter);
// GBP stuff is only here for legacy reasons
// GBP/SIO stuff is only here for legacy reasons
flags2 = GBASerializedHWFlags2SetGbpInputsPosted(flags2, hw->p->sio.gbp.inputsPosted);
flags2 = GBASerializedHWFlags2SetGbpTxPosition(flags2, hw->p->sio.gbp.txPosition);
STORE_32(hw->p->sio.gbp.event.when - mTimingCurrentTime(&hw->p->timing), 0, &state->hw.gbpNextEvent);
STORE_32(hw->p->sio.completeEvent.when - mTimingCurrentTime(&hw->p->timing), 0, &state->hw.sioNextEvent);
state->hw.flags2 = flags2;
}
@ -532,16 +532,16 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer
hw->lightSample = state->hw.lightSample;
hw->lightEdge = GBASerializedHWFlags1GetLightEdge(flags1);
// GBP stuff is only here for legacy reasons
// GBP/SIO stuff is only here for legacy reasons
hw->p->sio.gbp.inputsPosted = GBASerializedHWFlags2GetGbpInputsPosted(state->hw.flags2);
hw->p->sio.gbp.txPosition = GBASerializedHWFlags2GetGbpTxPosition(state->hw.flags2);
uint32_t when;
LOAD_32(when, 0, &state->hw.gbpNextEvent);
LOAD_32(when, 0, &state->hw.sioNextEvent);
if (hw->devices & HW_GB_PLAYER) {
GBASIOSetDriver(&hw->p->sio, &hw->p->sio.gbp.d, GBA_SIO_NORMAL_32);
if (hw->p->memory.io[GBA_REG(SIOCNT)] & 0x0080) {
mTimingSchedule(&hw->p->timing, &hw->p->sio.gbp.event, when);
}
GBASIOSetDriver(&hw->p->sio, &hw->p->sio.gbp.d);
}
if ((hw->p->memory.io[GBA_REG(SIOCNT)] & 0x0080) && when < 0x20000) {
mTimingSchedule(&hw->p->timing, &hw->p->sio.completeEvent, when);
}
}

View File

@ -842,7 +842,21 @@ static bool _GBACoreLoadExtraState(struct mCore* core, const struct mStateExtdat
if (type == gba->video.renderer->rendererId(gba->video.renderer)) {
ok = gba->video.renderer->loadState(gba->video.renderer,
(void*) ((uintptr_t) item.data + sizeof(uint32_t)),
item.size - sizeof(type));
item.size - sizeof(type)) && ok;
}
} else if (item.data) {
ok = false;
}
}
if (gba->sio.driver && gba->sio.driver->driverId && gba->sio.driver->loadState &&
mStateExtdataGet(extdata, EXTDATA_SUBSYSTEM_START + GBA_SUBSYSTEM_SIO_DRIVER, &item)) {
if ((uint32_t) item.size > sizeof(uint32_t)) {
uint32_t type;
LOAD_32(type, 0, item.data);
if (type == gba->sio.driver->driverId(gba->sio.driver)) {
ok = gba->sio.driver->loadState(gba->sio.driver,
(void*) ((uintptr_t) item.data + sizeof(uint32_t)),
item.size - sizeof(type)) && ok;
}
} else if (item.data) {
ok = false;
@ -868,6 +882,27 @@ static bool _GBACoreSaveExtraState(struct mCore* core, struct mStateExtdata* ext
}
if (buffer) {
free(buffer);
buffer = NULL;
}
size = 0;
if (gba->sio.driver && gba->sio.driver->driverId && gba->sio.driver->saveState) {
gba->sio.driver->saveState(gba->sio.driver, &buffer, &size);
if (size > 0 && buffer) {
struct mStateExtdataItem item;
item.size = size + sizeof(uint32_t);
item.data = malloc(item.size);
item.clean = free;
uint32_t type = gba->sio.driver->driverId(gba->sio.driver);
STORE_32(type, 0, item.data);
memcpy((void*) ((uintptr_t) item.data + sizeof(uint32_t)), buffer, size);
mStateExtdataPut(extdata, EXTDATA_SUBSYSTEM_START + GBA_SUBSYSTEM_SIO_DRIVER, &item);
}
if (buffer) {
free(buffer);
buffer = NULL;
}
size = 0;
}
return true;
@ -927,9 +962,8 @@ static void _GBACoreSetPeripheral(struct mCore* core, int type, void* periph) {
case mPERIPH_GBA_LUMINANCE:
gba->luminanceSource = periph;
break;
case mPERIPH_GBA_BATTLECHIP_GATE:
GBASIOSetDriver(&gba->sio, periph, GBA_SIO_MULTI);
GBASIOSetDriver(&gba->sio, periph, GBA_SIO_NORMAL_32);
case mPERIPH_GBA_LINK_PORT:
GBASIOSetDriver(&gba->sio, periph);
break;
default:
return;

View File

@ -33,28 +33,25 @@ enum {
BATTLECHIP_CONTINUE = 0xFFFF,
};
static bool GBASIOBattlechipGateLoad(struct GBASIODriver* driver);
static uint16_t GBASIOBattlechipGateWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
static void _battlechipTransfer(struct GBASIOBattlechipGate* gate);
static void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cyclesLate);
static bool GBASIOBattlechipGateInit(struct GBASIODriver* driver);
static uint16_t GBASIOBattlechipGateWriteSIOCNT(struct GBASIODriver* driver, uint16_t value);
static bool GBASIOBattlechipGateHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode);
static int GBASIOBattlechipGateConnectedDevices(struct GBASIODriver* driver);
static void GBASIOBattlechipGateFinishMultiplayer(struct GBASIODriver* driver, uint16_t data[4]);
void GBASIOBattlechipGateCreate(struct GBASIOBattlechipGate* gate) {
gate->d.init = NULL;
gate->d.deinit = NULL;
gate->d.load = GBASIOBattlechipGateLoad;
gate->d.unload = NULL;
gate->d.writeRegister = GBASIOBattlechipGateWriteRegister;
gate->event.context = gate;
gate->event.callback = _battlechipTransferEvent;
gate->event.priority = 0x80;
memset(&gate->d, 0, sizeof(gate->d));
gate->d.init = GBASIOBattlechipGateInit;
gate->d.writeSIOCNT = GBASIOBattlechipGateWriteSIOCNT;
gate->d.handlesMode = GBASIOBattlechipGateHandlesMode;
gate->d.connectedDevices = GBASIOBattlechipGateConnectedDevices;
gate->d.finishMultiplayer = GBASIOBattlechipGateFinishMultiplayer;
gate->chipId = 0;
gate->flavor = GBA_FLAVOR_BATTLECHIP_GATE;
}
bool GBASIOBattlechipGateLoad(struct GBASIODriver* driver) {
bool GBASIOBattlechipGateInit(struct GBASIODriver* driver) {
struct GBASIOBattlechipGate* gate = (struct GBASIOBattlechipGate*) driver;
gate->state = BATTLECHIP_STATE_SYNC;
gate->data[0] = 0x00FE;
@ -62,58 +59,34 @@ bool GBASIOBattlechipGateLoad(struct GBASIODriver* driver) {
return true;
}
uint16_t GBASIOBattlechipGateWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
struct GBASIOBattlechipGate* gate = (struct GBASIOBattlechipGate*) driver;
switch (address) {
case GBA_REG_SIOCNT:
uint16_t GBASIOBattlechipGateWriteSIOCNT(struct GBASIODriver* driver, uint16_t value) {
UNUSED(driver);
value &= ~0xC;
value |= 0x8;
if (value & 0x80) {
_battlechipTransfer(gate);
}
break;
case GBA_REG_SIOMLT_SEND:
break;
case GBA_REG_RCNT:
break;
default:
break;
}
return value;
}
void _battlechipTransfer(struct GBASIOBattlechipGate* gate) {
int32_t cycles;
if (gate->d.p->mode == GBA_SIO_NORMAL_32) {
cycles = GBA_ARM7TDMI_FREQUENCY / 0x40000;
} else {
cycles = GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(gate->d.p->siocnt)][1];
static bool GBASIOBattlechipGateHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode) {
UNUSED(driver);
switch (mode) {
case GBA_SIO_NORMAL_32:
case GBA_SIO_MULTI:
return true;
default:
return false;
}
mTimingDeschedule(&gate->d.p->p->timing, &gate->event);
mTimingSchedule(&gate->d.p->p->timing, &gate->event, cycles);
}
void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cyclesLate) {
UNUSED(timing);
struct GBASIOBattlechipGate* gate = user;
static int GBASIOBattlechipGateConnectedDevices(struct GBASIODriver* driver) {
UNUSED(driver);
return 1;
}
if (gate->d.p->mode == GBA_SIO_NORMAL_32) {
gate->d.p->p->memory.io[GBA_REG(SIODATA32_LO)] = 0;
gate->d.p->p->memory.io[GBA_REG(SIODATA32_HI)] = 0;
gate->d.p->siocnt = GBASIONormalClearStart(gate->d.p->siocnt);
if (GBASIONormalIsIrq(gate->d.p->siocnt)) {
GBARaiseIRQ(gate->d.p->p, GBA_IRQ_SIO, cyclesLate);
}
return;
}
static void GBASIOBattlechipGateFinishMultiplayer(struct GBASIODriver* driver, uint16_t data[4]) {
struct GBASIOBattlechipGate* gate = (struct GBASIOBattlechipGate*) driver;
uint16_t cmd = gate->d.p->p->memory.io[GBA_REG(SIOMLT_SEND)];
uint16_t reply = 0xFFFF;
gate->d.p->p->memory.io[GBA_REG(SIOMULTI0)] = cmd;
gate->d.p->p->memory.io[GBA_REG(SIOMULTI2)] = 0xFFFF;
gate->d.p->p->memory.io[GBA_REG(SIOMULTI3)] = 0xFFFF;
gate->d.p->siocnt = GBASIOMultiplayerClearBusy(gate->d.p->siocnt);
gate->d.p->siocnt = GBASIOMultiplayerSetId(gate->d.p->siocnt, 0);
mLOG(GBA_BATTLECHIP, DEBUG, "Game: %04X (%i)", cmd, gate->state);
@ -191,9 +164,8 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle
mLOG(GBA_BATTLECHIP, DEBUG, "Gate: %04X (%i)", reply, gate->state);
++gate->state;
gate->d.p->p->memory.io[GBA_REG(SIOMULTI1)] = reply;
if (GBASIOMultiplayerIsIrq(gate->d.p->siocnt)) {
GBARaiseIRQ(gate->d.p->p, GBA_IRQ_SIO, cyclesLate);
}
data[0] = cmd;
data[1] = reply;
data[2] = 0xFFFF;
data[3] = 0xFFFF;
}

View File

@ -263,8 +263,8 @@ void GBAReset(struct ARMCore* cpu) {
// GB Player SIO control should not be engaged before detection, even if we already know it's GBP
gba->memory.hw.devices &= ~HW_GB_PLAYER;
if (gba->sio.drivers.normal == &gba->sio.gbp.d) {
GBASIOSetDriver(&gba->sio, NULL, GBA_SIO_NORMAL_32);
if (gba->sio.driver == &gba->sio.gbp.d) {
GBASIOSetDriver(&gba->sio, NULL);
}
bool isELF = false;

View File

@ -214,8 +214,8 @@ static const int _isRSpecialRegister[GBA_REG(INTERNAL_MAX)] = {
/* 10 */ 1, 1, 1, 1, 1, 1, 1, 1,
/* 11 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* SIO */
/* 12 */ 1, 1, 1, 1, 1, 0, 0, 0,
/* 13 */ 1, 1, 1, 0, 0, 0, 0, 0,
/* 12 */ 1, 1, 1, 1, 0, 0, 0, 0,
/* 13 */ 1, 1, 0, 0, 0, 0, 0, 0,
/* 14 */ 1, 0, 0, 0, 0, 0, 0, 0,
/* 15 */ 1, 1, 1, 1, 1, 0, 0, 0,
/* 16 */ 0, 0, 0, 0, 0, 0, 0, 0,
@ -483,6 +483,7 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
// SIO
case GBA_REG_SIOCNT:
value &= 0x7FFF;
GBASIOWriteSIOCNT(&gba->sio, value);
break;
case GBA_REG_RCNT:

View File

@ -11,26 +11,14 @@
mLOG_DEFINE_CATEGORY(GBA_SIO, "GBA Serial I/O", "gba.sio");
const int GBASIOCyclesPerTransfer[4][MAX_GBAS] = {
static const int GBASIOCyclesPerTransfer[4][MAX_GBAS] = {
{ 31976, 63427, 94884, 125829 },
{ 8378, 16241, 24104, 31457 },
{ 5750, 10998, 16241, 20972 },
{ 3140, 5755, 8376, 10486 }
};
static struct GBASIODriver* _lookupDriver(struct GBASIO* sio, enum GBASIOMode mode) {
switch (mode) {
case GBA_SIO_NORMAL_8:
case GBA_SIO_NORMAL_32:
return sio->drivers.normal;
case GBA_SIO_MULTI:
return sio->drivers.multiplayer;
case GBA_SIO_JOYBUS:
return sio->drivers.joybus;
default:
return 0;
}
}
static void _sioFinish(struct mTiming* timing, void* user, uint32_t cyclesLate);
static const char* _modeName(enum GBASIOMode mode) {
switch (mode) {
@ -58,25 +46,36 @@ static void _switchMode(struct GBASIO* sio) {
newMode = (enum GBASIOMode) (mode & 0xC);
}
if (newMode != sio->mode) {
if (sio->activeDriver && sio->activeDriver->unload) {
sio->activeDriver->unload(sio->activeDriver);
}
if (sio->mode != (enum GBASIOMode) -1) {
mLOG(GBA_SIO, DEBUG, "Switching mode from %s to %s", _modeName(sio->mode), _modeName(newMode));
}
sio->mode = newMode;
sio->activeDriver = _lookupDriver(sio, sio->mode);
if (sio->activeDriver && sio->activeDriver->load) {
sio->activeDriver->load(sio->activeDriver);
if (sio->driver && sio->driver->setMode) {
sio->driver->setMode(sio->driver, newMode);
}
int id = 0;
switch (newMode) {
case GBA_SIO_MULTI:
if (sio->driver && sio->driver->deviceId) {
id = sio->driver->deviceId(sio->driver);
}
sio->rcnt = GBASIORegisterRCNTSetSi(sio->rcnt, !!id);
break;
default:
// TODO
break;
}
}
}
void GBASIOInit(struct GBASIO* sio) {
sio->drivers.normal = 0;
sio->drivers.multiplayer = 0;
sio->drivers.joybus = 0;
sio->activeDriver = 0;
sio->driver = NULL;
sio->completeEvent.context = sio;
sio->completeEvent.name = "GBA SIO Complete";
sio->completeEvent.callback = _sioFinish;
sio->completeEvent.priority = 0x80;
sio->gbp.p = sio->p;
GBASIOPlayerInit(&sio->gbp);
@ -85,64 +84,28 @@ void GBASIOInit(struct GBASIO* sio) {
}
void GBASIODeinit(struct GBASIO* sio) {
if (sio->activeDriver && sio->activeDriver->unload) {
sio->activeDriver->unload(sio->activeDriver);
}
if (sio->drivers.multiplayer && sio->drivers.multiplayer->deinit) {
sio->drivers.multiplayer->deinit(sio->drivers.multiplayer);
}
if (sio->drivers.joybus && sio->drivers.joybus->deinit) {
sio->drivers.joybus->deinit(sio->drivers.joybus);
}
if (sio->drivers.normal && sio->drivers.normal->deinit) {
sio->drivers.normal->deinit(sio->drivers.normal);
if (sio->driver && sio->driver->deinit) {
sio->driver->deinit(sio->driver);
}
}
void GBASIOReset(struct GBASIO* sio) {
if (sio->activeDriver && sio->activeDriver->unload) {
sio->activeDriver->unload(sio->activeDriver);
if (sio->driver && sio->driver->reset) {
sio->driver->reset(sio->driver);
}
sio->rcnt = RCNT_INITIAL;
sio->siocnt = 0;
sio->mode = -1;
sio->activeDriver = NULL;
_switchMode(sio);
GBASIOPlayerReset(&sio->gbp);
}
void GBASIOSetDriverSet(struct GBASIO* sio, struct GBASIODriverSet* drivers) {
GBASIOSetDriver(sio, drivers->normal, GBA_SIO_NORMAL_8);
GBASIOSetDriver(sio, drivers->multiplayer, GBA_SIO_MULTI);
GBASIOSetDriver(sio, drivers->joybus, GBA_SIO_JOYBUS);
}
void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver, enum GBASIOMode mode) {
struct GBASIODriver** driverLoc;
switch (mode) {
case GBA_SIO_NORMAL_8:
case GBA_SIO_NORMAL_32:
driverLoc = &sio->drivers.normal;
break;
case GBA_SIO_MULTI:
driverLoc = &sio->drivers.multiplayer;
break;
case GBA_SIO_JOYBUS:
driverLoc = &sio->drivers.joybus;
break;
default:
mLOG(GBA_SIO, ERROR, "Setting an unsupported SIO driver: %x", mode);
return;
}
if (*driverLoc) {
if ((*driverLoc)->unload) {
(*driverLoc)->unload(*driverLoc);
}
if ((*driverLoc)->deinit) {
(*driverLoc)->deinit(*driverLoc);
}
void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver) {
if (sio->driver && sio->driver->deinit) {
sio->driver->deinit(sio->driver);
}
sio->driver = driver;
if (driver) {
driver->p = sio;
@ -154,22 +117,42 @@ void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver, enum GBASI
}
}
}
if (sio->activeDriver == *driverLoc) {
sio->activeDriver = driver;
if (driver && driver->load) {
driver->load(driver);
}
}
*driverLoc = driver;
}
void GBASIOWriteRCNT(struct GBASIO* sio, uint16_t value) {
sio->rcnt &= 0xF;
sio->rcnt |= value & ~0xF;
sio->rcnt &= 0x1FF;
sio->rcnt |= value & 0xC000;
_switchMode(sio);
if (sio->activeDriver && sio->activeDriver->writeRegister) {
sio->activeDriver->writeRegister(sio->activeDriver, GBA_REG_RCNT, value);
if (sio->driver && sio->driver->writeRCNT) {
switch (sio->mode) {
case GBA_SIO_GPIO:
sio->rcnt = (sio->driver->writeRCNT(sio->driver, value) & 0x01FF) | (sio->rcnt & 0xC000);
break;
default:
sio->rcnt = (sio->driver->writeRCNT(sio->driver, value) & 0x01F0) | (sio->rcnt & 0xC00F);
}
} else if (sio->mode == GBA_SIO_GPIO) {
sio->rcnt &= 0xC000;
sio->rcnt |= value & 0x1FF;
} else {
sio->rcnt &= 0xC00F;
sio->rcnt |= value & 0x1F0;
}
}
static void _startTransfer(struct GBASIO* sio) {
if (sio->driver && sio->driver->start) {
if (!sio->driver->start(sio->driver)) {
// Transfer completion is handled internally to the driver
return;
}
}
int connected = 0;
if (sio->driver && sio->driver->connectedDevices) {
connected = sio->driver->connectedDevices(sio->driver);
}
mTimingDeschedule(&sio->p->timing, &sio->completeEvent);
mTimingSchedule(&sio->p->timing, &sio->completeEvent, GBASIOTransferCycles(sio->mode, sio->siocnt, connected));
}
void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) {
@ -177,25 +160,79 @@ void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) {
sio->siocnt = value & 0x3000;
_switchMode(sio);
}
if (sio->activeDriver && sio->activeDriver->writeRegister) {
value = sio->activeDriver->writeRegister(sio->activeDriver, GBA_REG_SIOCNT, value);
int id = 0;
int connected = 0;
bool handled = false;
if (sio->driver) {
handled = sio->driver->handlesMode(sio->driver, sio->mode);
if (handled) {
if (sio->driver->deviceId) {
id = sio->driver->deviceId(sio->driver);
}
connected = sio->driver->connectedDevices(sio->driver);
handled = !!sio->driver->writeSIOCNT;
}
}
switch (sio->mode) {
case GBA_SIO_MULTI:
value &= 0xFF83;
value = GBASIOMultiplayerSetSlave(value, id || !connected);
value = GBASIOMultiplayerSetId(value, id);
value |= sio->siocnt & 0x00FC;
// SC appears to float in multi mode when not doing a transfer. While
// it does spike at the end of a transfer, it appears to die down after
// around 20-30 microseconds. However, the docs on akkit.org
// (http://www.akkit.org/info/gba_comms.html) say this is high until
// a transfer starts and low while active. Further, the Mario Bros.
// multiplayer expects SC to be high in multi mode. This needs better
// investigation than I managed, apparently.
sio->rcnt = GBASIORegisterRCNTFillSc(sio->rcnt);
if (GBASIOMultiplayerIsBusy(value) && !GBASIOMultiplayerIsBusy(sio->siocnt)) {
if (!id) {
sio->p->memory.io[GBA_REG(SIOMULTI0)] = 0xFFFF;
sio->p->memory.io[GBA_REG(SIOMULTI1)] = 0xFFFF;
sio->p->memory.io[GBA_REG(SIOMULTI2)] = 0xFFFF;
sio->p->memory.io[GBA_REG(SIOMULTI3)] = 0xFFFF;
sio->rcnt = GBASIORegisterRCNTClearSc(sio->rcnt);
_startTransfer(sio);
} else {
// TODO
}
}
break;
case GBA_SIO_NORMAL_8:
case GBA_SIO_NORMAL_32:
// This line is pulled up by the clock owner while the clock is idle.
// If there is no clock owner it's just hi-Z.
if (GBASIONormalGetSc(value)) {
sio->rcnt = GBASIORegisterRCNTFillSc(sio->rcnt);
}
if (GBASIONormalIsStart(value) && !GBASIONormalIsStart(sio->siocnt)) {
if (GBASIONormalIsSc(value)) {
_startTransfer(sio);
} else {
// TODO
}
}
break;
default:
// TODO
break;
}
if (handled) {
value = sio->driver->writeSIOCNT(sio->driver, value);
} else {
// Dummy drivers
switch (sio->mode) {
case GBA_SIO_NORMAL_8:
case GBA_SIO_NORMAL_32:
value = GBASIONormalFillSi(value);
if ((value & 0x0081) == 0x0081) {
if (GBASIONormalIsIrq(value)) {
// TODO: Test this on hardware to see if this is correct
GBARaiseIRQ(sio->p, GBA_IRQ_SIO, 0);
}
value = GBASIONormalClearStart(value);
}
break;
case GBA_SIO_MULTI:
value &= 0xFF83;
value |= 0xC;
value = GBASIOMultiplayerFillReady(value);
break;
default:
// TODO
@ -206,22 +243,252 @@ void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) {
}
uint16_t GBASIOWriteRegister(struct GBASIO* sio, uint32_t address, uint16_t value) {
if (sio->activeDriver && sio->activeDriver->writeRegister) {
return sio->activeDriver->writeRegister(sio->activeDriver, address, value);
int id = 0;
if (sio->driver && sio->driver->deviceId) {
id = sio->driver->deviceId(sio->driver);
}
// Dummy drivers
bool handled = true;
switch (sio->mode) {
case GBA_SIO_JOYBUS:
switch (address) {
case GBA_REG_SIODATA8:
mLOG(GBA_SIO, DEBUG, "JOY write: SIODATA8 (?) <- %04X", value);
break;
case GBA_REG_JOYCNT:
return (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040);
mLOG(GBA_SIO, DEBUG, "JOY write: CNT <- %04X", value);
value = (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040);
break;
case GBA_REG_JOYSTAT:
return (value & 0x0030) | (sio->p->memory.io[GBA_REG(JOYSTAT)] & ~0x30);
}
mLOG(GBA_SIO, DEBUG, "JOY write: STAT <- %04X", value);
value = (value & 0x0030) | (sio->p->memory.io[GBA_REG(JOYSTAT)] & ~0x30);
break;
case GBA_REG_JOY_TRANS_LO:
mLOG(GBA_SIO, DEBUG, "JOY write: TRANS_LO <- %04X", value);
break;
case GBA_REG_JOY_TRANS_HI:
mLOG(GBA_SIO, DEBUG, "JOY write: TRANS_HI <- %04X", value);
break;
default:
// TODO
mLOG(GBA_SIO, GAME_ERROR, "JOY write: Unhandled %s <- %04X", GBAIORegisterNames[address >> 1], value);
handled = false;
break;
}
break;
case GBA_SIO_NORMAL_8:
switch (address) {
case GBA_REG_SIODATA8:
mLOG(GBA_SIO, DEBUG, "NORMAL8 %i write: SIODATA8 <- %04X", id, value);
break;
case GBA_REG_JOYCNT:
mLOG(GBA_SIO, DEBUG, "NORMAL8 %i write: JOYCNT (?) <- %04X", id, value);
value = (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040);
break;
default:
mLOG(GBA_SIO, GAME_ERROR, "NORMAL8 %i write: Unhandled %s <- %04X", id, GBAIORegisterNames[address >> 1], value);
handled = false;
break;
}
break;
case GBA_SIO_NORMAL_32:
switch (address) {
case GBA_REG_SIODATA32_LO:
mLOG(GBA_SIO, DEBUG, "NORMAL32 %i write: SIODATA32_LO <- %04X", id, value);
break;
case GBA_REG_SIODATA32_HI:
mLOG(GBA_SIO, DEBUG, "NORMAL32 %i write: SIODATA32_HI <- %04X", id, value);
break;
case GBA_REG_SIODATA8:
mLOG(GBA_SIO, DEBUG, "NORMAL32 %i write: SIODATA8 (?) <- %04X", id, value);
break;
case GBA_REG_JOYCNT:
mLOG(GBA_SIO, DEBUG, "NORMAL32 %i write: JOYCNT (?) <- %04X", id, value);
value = (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040);
break;
default:
mLOG(GBA_SIO, GAME_ERROR, "NORMAL32 %i write: Unhandled %s <- %04X", id, GBAIORegisterNames[address >> 1], value);
handled = false;
break;
}
break;
case GBA_SIO_MULTI:
switch (address) {
case GBA_REG_SIOMLT_SEND:
mLOG(GBA_SIO, DEBUG, "MULTI %i write: SIOMLT_SEND <- %04X", id, value);
break;
case GBA_REG_JOYCNT:
mLOG(GBA_SIO, DEBUG, "MULTI %i write: JOYCNT (?) <- %04X", id, value);
value = (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040);
break;
default:
mLOG(GBA_SIO, GAME_ERROR, "MULTI %i write: Unhandled %s <- %04X", id, GBAIORegisterNames[address >> 1], value);
handled = false;
break;
}
break;
case GBA_SIO_UART:
switch (address) {
case GBA_REG_SIODATA8:
mLOG(GBA_SIO, DEBUG, "UART write: SIODATA8 <- %04X", value);
break;
case GBA_REG_JOYCNT:
mLOG(GBA_SIO, DEBUG, "UART write: JOYCNT (?) <- %04X", value);
value = (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040);
break;
default:
mLOG(GBA_SIO, GAME_ERROR, "UART write: Unhandled %s <- %04X", GBAIORegisterNames[address >> 1], value);
handled = false;
break;
}
break;
case GBA_SIO_GPIO:
mLOG(GBA_SIO, STUB, "GPIO write: Unhandled %s <- %04X", GBAIORegisterNames[address >> 1], value);
handled = false;
break;
}
if (!handled) {
value = sio->p->memory.io[address >> 1];
}
return value;
}
int32_t GBASIOTransferCycles(enum GBASIOMode mode, uint16_t siocnt, int connected) {
if (connected < 0 || connected >= MAX_GBAS) {
mLOG(GBA_SIO, ERROR, "Invalid device count %i", connected);
return 0;
}
switch (mode) {
case GBA_SIO_MULTI:
return GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(siocnt)][connected];
case GBA_SIO_NORMAL_8:
return 8 * GBA_ARM7TDMI_FREQUENCY / ((GBASIONormalIsInternalSc(siocnt) ? 2048 : 256) * 1024);
case GBA_SIO_NORMAL_32:
return 32 * GBA_ARM7TDMI_FREQUENCY / ((GBASIONormalIsInternalSc(siocnt) ? 2048 : 256) * 1024);
default:
mLOG(GBA_SIO, STUB, "No cycle count implemented for mode %s", _modeName(mode));
break;
}
return 0;
}
void GBASIOMultiplayerFinishTransfer(struct GBASIO* sio, uint16_t data[4], uint32_t cyclesLate) {
int id = 0;
if (sio->driver && sio->driver->deviceId) {
id = sio->driver->deviceId(sio->driver);
}
sio->p->memory.io[GBA_REG(SIOMULTI0)] = data[0];
sio->p->memory.io[GBA_REG(SIOMULTI1)] = data[1];
sio->p->memory.io[GBA_REG(SIOMULTI2)] = data[2];
sio->p->memory.io[GBA_REG(SIOMULTI3)] = data[3];
sio->siocnt = GBASIOMultiplayerClearBusy(sio->siocnt);
sio->siocnt = GBASIOMultiplayerSetId(sio->siocnt, id);
sio->rcnt = GBASIORegisterRCNTFillSc(sio->rcnt);
if (GBASIOMultiplayerIsIrq(sio->siocnt)) {
GBARaiseIRQ(sio->p, GBA_IRQ_SIO, cyclesLate);
}
}
void GBASIONormal8FinishTransfer(struct GBASIO* sio, uint8_t data, uint32_t cyclesLate) {
sio->siocnt = GBASIONormalClearStart(sio->siocnt);
sio->p->memory.io[GBA_REG(SIODATA8)] = data;
if (GBASIONormalIsIrq(sio->siocnt)) {
GBARaiseIRQ(sio->p, GBA_IRQ_SIO, cyclesLate);
}
}
void GBASIONormal32FinishTransfer(struct GBASIO* sio, uint32_t data, uint32_t cyclesLate) {
sio->siocnt = GBASIONormalClearStart(sio->siocnt);
sio->p->memory.io[GBA_REG(SIODATA32_LO)] = data;
sio->p->memory.io[GBA_REG(SIODATA32_HI)] = data >> 16;
if (GBASIONormalIsIrq(sio->siocnt)) {
GBARaiseIRQ(sio->p, GBA_IRQ_SIO, cyclesLate);
}
}
static void _sioFinish(struct mTiming* timing, void* user, uint32_t cyclesLate) {
UNUSED(timing);
struct GBASIO* sio = user;
union {
uint16_t multi[4];
uint8_t normal8;
uint32_t normal32;
} data = {0};
switch (sio->mode) {
case GBA_SIO_MULTI:
if (sio->driver && sio->driver->finishMultiplayer) {
sio->driver->finishMultiplayer(sio->driver, data.multi);
}
GBASIOMultiplayerFinishTransfer(sio, data.multi, cyclesLate);
break;
case GBA_SIO_NORMAL_8:
if (sio->driver && sio->driver->finishNormal8) {
data.normal8 = sio->driver->finishNormal8(sio->driver);
}
GBASIONormal8FinishTransfer(sio, data.normal8, cyclesLate);
break;
case GBA_SIO_NORMAL_32:
if (sio->driver && sio->driver->finishNormal32) {
data.normal32 = sio->driver->finishNormal32(sio->driver);
}
GBASIONormal32FinishTransfer(sio, data.normal32, cyclesLate);
break;
default:
// TODO
mLOG(GBA_SIO, STUB, "No dummy finish implemented for mode %s", _modeName(sio->mode));
break;
}
}
int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data) {
switch (command) {
case JOY_RESET:
sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_RESET;
if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) {
GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0);
}
// Fall through
case JOY_POLL:
data[0] = 0x00;
data[1] = 0x04;
data[2] = sio->p->p->memory.io[GBA_REG(JOYSTAT)];
mLOG(GBA_SIO, DEBUG, "JOY %s: %02X (%02X)", command == JOY_POLL ? "poll" : "reset", data[2], sio->p->p->memory.io[GBA_REG(JOYCNT)]);
return 3;
case JOY_RECV:
sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_RECV;
sio->p->p->memory.io[GBA_REG(JOYSTAT)] |= JOYSTAT_RECV;
sio->p->p->memory.io[GBA_REG(JOY_RECV_LO)] = data[0] | (data[1] << 8);
sio->p->p->memory.io[GBA_REG(JOY_RECV_HI)] = data[2] | (data[3] << 8);
data[0] = sio->p->p->memory.io[GBA_REG(JOYSTAT)];
mLOG(GBA_SIO, DEBUG, "JOY recv: %02X (%02X)", data[0], sio->p->p->memory.io[GBA_REG(JOYCNT)]);
if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) {
GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0);
}
return 1;
case JOY_TRANS:
data[0] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_LO)];
data[1] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_LO)] >> 8;
data[2] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_HI)];
data[3] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_HI)] >> 8;
data[4] = sio->p->p->memory.io[GBA_REG(JOYSTAT)];
sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_TRANS;
sio->p->p->memory.io[GBA_REG(JOYSTAT)] &= ~JOYSTAT_TRANS;
mLOG(GBA_SIO, DEBUG, "JOY trans: %02X%02X%02X%02X:%02X (%02X)", data[0], data[1], data[2], data[3], data[4], sio->p->p->memory.io[GBA_REG(JOYCNT)]);
if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) {
GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0);
}
return 5;
}
return 0;
}

View File

@ -23,18 +23,22 @@ enum {
};
static bool GBASIODolphinInit(struct GBASIODriver* driver);
static bool GBASIODolphinLoad(struct GBASIODriver* driver);
static bool GBASIODolphinUnload(struct GBASIODriver* driver);
static void GBASIODolphinReset(struct GBASIODriver* driver);
static void GBASIODolphinSetMode(struct GBASIODriver* driver, enum GBASIOMode mode);
static bool GBASIODolphinHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode);
static int GBASIODolphinConnectedDevices(struct GBASIODriver* driver);
static void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate);
static int32_t _processCommand(struct GBASIODolphin* dol, uint32_t cyclesLate);
static void _flush(struct GBASIODolphin* dol);
void GBASIODolphinCreate(struct GBASIODolphin* dol) {
GBASIOJOYCreate(&dol->d);
memset(&dol->d, 0, sizeof(dol->d));
dol->d.init = GBASIODolphinInit;
dol->d.load = GBASIODolphinLoad;
dol->d.unload = GBASIODolphinUnload;
dol->d.reset = GBASIODolphinReset;
dol->d.setMode = GBASIODolphinSetMode;
dol->d.handlesMode = GBASIODolphinHandlesMode;
dol->d.connectedDevices = GBASIODolphinConnectedDevices;
dol->event.context = dol;
dol->event.name = "GB SIO Lockstep";
dol->event.callback = GBASIODolphinProcessEvents;
@ -94,26 +98,33 @@ bool GBASIODolphinConnect(struct GBASIODolphin* dol, const struct Address* addre
static bool GBASIODolphinInit(struct GBASIODriver* driver) {
struct GBASIODolphin* dol = (struct GBASIODolphin*) driver;
dol->active = false;
dol->clockSlice = 0;
dol->state = WAIT_FOR_FIRST_CLOCK;
_flush(dol);
GBASIODolphinReset(driver);
return true;
}
static bool GBASIODolphinLoad(struct GBASIODriver* driver) {
static void GBASIODolphinReset(struct GBASIODriver* driver) {
struct GBASIODolphin* dol = (struct GBASIODolphin*) driver;
dol->active = true;
dol->active = false;
_flush(dol);
mTimingDeschedule(&dol->d.p->p->timing, &dol->event);
mTimingSchedule(&dol->d.p->p->timing, &dol->event, 0);
return true;
}
static bool GBASIODolphinUnload(struct GBASIODriver* driver) {
static void GBASIODolphinSetMode(struct GBASIODriver* driver, enum GBASIOMode mode) {
struct GBASIODolphin* dol = (struct GBASIODolphin*) driver;
dol->active = false;
return true;
dol->active = mode == GBA_SIO_JOYBUS;
}
static bool GBASIODolphinHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode) {
UNUSED(driver);
return mode == GBA_SIO_JOYBUS;
}
static int GBASIODolphinConnectedDevices(struct GBASIODriver* driver) {
UNUSED(driver);
return 1;
}
void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate) {

View File

@ -13,8 +13,11 @@
#include <mgba-util/memory.h>
static uint16_t _gbpRead(struct mKeyCallback*);
static uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
static void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate);
static uint16_t _gbpSioWriteSIOCNT(struct GBASIODriver* driver, uint16_t value);
static bool _gbpSioHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode);
static int _gbpSioConnectedDevices(struct GBASIODriver* driver);
static bool _gbpSioStart(struct GBASIODriver* driver);
static uint32_t _gbpSioFinishNormal32(struct GBASIODriver* driver);
static const uint8_t _logoPalette[] = {
0xDF, 0xFF, 0x0C, 0x64, 0x0C, 0xE4, 0x2D, 0xE4, 0x4E, 0x64, 0x4E, 0xE4, 0x6E, 0xE4, 0xAF, 0x68,
@ -43,20 +46,17 @@ void GBASIOPlayerInit(struct GBASIOPlayer* gbp) {
gbp->callback.d.readKeys = _gbpRead;
gbp->callback.d.requireOpposingDirections = true;
gbp->callback.p = gbp;
gbp->d.init = 0;
gbp->d.deinit = 0;
gbp->d.load = 0;
gbp->d.unload = 0;
gbp->d.writeRegister = _gbpSioWriteRegister;
gbp->event.context = gbp;
gbp->event.name = "GBA SIO Game Boy Player";
gbp->event.callback = _gbpSioProcessEvents;
gbp->event.priority = 0x80;
memset(&gbp->d, 0, sizeof(gbp->d));
gbp->d.writeSIOCNT = _gbpSioWriteSIOCNT;
gbp->d.handlesMode = _gbpSioHandlesMode;
gbp->d.connectedDevices = _gbpSioConnectedDevices;
gbp->d.start = _gbpSioStart;
gbp->d.finishNormal32 = _gbpSioFinishNormal32;
}
void GBASIOPlayerReset(struct GBASIOPlayer* gbp) {
if (gbp->p->sio.drivers.normal == &gbp->d) {
GBASIOSetDriver(&gbp->p->sio, NULL, GBA_SIO_NORMAL_32);
if (gbp->p->sio.driver == &gbp->d) {
GBASIOSetDriver(&gbp->p->sio, NULL);
}
}
@ -87,8 +87,9 @@ void GBASIOPlayerUpdate(struct GBA* gba) {
gba->sio.gbp.inputsPosted = 0;
gba->sio.gbp.oldCallback = gba->keyCallback;
gba->keyCallback = &gba->sio.gbp.callback.d;
// TODO: Check if the SIO driver is actually used first
GBASIOSetDriver(&gba->sio, &gba->sio.gbp.d, GBA_SIO_NORMAL_32);
if (!gba->sio.driver) {
GBASIOSetDriver(&gba->sio, &gba->sio.gbp.d);
}
}
}
@ -100,10 +101,13 @@ uint16_t _gbpRead(struct mKeyCallback* callback) {
return 0;
}
uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
uint16_t _gbpSioWriteSIOCNT(struct GBASIODriver* driver, uint16_t value) {
UNUSED(driver);
return value & 0x78FB;
}
bool _gbpSioStart(struct GBASIODriver* driver) {
struct GBASIOPlayer* gbp = (struct GBASIOPlayer*) driver;
if (address == GBA_REG_SIOCNT) {
if (value & 0x0080) {
uint32_t rx = gbp->p->memory.io[GBA_REG(SIODATA32_LO)] | (gbp->p->memory.io[GBA_REG(SIODATA32_HI)] << 16);
if (gbp->txPosition < 12 && gbp->txPosition > 0) {
// TODO: Check expected
@ -117,18 +121,21 @@ uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uin
gbp->p->lastRumble = currentTime;
}
}
mTimingDeschedule(&gbp->p->timing, &gbp->event);
mTimingSchedule(&gbp->p->timing, &gbp->event, 2048);
}
value &= 0x78FB;
}
return value;
return true;
}
void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
UNUSED(timing);
UNUSED(cyclesLate);
struct GBASIOPlayer* gbp = user;
static bool _gbpSioHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode) {
UNUSED(driver);
return mode == GBA_SIO_NORMAL_32;
}
static int _gbpSioConnectedDevices(struct GBASIODriver* driver) {
UNUSED(driver);
return 1;
}
uint32_t _gbpSioFinishNormal32(struct GBASIODriver* driver) {
struct GBASIOPlayer* gbp = (struct GBASIOPlayer*) driver;
uint32_t tx = 0;
int txPosition = gbp->txPosition;
if (txPosition > 16) {
@ -139,11 +146,5 @@ void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLat
}
tx = _gbpTxData[txPosition];
++gbp->txPosition;
gbp->p->memory.io[GBA_REG(SIODATA32_LO)] = tx;
gbp->p->memory.io[GBA_REG(SIODATA32_HI)] = tx >> 16;
if (GBASIONormalIsIrq(gbp->d.p->siocnt)) {
GBARaiseIRQ(gbp->p, GBA_IRQ_SIO, cyclesLate);
}
gbp->d.p->siocnt = GBASIONormalClearStart(gbp->d.p->siocnt);
gbp->p->memory.io[GBA_REG(SIOCNT)] = gbp->d.p->siocnt & ~0x0080;
return tx;
}

View File

@ -1,92 +0,0 @@
/* Copyright (c) 2013-2017 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/internal/gba/sio.h>
#include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/io.h>
static uint16_t GBASIOJOYWriteRegister(struct GBASIODriver* sio, uint32_t address, uint16_t value);
void GBASIOJOYCreate(struct GBASIODriver* sio) {
sio->init = NULL;
sio->deinit = NULL;
sio->load = NULL;
sio->unload = NULL;
sio->writeRegister = GBASIOJOYWriteRegister;
}
uint16_t GBASIOJOYWriteRegister(struct GBASIODriver* sio, uint32_t address, uint16_t value) {
switch (address) {
case GBA_REG_JOYCNT:
mLOG(GBA_SIO, DEBUG, "JOY write: CNT <- %04X", value);
return (value & 0x0040) | (sio->p->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040);
case GBA_REG_JOYSTAT:
mLOG(GBA_SIO, DEBUG, "JOY write: STAT <- %04X", value);
return (value & 0x0030) | (sio->p->p->memory.io[GBA_REG(JOYSTAT)] & ~0x30);
case GBA_REG_JOY_TRANS_LO:
mLOG(GBA_SIO, DEBUG, "JOY write: TRANS_LO <- %04X", value);
break;
case GBA_REG_JOY_TRANS_HI:
mLOG(GBA_SIO, DEBUG, "JOY write: TRANS_HI <- %04X", value);
break;
default:
mLOG(GBA_SIO, DEBUG, "JOY write: Unknown reg %03X <- %04X", address, value);
// Fall through
case GBA_REG_RCNT:
break;
}
return value;
}
int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data) {
switch (command) {
case JOY_RESET:
sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_RESET;
if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) {
GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0);
}
// Fall through
case JOY_POLL:
data[0] = 0x00;
data[1] = 0x04;
data[2] = sio->p->p->memory.io[GBA_REG(JOYSTAT)];
mLOG(GBA_SIO, DEBUG, "JOY %s: %02X (%02X)", command == JOY_POLL ? "poll" : "reset", data[2], sio->p->p->memory.io[GBA_REG(JOYCNT)]);
return 3;
case JOY_RECV:
sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_RECV;
sio->p->p->memory.io[GBA_REG(JOYSTAT)] |= JOYSTAT_RECV;
sio->p->p->memory.io[GBA_REG(JOY_RECV_LO)] = data[0] | (data[1] << 8);
sio->p->p->memory.io[GBA_REG(JOY_RECV_HI)] = data[2] | (data[3] << 8);
data[0] = sio->p->p->memory.io[GBA_REG(JOYSTAT)];
mLOG(GBA_SIO, DEBUG, "JOY recv: %02X (%02X)", data[0], sio->p->p->memory.io[GBA_REG(JOYCNT)]);
if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) {
GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0);
}
return 1;
case JOY_TRANS:
data[0] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_LO)];
data[1] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_LO)] >> 8;
data[2] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_HI)];
data[3] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_HI)] >> 8;
data[4] = sio->p->p->memory.io[GBA_REG(JOYSTAT)];
sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_TRANS;
sio->p->p->memory.io[GBA_REG(JOYSTAT)] &= ~JOYSTAT_TRANS;
mLOG(GBA_SIO, DEBUG, "JOY trans: %02X%02X%02X%02X:%02X (%02X)", data[0], data[1], data[2], data[3], data[4], sio->p->p->memory.io[GBA_REG(JOYCNT)]);
if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) {
GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0);
}
return 5;
}
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -52,72 +52,6 @@ class GBA(Core):
super(GBA, self)._load()
self.memory = GBAMemory(self._core, self._native.memory.romSize)
def attach_sio(self, link, mode=lib.GBA_SIO_MULTI):
self._sio.add(mode)
lib.GBASIOSetDriver(ffi.addressof(self._native.sio), link._native, mode)
def __del__(self):
for mode in self._sio:
lib.GBASIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL, mode)
create_callback("GBASIOPythonDriver", "init")
create_callback("GBASIOPythonDriver", "deinit")
create_callback("GBASIOPythonDriver", "load")
create_callback("GBASIOPythonDriver", "unload")
create_callback("GBASIOPythonDriver", "writeRegister")
class GBASIODriver(object):
def __init__(self):
super(GBASIODriver, self).__init__()
self._handle = ffi.new_handle(self)
self._native = ffi.gc(lib.GBASIOPythonDriverCreate(self._handle), lib.free)
def init(self):
return True
def deinit(self):
pass
def load(self):
return True
def unload(self):
return True
def write_register(self, address, value):
return value
class GBASIOJOYDriver(GBASIODriver):
RESET = lib.JOY_RESET
POLL = lib.JOY_POLL
TRANS = lib.JOY_TRANS
RECV = lib.JOY_RECV
def __init__(self):
super(GBASIOJOYDriver, self).__init__()
self._native = ffi.gc(lib.GBASIOJOYPythonDriverCreate(self._handle), lib.free)
def send_command(self, cmd, data):
buffer = ffi.new('uint8_t[5]')
try:
buffer[0] = data[0]
buffer[1] = data[1]
buffer[2] = data[2]
buffer[3] = data[3]
buffer[4] = data[4]
except IndexError:
pass
outlen = lib.GBASIOJOYSendCommand(self._native, cmd, buffer)
if outlen > 0 and outlen <= 5:
return bytes(buffer[0:outlen])
return None
class GBAMemory(Memory):
def __init__(self, core, romSize=lib.SIZE_CART0):

View File

@ -1,88 +0,0 @@
/* Copyright (c) 2013-2017 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/flags.h>
#define CREATE_SHIM(PLAT, NAME, RETURN) \
RETURN _py ## PLAT ## SIOPythonDriver ## NAME (void* driver); \
static RETURN _py ## PLAT ## SIOPythonDriver ## NAME ## Shim(struct PLAT ## SIODriver* driver) { \
struct PLAT ## SIODriver* py = (struct PLAT ## SIODriver*) driver; \
return _py ## PLAT ## SIOPythonDriver ## NAME(py); \
}
#define CREATE_SHIM_ARGS(PLAT, NAME, RETURN, TYPES, ...) \
RETURN _py ## PLAT ## SIOPythonDriver ## NAME TYPES; \
static RETURN _py ## PLAT ## SIOPythonDriver ## NAME ## Shim TYPES { \
struct PLAT ## SIODriver* py = (struct PLAT ## SIODriver*) driver; \
return _py ## PLAT ## SIOPythonDriver ## NAME(py, __VA_ARGS__); \
}
#ifdef M_CORE_GBA
#include <mgba/gba/interface.h>
struct GBASIOPythonDriver {
struct GBASIODriver d;
void* pyobj;
};
CREATE_SHIM(GBA, Init, bool);
CREATE_SHIM(GBA, Deinit, void);
CREATE_SHIM(GBA, Load, bool);
CREATE_SHIM(GBA, Unload, bool);
CREATE_SHIM_ARGS(GBA, WriteRegister, uint16_t, (struct GBASIODriver* driver, uint32_t address, uint16_t value), address, value);
struct GBASIODriver* GBASIOPythonDriverCreate(void* pyobj) {
struct GBASIOPythonDriver* driver = malloc(sizeof(*driver));
driver->d.init = _pyGBASIOPythonDriverInitShim;
driver->d.deinit = _pyGBASIOPythonDriverDeinitShim;
driver->d.load = _pyGBASIOPythonDriverLoadShim;
driver->d.unload = _pyGBASIOPythonDriverUnloadShim;
driver->d.writeRegister = _pyGBASIOPythonDriverWriteRegisterShim;
driver->pyobj = pyobj;
return &driver->d;
}
struct GBASIODriver* GBASIOJOYPythonDriverCreate(void* pyobj) {
struct GBASIOPythonDriver* driver = malloc(sizeof(*driver));
GBASIOJOYCreate(&driver->d);
driver->d.init = _pyGBASIOPythonDriverInitShim;
driver->d.deinit = _pyGBASIOPythonDriverDeinitShim;
driver->d.load = _pyGBASIOPythonDriverLoadShim;
driver->d.unload = _pyGBASIOPythonDriverUnloadShim;
driver->pyobj = pyobj;
return &driver->d;
}
#endif
#ifdef M_CORE_GB
#include <mgba/gb/interface.h>
struct GBSIOPythonDriver {
struct GBSIODriver d;
void* pyobj;
};
CREATE_SHIM(GB, Init, bool);
CREATE_SHIM(GB, Deinit, void);
CREATE_SHIM_ARGS(GB, WriteSB, void, (struct GBSIODriver* driver, uint8_t value), value);
CREATE_SHIM_ARGS(GB, WriteSC, uint8_t, (struct GBSIODriver* driver, uint8_t value), value);
struct GBSIODriver* GBSIOPythonDriverCreate(void* pyobj) {
struct GBSIOPythonDriver* driver = malloc(sizeof(*driver));
driver->d.init = _pyGBSIOPythonDriverInitShim;
driver->d.deinit = _pyGBSIOPythonDriverDeinitShim;
driver->d.writeSB = _pyGBSIOPythonDriverWriteSBShim;
driver->d.writeSC = _pyGBSIOPythonDriverWriteSCShim;
driver->pyobj = pyobj;
return &driver->d;
}
#endif

View File

@ -1,42 +0,0 @@
/* Copyright (c) 2013-2017 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifdef M_CORE_GBA
#include <mgba/gba/interface.h>
struct GBASIOPythonDriver {
struct GBASIODriver d;
void* pyobj;
};
struct GBASIODriver* GBASIOPythonDriverCreate(void* pyobj);
struct GBASIODriver* GBASIOJOYPythonDriverCreate(void* pyobj);
PYEXPORT bool _pyGBASIOPythonDriverInit(void* driver);
PYEXPORT void _pyGBASIOPythonDriverDeinit(void* driver);
PYEXPORT bool _pyGBASIOPythonDriverLoad(void* driver);
PYEXPORT bool _pyGBASIOPythonDriverUnload(void* driver);
PYEXPORT uint16_t _pyGBASIOPythonDriverWriteRegister(void* driver, uint32_t address, uint16_t value);
#endif
#ifdef M_CORE_GB
#include <mgba/gb/interface.h>
struct GBSIOPythonDriver {
struct GBSIODriver d;
void* pyobj;
};
struct GBSIODriver* GBSIOPythonDriverCreate(void* pyobj);
PYEXPORT bool _pyGBSIOPythonDriverInit(void* driver);
PYEXPORT void _pyGBSIOPythonDriverDeinit(void* driver);
PYEXPORT void _pyGBSIOPythonDriverWriteSB(void* driver, uint8_t value);
PYEXPORT uint8_t _pyGBSIOPythonDriverWriteSC(void* driver, uint8_t value);
#endif

View File

@ -423,8 +423,8 @@ bool CoreController::attachDolphin(const Address& address) {
return false;
}
if (GBASIODolphinConnect(&m_dolphin, &address, 0, 0)) {
GBA* gba = static_cast<GBA*>(m_threadContext.core->board);
GBASIOSetDriver(&gba->sio, &m_dolphin.d, GBA_SIO_JOYBUS);
clearMultiplayerController();
m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_LINK_PORT, &m_dolphin.d);
return true;
}
return false;
@ -432,8 +432,8 @@ bool CoreController::attachDolphin(const Address& address) {
void CoreController::detachDolphin() {
if (platform() == mPLATFORM_GBA) {
GBA* gba = static_cast<GBA*>(m_threadContext.core->board);
GBASIOSetDriver(&gba->sio, nullptr, GBA_SIO_JOYBUS);
// TODO: Reattach to multiplayer controller
m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_LINK_PORT, NULL);
}
GBASIODolphinDestroy(&m_dolphin);
}
@ -1094,7 +1094,7 @@ void CoreController::attachBattleChipGate() {
Interrupter interrupter(this);
clearMultiplayerController();
GBASIOBattlechipGateCreate(&m_battlechip);
m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_BATTLECHIP_GATE, &m_battlechip);
m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_LINK_PORT, &m_battlechip);
}
void CoreController::detachBattleChipGate() {
@ -1102,7 +1102,7 @@ void CoreController::detachBattleChipGate() {
return;
}
Interrupter interrupter(this);
m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_BATTLECHIP_GATE, nullptr);
m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_LINK_PORT, nullptr);
}
void CoreController::setBattleChipId(uint16_t id) {

View File

@ -7,6 +7,7 @@
#include "CoreController.h"
#include "LogController.h"
#include "utils.h"
#ifdef M_CORE_GBA
#include <mgba/internal/gba/gba.h>
@ -27,8 +28,14 @@ MultiplayerController::Player::Player(CoreController* coreController)
int MultiplayerController::Player::id() const {
switch (controller->platform()) {
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
return node.gba->id;
case mPLATFORM_GBA: {
int id = node.gba->d.deviceId(&node.gba->d);
if (id >= 0) {
return id;
} else {
return preferredId;
}
}
#endif
#ifdef M_CORE_GB
case mPLATFORM_GB:
@ -89,25 +96,7 @@ MultiplayerController::MultiplayerController() {
switch (player->controller->platform()) {
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
if (!id) {
for (int i = 1; i < controller->m_players.count(); ++i) {
player = controller->player(i);
if (player->node.gba->d.p->mode > GBA_SIO_MULTI) {
player->controller->setSync(true);
continue;
}
player->controller->setSync(false);
player->cyclesPosted += cycles;
if (player->awake < 1) {
player->node.gba->nextEvent += player->cyclesPosted;
}
mCoreThreadStopWaiting(player->controller->thread());
player->awake = 1;
}
} else {
player->controller->setSync(true);
player->cyclesPosted += cycles;
}
abort();
break;
#endif
#ifdef M_CORE_GB
@ -169,7 +158,6 @@ MultiplayerController::MultiplayerController() {
switch (player->controller->platform()) {
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
player->cyclesPosted += reinterpret_cast<GBASIOLockstep*>(lockstep)->players[0]->eventDiff;
break;
#endif
#ifdef M_CORE_GB
@ -184,7 +172,6 @@ MultiplayerController::MultiplayerController() {
switch (player->controller->platform()) {
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
player->node.gba->nextEvent += player->cyclesPosted;
break;
#endif
#ifdef M_CORE_GB
@ -214,11 +201,12 @@ bool MultiplayerController::attachGame(CoreController* controller) {
interrupters.append(p.controller);
}
if (m_lockstep.attached == 0) {
bool doDelayedAttach = false;
if (m_platform == mPLATFORM_NONE) {
switch (controller->platform()) {
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
GBASIOLockstepInit(&m_gbaLockstep);
GBASIOLockstepCoordinatorInit(&m_gbaCoordinator);
break;
#endif
#ifdef M_CORE_GB
@ -240,28 +228,50 @@ bool MultiplayerController::attachGame(CoreController* controller) {
}
Player player{controller};
for (int i = 0; i < MAX_GBAS; ++i) {
if (m_claimedIds & (1 << i)) {
continue;
}
player.preferredId = i;
m_claimedIds |= 1 << i;
break;
}
switch (controller->platform()) {
#ifdef M_CORE_GBA
case mPLATFORM_GBA: {
if (m_lockstep.attached >= MAX_GBAS) {
if (attached() >= MAX_GBAS) {
return false;
}
GBA* gba = static_cast<GBA*>(thread->core->board);
GBASIOLockstepDriver* node = new GBASIOLockstepDriver;
LockstepUser* user = new LockstepUser;
mLockstepThreadUserInit(user, thread);
user->controller = this;
user->pid = m_nextPid;
user->d.requestedId = [](mLockstepUser* ctx) {
mLockstepThreadUser* tctx = reinterpret_cast<mLockstepThreadUser*>(ctx);
LockstepUser* user = static_cast<LockstepUser*>(tctx);
MultiplayerController* controller = user->controller;
const auto iter = controller->m_pids.find(user->pid);
if (iter == controller->m_pids.end()) {
return -1;
}
const Player& p = iter.value();
return p.preferredId;
};
GBASIOLockstepNode* node = new GBASIOLockstepNode;
GBASIOLockstepNodeCreate(node);
GBASIOLockstepAttachNode(&m_gbaLockstep, node);
GBASIOLockstepDriverCreate(node, &user->d);
player.node.gba = node;
GBASIOSetDriver(&gba->sio, &node->d, GBA_SIO_MULTI);
GBASIOSetDriver(&gba->sio, &node->d, GBA_SIO_NORMAL_32);
if (m_pids.size()) {
doDelayedAttach = true;
}
break;
}
#endif
#ifdef M_CORE_GB
case mPLATFORM_GB: {
if (m_lockstep.attached >= 2) {
if (attached() >= 2) {
return false;
}
@ -271,6 +281,7 @@ bool MultiplayerController::attachGame(CoreController* controller) {
GBSIOLockstepNodeCreate(node);
GBSIOLockstepAttachNode(&m_gbLockstep, node);
player.node.gb = node;
player.attached = true;
GBSIOSetDriver(&gb->sio, &node->d);
break;
@ -281,7 +292,7 @@ bool MultiplayerController::attachGame(CoreController* controller) {
}
QPair<QString, QString> path(controller->path(), controller->baseDirectory());
int claimed = m_claimed[path];
int claimed = m_claimedSaves[path];
int saveId = 0;
mCoreConfigGetIntValue(&controller->thread()->core->config, "savePlayerId", &saveId);
@ -304,12 +315,25 @@ bool MultiplayerController::attachGame(CoreController* controller) {
} else {
player.saveId = 1;
}
m_claimed[path] |= 1 << (player.saveId - 1);
m_claimedSaves[path] |= 1 << (player.saveId - 1);
m_pids.insert(m_nextPid, player);
++m_nextPid;
fixOrder();
if (doDelayedAttach) {
for (auto pid: m_players) {
Player& player = m_pids.find(pid).value();
if (player.attached) {
continue;
}
struct mCore* core = player.controller->thread()->core;
GBASIOLockstepCoordinatorAttach(&m_gbaCoordinator, player.node.gba);
core->setPeripheral(core, mPERIPH_GBA_LINK_PORT, &player.node.gba->d);
player.attached = true;
}
}
emit gameAttached();
return true;
}
@ -328,8 +352,7 @@ void MultiplayerController::detachGame(CoreController* controller) {
for (int i = 0; i < m_players.count(); ++i) {
Player* p = player(i);
if (!p) {
LOG(QT, ERROR) << tr("Trying to detach a multiplayer player that's not attached");
return;
continue;
}
CoreController* playerController = p->controller;
if (playerController == controller) {
@ -337,17 +360,24 @@ void MultiplayerController::detachGame(CoreController* controller) {
}
interrupters.append(playerController);
}
if (pid < 0) {
LOG(QT, WARN) << tr("Trying to detach a multiplayer player that's not attached");
return;
}
switch (controller->platform()) {
#ifdef M_CORE_GBA
case mPLATFORM_GBA: {
GBA* gba = static_cast<GBA*>(thread->core->board);
GBASIOLockstepNode* node = reinterpret_cast<GBASIOLockstepNode*>(gba->sio.drivers.multiplayer);
GBASIOSetDriver(&gba->sio, nullptr, GBA_SIO_MULTI);
GBASIOSetDriver(&gba->sio, nullptr, GBA_SIO_NORMAL_32);
if (node) {
GBASIOLockstepDetachNode(&m_gbaLockstep, node);
delete node;
Player& p = m_pids.find(pid).value();
GBASIODriver* node = gba->sio.driver;
if (node == &p.node.gba->d) {
thread->core->setPeripheral(thread->core, mPERIPH_GBA_LINK_PORT, NULL);
}
if (p.attached) {
GBASIOLockstepCoordinatorDetach(&m_gbaCoordinator, p.node.gba);
}
delete p.node.gba->user;
delete p.node.gba;
break;
}
#endif
@ -371,14 +401,20 @@ void MultiplayerController::detachGame(CoreController* controller) {
QPair<QString, QString> path(controller->path(), controller->baseDirectory());
Player& p = m_pids.find(pid).value();
if (!p.saveId) {
LOG(QT, ERROR) << "Clearing invalid save ID";
LOG(QT, WARN) << "Clearing invalid save ID";
} else {
m_claimed[path] &= ~(1 << (p.saveId - 1));
if (!m_claimed[path]) {
m_claimed.remove(path);
m_claimedSaves[path] &= ~(1 << (p.saveId - 1));
if (!m_claimedSaves[path]) {
m_claimedSaves.remove(path);
}
}
if (p.preferredId < 0) {
LOG(QT, WARN) << "Clearing invalid preferred ID";
} else {
m_claimedIds &= ~(1 << p.preferredId);
}
m_pids.remove(pid);
if (m_pids.size() == 0) {
m_platform = mPLATFORM_NONE;
@ -417,8 +453,17 @@ int MultiplayerController::saveId(CoreController* controller) const {
}
int MultiplayerController::attached() {
int num;
int num = 0;
switch (m_platform) {
case mPLATFORM_GB:
num = m_lockstep.attached;
break;
case mPLATFORM_GBA:
num = saturateCast<int>(GBASIOLockstepCoordinatorAttached(&m_gbaCoordinator));
break;
default:
break;
}
return num;
}
@ -453,12 +498,13 @@ void MultiplayerController::fixOrder() {
switch (m_platform) {
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
for (int pid : m_pids.keys()) {
// TODO: fix
/*for (int pid : m_pids.keys()) {
Player& p = m_pids.find(pid).value();
GBA* gba = static_cast<GBA*>(p.controller->thread()->core->board);
GBASIOLockstepNode* node = reinterpret_cast<GBASIOLockstepNode*>(gba->sio.drivers.multiplayer);
m_players[node->id] = pid;
}
GBASIOLockstepDriver* node = reinterpret_cast<GBASIOLockstepDriver*>(gba->sio.driver);
m_players[node->d.deviceId(&node->d)] = pid;
}*/
break;
#endif
#ifdef M_CORE_GB

View File

@ -49,7 +49,7 @@ signals:
private:
union Node {
GBSIOLockstepNode* gb;
GBASIOLockstepNode* gba;
GBASIOLockstepDriver* gba;
};
struct Player {
Player(CoreController* controller);
@ -63,6 +63,12 @@ private:
int32_t cyclesPosted = 0;
unsigned waitMask = 0;
int saveId = 1;
int preferredId = 0;
bool attached = false;
};
struct LockstepUser : mLockstepThreadUser {
MultiplayerController* controller;
int pid;
};
Player* player(int id);
@ -73,18 +79,20 @@ private:
mLockstep m_lockstep;
#ifdef M_CORE_GB
GBSIOLockstep m_gbLockstep;
#endif
#ifdef M_CORE_GBA
GBASIOLockstep m_gbaLockstep;
#endif
};
#ifdef M_CORE_GBA
GBASIOLockstepCoordinator m_gbaCoordinator;
#endif
mPlatform m_platform = mPLATFORM_NONE;
int m_nextPid = 0;
int m_claimedIds = 0;
QHash<int, Player> m_pids;
QList<int> m_players;
QMutex m_lock;
QHash<QPair<QString, QString>, int> m_claimed;
QHash<QPair<QString, QString>, int> m_claimedSaves;
};
}