Fixed link cable stability
@@ -23,10 +23,14 @@ int attached;
enum mLockstepPhase transferActive; int32_t transferCycles; + void (*lock)(struct mLockstep*); + void (*unlock)(struct mLockstep*); + 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); + int32_t (*unusedCycles)(struct mLockstep*, int id); void (*unload)(struct mLockstep*, int id); void* context; #ifndef NDEBUG@@ -35,6 +39,19 @@ #endif
}; void mLockstepInit(struct mLockstep*); +void mLockstepDeinit(struct mLockstep*); + +static inline void mLockstepLock(struct mLockstep* lockstep) { + if (lockstep->lock) { + lockstep->lock(lockstep); + } +} + +static inline void mLockstepUnlock(struct mLockstep* lockstep) { + if (lockstep->unlock) { + lockstep->unlock(lockstep); + } +} CXX_GUARD_END
@@ -11,6 +11,12 @@ lockstep->transferActive = 0;
#ifndef NDEBUG lockstep->transferId = 0; #endif + lockstep->lock = NULL; + lockstep->unlock = NULL; +} + +void mLockstepDeinit(struct mLockstep* lockstep) { + UNUSED(lockstep); } // TODO: Migrate nodes
@@ -17,7 +17,6 @@ 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;
@@ -8,7 +8,8 @@
#include <mgba/internal/gba/gba.h> #include <mgba/internal/gba/io.h> -#define LOCKSTEP_INCREMENT 3000 +#define LOCKSTEP_INCREMENT 2000 +#define LOCKSTEP_TRANSFER 512 static bool GBASIOLockstepNodeInit(struct GBASIODriver* driver); static void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver);@@ -17,9 +18,9 @@ static bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver);
static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value); static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value); static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* driver, uint32_t cyclesLate); +static void _finishTransfer(struct GBASIOLockstepNode* node); void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) { - mLockstepInit(&lockstep->d); lockstep->players[0] = 0; lockstep->players[1] = 0; lockstep->players[2] = 0;@@ -88,7 +89,11 @@ struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
node->nextEvent = 0; node->eventDiff = 0; mTimingSchedule(&driver->p->p->timing, &node->event, 0); + + mLockstepLock(&node->p->d); + node->mode = driver->p->mode; + switch (node->mode) { case SIO_MULTI: node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;@@ -110,11 +115,17 @@ #ifndef NDEBUG
node->phase = node->p->d.transferActive; node->transferId = node->p->d.transferId; #endif + + mLockstepUnlock(&node->p->d); + return true; } bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) { struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + + mLockstepLock(&node->p->d); + node->mode = driver->p->mode; switch (node->mode) { case SIO_MULTI:@@ -123,13 +134,43 @@ break;
default: break; } + + // Flush ongoing transfer + if (mTimingIsScheduled(&driver->p->p->timing, &node->event)) { + int oldWhen = node->event.when; + + mTimingDeschedule(&driver->p->p->timing, &node->event); + mTimingSchedule(&driver->p->p->timing, &node->event, 0); + node->eventDiff -= oldWhen - node->event.when; + mTimingDeschedule(&driver->p->p->timing, &node->event); + } + node->p->d.unload(&node->p->d, node->id); - mTimingDeschedule(&driver->p->p->timing, &node->event); + + node->p->multiRecv[0] = 0xFFFF; + node->p->multiRecv[1] = 0xFFFF; + node->p->multiRecv[2] = 0xFFFF; + node->p->multiRecv[3] = 0xFFFF; + + _finishTransfer(node); + + if (!node->id) { + ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE); + } + + // Invalidate SIO mode + node->mode = SIO_GPIO; + + mLockstepUnlock(&node->p->d); + return true; } static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) { struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + + mLockstepLock(&node->p->d); + if (address == REG_SIOCNT) { mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);@@ -141,8 +182,16 @@ if (!node->id && node->d.p->multiplayerControl.ready) {
mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id); ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING); ATOMIC_STORE(node->p->d.transferCycles, GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->d.attached - 1]); + + bool scheduled = mTimingIsScheduled(&driver->p->p->timing, &node->event); + int oldWhen = node->event.when; + mTimingDeschedule(&driver->p->p->timing, &node->event); mTimingSchedule(&driver->p->p->timing, &node->event, 0); + + if (scheduled) { + node->eventDiff -= oldWhen - node->event.when; + } } else { value &= ~0x0080; }@@ -152,6 +201,9 @@ value |= driver->p->siocnt & 0x00FC;
} else if (address == REG_SIOMLT_SEND) { mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOMLT_SEND <- %04x", node->id, value); } + + mLockstepUnlock(&node->p->d); + return value; }@@ -159,6 +211,7 @@ static void _finishTransfer(struct GBASIOLockstepNode* node) {
if (node->transferFinished) { return; } + struct GBASIO* sio = node->d.p; switch (node->mode) { case SIO_MULTI:@@ -230,18 +283,21 @@ break;
case TRANSFER_STARTING: // Start the transfer, but wait for the other GBAs to catch up node->transferFinished = false; - node->p->multiRecv[0] = 0xFFFF; + node->p->multiRecv[0] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1]; + node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF; + node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF; + node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF; + node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF; node->p->multiRecv[1] = 0xFFFF; node->p->multiRecv[2] = 0xFFFF; node->p->multiRecv[3] = 0xFFFF; needsToWait = true; ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTED); - node->nextEvent += 512; + node->nextEvent += LOCKSTEP_TRANSFER; 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; + node->nextEvent += LOCKSTEP_TRANSFER; ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHING); break; case TRANSFER_FINISHING:@@ -281,6 +337,7 @@ node->p->d.addCycles(&node->p->d, 0, node->eventDiff);
#ifndef NDEBUG node->phase = node->p->d.transferActive; #endif + if (needsToWait) { return 0; }@@ -307,6 +364,9 @@ case TRANSFER_STARTING:
case TRANSFER_FINISHING: break; case TRANSFER_STARTED: + if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) { + break; + } node->transferFinished = false; switch (node->mode) { case SIO_MULTI:@@ -334,6 +394,9 @@ }
signal = true; break; case TRANSFER_FINISHED: + if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) { + break; + } _finishTransfer(node); signal = true; break;@@ -344,16 +407,20 @@ #endif
if (signal) { 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; + mLockstepLock(&node->p->d); if (node->p->d.attached < 2) { + mLockstepUnlock(&node->p->d); return; } int32_t cycles = 0; node->nextEvent -= cyclesLate; + node->eventDiff += cyclesLate; if (node->nextEvent <= 0) { if (!node->id) { cycles = _masterUpdate(node);@@ -372,12 +439,18 @@ mTimingDeschedule(timing, &node->event);
mTimingSchedule(timing, &node->event, cycles); } else { node->d.p->p->earlyExit = true; - mTimingSchedule(timing, &node->event, cyclesLate + 1); + node->eventDiff += 1; + mTimingSchedule(timing, &node->event, 1); } + + mLockstepUnlock(&node->p->d); } static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) { struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + + mLockstepLock(&node->p->d); + if (address == REG_SIOCNT) { mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value); value &= 0xFF8B;@@ -401,5 +474,8 @@ mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04x", node->id, value);
} else if (address == REG_SIODATA32_HI) { mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04x", node->id, value); } + + mLockstepUnlock(&node->p->d); + return value; }
@@ -16,26 +16,43 @@ #endif
using namespace QGBA; +MultiplayerController::Player::Player(CoreController* coreController, GBSIOLockstepNode* node) + : controller(coreController) + , gbNode(node) +{ +} + +MultiplayerController::Player::Player(CoreController* coreController, GBASIOLockstepNode* node) + : controller(coreController) + , gbaNode(node) +{ +} + MultiplayerController::MultiplayerController() { mLockstepInit(&m_lockstep); m_lockstep.context = this; + m_lockstep.lock = [](mLockstep* lockstep) { + MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context); + controller->m_lock.lock(); + }; + m_lockstep.unlock = [](mLockstep* lockstep) { + MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context); + controller->m_lock.unlock(); + }; m_lockstep.signal = [](mLockstep* lockstep, unsigned mask) { MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context); Player* player = &controller->m_players[0]; bool woke = false; - controller->m_lock.lock(); player->waitMask &= ~mask; if (!player->waitMask && player->awake < 1) { mCoreThreadStopWaiting(player->controller->thread()); player->awake = 1; woke = true; } - controller->m_lock.unlock(); return woke; }; m_lockstep.wait = [](mLockstep* lockstep, unsigned mask) { MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context); - controller->m_lock.lock(); Player* player = &controller->m_players[0]; bool slept = false; player->waitMask |= mask;@@ -44,7 +61,6 @@ mCoreThreadWaitFromThread(player->controller->thread());
player->awake = 0; slept = true; } - controller->m_lock.unlock(); return slept; }; m_lockstep.addCycles = [](mLockstep* lockstep, int id, int32_t cycles) {@@ -52,7 +68,6 @@ if (cycles < 0) {
abort(); } MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context); - controller->m_lock.lock(); if (!id) { for (int i = 1; i < controller->m_players.count(); ++i) { Player* player = &controller->m_players[i];@@ -85,11 +100,9 @@ } else {
controller->m_players[id].controller->setSync(true); controller->m_players[id].cyclesPosted += cycles; } - controller->m_lock.unlock(); }; 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]; player->cyclesPosted -= cycles; if (player->cyclesPosted <= 0) {@@ -97,15 +110,23 @@ mCoreThreadWaitFromThread(player->controller->thread());
player->awake = 0; } cycles = player->cyclesPosted; - controller->m_lock.unlock(); return cycles; }; - m_lockstep.unload = [](mLockstep* lockstep, int id) { + m_lockstep.unusedCycles= [](mLockstep* lockstep, int id) { MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context); - controller->m_lock.lock(); Player* player = &controller->m_players[id]; + auto cycles = player->cyclesPosted; + return cycles; + }; + m_lockstep.unload = [](mLockstep* lockstep, int id) { + MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context); if (id) { + Player* player = &controller->m_players[id]; player->controller->setSync(true); + player->cyclesPosted = 0; + + // release master GBA if it is waiting for this GBA + player = &controller->m_players[0]; player->waitMask &= ~(1 << id); if (!player->waitMask && player->awake < 1) { mCoreThreadStopWaiting(player->controller->thread());@@ -149,8 +170,11 @@ player->awake = 1;
} } } - controller->m_lock.unlock(); }; +} + +MultiplayerController::~MultiplayerController() { + mLockstepDeinit(&m_lockstep); } bool MultiplayerController::attachGame(CoreController* controller) {@@ -188,14 +212,7 @@
GBASIOLockstepNode* node = new GBASIOLockstepNode; GBASIOLockstepNodeCreate(node); GBASIOLockstepAttachNode(&m_gbaLockstep, node); - m_players.append({ - controller, - nullptr, - node, - 1, - 0, - 0 - }); + m_players.append({controller, node}); GBASIOSetDriver(&gba->sio, &node->d, SIO_MULTI);@@ -210,14 +227,7 @@
GBSIOLockstepNode* node = new GBSIOLockstepNode; GBSIOLockstepNodeCreate(node); GBSIOLockstepAttachNode(&m_gbLockstep, node); - m_players.append({ - controller, - node, - nullptr, - 1, - 0, - 0 - }); + m_players.append({controller, node}); GBSIOSetDriver(&gb->sio, &node->d);
@@ -17,6 +17,8 @@ #ifdef M_CORE_GB
#include <mgba/internal/gb/sio/lockstep.h> #endif +#include <memory> + struct GBSIOLockstepNode; struct GBASIOLockstepNode;@@ -29,6 +31,7 @@ Q_OBJECT
public: MultiplayerController(); + ~MultiplayerController(); bool attachGame(CoreController*); void detachGame(CoreController*);@@ -42,12 +45,15 @@ void gameDetached();
private: struct Player { + Player(CoreController* controller, GBSIOLockstepNode* node); + Player(CoreController* controller, GBASIOLockstepNode* node); + CoreController* controller; - GBSIOLockstepNode* gbNode; - GBASIOLockstepNode* gbaNode; - int awake; - int32_t cyclesPosted; - unsigned waitMask; + GBSIOLockstepNode* gbNode = nullptr; + GBASIOLockstepNode* gbaNode = nullptr; + int awake = 1; + int32_t cyclesPosted = 0; + unsigned waitMask = 0; }; union { mLockstep m_lockstep;