all repos — mgba @ fc3e47a4bab1465b91e7368c6396b222f65f51cb

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