GB: First pass at lockstep multiplayer

This commit is contained in:
Jeffrey Pfau 2016-12-25 20:39:11 -08:00
parent 4ac4733cfd
commit ceea51b55e
15 changed files with 590 additions and 103 deletions

View File

@ -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(NOT MINIMAL_CORE)
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}

16
src/core/lockstep.c Normal file
View File

@ -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

37
src/core/lockstep.h Normal file
View File

@ -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

View File

@ -433,6 +433,7 @@ void GBReset(struct LR35902Core* cpu) {
}
gb->cpuBlocked = false;
gb->earlyExit = false;
gb->doubleSpeed = 0;
cpu->memory.setActiveRegion(cpu, cpu->pc);
@ -548,6 +549,10 @@ void GBProcessEvents(struct LR35902Core* cpu) {
} 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) {

View File

@ -79,6 +79,7 @@ struct GB {
struct mAVStream* stream;
bool cpuBlocked;
bool earlyExit;
struct mTimingEvent eiPending;
unsigned doubleSpeed;
};

View File

@ -158,6 +158,9 @@ void GBIOReset(struct GB* gb) {
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 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) {
}
break;
case REG_JOYP:
case REG_SB:
case REG_TIMA:
case REG_TMA:
// Handled transparently by the registers

View File

@ -9,6 +9,13 @@
#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 @@ void GBSIOInit(struct GBSIO* 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 @@ void GBSIODeinit(struct GBSIO* 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 @@ void _GBSIOProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesL
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);
}
}

View File

@ -8,13 +8,22 @@
#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 @@ struct GBSIO {
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 @@ DECL_BIT(GBRegisterSC, Enable, 7);
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

231
src/gb/sio/lockstep.c Normal file
View File

@ -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;
}

44
src/gb/sio/lockstep.h Normal file
View File

@ -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

View File

@ -20,6 +20,7 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
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 @@ void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) {
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 @@ void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) {
}
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 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
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 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
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 @@ bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
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
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 void _finishTransfer(struct GBASIOLockstepNode* node) {
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 @@ static int32_t _masterUpdate(struct GBASIOLockstepNode* node) {
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 @@ static int32_t _masterUpdate(struct GBASIOLockstepNode* node) {
}
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 @@ static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
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 @@ static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user,
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 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
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) {

View File

@ -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 @@ struct GBASIOLockstepNode {
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*);

View File

@ -16,6 +16,7 @@ struct NoIntroDB;
extern "C" {
#include "core/log.h"
#include "gba/sio.h"
}
mLOG_DECLARE_CATEGORY(QT);

View File

@ -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 @@ MultiplayerController::MultiplayerController() {
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 @@ MultiplayerController::MultiplayerController() {
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 @@ MultiplayerController::MultiplayerController() {
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 @@ MultiplayerController::MultiplayerController() {
}
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 @@ MultiplayerController::MultiplayerController() {
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 @@ MultiplayerController::MultiplayerController() {
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 @@ MultiplayerController::MultiplayerController() {
};
}
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 @@ bool MultiplayerController::attachGame(GameController* controller) {
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 @@ void MultiplayerController::detachGame(GameController* controller) {
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) {

View File

@ -11,7 +11,13 @@
#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 @@ signals:
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;
};