all repos — mgba @ ceea51b55ea2f112c49b4ad22e6d70b08ad430e7

mGBA Game Boy Advance Emulator

GB: First pass at lockstep multiplayer
Jeffrey Pfau jeffrey@endrift.com
Sun, 25 Dec 2016 20:39:11 -0800
commit

ceea51b55ea2f112c49b4ad22e6d70b08ad430e7

parent

4ac4733cfd4c4524c6a56d0406ba138d173fad61

M CMakeLists.txtCMakeLists.txt

@@ -51,7 +51,8 @@ file(GLOB UTIL_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/*.[cSs])

file(GLOB UTIL_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/test/*.c) file(GLOB GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/gui/*.c ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/gui/*.c) file(GLOB GBA_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/renderers/*.c) -file(GLOB SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/sio/lockstep.c) +file(GLOB GBA_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/sio/lockstep.c) +file(GLOB GB_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/sio/lockstep.c) file(GLOB GB_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/renderers/*.c) file(GLOB THIRD_PARTY_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/inih/*.c) set(CLI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/commandline.c)

@@ -59,9 +60,9 @@ set(CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-mem.c)

set(VFS_SRC) source_group("ARM core" FILES ${ARM_SRC}) source_group("LR35902 core" FILES ${LR35902_SRC}) -source_group("GBA board" FILES ${GBA_SRC} ${GBA_RENDERER_SRC} ${SIO_SRC}) +source_group("GBA board" FILES ${GBA_SRC} ${GBA_RENDERER_SRC} ${GBA_SIO_SRC}) source_group("GBA extra" FILES ${GBA_CHEATS_SRC} ${GBA_RR_SRC}) -source_group("GB board" FILES ${GB_SRC}) +source_group("GB board" FILES ${GB_SRC} ${GB_SIO_SRC}) source_group("Utilities" FILES ${UTIL_SRC}) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/src)

@@ -616,7 +617,11 @@ if(M_CORE_GBA)

list(APPEND SRC ${GBA_RR_SRC} ${GBA_EXTRA_SRC} - ${SIO_SRC}) + ${GBA_SIO_SRC}) + endif() + if(M_CORE_GB) + list(APPEND SRC + ${GB_SIO_SRC}) endif() list(APPEND SRC ${FEATURE_SRC}
A src/core/lockstep.c

@@ -0,0 +1,16 @@

+/* Copyright (c) 2013-2016 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 "lockstep.h" + +void mLockstepInit(struct mLockstep* lockstep) { + lockstep->attached = 0; + lockstep->transferActive = 0; +#ifndef NDEBUG + lockstep->transferId = 0; +#endif +} + +// TODO: Migrate nodes
A src/core/lockstep.h

@@ -0,0 +1,37 @@

+/* Copyright (c) 2013-2016 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/. */ +#ifndef SIO_LOCKSTEP_H +#define SIO_LOCKSTEP_H + +#include "util/common.h" + +enum mLockstepPhase { + TRANSFER_IDLE = 0, + TRANSFER_STARTING, + TRANSFER_STARTED, + TRANSFER_FINISHING, + TRANSFER_FINISHED +}; + +struct mLockstep { + int attached; + enum mLockstepPhase transferActive; + int32_t transferCycles; + + bool (*signal)(struct mLockstep*, unsigned mask); + bool (*wait)(struct mLockstep*, unsigned mask); + void (*addCycles)(struct mLockstep*, int id, int32_t cycles); + int32_t (*useCycles)(struct mLockstep*, int id, int32_t cycles); + void (*unload)(struct mLockstep*, int id); + void* context; +#ifndef NDEBUG + int transferId; +#endif +}; + +void mLockstepInit(struct mLockstep*); + +#endif
M src/gb/gb.csrc/gb/gb.c

@@ -433,6 +433,7 @@ cpu->pc = 0x100;

} gb->cpuBlocked = false; + gb->earlyExit = false; gb->doubleSpeed = 0; cpu->memory.setActiveRegion(cpu, cpu->pc);

@@ -548,6 +549,10 @@ nextEvent = mTimingTick(&gb->timing, nextEvent);

} while (gb->cpuBlocked); cpu->nextEvent = nextEvent; + if (gb->earlyExit) { + gb->earlyExit = false; + break; + } if (cpu->halted) { cpu->cycles = cpu->nextEvent; if (!gb->memory.ie || !gb->memory.ime) {
M src/gb/gb.hsrc/gb/gb.h

@@ -79,6 +79,7 @@ struct mCoreCallbacks* coreCallbacks;

struct mAVStream* stream; bool cpuBlocked; + bool earlyExit; struct mTimingEvent eiPending; unsigned doubleSpeed; };
M src/gb/io.csrc/gb/io.c

@@ -158,6 +158,9 @@ }

