all repos — mgba @ 0de8f432ecbfa778591aa8db92ee551e48d7a2db

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 <mgba/internal/gba/sio/lockstep.h>
  7
  8#include <mgba/internal/gba/gba.h>
  9#include <mgba/internal/gba/io.h>
 10
 11#define LOCKSTEP_INCREMENT 2000
 12#define LOCKSTEP_TRANSFER 512
 13
 14static bool GBASIOLockstepNodeInit(struct GBASIODriver* driver);
 15static void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver);
 16static bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver);
 17static bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver);
 18static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
 19static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
 20static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* driver, uint32_t cyclesLate);
 21static void _finishTransfer(struct GBASIOLockstepNode* node);
 22
 23void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) {
 24	lockstep->players[0] = 0;
 25	lockstep->players[1] = 0;
 26	lockstep->players[2] = 0;
 27	lockstep->players[3] = 0;
 28	lockstep->multiRecv[0] = 0xFFFF;
 29	lockstep->multiRecv[1] = 0xFFFF;
 30	lockstep->multiRecv[2] = 0xFFFF;
 31	lockstep->multiRecv[3] = 0xFFFF;
 32	lockstep->attachedMulti = 0;
 33}
 34
 35void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) {
 36	node->d.init = GBASIOLockstepNodeInit;
 37	node->d.deinit = GBASIOLockstepNodeDeinit;
 38	node->d.load = GBASIOLockstepNodeLoad;
 39	node->d.unload = GBASIOLockstepNodeUnload;
 40	node->d.writeRegister = 0;
 41}
 42
 43bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
 44	if (lockstep->d.attached == MAX_GBAS) {
 45		return false;
 46	}
 47	lockstep->players[lockstep->d.attached] = node;
 48	node->p = lockstep;
 49	node->id = lockstep->d.attached;
 50	node->transferFinished = true;
 51	++lockstep->d.attached;
 52	return true;
 53}
 54
 55void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
 56	if (lockstep->d.attached == 0) {
 57		return;
 58	}
 59	int i;
 60	for (i = 0; i < lockstep->d.attached; ++i) {
 61		if (lockstep->players[i] != node) {
 62			continue;
 63		}
 64		for (++i; i < lockstep->d.attached; ++i) {
 65			lockstep->players[i - 1] = lockstep->players[i];
 66			lockstep->players[i - 1]->id = i - 1;
 67		}
 68		--lockstep->d.attached;
 69		break;
 70	}
 71}
 72
 73bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) {
 74	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
 75	node->d.p->siocnt = GBASIOMultiplayerSetSlave(node->d.p->siocnt, node->id > 0);
 76	mLOG(GBA_SIO, DEBUG, "Lockstep %i: Node init", node->id);
 77	node->event.context = node;
 78	node->event.name = "GBA SIO Lockstep";
 79	node->event.callback = _GBASIOLockstepNodeProcessEvents;
 80	node->event.priority = 0x80;
 81	return true;
 82}
 83
 84void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver) {
 85	UNUSED(driver);
 86}
 87
 88bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
 89	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
 90	node->nextEvent = 0;
 91	node->eventDiff = 0;
 92	mTimingSchedule(&driver->p->p->timing, &node->event, 0);
 93
 94	mLockstepLock(&node->p->d);
 95
 96	node->mode = driver->p->mode;
 97
 98	switch (node->mode) {
 99	case SIO_MULTI:
100		node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;
101		node->d.p->rcnt |= 3;
102		ATOMIC_ADD(node->p->attachedMulti, 1);
103		node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, node->p->attachedMulti == node->p->d.attached);
104		if (node->id) {
105			node->d.p->rcnt |= 4;
106			node->d.p->siocnt = GBASIOMultiplayerFillSlave(node->d.p->siocnt);
107		}
108		break;
109	case SIO_NORMAL_32:
110		node->d.writeRegister = GBASIOLockstepNodeNormalWriteRegister;
111		break;
112	default:
113		break;
114	}
115#ifndef NDEBUG
116	node->phase = node->p->d.transferActive;
117	node->transferId = node->p->d.transferId;
118#endif
119
120	mLockstepUnlock(&node->p->d);
121
122	return true;
123}
124
125bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
126	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
127
128	mLockstepLock(&node->p->d);
129
130	node->mode = driver->p->mode;
131	switch (node->mode) {
132	case SIO_MULTI:
133		ATOMIC_SUB(node->p->attachedMulti, 1);
134		break;
135	default:
136		break;
137	}
138
139	// Flush ongoing transfer
140	if (mTimingIsScheduled(&driver->p->p->timing, &node->event)) {
141		int oldWhen = node->event.when;
142
143		mTimingDeschedule(&driver->p->p->timing, &node->event);
144		mTimingSchedule(&driver->p->p->timing, &node->event, 0);
145		node->eventDiff -= oldWhen - node->event.when;
146		mTimingDeschedule(&driver->p->p->timing, &node->event);
147	}
148
149	node->p->d.unload(&node->p->d, node->id);
150
151	node->p->multiRecv[0] = 0xFFFF;
152	node->p->multiRecv[1] = 0xFFFF;
153	node->p->multiRecv[2] = 0xFFFF;
154	node->p->multiRecv[3] = 0xFFFF;
155
156	_finishTransfer(node);
157
158	if (!node->id) {
159		ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE);
160	}
161
162	// Invalidate SIO mode
163	node->mode = SIO_GPIO;
164
165	mLockstepUnlock(&node->p->d);
166
167	return true;
168}
169
170static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
171	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
172
173	mLockstepLock(&node->p->d);
174
175	if (address == REG_SIOCNT) {
176		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
177
178		enum mLockstepPhase transferActive;
179		ATOMIC_LOAD(transferActive, node->p->d.transferActive);
180
181		if (value & 0x0080 && transferActive == TRANSFER_IDLE) {
182			if (!node->id && GBASIOMultiplayerIsReady(node->d.p->siocnt)) {
183				mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
184				ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
185				ATOMIC_STORE(node->p->d.transferCycles, GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][node->p->d.attached - 1]);
186
187				bool scheduled = mTimingIsScheduled(&driver->p->p->timing, &node->event);
188				int oldWhen = node->event.when;
189
190				mTimingDeschedule(&driver->p->p->timing, &node->event);
191				mTimingSchedule(&driver->p->p->timing, &node->event, 0);
192
193				if (scheduled) {
194					node->eventDiff -= oldWhen - node->event.when;
195				}
196			} else {
197				value &= ~0x0080;
198			}
199		}
200		value &= 0xFF83;
201		value |= driver->p->siocnt & 0x00FC;
202	} else if (address == REG_SIOMLT_SEND) {
203		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOMLT_SEND <- %04x", node->id, value);
204	}
205
206	mLockstepUnlock(&node->p->d);
207
208	return value;
209}
210
211static void _finishTransfer(struct GBASIOLockstepNode* node) {
212	if (node->transferFinished) {
213		return;
214	}
215
216	struct GBASIO* sio = node->d.p;
217	switch (node->mode) {
218	case SIO_MULTI:
219		sio->p->memory.io[REG_SIOMULTI0 >> 1] = node->p->multiRecv[0];
220		sio->p->memory.io[REG_SIOMULTI1 >> 1] = node->p->multiRecv[1];
221		sio->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2];
222		sio->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3];
223		sio->rcnt |= 1;
224		sio->siocnt = GBASIOMultiplayerClearBusy(sio->siocnt);
225		sio->siocnt = GBASIOMultiplayerSetId(sio->siocnt, node->id);
226		if (GBASIOMultiplayerIsIrq(sio->siocnt)) {
227			GBARaiseIRQ(sio->p, IRQ_SIO, 0);
228		}
229		break;
230	case SIO_NORMAL_8:
231		// TODO
232		sio->siocnt = GBASIONormalClearStart(sio->siocnt);
233		if (node->id) {
234			sio->siocnt = GBASIONormalSetSi(sio->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt));
235			node->d.p->p->memory.io[REG_SIODATA8 >> 1] = node->p->normalRecv[node->id - 1] & 0xFF;
236		} else {
237			node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF;
238		}
239		if (GBASIONormalIsIrq(sio->siocnt)) {
240			GBARaiseIRQ(sio->p, IRQ_SIO, 0);
241		}
242		break;
243	case SIO_NORMAL_32:
244		// TODO
245		sio->siocnt = GBASIONormalClearStart(sio->siocnt);
246		if (node->id) {
247			sio->siocnt = GBASIONormalSetSi(sio->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt));
248			node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = node->p->normalRecv[node->id - 1];
249			node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] |= node->p->normalRecv[node->id - 1] >> 16;
250		} else {
251			node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0xFFFF;
252			node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0xFFFF;
253		}
254		if (GBASIONormalIsIrq(sio->siocnt)) {
255			GBARaiseIRQ(sio->p, IRQ_SIO, 0);
256		}
257		break;
258	default:
259		break;
260	}
261	node->transferFinished = true;
262#ifndef NDEBUG
263	++node->transferId;
264#endif
265}
266
267static int32_t _masterUpdate(struct GBASIOLockstepNode* node) {
268	bool needsToWait = false;
269	int i;
270
271	enum mLockstepPhase transferActive;
272	int attachedMulti, attached;
273
274	ATOMIC_LOAD(transferActive, node->p->d.transferActive);
275	ATOMIC_LOAD(attachedMulti, node->p->attachedMulti);
276	ATOMIC_LOAD(attached, node->p->d.attached);
277
278	switch (transferActive) {
279	case TRANSFER_IDLE:
280		// If the master hasn't initiated a transfer, it can keep going.
281		node->nextEvent += LOCKSTEP_INCREMENT;
282		if (node->mode == SIO_MULTI) {
283			node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached);
284		}
285		break;
286	case TRANSFER_STARTING:
287		// Start the transfer, but wait for the other GBAs to catch up
288		node->transferFinished = false;
289		switch (node->mode) {
290		case SIO_MULTI:
291			node->p->multiRecv[0] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
292			node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;
293			node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
294			node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
295			node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
296			node->p->multiRecv[1] = 0xFFFF;
297			node->p->multiRecv[2] = 0xFFFF;
298			node->p->multiRecv[3] = 0xFFFF;
299			break;
300		case SIO_NORMAL_8:
301			node->p->multiRecv[0] = 0xFFFF;
302			node->p->normalRecv[0] = node->d.p->p->memory.io[REG_SIODATA8 >> 1] & 0xFF;
303			break;
304		case SIO_NORMAL_32:
305			node->p->multiRecv[0] = 0xFFFF;
306			mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04x", node->id, node->d.p->p->memory.io[REG_SIODATA32_LO >> 1]);
307			mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04x", node->id, node->d.p->p->memory.io[REG_SIODATA32_HI >> 1]);
308			node->p->normalRecv[0] = node->d.p->p->memory.io[REG_SIODATA32_LO >> 1];
309			node->p->normalRecv[0] |= node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] << 16;
310			break;
311		default:
312			node->p->multiRecv[0] = 0xFFFF;
313			break;
314		}
315		needsToWait = true;
316		ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTED);
317		node->nextEvent += LOCKSTEP_TRANSFER;
318		break;
319	case TRANSFER_STARTED:
320		// All the other GBAs have caught up and are sleeping, we can all continue now
321		node->nextEvent += LOCKSTEP_TRANSFER;
322		ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHING);
323		break;
324	case TRANSFER_FINISHING:
325		// Finish the transfer
326		// We need to make sure the other GBAs catch up so they don't get behind
327		node->nextEvent += node->p->d.transferCycles - 1024; // Split the cycles to avoid waiting too long
328#ifndef NDEBUG
329		ATOMIC_ADD(node->p->d.transferId, 1);
330#endif
331		needsToWait = true;
332		ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHED);
333		break;
334	case TRANSFER_FINISHED:
335		// Everything's settled. We're done.
336		_finishTransfer(node);
337		node->nextEvent += LOCKSTEP_INCREMENT;
338		ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE);
339		break;
340	}
341	int mask = 0;
342	for (i = 1; i < node->p->d.attached; ++i) {
343		if (node->p->players[i]->mode == node->mode) {
344			mask |= 1 << i;
345		}
346	}
347	if (mask) {
348		if (needsToWait) {
349			if (!node->p->d.wait(&node->p->d, mask)) {
350				abort();
351			}
352		} else {
353			node->p->d.signal(&node->p->d, mask);
354		}
355	}
356	// Tell the other GBAs they can continue up to where we were
357	node->p->d.addCycles(&node->p->d, 0, node->eventDiff);
358#ifndef NDEBUG
359	node->phase = node->p->d.transferActive;
360#endif
361
362	if (needsToWait) {
363		return 0;
364	}
365	return node->nextEvent;
366}
367
368static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
369	enum mLockstepPhase transferActive;
370	int attachedMulti, attached;
371
372	ATOMIC_LOAD(transferActive, node->p->d.transferActive);
373	ATOMIC_LOAD(attachedMulti, node->p->attachedMulti);
374	ATOMIC_LOAD(attached, node->p->d.attached);
375
376	node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached);
377	bool signal = false;
378	switch (transferActive) {
379	case TRANSFER_IDLE:
380		if (!GBASIOMultiplayerIsReady(node->d.p->siocnt)) {
381			node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT);
382		}
383		break;
384	case TRANSFER_STARTING:
385	case TRANSFER_FINISHING:
386		break;
387	case TRANSFER_STARTED:
388		if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
389			break;
390		}
391		node->transferFinished = false;
392		switch (node->mode) {
393		case SIO_MULTI:
394			node->d.p->rcnt &= ~1;
395			node->p->multiRecv[node->id] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
396			node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;
397			node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
398			node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
399			node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
400			node->d.p->siocnt = GBASIOMultiplayerFillBusy(node->d.p->siocnt);
401			break;
402		case SIO_NORMAL_8:
403			node->p->multiRecv[node->id] = 0xFFFF;
404			node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA8 >> 1] & 0xFF;
405			break;
406		case SIO_NORMAL_32:
407			node->p->multiRecv[node->id] = 0xFFFF;
408			node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA32_LO >> 1];
409			node->p->normalRecv[node->id] |= node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] << 16;
410			break;
411		default:
412			node->p->multiRecv[node->id] = 0xFFFF;
413			break;
414		}
415		signal = true;
416		break;
417	case TRANSFER_FINISHED:
418		if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
419			break;
420		}
421		_finishTransfer(node);
422		signal = true;
423		break;
424	}
425#ifndef NDEBUG
426	node->phase = node->p->d.transferActive;
427#endif
428	if (signal) {
429		node->p->d.signal(&node->p->d, 1 << node->id);
430	}
431
432	return 0;
433}
434
435static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
436	struct GBASIOLockstepNode* node = user;
437	mLockstepLock(&node->p->d);
438	if (node->p->d.attached < 2) {
439		mLockstepUnlock(&node->p->d);
440		return;
441	}
442	int32_t cycles = 0;
443	node->nextEvent -= cyclesLate;
444	node->eventDiff += cyclesLate;
445	if (node->nextEvent <= 0) {
446		if (!node->id) {
447			cycles = _masterUpdate(node);
448		} else {
449			cycles = _slaveUpdate(node);
450			cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff);
451		}
452		node->eventDiff = 0;
453	} else {
454		cycles = node->nextEvent;
455	}
456	if (cycles > 0) {
457		node->nextEvent = 0;
458		node->eventDiff += cycles;
459		mTimingDeschedule(timing, &node->event);
460		mTimingSchedule(timing, &node->event, cycles);
461	} else {
462		node->d.p->p->earlyExit = true;
463		node->eventDiff += 1;
464		mTimingSchedule(timing, &node->event, 1);
465	}
466
467	mLockstepUnlock(&node->p->d);
468}
469
470static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
471	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
472
473	mLockstepLock(&node->p->d);
474
475	if (address == REG_SIOCNT) {
476		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
477		value &= 0xFF8B;
478		if (!node->id) {
479			value = GBASIONormalFillSi(value);
480		}
481		if (value & 0x0080 && !node->id) {
482			// Internal shift clock
483			if (value & 1) {
484				ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
485			}
486			// Frequency
487			if (value & 2) {
488				node->p->d.transferCycles = GBA_ARM7TDMI_FREQUENCY / 1024;
489			} else {
490				node->p->d.transferCycles = GBA_ARM7TDMI_FREQUENCY / 8192;
491			}
492		}
493	} else if (address == REG_SIODATA32_LO) {
494		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04x", node->id, value);
495	} else if (address == REG_SIODATA32_HI) {
496		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04x", node->id, value);
497	}
498
499	mLockstepUnlock(&node->p->d);
500
501	return value;
502}