all repos — mgba @ a9ccb0fdd723cfa45a226925e2a059c05057389c

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 "lockstep.h"
  7
  8#include "gba/gba.h"
  9#include "gba/io.h"
 10#include "gba/video.h"
 11
 12#define LOCKSTEP_INCREMENT 3000
 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);
 21
 22void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) {
 23	lockstep->players[0] = 0;
 24	lockstep->players[1] = 0;
 25	lockstep->players[2] = 0;
 26	lockstep->players[3] = 0;
 27	lockstep->multiRecv[0] = 0xFFFF;
 28	lockstep->multiRecv[1] = 0xFFFF;
 29	lockstep->multiRecv[2] = 0xFFFF;
 30	lockstep->multiRecv[3] = 0xFFFF;
 31	lockstep->attached = 0;
 32	lockstep->attachedMulti = 0;
 33	lockstep->transferActive = 0;
 34#ifndef NDEBUG
 35	lockstep->transferId = 0;
 36#endif
 37}
 38
 39void GBASIOLockstepDeinit(struct GBASIOLockstep* lockstep) {
 40	UNUSED(lockstep);
 41}
 42
 43void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) {
 44	node->d.init = GBASIOLockstepNodeInit;
 45	node->d.deinit = GBASIOLockstepNodeDeinit;
 46	node->d.load = GBASIOLockstepNodeLoad;
 47	node->d.unload = GBASIOLockstepNodeUnload;
 48	node->d.writeRegister = 0;
 49}
 50
 51bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
 52	if (lockstep->attached == MAX_GBAS) {
 53		return false;
 54	}
 55	lockstep->players[lockstep->attached] = node;
 56	node->p = lockstep;
 57	node->id = lockstep->attached;
 58	++lockstep->attached;
 59	return true;
 60}
 61
 62void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
 63	if (lockstep->attached == 0) {
 64		return;
 65	}
 66	int i;
 67	for (i = 0; i < lockstep->attached; ++i) {
 68		if (lockstep->players[i] != node) {
 69			continue;
 70		}
 71		for (++i; i < lockstep->attached; ++i) {
 72			lockstep->players[i - 1] = lockstep->players[i];
 73			lockstep->players[i - 1]->id = i - 1;
 74		}
 75		--lockstep->attached;
 76		break;
 77	}
 78}
 79
 80bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) {
 81	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
 82	node->d.p->multiplayerControl.slave = 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	node->mode = driver->p->mode;
101	switch (node->mode) {
102	case SIO_MULTI:
103		node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;
104		node->d.p->rcnt |= 3;
105		++node->p->attachedMulti;
106		node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached;
107		if (node->id) {
108			node->d.p->rcnt |= 4;
109			node->d.p->multiplayerControl.slave = 1;
110		}
111		break;
112	case SIO_NORMAL_32:
113		node->d.writeRegister = GBASIOLockstepNodeNormalWriteRegister;
114		break;
115	default:
116		break;
117	}
118#ifndef NDEBUG
119	node->phase = node->p->transferActive;
120	node->transferId = node->p->transferId;
121#endif
122	return true;
123}
124
125bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
126	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
127	node->mode = driver->p->mode;
128	switch (node->mode) {
129	case SIO_MULTI:
130		--node->p->attachedMulti;
131		break;
132	default:
133		break;
134	}
135	node->p->unload(node->p, node->id);
136	mTimingDeschedule(&driver->p->p->timing, &node->event);
137	return true;
138}
139
140static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
141	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
142	if (address == REG_SIOCNT) {
143		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
144		if (value & 0x0080 && node->p->transferActive == TRANSFER_IDLE) {
145			if (!node->id && node->d.p->multiplayerControl.ready) {
146				mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
147				node->p->transferActive = TRANSFER_STARTING;
148				node->p->transferCycles = GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->attached - 1];
149				mTimingDeschedule(&driver->p->p->timing, &node->event);
150				mTimingSchedule(&driver->p->p->timing, &node->event, 0);
151			} else {
152				value &= ~0x0080;
153			}
154		}
155		value &= 0xFF83;
156		value |= driver->p->siocnt & 0x00FC;
157	} else if (address == REG_SIOMLT_SEND) {
158		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOMLT_SEND <- %04x", node->id, value);
159	}
160	return value;
161}
162
163static void _finishTransfer(struct GBASIOLockstepNode* node) {
164	if (node->transferFinished) {
165		return;
166	}
167	struct GBASIO* sio = node->d.p;
168	switch (node->mode) {
169	case SIO_MULTI:
170		sio->p->memory.io[REG_SIOMULTI0 >> 1] = node->p->multiRecv[0];
171		sio->p->memory.io[REG_SIOMULTI1 >> 1] = node->p->multiRecv[1];
172		sio->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2];
173		sio->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3];
174		sio->rcnt |= 1;
175		sio->multiplayerControl.busy = 0;
176		sio->multiplayerControl.id = node->id;
177		if (sio->multiplayerControl.irq) {
178			GBARaiseIRQ(sio->p, IRQ_SIO);
179		}
180		break;
181	case SIO_NORMAL_8:
182		// TODO
183		sio->normalControl.start = 0;
184		if (node->id) {
185			sio->normalControl.si = node->p->players[node->id - 1]->d.p->normalControl.idleSo;
186			node->d.p->p->memory.io[REG_SIODATA8 >> 1] = node->p->normalRecv[node->id - 1] & 0xFF;
187		} else {
188			node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF;
189		}
190		if (sio->multiplayerControl.irq) {
191			GBARaiseIRQ(sio->p, IRQ_SIO);
192		}
193		break;
194	case SIO_NORMAL_32:
195		// TODO
196		sio->normalControl.start = 0;
197		if (node->id) {
198			sio->normalControl.si = node->p->players[node->id - 1]->d.p->normalControl.idleSo;
199			node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = node->p->normalRecv[node->id - 1];
200			node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] |= node->p->normalRecv[node->id - 1] >> 16;
201		} else {
202			node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0xFFFF;
203			node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0xFFFF;
204		}
205		if (sio->multiplayerControl.irq) {
206			GBARaiseIRQ(sio->p, IRQ_SIO);
207		}
208		break;
209	default:
210		break;
211	}
212	node->transferFinished = true;
213#ifndef NDEBUG
214	++node->transferId;
215#endif
216}
217
218static int32_t _masterUpdate(struct GBASIOLockstepNode* node) {
219	bool needsToWait = false;
220	int i;
221	switch (node->p->transferActive) {
222	case TRANSFER_IDLE:
223		// If the master hasn't initiated a transfer, it can keep going.
224		node->nextEvent += LOCKSTEP_INCREMENT;
225		node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached;
226		break;
227	case TRANSFER_STARTING:
228		// Start the transfer, but wait for the other GBAs to catch up
229		node->transferFinished = false;
230		node->p->multiRecv[0] = 0xFFFF;
231		node->p->multiRecv[1] = 0xFFFF;
232		node->p->multiRecv[2] = 0xFFFF;
233		node->p->multiRecv[3] = 0xFFFF;
234		needsToWait = true;
235		ATOMIC_STORE(node->p->transferActive, TRANSFER_STARTED);
236		node->nextEvent += 512;
237		break;
238	case TRANSFER_STARTED:
239		// All the other GBAs have caught up and are sleeping, we can all continue now
240		node->p->multiRecv[0] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
241		node->nextEvent += 512;
242		ATOMIC_STORE(node->p->transferActive, TRANSFER_FINISHING);
243		break;
244	case TRANSFER_FINISHING:
245		// Finish the transfer
246		// We need to make sure the other GBAs catch up so they don't get behind
247		node->nextEvent += node->p->transferCycles - 1024; // Split the cycles to avoid waiting too long
248#ifndef NDEBUG
249		ATOMIC_ADD(node->p->transferId, 1);
250#endif
251		needsToWait = true;
252		ATOMIC_STORE(node->p->transferActive, TRANSFER_FINISHED);
253		break;
254	case TRANSFER_FINISHED:
255		// Everything's settled. We're done.
256		_finishTransfer(node);
257		node->nextEvent += LOCKSTEP_INCREMENT;
258		ATOMIC_STORE(node->p->transferActive, TRANSFER_IDLE);
259		break;
260	}
261	int mask = 0;
262	for (i = 1; i < node->p->attached; ++i) {
263		if (node->p->players[i]->mode == node->mode) {
264			mask |= 1 << i;
265		}
266	}
267	if (mask) {
268		if (needsToWait) {
269			if (!node->p->wait(node->p, mask)) {
270				abort();
271			}
272		} else {
273			node->p->signal(node->p, mask);
274		}
275	}
276	// Tell the other GBAs they can continue up to where we were
277	node->p->addCycles(node->p, 0, node->eventDiff);
278#ifndef NDEBUG
279	node->phase = node->p->transferActive;
280#endif
281	if (needsToWait) {
282		return 0;
283	}
284	return node->nextEvent;
285}
286
287static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
288	node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached;
289	bool signal = false;
290	switch (node->p->transferActive) {
291	case TRANSFER_IDLE:
292		if (!node->d.p->multiplayerControl.ready) {
293			node->p->addCycles(node->p, node->id, LOCKSTEP_INCREMENT);
294		}
295		break;
296	case TRANSFER_STARTING:
297	case TRANSFER_FINISHING:
298		break;
299	case TRANSFER_STARTED:
300		node->transferFinished = false;
301		switch (node->mode) {
302		case SIO_MULTI:
303			node->d.p->rcnt &= ~1;
304			node->p->multiRecv[node->id] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
305			node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;
306			node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
307			node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
308			node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
309			node->d.p->multiplayerControl.busy = 1;
310			break;
311		case SIO_NORMAL_8:
312			node->p->multiRecv[node->id] = 0xFFFF;
313			node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA8 >> 1] & 0xFF;
314			break;
315		case SIO_NORMAL_32:
316			node->p->multiRecv[node->id] = 0xFFFF;
317			node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA32_LO >> 1];
318			node->p->normalRecv[node->id] |= node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] << 16;
319			break;
320		default:
321			node->p->multiRecv[node->id] = 0xFFFF;
322			break;
323		}
324		signal = true;
325		break;
326	case TRANSFER_FINISHED:
327		_finishTransfer(node);
328		signal = true;
329		break;
330	}
331#ifndef NDEBUG
332	node->phase = node->p->transferActive;
333#endif
334	if (signal) {
335		node->p->signal(node->p, 1 << node->id);
336	}
337	return 0;
338}
339
340static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
341	struct GBASIOLockstepNode* node = user;
342	if (node->p->attached < 2) {
343		return;
344	}
345	int32_t cycles = 0;
346	node->nextEvent -= cyclesLate;
347	if (node->nextEvent <= 0) {
348		if (!node->id) {
349			cycles = _masterUpdate(node);
350		} else {
351			cycles = _slaveUpdate(node);
352			cycles += node->p->useCycles(node->p, node->id, node->eventDiff);
353		}
354		node->eventDiff = 0;
355	} else {
356		cycles = node->nextEvent;
357	}
358	if (cycles > 0) {
359		node->nextEvent = 0;
360		node->eventDiff += cycles;
361		mTimingDeschedule(timing, &node->event);
362		mTimingSchedule(timing, &node->event, cycles);
363	} else {
364		node->d.p->p->earlyExit = true;
365		mTimingSchedule(timing, &node->event, cyclesLate + 1);
366	}
367}
368
369static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
370	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
371	if (address == REG_SIOCNT) {
372		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
373		value &= 0xFF8B;
374		if (!node->id) {
375			driver->p->normalControl.si = 1;
376		}
377		if (value & 0x0080 && !node->id) {
378			// Internal shift clock
379			if (value & 1) {
380				node->p->transferActive = TRANSFER_STARTING;
381			}
382			// Frequency
383			if (value & 2) {
384				node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 1024;
385			} else {
386				node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 8192;
387			}
388		}
389	} else if (address == REG_SIODATA32_LO) {
390		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04x", node->id, value);
391	} else if (address == REG_SIODATA32_HI) {
392		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04x", node->id, value);
393	}
394	return value;
395}