void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { switch (address) { + case REG_SB: + GBSIOWriteSB(&gb->sio, value); + break; case REG_SC: GBSIOWriteSC(&gb->sio, value); break;

@@ -338,7 +341,6 @@ gb->audio.ch3.wavedata8[gb->audio.ch3.window >> 1] = value;

} break; case REG_JOYP: - case REG_SB: case REG_TIMA: case REG_TMA: // Handled transparently by the registers
M src/gb/sio.csrc/gb/sio.c

@@ -9,6 +9,13 @@ #include "gb/gb.h"

#include "gb/io.h" #include "gb/serialize.h" +mLOG_DEFINE_CATEGORY(GB_SIO, "GB Serial I/O"); + +const int GBSIOCyclesPerTransfer[2] = { + 512, + 16 +}; + void _GBSIOProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate); void GBSIOInit(struct GBSIO* sio) {

@@ -17,6 +24,8 @@ sio->event.context = sio;

sio->event.name = "GB SIO"; sio->event.callback = _GBSIOProcessEvents; sio->event.priority = 0x30; + + sio->driver = NULL; } void GBSIOReset(struct GBSIO* sio) {

@@ -29,6 +38,26 @@ UNUSED(sio);

// Nothing to do yet } +void GBSIOSetDriver(struct GBSIO* sio, struct GBSIODriver* driver) { + if (sio->driver) { + if (sio->driver->deinit) { + sio->driver->deinit(sio->driver); + } + } + if (driver) { + driver->p = sio; + + if (driver->init) { + if (!driver->init(driver)) { + driver->deinit(driver); + mLOG(GB_SIO, ERROR, "Could not initialize SIO driver"); + return; + } + } + } + sio->driver = driver; +} + void _GBSIOProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate) { UNUSED(cyclesLate); struct GBSIO* sio = context;

@@ -38,19 +67,29 @@ sio->p->memory.io[REG_SB] |= sio->pendingSB & ~(8 >> sio->remainingBits);

if (!sio->remainingBits) { sio->p->memory.io[REG_IF] |= (1 << GB_IRQ_SIO); sio->p->memory.io[REG_SC] = GBRegisterSCClearEnable(sio->p->memory.io[REG_SC]); + sio->pendingSB = 0xFF; GBUpdateIRQs(sio->p); } else { mTimingSchedule(timing, &sio->event, sio->period); } } +void GBSIOWriteSB(struct GBSIO* sio, uint8_t sb) { + if (!sio->driver) { + return; + } + sio->driver->writeSB(sio->driver, sb); +} + void GBSIOWriteSC(struct GBSIO* sio, uint8_t sc) { - sio->period = 0x1000; // TODO Shift Clock + sio->period = GBSIOCyclesPerTransfer[GBRegisterSCGetClockSpeed(sc)]; // TODO Shift Clock if (GBRegisterSCIsEnable(sc)) { - if (GBRegisterSCIsShiftClock(sc)) { - mTimingSchedule(&sio->p->timing, &sio->event, sio->period); - } + mTimingDeschedule(&sio->p->timing, &sio->event); + mTimingSchedule(&sio->p->timing, &sio->event, sio->period); sio->remainingBits = 8; + } + if (sio->driver) { + sio->driver->writeSC(sio->driver, sc); } }
M src/gb/sio.hsrc/gb/sio.h

@@ -8,13 +8,22 @@ #define GB_SIO_H

#include "util/common.h" +#include "core/log.h" #include "core/timing.h" +#define MAX_GBS 2 + +extern const int GBSIOCyclesPerTransfer[2]; + +mLOG_DECLARE_CATEGORY(GB_SIO); + struct GB; +struct GBSIODriver; struct GBSIO { struct GB* p; struct mTimingEvent event; + struct GBSIODriver* driver; int32_t nextEvent; int32_t period;

@@ -23,6 +32,15 @@

uint8_t pendingSB; }; +struct GBSIODriver { + struct GBSIO* p; + + bool (*init)(struct GBSIODriver* driver); + void (*deinit)(struct GBSIODriver* driver); + void (*writeSB)(struct GBSIODriver* driver, uint8_t value); + uint8_t (*writeSC)(struct GBSIODriver* driver, uint8_t value); +}; + DECL_BITFIELD(GBRegisterSC, uint8_t); DECL_BIT(GBRegisterSC, ShiftClock, 0); DECL_BIT(GBRegisterSC, ClockSpeed, 1);

@@ -31,6 +49,8 @@

