all repos — mgba @ 0293e723d805c3255c2873f4a1cb9de0128ee217

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