all repos — mgba @ bda43168394b3edf785ef916186d131f871e4029

mGBA Game Boy Advance Emulator

src/gb/sio/lockstep.c (view raw)

  1/* Copyright (c) 2013-2016 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include <mgba/internal/gb/sio/lockstep.h>
  7
  8#include <mgba/internal/gb/gb.h>
  9#include <mgba/internal/gb/io.h>
 10
 11#define LOCKSTEP_INCREMENT 512
 12
 13static bool GBSIOLockstepNodeInit(struct GBSIODriver* driver);
 14static void GBSIOLockstepNodeDeinit(struct GBSIODriver* driver);
 15static void GBSIOLockstepNodeWriteSB(struct GBSIODriver* driver, uint8_t value);
 16static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t value);
 17static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* driver, uint32_t cyclesLate);
 18
 19void GBSIOLockstepInit(struct GBSIOLockstep* lockstep) {
 20	lockstep->players[0] = NULL;
 21	lockstep->players[1] = NULL;
 22	lockstep->pendingSB[0] = 0xFF;
 23	lockstep->pendingSB[1] = 0xFF;
 24	lockstep->masterClaimed = false;
 25}
 26
 27void GBSIOLockstepNodeCreate(struct GBSIOLockstepNode* node) {
 28	node->d.init = GBSIOLockstepNodeInit;
 29	node->d.deinit = GBSIOLockstepNodeDeinit;
 30	node->d.writeSB = GBSIOLockstepNodeWriteSB;
 31	node->d.writeSC = GBSIOLockstepNodeWriteSC;
 32}
 33
 34bool GBSIOLockstepAttachNode(struct GBSIOLockstep* lockstep, struct GBSIOLockstepNode* node) {
 35	if (lockstep->d.attached == MAX_GBS) {
 36		return false;
 37	}
 38	lockstep->players[lockstep->d.attached] = node;
 39	node->p = lockstep;
 40	node->id = lockstep->d.attached;
 41	++lockstep->d.attached;
 42	return true;
 43}
 44
 45void GBSIOLockstepDetachNode(struct GBSIOLockstep* lockstep, struct GBSIOLockstepNode* node) {
 46	if (lockstep->d.attached == 0) {
 47		return;
 48	}
 49	int i;
 50	for (i = 0; i < lockstep->d.attached; ++i) {
 51		if (lockstep->players[i] != node) {
 52			continue;
 53		}
 54		for (++i; i < lockstep->d.attached; ++i) {
 55			lockstep->players[i - 1] = lockstep->players[i];
 56			lockstep->players[i - 1]->id = i - 1;
 57		}
 58		--lockstep->d.attached;
 59		break;
 60	}
 61}
 62
 63bool GBSIOLockstepNodeInit(struct GBSIODriver* driver) {
 64	struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
 65	mLOG(GB_SIO, DEBUG, "Lockstep %i: Node init", node->id);
 66	node->event.context = node;
 67	node->event.name = "GB SIO Lockstep";
 68	node->event.callback = _GBSIOLockstepNodeProcessEvents;
 69	node->event.priority = 0x80;
 70
 71	node->nextEvent = 0;
 72	node->eventDiff = 0;
 73	mTimingSchedule(&driver->p->p->timing, &node->event, 0);
 74#ifndef NDEBUG
 75	node->phase = node->p->d.transferActive;
 76	node->transferId = node->p->d.transferId;
 77#endif
 78	return true;
 79}
 80
 81void GBSIOLockstepNodeDeinit(struct GBSIODriver* driver) {
 82	struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
 83	node->p->d.unload(&node->p->d, node->id);
 84	mTimingDeschedule(&driver->p->p->timing, &node->event);
 85}
 86
 87static void _finishTransfer(struct GBSIOLockstepNode* node) {
 88	if (node->transferFinished) {
 89		return;
 90	}
 91	struct GBSIO* sio = node->d.p;
 92	sio->pendingSB = node->p->pendingSB[!node->id];
 93	if (GBRegisterSCIsEnable(sio->p->memory.io[GB_REG_SC])) {
 94		sio->remainingBits = 8;
 95		mTimingDeschedule(&sio->p->timing, &sio->event);
 96		mTimingSchedule(&sio->p->timing, &sio->event, 0);
 97	}
 98	node->transferFinished = true;
 99#ifndef NDEBUG
100	++node->transferId;
101#endif
102}
103
104
105static int32_t _masterUpdate(struct GBSIOLockstepNode* node) {
106	enum mLockstepPhase transferActive;
107	ATOMIC_LOAD(transferActive, node->p->d.transferActive);
108
109	bool needsToWait = false;
110	int i;
111	switch (transferActive) {
112	case TRANSFER_IDLE:
113		// If the master hasn't initiated a transfer, it can keep going.
114		node->nextEvent += LOCKSTEP_INCREMENT;
115		break;
116	case TRANSFER_STARTING:
117		// Start the transfer, but wait for the other GBs to catch up
118		node->transferFinished = false;
119		needsToWait = true;
120		ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTED);
121		node->nextEvent += 4;
122		break;
123	case TRANSFER_STARTED:
124		// All the other GBs have caught up and are sleeping, we can all continue now
125		node->nextEvent += 4;
126		ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHING);
127		break;
128	case TRANSFER_FINISHING:
129		// Finish the transfer
130		// We need to make sure the other GBs catch up so they don't get behind
131		node->nextEvent += node->d.p->period * (2 - node->d.p->p->doubleSpeed) - 8; // Split the cycles to avoid waiting too long
132#ifndef NDEBUG
133		ATOMIC_ADD(node->p->d.transferId, 1);
134#endif
135		needsToWait = true;
136		ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHED);
137		break;
138	case TRANSFER_FINISHED:
139		// Everything's settled. We're done.
140		_finishTransfer(node);
141		ATOMIC_STORE(node->p->masterClaimed, false);
142		node->nextEvent += LOCKSTEP_INCREMENT;
143		ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE);
144		break;
145	}
146	int mask = 0;
147	for (i = 1; i < node->p->d.attached; ++i) {
148		mask |= 1 << i;
149	}
150	if (mask) {
151		if (needsToWait) {
152			if (!node->p->d.wait(&node->p->d, mask)) {
153				abort();
154			}
155		} else {
156			node->p->d.signal(&node->p->d, mask);
157		}
158	}
159	// Tell the other GBs they can continue up to where we were
160	node->p->d.addCycles(&node->p->d, 0, node->eventDiff);
161#ifndef NDEBUG
162	node->phase = node->p->d.transferActive;
163#endif
164	if (needsToWait) {
165		return 0;
166	}
167	return node->nextEvent;
168}
169
170static uint32_t _slaveUpdate(struct GBSIOLockstepNode* node) {
171	enum mLockstepPhase transferActive;
172
173	ATOMIC_LOAD(transferActive, node->p->d.transferActive);
174
175	bool signal = false;
176	switch (transferActive) {
177	case TRANSFER_IDLE:
178		node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT);
179		break;
180	case TRANSFER_STARTING:
181	case TRANSFER_FINISHING:
182		break;
183	case TRANSFER_STARTED:
184		if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
185			break;
186		}
187		node->transferFinished = false;
188		signal = true;
189		break;
190	case TRANSFER_FINISHED:
191		if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
192			break;
193		}
194		_finishTransfer(node);
195		signal = true;
196		break;
197	}
198#ifndef NDEBUG
199	node->phase = node->p->d.transferActive;
200#endif
201	if (signal) {
202		node->p->d.signal(&node->p->d, 1 << node->id);
203	}
204	return 0;
205}
206
207static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
208	struct GBSIOLockstepNode* node = user;
209	mLockstepLock(&node->p->d);
210	if (node->p->d.attached < 2) {
211		mTimingSchedule(timing, &node->event, (GBSIOCyclesPerTransfer[0] >> 1) * (2 - node->d.p->p->doubleSpeed) - cyclesLate);
212		mLockstepUnlock(&node->p->d);
213		return;
214	}
215	int32_t cycles = 0;
216	node->nextEvent -= cyclesLate;
217	if (node->nextEvent <= 0) {
218		if (!node->id) {
219			cycles = _masterUpdate(node);
220		} else {
221			cycles = _slaveUpdate(node);
222			cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff);
223		}
224		node->eventDiff = 0;
225	} else {
226		cycles = node->nextEvent;
227	}
228	mLockstepUnlock(&node->p->d);
229
230	if (cycles > 0) {
231		node->nextEvent = 0;
232		node->eventDiff += cycles;
233		mTimingDeschedule(timing, &node->event);
234		mTimingSchedule(timing, &node->event, cycles);
235	} else {
236		node->d.p->p->earlyExit = true;
237		mTimingSchedule(timing, &node->event, cyclesLate + 1);
238	}
239}
240
241static void GBSIOLockstepNodeWriteSB(struct GBSIODriver* driver, uint8_t value) {
242	struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
243	node->p->pendingSB[node->id] = value;
244}
245
246static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t value) {
247	struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
248	int attached;
249	ATOMIC_LOAD(attached, node->p->d.attached);
250
251	if ((value & 0x81) == 0x81 && attached > 1) {
252		mLockstepLock(&node->p->d);
253		bool claimed = false;
254		if (ATOMIC_CMPXCHG(node->p->masterClaimed, claimed, true)) {
255			ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
256			ATOMIC_STORE(node->p->d.transferCycles, GBSIOCyclesPerTransfer[(value >> 1) & 1]);
257			mTimingDeschedule(&driver->p->p->timing, &driver->p->event);
258			mTimingDeschedule(&driver->p->p->timing, &node->event);
259			mTimingSchedule(&driver->p->p->timing, &node->event, 0);
260		} else {
261			mLOG(GB_SIO, DEBUG, "GBSIOLockstepNodeWriteSC() failed to write to masterClaimed\n");
262		}
263		mLockstepUnlock(&node->p->d);
264	}
265	return value;
266}