all repos — mgba @ 66f2c3555fa9a000429397c0a6db90eea88e90f4

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		node->eventDiff -= node->event.when - mTimingCurrentTime(&driver->p->p->timing);
153		mTimingDeschedule(&driver->p->p->timing, &node->event);
154	}
155
156	node->p->d.unload(&node->p->d, node->id);
157
158	_finishTransfer(node);
159
160	if (!node->id) {
161		ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE);
162	}
163
164	// Invalidate SIO mode
165	node->mode = SIO_GPIO;
166
167	mLockstepUnlock(&node->p->d);
168
169	return true;
170}
171
172static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
173	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
174
175	mLockstepLock(&node->p->d);
176
177	if (address == REG_SIOCNT) {
178		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04X", node->id, value);
179
180		enum mLockstepPhase transferActive;
181		ATOMIC_LOAD(transferActive, node->p->d.transferActive);
182
183		if (value & 0x0080 && transferActive == TRANSFER_IDLE) {
184			if (!node->id && GBASIOMultiplayerIsReady(node->d.p->siocnt)) {
185				mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
186				ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
187				ATOMIC_STORE(node->p->d.transferCycles, GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][node->p->d.attached - 1]);
188
189				if (mTimingIsScheduled(&driver->p->p->timing, &node->event)) {
190					node->eventDiff -= node->event.when - mTimingCurrentTime(&driver->p->p->timing);
191					mTimingDeschedule(&driver->p->p->timing, &node->event);
192				}
193				mTimingSchedule(&driver->p->p->timing, &node->event, 0);
194			} else {
195				value &= ~0x0080;
196			}
197		}
198		value &= 0xFF83;
199		value |= driver->p->siocnt & 0x00FC;
200	} else if (address == REG_SIOMLT_SEND) {
201		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOMLT_SEND <- %04X", node->id, value);
202	} else {
203		mLOG(GBA_SIO, STUB, "Lockstep %i: Unknown reg %03X <- %04X", node->id, address, 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 attached;
371	int attachedMode;
372
373	ATOMIC_LOAD(transferActive, node->p->d.transferActive);
374	ATOMIC_LOAD(attached, node->p->d.attached);
375
376	if (node->mode == SIO_MULTI) {
377		ATOMIC_LOAD(attachedMode, node->p->attachedMulti);
378		node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMode == attached);
379	} else {
380		ATOMIC_LOAD(attachedMode, node->p->attachedNormal);
381	}
382	bool signal = false;
383	switch (transferActive) {
384	case TRANSFER_IDLE:
385		if (attachedMode != attached) {
386			node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT);
387		}
388		break;
389	case TRANSFER_STARTING:
390	case TRANSFER_FINISHING:
391		break;
392	case TRANSFER_STARTED:
393		if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
394			break;
395		}
396		node->transferFinished = false;
397		switch (node->mode) {
398		case SIO_MULTI:
399			node->d.p->rcnt &= ~1;
400			node->p->multiRecv[node->id] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
401			node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;
402			node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
403			node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
404			node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
405			node->d.p->siocnt = GBASIOMultiplayerFillBusy(node->d.p->siocnt);
406			break;
407		case SIO_NORMAL_8:
408			node->p->multiRecv[node->id] = 0xFFFF;
409			node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA8 >> 1] & 0xFF;
410			break;
411		case SIO_NORMAL_32:
412			node->p->multiRecv[node->id] = 0xFFFF;
413			node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA32_LO >> 1];
414			node->p->normalRecv[node->id] |= node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] << 16;
415			break;
416		default:
417			node->p->multiRecv[node->id] = 0xFFFF;
418			break;
419		}
420		signal = true;
421		break;
422	case TRANSFER_FINISHED:
423		if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
424			break;
425		}
426		_finishTransfer(node);
427		signal = true;
428		break;
429	}
430#ifndef NDEBUG
431	node->phase = node->p->d.transferActive;
432#endif
433	if (signal) {
434		node->p->d.signal(&node->p->d, 1 << node->id);
435	}
436
437	return 0;
438}
439
440static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
441	struct GBASIOLockstepNode* node = user;
442	mLockstepLock(&node->p->d);
443
444	int32_t cycles = 0;
445	node->nextEvent -= cyclesLate;
446	node->eventDiff += cyclesLate;
447	if (node->p->d.attached < 2) {
448		cycles = GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][0];
449	} else if (node->nextEvent <= 0) {
450		if (!node->id) {
451			cycles = _masterUpdate(node);
452		} else {
453			cycles = _slaveUpdate(node);
454			cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff);
455		}
456		node->eventDiff = 0;
457	} else {
458		cycles = node->nextEvent;
459	}
460	if (cycles > 0) {
461		node->nextEvent = 0;
462		node->eventDiff += cycles;
463		mTimingDeschedule(timing, &node->event);
464		mTimingSchedule(timing, &node->event, cycles);
465	} else {
466		node->d.p->p->earlyExit = true;
467		node->eventDiff += 1;
468		mTimingSchedule(timing, &node->event, 1);
469	}
470
471	mLockstepUnlock(&node->p->d);
472}
473
474static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
475	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
476
477	mLockstepLock(&node->p->d);
478
479	if (address == REG_SIOCNT) {
480		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04X", node->id, value);
481		value &= 0xFF8B;
482		if (!node->id) {
483			value = GBASIONormalClearSi(value);
484		}
485		if (value & 0x0080) {
486			if (!node->id) {
487				// Frequency
488				int32_t cycles;
489				if (value & 2) {
490					cycles = 8 * 8;
491				} else {
492					cycles = 64 * 8;
493				}
494				if (value & 0x1000) {
495					cycles *= 4;
496				}
497
498				enum mLockstepPhase transferActive;
499				ATOMIC_LOAD(transferActive, node->p->d.transferActive);
500
501				if (transferActive == TRANSFER_IDLE) {
502					mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
503					ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
504					ATOMIC_STORE(node->p->d.transferCycles, cycles);
505
506					if (mTimingIsScheduled(&driver->p->p->timing, &node->event)) {
507						node->eventDiff -= node->event.when - mTimingCurrentTime(&driver->p->p->timing);
508						mTimingDeschedule(&driver->p->p->timing, &node->event);
509					}
510					mTimingSchedule(&driver->p->p->timing, &node->event, 0);
511				} else {
512					value &= ~0x0080;
513				}
514			} else {
515
516			}
517		}
518	} else if (address == REG_SIODATA32_LO) {
519		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04X", node->id, value);
520	} else if (address == REG_SIODATA32_HI) {
521		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04X", node->id, value);
522	}
523
524	mLockstepUnlock(&node->p->d);
525
526	return value;
527}