void GBSIOInit(struct GBSIO* sio); void GBSIOReset(struct GBSIO* sio); void GBSIODeinit(struct GBSIO* sio); +void GBSIOSetDriver(struct GBSIO* sio, struct GBSIODriver* driver); void GBSIOWriteSC(struct GBSIO* sio, uint8_t sc); +void GBSIOWriteSB(struct GBSIO* sio, uint8_t sb); #endif
A src/gb/sio/lockstep.c

@@ -0,0 +1,231 @@

+/* Copyright (c) 2013-2016 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 "lockstep.h" + +#include "gb/gb.h" +#include "gb/io.h" + +#define LOCKSTEP_INCREMENT 500 + +static bool GBSIOLockstepNodeInit(struct GBSIODriver* driver); +static void GBSIOLockstepNodeDeinit(struct GBSIODriver* driver); +static void GBSIOLockstepNodeWriteSB(struct GBSIODriver* driver, uint8_t value); +static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t value); +static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* driver, uint32_t cyclesLate); + +void GBSIOLockstepInit(struct GBSIOLockstep* lockstep) { + mLockstepInit(&lockstep->d); + lockstep->players[0] = NULL; + lockstep->players[1] = NULL; + lockstep->pendingSB[0] = 0xFF; + lockstep->pendingSB[1] = 0xFF; +} + +void GBSIOLockstepNodeCreate(struct GBSIOLockstepNode* node) { + node->d.init = GBSIOLockstepNodeInit; + node->d.deinit = GBSIOLockstepNodeDeinit; + node->d.writeSB = GBSIOLockstepNodeWriteSB; + node->d.writeSC = GBSIOLockstepNodeWriteSC; +} + +bool GBSIOLockstepAttachNode(struct GBSIOLockstep* lockstep, struct GBSIOLockstepNode* node) { + if (lockstep->d.attached == MAX_GBS) { + return false; + } + lockstep->players[lockstep->d.attached] = node; + node->p = lockstep; + node->id = lockstep->d.attached; + ++lockstep->d.attached; + return true; +} + +void GBSIOLockstepDetachNode(struct GBSIOLockstep* lockstep, struct GBSIOLockstepNode* node) { + if (lockstep->d.attached == 0) { + return; + } + int i; + for (i = 0; i < lockstep->d.attached; ++i) { + if (lockstep->players[i] != node) { + continue; + } + for (++i; i < lockstep->d.attached; ++i) { + lockstep->players[i - 1] = lockstep->players[i]; + lockstep->players[i - 1]->id = i - 1; + } + --lockstep->d.attached; + break; + } +} + +bool GBSIOLockstepNodeInit(struct GBSIODriver* driver) { + struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver; + mLOG(GB_SIO, DEBUG, "Lockstep %i: Node init", node->id); + node->event.context = node; + node->event.name = "GB SIO Lockstep"; + node->event.callback = _GBSIOLockstepNodeProcessEvents; + node->event.priority = 0x80; + + node->nextEvent = 0; + node->eventDiff = 0; + mTimingSchedule(&driver->p->p->timing, &node->event, 0); +#ifndef NDEBUG + node->phase = node->p->d.transferActive; + node->transferId = node->p->d.transferId; +#endif + return true; +} + +void GBSIOLockstepNodeDeinit(struct GBSIODriver* driver) { + struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver; + node->p->d.unload(&node->p->d, node->id); + mTimingDeschedule(&driver->p->p->timing, &node->event); +} + +static void _finishTransfer(struct GBSIOLockstepNode* node) { + if (node->transferFinished) { + return; + } + struct GBSIO* sio = node->d.p; + sio->pendingSB = node->p->pendingSB[!node->id]; + node->transferFinished = true; +#ifndef NDEBUG + ++node->transferId; +#endif +} + + +static int32_t _masterUpdate(struct GBSIOLockstepNode* node) { + bool needsToWait = false; + int i; + switch (node->p->d.transferActive) { + case TRANSFER_IDLE: + // If the master hasn't initiated a transfer, it can keep going. + node->nextEvent += LOCKSTEP_INCREMENT; + break; + case TRANSFER_STARTING: + // Start the transfer, but wait for the other GBs to catch up + node->transferFinished = false; + needsToWait = true; + ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTED); + node->nextEvent += 128; + break; + case TRANSFER_STARTED: + // All the other GBs have caught up and are sleeping, we can all continue now + node->nextEvent += 128; + ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHING); + break; + case TRANSFER_FINISHING: + // Finish the transfer + // We need to make sure the other GBs catch up so they don't get behind + node->nextEvent += LOCKSTEP_INCREMENT - 256; // Split the cycles to avoid waiting too long +#ifndef NDEBUG + ATOMIC_ADD(node->p->d.transferId, 1); +#endif + needsToWait = true; + ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHED); + break; + case TRANSFER_FINISHED: + // Everything's settled. We're done. + _finishTransfer(node); + node->nextEvent += LOCKSTEP_INCREMENT; + ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE); + break; + } + int mask = 0; + for (i = 1; i < node->p->d.attached; ++i) { + mask |= 1 << i; + } + if (mask) { + if (needsToWait) { + if (!node->p->d.wait(&node->p->d, mask)) { + abort(); + } + } else { + node->p->d.signal(&node->p->d, mask); + } + } + // Tell the other GBs they can continue up to where we were + node->p->d.addCycles(&node->p->d, 0, node->eventDiff); +#ifndef NDEBUG + node->phase = node->p->d.transferActive; +#endif + if (needsToWait) { + return 0; + } + return node->nextEvent; +} + +static uint32_t _slaveUpdate(struct GBSIOLockstepNode* node) { + bool signal = false; + switch (node->p->d.transferActive) { + case TRANSFER_IDLE: + node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT); + break; + case TRANSFER_STARTING: + case TRANSFER_FINISHING: + break; + case TRANSFER_STARTED: + node->transferFinished = false; + signal = true; + break; + case TRANSFER_FINISHED: + _finishTransfer(node); + signal = true; + break; + } +#ifndef NDEBUG + node->phase = node->p->d.transferActive; +#endif + if (signal) { + node->p->d.signal(&node->p->d, 1 << node->id); + } + return 0; +} + +static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) { + struct GBSIOLockstepNode* node = user; + if (node->p->d.attached < 2) { + return; + } + int32_t cycles = 0; + node->nextEvent -= cyclesLate; + if (node->nextEvent <= 0) { + if (!node->id) { + cycles = _masterUpdate(node); + } else { + cycles = _slaveUpdate(node); + cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff); + } + node->eventDiff = 0; + } else { + cycles = node->nextEvent; + } + if (cycles > 0) { + node->nextEvent = 0; + node->eventDiff += cycles; + mTimingDeschedule(timing, &node->event); + mTimingSchedule(timing, &node->event, cycles); + } else { + node->d.p->p->earlyExit = true; + mTimingSchedule(timing, &node->event, cyclesLate + 1); + } +} + +static void GBSIOLockstepNodeWriteSB(struct GBSIODriver* driver, uint8_t value) { + struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver; + node->p->pendingSB[node->id] = value; +} + +static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t value) { + struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver; + if (!node->id && (value & 0x81) == 0x81) { + node->p->d.transferActive = TRANSFER_STARTING; + node->p->d.transferCycles = GBSIOCyclesPerTransfer[(value >> 1) & 1]; + mTimingDeschedule(&driver->p->p->timing, &node->event); + mTimingSchedule(&driver->p->p->timing, &node->event, 0); + } + return value; +}
A src/gb/sio/lockstep.h

@@ -0,0 +1,44 @@

+/* Copyright (c) 2013-2016 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/. */ +#ifndef GB_SIO_LOCKSTEP_H +#define GB_SIO_LOCKSTEP_H + +#include "util/common.h" + +#include "core/lockstep.h" +#include "core/timing.h" +#include "gb/sio.h" + +struct GBSIOLockstep { + struct mLockstep d; + struct GBSIOLockstepNode* players[MAX_GBS]; + + uint8_t pendingSB[MAX_GBS]; +}; + +struct GBSIOLockstepNode { + struct GBSIODriver d; + struct GBSIOLockstep* p; + struct mTimingEvent event; + + volatile int32_t nextEvent; + int32_t eventDiff; + int id; + bool transferFinished; +#ifndef NDEBUG + int transferId; + enum mLockstepPhase phase; +#endif +}; + +void GBSIOLockstepInit(struct GBSIOLockstep*); + +void GBSIOLockstepNodeCreate(struct GBSIOLockstepNode*); + +bool GBSIOLockstepAttachNode(struct GBSIOLockstep*, struct GBSIOLockstepNode*); +void GBSIOLockstepDetachNode(struct GBSIOLockstep*, struct GBSIOLockstepNode*); + +#endif
M src/gba/sio/lockstep.csrc/gba/sio/lockstep.c

