all repos — mgba @ 5e37df6cf5b7f9744bdbb64c550cf13a98892b35

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