all repos — mgba @ c9ede88332bf9d1def0b3af005ad4d29456289e9

mGBA Game Boy Advance Emulator

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

  1/* Copyright (c) 2013-2015 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 "gba/gba.h"
  9#include "gba/io.h"
 10#include "gba/video.h"
 11
 12#define LOCKSTEP_INCREMENT 3000
 13
 14static bool _nodeWait(struct GBASIOLockstepNode* node, uint32_t mask) {
 15	uint32_t oldMask = 0;
 16	if (ATOMIC_CMPXCHG(node->p->waiting[node->id], oldMask, mask | 0x10)) {
 17		node->p->wait(node->p, node->id);
 18	}
 19
 20	return true;
 21}
 22
 23static bool _nodeSignal(struct GBASIOLockstepNode* node, uint32_t mask) {
 24	bool eventTriggered = false;
 25	int i;
 26	for (i = 0; i < node->p->attached; ++i) {
 27		uint32_t waiting = ATOMIC_AND(node->p->waiting[i], ~mask);
 28		if (waiting == 0x10) {
 29			if (!ATOMIC_CMPXCHG(node->p->waiting[i], waiting, 0)) {
 30				return false;
 31			}
 32			node->p->signal(node->p, i);
 33			eventTriggered = true;
 34		}
 35	}
 36	if (!eventTriggered) {
 37		ATOMIC_STORE(node->p->waitMask, mask);
 38	} else {
 39		ATOMIC_STORE(node->p->waitMask, 0);
 40	}
 41	return eventTriggered;
 42}
 43
 44
 45static bool GBASIOLockstepNodeInit(struct GBASIODriver* driver);
 46static void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver);
 47static bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver);
 48static bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver);
 49static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
 50static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
 51static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles);
 52static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles);
 53
 54void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) {
 55	lockstep->players[0] = 0;
 56	lockstep->players[1] = 0;
 57	lockstep->players[2] = 0;
 58	lockstep->players[3] = 0;
 59	lockstep->multiRecv[0] = 0xFFFF;
 60	lockstep->multiRecv[1] = 0xFFFF;
 61	lockstep->multiRecv[2] = 0xFFFF;
 62	lockstep->multiRecv[3] = 0xFFFF;
 63	lockstep->attached = 0;
 64	lockstep->attachedMulti = 0;
 65	lockstep->transferActive = 0;
 66	memset(lockstep->waiting, 0, sizeof(lockstep->waiting));
 67#ifndef NDEBUG
 68	lockstep->transferId = 0;
 69#endif
 70}
 71
 72void GBASIOLockstepDeinit(struct GBASIOLockstep* lockstep) {
 73	UNUSED(lockstep);
 74}
 75
 76void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) {
 77	node->d.init = GBASIOLockstepNodeInit;
 78	node->d.deinit = GBASIOLockstepNodeDeinit;
 79	node->d.load = GBASIOLockstepNodeLoad;
 80	node->d.unload = GBASIOLockstepNodeUnload;
 81	node->d.writeRegister = 0;
 82	node->d.processEvents = GBASIOLockstepNodeProcessEvents;
 83}
 84
 85bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
 86	if (lockstep->attached == MAX_GBAS) {
 87		return false;
 88	}
 89	lockstep->players[lockstep->attached] = node;
 90	node->p = lockstep;
 91	node->id = lockstep->attached;
 92	++lockstep->attached;
 93	return true;
 94}
 95
 96void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
 97	if (lockstep->attached == 0) {
 98		return;
 99	}
100	int i;
101	for (i = 0; i < lockstep->attached; ++i) {
102		if (lockstep->players[i] != node) {
103			continue;
104		}
105		for (++i; i < lockstep->attached; ++i) {
106			lockstep->players[i - 1] = lockstep->players[i];
107			lockstep->players[i - 1]->id = i - 1;
108		}
109		--lockstep->attached;
110		break;
111	}
112}
113
114bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) {
115	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
116	node->needsToWait = false;
117	node->d.p->multiplayerControl.slave = node->id > 0;
118	mLOG(GBA_SIO, DEBUG, "Lockstep %i: Node init", node->id);
119	return true;
120}
121
122void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver) {
123	UNUSED(driver);
124}
125
126bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
127	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
128	node->nextEvent = 0;
129	node->eventDiff = 0;
130	node->mode = driver->p->mode;
131	switch (node->mode) {
132	case SIO_MULTI:
133		node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;
134		node->d.p->rcnt |= 3;
135		++node->p->attachedMulti;
136		node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached;
137		if (node->id) {
138			node->d.p->rcnt |= 4;
139			node->d.p->multiplayerControl.slave = 1;
140		}
141		break;
142	case SIO_NORMAL_32:
143		node->d.writeRegister = GBASIOLockstepNodeNormalWriteRegister;
144		break;
145	default:
146		break;
147	}
148#ifndef NDEBUG
149	node->phase = node->p->transferActive;
150	node->transferId = node->p->transferId;
151#endif
152	return true;
153}
154
155bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
156	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
157	node->mode = driver->p->mode;
158	switch (node->mode) {
159	case SIO_MULTI:
160		--node->p->attachedMulti;
161		break;
162	default:
163		break;
164	}
165	_nodeSignal(node, (1 << node->id) ^ 0xF);
166	return true;
167}
168
169static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
170	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
171	if (address == REG_SIOCNT) {
172		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
173		if (value & 0x0080 && node->p->transferActive == TRANSFER_IDLE) {
174			if (!node->id && node->d.p->multiplayerControl.ready) {
175				mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
176				node->p->transferActive = TRANSFER_STARTING;
177				node->p->transferCycles = GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->attached - 1];
178				node->nextEvent = 0;
179			} else {
180				value &= ~0x0080;
181			}
182		}
183		value &= 0xFF83;
184		value |= driver->p->siocnt & 0x00FC;
185	} else if (address == REG_SIOMLT_SEND) {
186		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOMLT_SEND <- %04x", node->id, value);
187	}
188	return value;
189}
190
191static void _finishTransfer(struct GBASIOLockstepNode* node) {
192	if (node->transferFinished) {
193		return;
194	}
195	struct GBASIO* sio = node->d.p;
196	switch (node->mode) {
197	case SIO_MULTI:
198		sio->p->memory.io[REG_SIOMULTI0 >> 1] = node->p->multiRecv[0];
199		sio->p->memory.io[REG_SIOMULTI1 >> 1] = node->p->multiRecv[1];
200		sio->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2];
201		sio->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3];
202		sio->rcnt |= 1;
203		sio->multiplayerControl.busy = 0;
204		sio->multiplayerControl.id = node->id;
205		if (sio->multiplayerControl.irq) {
206			GBARaiseIRQ(sio->p, IRQ_SIO);
207		}
208		break;
209	case SIO_NORMAL_8:
210	case SIO_NORMAL_32:
211		// TODO
212		sio->normalControl.start = 0;
213		if (sio->multiplayerControl.irq) {
214			GBARaiseIRQ(sio->p, IRQ_SIO);
215		}
216		break;
217	default:
218		break;
219	}
220	node->transferFinished = true;
221#ifndef NDEBUG
222	++node->transferId;
223#endif
224}
225
226static void _masterUpdate(struct GBASIOLockstepNode* node) {
227	ATOMIC_STORE(node->needsToWait, false);
228	int i;
229	switch (node->p->transferActive) {
230	case TRANSFER_IDLE:
231		// If the master hasn't initiated a transfer, it can keep going.
232		node->nextEvent += LOCKSTEP_INCREMENT;
233		node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached;
234		break;
235	case TRANSFER_STARTING:
236		// Start the transfer, but wait for the other GBAs to catch up
237		node->transferFinished = false;
238		node->p->multiRecv[0] = 0xFFFF;
239		node->p->multiRecv[1] = 0xFFFF;
240		node->p->multiRecv[2] = 0xFFFF;
241		node->p->multiRecv[3] = 0xFFFF;
242		ATOMIC_STORE(node->needsToWait, true);
243		ATOMIC_STORE(node->p->transferActive, TRANSFER_STARTED);
244		node->nextEvent += 512;
245		break;
246	case TRANSFER_STARTED:
247		// All the other GBAs have caught up and are sleeping, we can all continue now
248		node->p->multiRecv[0] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
249		node->nextEvent += 512;
250		ATOMIC_STORE(node->p->transferActive, TRANSFER_FINISHING);
251		break;
252	case TRANSFER_FINISHING:
253		// Finish the transfer
254		// We need to make sure the other GBAs catch up so they don't get behind
255		node->nextEvent += node->p->transferCycles - 1024; // Split the cycles to avoid waiting too long
256#ifndef NDEBUG
257		ATOMIC_ADD(node->p->transferId, 1);
258#endif
259		ATOMIC_STORE(node->needsToWait, true);
260		ATOMIC_STORE(node->p->transferActive, TRANSFER_FINISHED);
261		break;
262	case TRANSFER_FINISHED:
263		// Everything's settled. We're done.
264		_finishTransfer(node);
265		node->nextEvent += LOCKSTEP_INCREMENT;
266		ATOMIC_STORE(node->p->transferActive, TRANSFER_IDLE);
267		break;
268	}
269	if (node->needsToWait) {
270		int mask = 0;
271		for (i = 1; i < node->p->attached; ++i) {
272			if (node->p->players[i]->mode == node->mode) {
273				mask |= 1 << i;
274			}
275		}
276		if (mask) {
277			if (!_nodeWait(node, mask)) {
278#ifndef NDEBUG
279				abort();
280#endif
281			}
282		}
283	}
284	// Tell the other GBAs they can continue up to where we were
285	for (i = 1; i < node->p->attached; ++i) {
286		ATOMIC_ADD(node->p->players[i]->nextEvent, node->eventDiff);
287		ATOMIC_STORE(node->p->players[i]->needsToWait, false);
288	}
289#ifndef NDEBUG
290	node->phase = node->p->transferActive;
291#endif
292	_nodeSignal(node, 1);
293}
294
295static void _slaveUpdate(struct GBASIOLockstepNode* node) {
296	ATOMIC_STORE(node->needsToWait, true);
297	node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached;
298	bool signal = false;
299	switch (node->p->transferActive) {
300	case TRANSFER_IDLE:
301		if (!node->d.p->multiplayerControl.ready) {
302			node->nextEvent += LOCKSTEP_INCREMENT;
303			ATOMIC_STORE(node->needsToWait, false);
304			return;
305		}
306		break;
307	case TRANSFER_STARTING:
308	case TRANSFER_FINISHING:
309		break;
310	case TRANSFER_STARTED:
311		node->transferFinished = false;
312		switch (node->mode) {
313		case SIO_MULTI:
314			node->d.p->rcnt &= ~1;
315			node->p->multiRecv[node->id] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
316			node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;
317			node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
318			node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
319			node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
320			node->d.p->multiplayerControl.busy = 1;
321			break;
322		default:
323			node->p->multiRecv[node->id]= 0xFFFF;
324			break;
325		}
326		signal = true;
327		break;
328	case TRANSFER_FINISHED:
329		_finishTransfer(node);
330		signal = true;
331		break;
332	}
333	_nodeWait(node, 1);
334#ifndef NDEBUG
335	node->phase = node->p->transferActive;
336#endif
337	if (signal) {
338		_nodeSignal(node, 1 << node->id);
339	}
340}
341
342static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles) {
343	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
344	if (node->p->attached < 2) {
345		return INT_MAX;
346	}
347	node->eventDiff += cycles;
348	cycles = ATOMIC_ADD(node->nextEvent, -cycles);
349	if (cycles <= 0) {
350		if (!node->id) {
351			_masterUpdate(node);
352		} else {
353			_slaveUpdate(node);
354		}
355		node->eventDiff = 0;
356		bool needsToWait;
357		ATOMIC_LOAD(needsToWait, node->needsToWait);
358		if (needsToWait) {
359			return 0;
360		}
361		ATOMIC_LOAD(cycles, node->nextEvent);
362		if (cycles <= 0 && !needsToWait) {
363			int i;
364			// XXX: If we're multithreaded, the atomics may not be sufficient, so wait a few usecs
365			for (i = 0; i < 0x40; ++i) {
366				usleep(10);
367				ATOMIC_LOAD(cycles, node->nextEvent);
368				if (cycles > 0) {
369					return cycles;
370				}
371			}
372			abort();
373			mLOG(GBA_SIO, WARN, "Sleeping needlessly");
374		}
375		return cycles;
376	}
377	return cycles;
378}
379
380static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
381	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
382	if (address == REG_SIOCNT) {
383		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
384		value &= 0xFF8B;
385		if (value & 0x0080) {
386			// Internal shift clock
387			if (value & 1) {
388				node->p->transferActive = TRANSFER_STARTING;
389			}
390			// Frequency
391			if (value & 2) {
392				node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 1024;
393			} else {
394				node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 8192;
395			}
396			node->normalSO = !!(value & 8);
397			// Opponent's SO
398			if (node->id) {
399				value |= node->p->players[node->id - 1]->normalSO << 2;
400			}
401		}
402	} else if (address == REG_SIODATA32_LO) {
403		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04x", node->id, value);
404	} else if (address == REG_SIODATA32_HI) {
405		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04x", node->id, value);
406	}
407	return value;
408}