@@ -20,6 +20,7 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);

static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* driver, uint32_t cyclesLate); void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) { + mLockstepInit(&lockstep->d); lockstep->players[0] = 0; lockstep->players[1] = 0; lockstep->players[2] = 0;

@@ -28,16 +29,7 @@ lockstep->multiRecv[0] = 0xFFFF;

lockstep->multiRecv[1] = 0xFFFF; lockstep->multiRecv[2] = 0xFFFF; lockstep->multiRecv[3] = 0xFFFF; - lockstep->attached = 0; lockstep->attachedMulti = 0; - lockstep->transferActive = 0; -#ifndef NDEBUG - lockstep->transferId = 0; -#endif -} - -void GBASIOLockstepDeinit(struct GBASIOLockstep* lockstep) { - UNUSED(lockstep); } void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) {

@@ -49,30 +41,30 @@ node->d.writeRegister = 0;

} bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) { - if (lockstep->attached == MAX_GBAS) { + if (lockstep->d.attached == MAX_GBAS) { return false; } - lockstep->players[lockstep->attached] = node; + lockstep->players[lockstep->d.attached] = node; node->p = lockstep; - node->id = lockstep->attached; - ++lockstep->attached; + node->id = lockstep->d.attached; + ++lockstep->d.attached; return true; } void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) { - if (lockstep->attached == 0) { + if (lockstep->d.attached == 0) { return; } int i; - for (i = 0; i < lockstep->attached; ++i) { + for (i = 0; i < lockstep->d.attached; ++i) { if (lockstep->players[i] != node) { continue; } - for (++i; i < lockstep->attached; ++i) { + for (++i; i < lockstep->d.attached; ++i) { lockstep->players[i - 1] = lockstep->players[i]; lockstep->players[i - 1]->id = i - 1; } - --lockstep->attached; + --lockstep->d.attached; break; } }

@@ -103,7 +95,7 @@ case SIO_MULTI:

node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister; node->d.p->rcnt |= 3; ++node->p->attachedMulti; - node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached; + node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->d.attached; if (node->id) { node->d.p->rcnt |= 4; node->d.p->multiplayerControl.slave = 1;

@@ -116,8 +108,8 @@ default:

break; } #ifndef NDEBUG - node->phase = node->p->transferActive; - node->transferId = node->p->transferId; + node->phase = node->p->d.transferActive; + node->transferId = node->p->d.transferId; #endif return true; }

@@ -132,7 +124,7 @@ break;

default: break; } - node->p->unload(node->p, node->id); + node->p->d.unload(&node->p->d, node->id); mTimingDeschedule(&driver->p->p->timing, &node->event); return true; }

@@ -141,11 +133,11 @@ static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {

struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; if (address == REG_SIOCNT) { mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value); - if (value & 0x0080 && node->p->transferActive == TRANSFER_IDLE) { + if (value & 0x0080 && node->p->d.transferActive == TRANSFER_IDLE) { if (!node->id && node->d.p->multiplayerControl.ready) { mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id); - node->p->transferActive = TRANSFER_STARTING; - node->p->transferCycles = GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->attached - 1]; + node->p->d.transferActive = TRANSFER_STARTING; + node->p->d.transferCycles = GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->d.attached - 1]; mTimingDeschedule(&driver->p->p->timing, &node->event); mTimingSchedule(&driver->p->p->timing, &node->event, 0); } else {

@@ -218,11 +210,11 @@

static int32_t _masterUpdate(struct GBASIOLockstepNode* node) { bool needsToWait = false; int i; - switch (node->p->transferActive) { + switch (node->p->d.transferActive) { case TRANSFER_IDLE: // If the master hasn't initiated a transfer, it can keep going. node->nextEvent += LOCKSTEP_INCREMENT; - node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached; + node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->d.attached; break; case TRANSFER_STARTING: // Start the transfer, but wait for the other GBAs to catch up

@@ -232,51 +224,51 @@ node->p->multiRecv[1] = 0xFFFF;

node->p->multiRecv[2] = 0xFFFF; node->p->multiRecv[3] = 0xFFFF; needsToWait = true; - ATOMIC_STORE(node->p->transferActive, TRANSFER_STARTED); + ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTED); node->nextEvent += 512; break; case TRANSFER_STARTED: // All the other GBAs have caught up and are sleeping, we can all continue now node->p->multiRecv[0] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1]; node->nextEvent += 512; - ATOMIC_STORE(node->p->transferActive, TRANSFER_FINISHING); + ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHING); break; case TRANSFER_FINISHING: // Finish the transfer // We need to make sure the other GBAs catch up so they don't get behind - node->nextEvent += node->p->transferCycles - 1024; // Split the cycles to avoid waiting too long + node->nextEvent += node->p->d.transferCycles - 1024; // Split the cycles to avoid waiting too long #ifndef NDEBUG - ATOMIC_ADD(node->p->transferId, 1); + ATOMIC_ADD(node->p->d.transferId, 1); #endif needsToWait = true; - ATOMIC_STORE(node->p->transferActive, TRANSFER_FINISHED); + ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHED); break; case TRANSFER_FINISHED: // Everything's settled. We're done. _finishTransfer(node); node->nextEvent += LOCKSTEP_INCREMENT; - ATOMIC_STORE(node->p->transferActive, TRANSFER_IDLE); + ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE); break; } int mask = 0; - for (i = 1; i < node->p->attached; ++i) { + for (i = 1; i < node->p->d.attached; ++i) { if (node->p->players[i]->mode == node->mode) { mask |= 1 << i; } } if (mask) { if (needsToWait) { - if (!node->p->wait(node->p, mask)) { + if (!node->p->d.wait(&node->p->d, mask)) { abort(); } } else { - node->p->signal(node->p, mask); + node->p->d.signal(&node->p->d, mask); } } // Tell the other GBAs they can continue up to where we were - node->p->addCycles(node->p, 0, node->eventDiff); + node->p->d.addCycles(&node->p->d, 0, node->eventDiff); #ifndef NDEBUG - node->phase = node->p->transferActive; + node->phase = node->p->d.transferActive; #endif if (needsToWait) { return 0;

@@ -285,12 +277,12 @@ return node->nextEvent;

} static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) { - node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached; + node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->d.attached; bool signal = false; - switch (node->p->transferActive) { + switch (node->p->d.transferActive) { case TRANSFER_IDLE: if (!node->d.p->multiplayerControl.ready) { - node->p->addCycles(node->p, node->id, LOCKSTEP_INCREMENT); + node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT); } break; case TRANSFER_STARTING:

@@ -329,17 +321,17 @@ signal = true;

break; } #ifndef NDEBUG - node->phase = node->p->transferActive; + node->phase = node->p->d.transferActive; #endif if (signal) { - node->p->signal(node->p, 1 << node->id); + node->p->d.signal(&node->p->d, 1 << node->id); } return 0; } static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) { struct GBASIOLockstepNode* node = user; - if (node->p->attached < 2) { + if (node->p->d.attached < 2) { return; } int32_t cycles = 0;

@@ -349,7 +341,7 @@ if (!node->id) {

cycles = _masterUpdate(node); } else { cycles = _slaveUpdate(node); - cycles += node->p->useCycles(node->p, node->id, node->eventDiff); + cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff); } node->eventDiff = 0; } else {

@@ -377,13 +369,13 @@ }

if (value & 0x0080 && !node->id) { // Internal shift clock if (value & 1) { - node->p->transferActive = TRANSFER_STARTING; + node->p->d.transferActive = TRANSFER_STARTING; } // Frequency if (value & 2) { - node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 1024; + node->p->d.transferCycles = GBA_ARM7TDMI_FREQUENCY / 1024; } else { - node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 8192; + node->p->d.transferCycles = GBA_ARM7TDMI_FREQUENCY / 8192; } } } else if (address == REG_SIODATA32_LO) {
M src/gba/sio/lockstep.hsrc/gba/sio/lockstep.h

@@ -3,40 +3,23 @@ *

* 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/. */ -#ifndef SIO_LOCKSTEP_H -#define SIO_LOCKSTEP_H +#ifndef GBA_SIO_LOCKSTEP_H +#define GBA_SIO_LOCKSTEP_H +#include "util/common.h" + +#include "core/lockstep.h" #include "core/timing.h" #include "gba/sio.h" -enum GBASIOLockstepPhase { - TRANSFER_IDLE = 0, - TRANSFER_STARTING, - TRANSFER_STARTED, - TRANSFER_FINISHING, - TRANSFER_FINISHED -}; - struct GBASIOLockstep { + struct mLockstep d; struct GBASIOLockstepNode* players[MAX_GBAS]; - int attached; int attachedMulti; int attachedNormal; uint16_t multiRecv[MAX_GBAS]; uint32_t normalRecv[MAX_GBAS]; - enum GBASIOLockstepPhase transferActive; - int32_t transferCycles; - - bool (*signal)(struct GBASIOLockstep*, unsigned mask); - bool (*wait)(struct GBASIOLockstep*, unsigned mask); - void (*addCycles)(struct GBASIOLockstep*, int id, int32_t cycles); - int32_t (*useCycles)(struct GBASIOLockstep*, int id, int32_t cycles); - void (*unload)(struct GBASIOLockstep*, int id); - void* context; -#ifndef NDEBUG - int transferId; -#endif }; struct GBASIOLockstepNode {

@@ -52,12 +35,11 @@ enum GBASIOMode mode;

bool transferFinished; #ifndef NDEBUG int transferId; - enum GBASIOLockstepPhase phase; + enum mLockstepPhase phase; #endif }; void GBASIOLockstepInit(struct GBASIOLockstep*); -void GBASIOLockstepDeinit(struct GBASIOLockstep*); void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode*);
M src/platform/qt/GBAApp.hsrc/platform/qt/GBAApp.h

@@ -16,6 +16,7 @@ struct NoIntroDB;

extern "C" { #include "core/log.h" +#include "gba/sio.h" } mLOG_DECLARE_CATEGORY(QT);
M src/platform/qt/MultiplayerController.cppsrc/platform/qt/MultiplayerController.cpp

@@ -11,15 +11,17 @@ extern "C" {

#ifdef M_CORE_GBA #include "gba/gba.h" #endif +#ifdef M_CORE_GB +#include "gb/gb.h" +#endif } - using namespace QGBA; MultiplayerController::MultiplayerController() { - GBASIOLockstepInit(&m_lockstep); + mLockstepInit(&m_lockstep); m_lockstep.context = this; - m_lockstep.signal = [](GBASIOLockstep* lockstep, unsigned mask) { + m_lockstep.signal = [](mLockstep* lockstep, unsigned mask) { MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context); Player* player = &controller->m_players[0]; bool woke = false;

@@ -33,7 +35,7 @@ }

controller->m_lock.unlock(); return woke; }; - m_lockstep.wait = [](GBASIOLockstep* lockstep, unsigned mask) { + m_lockstep.wait = [](mLockstep* lockstep, unsigned mask) { MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context); controller->m_lock.lock(); Player* player = &controller->m_players[0];

@@ -47,7 +49,7 @@ }

controller->m_lock.unlock(); return slept; }; - m_lockstep.addCycles = [](GBASIOLockstep* lockstep, int id, int32_t cycles) { + m_lockstep.addCycles = [](mLockstep* lockstep, int id, int32_t cycles) { if (cycles < 0) { abort(); }

@@ -56,14 +58,27 @@ controller->m_lock.lock();

if (!id) { for (int i = 1; i < controller->m_players.count(); ++i) { Player* player = &controller->m_players[i]; - if (player->node->d.p->mode != controller->m_players[0].node->d.p->mode) { + if (player->controller->platform() == PLATFORM_GBA && player->gbaNode->d.p->mode != controller->m_players[0].gbaNode->d.p->mode) { player->controller->setSync(true); continue; } player->controller->setSync(false); player->cyclesPosted += cycles; if (player->awake < 1) { - player->node->nextEvent += player->cyclesPosted; + switch (player->controller->platform()) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + player->gbaNode->nextEvent += player->cyclesPosted; + break; +#endif +#ifdef M_CORE_GB + case PLATFORM_GB: + player->gbNode->nextEvent += player->cyclesPosted; + break; +#endif + default: + break; + } mCoreThreadStopWaiting(player->controller->thread()); player->awake = 1; }

@@ -74,7 +89,7 @@ controller->m_players[id].cyclesPosted += cycles;

} controller->m_lock.unlock(); }; - m_lockstep.useCycles = [](GBASIOLockstep* lockstep, int id, int32_t cycles) { + m_lockstep.useCycles = [](mLockstep* lockstep, int id, int32_t cycles) { MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context); controller->m_lock.lock(); Player* player = &controller->m_players[id];

@@ -87,7 +102,7 @@ cycles = player->cyclesPosted;

controller->m_lock.unlock(); return cycles; }; - m_lockstep.unload = [](GBASIOLockstep* lockstep, int id) { + m_lockstep.unload = [](mLockstep* lockstep, int id) { MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context); controller->m_lock.lock(); Player* player = &controller->m_players[id];

@@ -102,9 +117,35 @@ } else {

for (int i = 1; i < controller->m_players.count(); ++i) { Player* player = &controller->m_players[i]; player->controller->setSync(true); - player->cyclesPosted += lockstep->players[0]->eventDiff; + switch (player->controller->platform()) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + player->cyclesPosted += reinterpret_cast<GBASIOLockstep*>(lockstep)->players[0]->eventDiff; + break; +#endif +#ifdef M_CORE_GB + case PLATFORM_GB: + player->cyclesPosted += reinterpret_cast<GBSIOLockstep*>(lockstep)->players[0]->eventDiff; + break; +#endif + default: + break; + } if (player->awake < 1) { - player->node->nextEvent += player->cyclesPosted; + switch (player->controller->platform()) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + player->gbaNode->nextEvent += player->cyclesPosted; + break; +#endif +#ifdef M_CORE_GB + case PLATFORM_GB: + player->gbNode->nextEvent += player->cyclesPosted; + break; +#endif + default: + break; + } mCoreThreadStopWaiting(player->controller->thread()); player->awake = 1; }

@@ -114,29 +155,44 @@ controller->m_lock.unlock();

}; } -MultiplayerController::~MultiplayerController() { - GBASIOLockstepDeinit(&m_lockstep); -} - bool MultiplayerController::attachGame(GameController* controller) { if (m_lockstep.attached == MAX_GBAS) { return false; } + if (m_lockstep.attached == 0) { + switch (controller->platform()) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + GBASIOLockstepInit(&m_gbaLockstep); + break; +#endif +#ifdef M_CORE_GB + case PLATFORM_GB: + GBSIOLockstepInit(&m_gbLockstep); + break; +#endif + default: + return false; + } + } + mCoreThread* thread = controller->thread(); if (!thread) { return false; } + switch (controller->platform()) { #ifdef M_CORE_GBA - if (controller->platform() == PLATFORM_GBA) { + case PLATFORM_GBA: { GBA* gba = static_cast<GBA*>(thread->core->board); GBASIOLockstepNode* node = new GBASIOLockstepNode; GBASIOLockstepNodeCreate(node); - GBASIOLockstepAttachNode(&m_lockstep, node); + GBASIOLockstepAttachNode(&m_gbaLockstep, node); m_players.append({ controller, + nullptr, node, 1, 0,

@@ -149,6 +205,31 @@ emit gameAttached();

return true; } #endif +#ifdef M_CORE_GB + case PLATFORM_GB: { + GB* gb = static_cast<GB*>(thread->core->board); + + GBSIOLockstepNode* node = new GBSIOLockstepNode; + GBSIOLockstepNodeCreate(node); + GBSIOLockstepAttachNode(&m_gbLockstep, node); + m_players.append({ + controller, + node, + nullptr, + 1, + 0, + 0 + }); + + GBSIOSetDriver(&gb->sio, &node->d); + + emit gameAttached(); + return true; + } +#endif + default: + break; + } return false; }

@@ -161,17 +242,34 @@ }

for (int i = 0; i < m_players.count(); ++i) { m_players[i].controller->threadInterrupt(); } + switch (controller->platform()) { #ifdef M_CORE_GBA - if (controller->platform() == PLATFORM_GBA) { + case PLATFORM_GBA: { GBA* gba = static_cast<GBA*>(thread->core->board); GBASIOLockstepNode* node = reinterpret_cast<GBASIOLockstepNode*>(gba->sio.drivers.multiplayer); GBASIOSetDriver(&gba->sio, nullptr, SIO_MULTI); if (node) { - GBASIOLockstepDetachNode(&m_lockstep, node); + GBASIOLockstepDetachNode(&m_gbaLockstep, node); delete node; } + break; } #endif +#ifdef M_CORE_GB + case PLATFORM_GB: { + GB* gb = static_cast<GB*>(thread->core->board); + GBSIOLockstepNode* node = reinterpret_cast<GBSIOLockstepNode*>(gb->sio.driver); + GBSIOSetDriver(&gb->sio, nullptr); + if (node) { + GBSIOLockstepDetachNode(&m_gbLockstep, node); + delete node; + } + break; + } +#endif + default: + break; + } controller->threadContinue(); for (int i = 0; i < m_players.count(); ++i) {
M src/platform/qt/MultiplayerController.hsrc/platform/qt/MultiplayerController.h

@@ -11,7 +11,13 @@ #include <QList>

#include <QObject> extern "C" { +#include "core/lockstep.h" +#ifdef M_CORE_GBA #include "gba/sio/lockstep.h" +#endif +#ifdef M_CORE_GB +#include "gb/sio/lockstep.h" +#endif } namespace QGBA {

@@ -23,7 +29,6 @@ Q_OBJECT

public: MultiplayerController(); - ~MultiplayerController(); bool attachGame(GameController*); void detachGame(GameController*);

@@ -38,12 +43,21 @@

private: struct Player { GameController* controller; - GBASIOLockstepNode* node; + GBSIOLockstepNode* gbNode; + GBASIOLockstepNode* gbaNode; int awake; int32_t cyclesPosted; unsigned waitMask; }; - GBASIOLockstep m_lockstep; + union { + mLockstep m_lockstep; +#ifdef M_CORE_GB + GBSIOLockstep m_gbLockstep; +#endif +#ifdef M_CORE_GBA + GBASIOLockstep m_gbaLockstep; +#endif + }; QList<Player> m_players; QMutex m_lock; };