all repos — mgba @ daaa2fa5236df5e8976e7e0ba7ac39e0d9233422

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