all repos — mgba @ 2bacae3075d4191c26d0f66e40da7ae360923bf8

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		node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached);
283		break;
284	case TRANSFER_STARTING:
285		// Start the transfer, but wait for the other GBAs to catch up
286		node->transferFinished = false;
287		node->p->multiRecv[0] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
288		node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;
289		node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
290		node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
291		node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
292		node->p->multiRecv[1] = 0xFFFF;
293		node->p->multiRecv[2] = 0xFFFF;
294		node->p->multiRecv[3] = 0xFFFF;
295		needsToWait = true;
296		ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTED);
297		node->nextEvent += LOCKSTEP_TRANSFER;
298		break;
299	case TRANSFER_STARTED:
300		// All the other GBAs have caught up and are sleeping, we can all continue now
301		node->nextEvent += LOCKSTEP_TRANSFER;
302		ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHING);
303		break;
304	case TRANSFER_FINISHING:
305		// Finish the transfer
306		// We need to make sure the other GBAs catch up so they don't get behind
307		node->nextEvent += node->p->d.transferCycles - 1024; // Split the cycles to avoid waiting too long
308#ifndef NDEBUG
309		ATOMIC_ADD(node->p->d.transferId, 1);
310#endif
311		needsToWait = true;
312		ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHED);
313		break;
314	case TRANSFER_FINISHED:
315		// Everything's settled. We're done.
316		_finishTransfer(node);
317		node->nextEvent += LOCKSTEP_INCREMENT;
318		ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE);
319		break;
320	}
321	int mask = 0;
322	for (i = 1; i < node->p->d.attached; ++i) {
323		if (node->p->players[i]->mode == node->mode) {
324			mask |= 1 << i;
325		}
326	}
327	if (mask) {
328		if (needsToWait) {
329			if (!node->p->d.wait(&node->p->d, mask)) {
330				abort();
331			}
332		} else {
333			node->p->d.signal(&node->p->d, mask);
334		}
335	}
336	// Tell the other GBAs they can continue up to where we were
337	node->p->d.addCycles(&node->p->d, 0, node->eventDiff);
338#ifndef NDEBUG
339	node->phase = node->p->d.transferActive;
340#endif
341
342	if (needsToWait) {
343		return 0;
344	}
345	return node->nextEvent;
346}
347
348static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
349	enum mLockstepPhase transferActive;
350	int attachedMulti, attached;
351
352	ATOMIC_LOAD(transferActive, node->p->d.transferActive);
353	ATOMIC_LOAD(attachedMulti, node->p->attachedMulti);
354	ATOMIC_LOAD(attached, node->p->d.attached);
355
356	node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached);
357	bool signal = false;
358	switch (transferActive) {
359	case TRANSFER_IDLE:
360		if (!GBASIOMultiplayerIsReady(node->d.p->siocnt)) {
361			node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT);
362		}
363		break;
364	case TRANSFER_STARTING:
365	case TRANSFER_FINISHING:
366		break;
367	case TRANSFER_STARTED:
368		if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
369			break;
370		}
371		node->transferFinished = false;
372		switch (node->mode) {
373		case SIO_MULTI:
374			node->d.p->rcnt &= ~1;
375			node->p->multiRecv[node->id] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
376			node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;
377			node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
378			node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
379			node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
380			node->d.p->siocnt = GBASIOMultiplayerFillBusy(node->d.p->siocnt);
381			break;
382		case SIO_NORMAL_8:
383			node->p->multiRecv[node->id] = 0xFFFF;
384			node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA8 >> 1] & 0xFF;
385			break;
386		case SIO_NORMAL_32:
387			node->p->multiRecv[node->id] = 0xFFFF;
388			node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA32_LO >> 1];
389			node->p->normalRecv[node->id] |= node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] << 16;
390			break;
391		default:
392			node->p->multiRecv[node->id] = 0xFFFF;
393			break;
394		}
395		signal = true;
396		break;
397	case TRANSFER_FINISHED:
398		if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
399			break;
400		}
401		_finishTransfer(node);
402		signal = true;
403		break;
404	}
405#ifndef NDEBUG
406	node->phase = node->p->d.transferActive;
407#endif
408	if (signal) {
409		node->p->d.signal(&node->p->d, 1 << node->id);
410	}
411
412	return 0;
413}
414
415static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
416	struct GBASIOLockstepNode* node = user;
417	mLockstepLock(&node->p->d);
418	if (node->p->d.attached < 2) {
419		mLockstepUnlock(&node->p->d);
420		return;
421	}
422	int32_t cycles = 0;
423	node->nextEvent -= cyclesLate;
424	node->eventDiff += cyclesLate;
425	if (node->nextEvent <= 0) {
426		if (!node->id) {
427			cycles = _masterUpdate(node);
428		} else {
429			cycles = _slaveUpdate(node);
430			cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff);
431		}
432		node->eventDiff = 0;
433	} else {
434		cycles = node->nextEvent;
435	}
436	if (cycles > 0) {
437		node->nextEvent = 0;
438		node->eventDiff += cycles;
439		mTimingDeschedule(timing, &node->event);
440		mTimingSchedule(timing, &node->event, cycles);
441	} else {
442		node->d.p->p->earlyExit = true;
443		node->eventDiff += 1;
444		mTimingSchedule(timing, &node->event, 1);
445	}
446
447	mLockstepUnlock(&node->p->d);
448}
449
450static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
451	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
452
453	mLockstepLock(&node->p->d);
454
455	if (address == REG_SIOCNT) {
456		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
457		value &= 0xFF8B;
458		if (!node->id) {
459			driver->p->siocnt = GBASIONormalFillSi(driver->p->siocnt);
460		}
461		if (value & 0x0080 && !node->id) {
462			// Internal shift clock
463			if (value & 1) {
464				ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
465			}
466			// Frequency
467			if (value & 2) {
468				node->p->d.transferCycles = GBA_ARM7TDMI_FREQUENCY / 1024;
469			} else {
470				node->p->d.transferCycles = GBA_ARM7TDMI_FREQUENCY / 8192;
471			}
472		}
473	} else if (address == REG_SIODATA32_LO) {
474		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04x", node->id, value);
475	} else if (address == REG_SIODATA32_HI) {
476		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04x", node->id, value);
477	}
478
479	mLockstepUnlock(&node->p->d);
480
481	return value;
482}