all repos — mgba @ 08b78cb468d97102d896c68a07a25f705faaebc7

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	if (node->id) {
133		node->p->signal(node->p, 1 << node->id);
134	} else {
135		node->p->addCycles(node->p, 0, node->eventDiff);
136	}
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				node->nextEvent = 0;
150			} else {
151				value &= ~0x0080;
152			}
153		}
154		value &= 0xFF83;
155		value |= driver->p->siocnt & 0x00FC;
156	} else if (address == REG_SIOMLT_SEND) {
157		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOMLT_SEND <- %04x", node->id, value);
158	}
159	return value;
160}
161
162static void _finishTransfer(struct GBASIOLockstepNode* node) {
163	if (node->transferFinished) {
164		return;
165	}
166	struct GBASIO* sio = node->d.p;
167	switch (node->mode) {
168	case SIO_MULTI:
169		sio->p->memory.io[REG_SIOMULTI0 >> 1] = node->p->multiRecv[0];
170		sio->p->memory.io[REG_SIOMULTI1 >> 1] = node->p->multiRecv[1];
171		sio->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2];
172		sio->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3];
173		sio->rcnt |= 1;
174		sio->multiplayerControl.busy = 0;
175		sio->multiplayerControl.id = node->id;
176		if (sio->multiplayerControl.irq) {
177			GBARaiseIRQ(sio->p, IRQ_SIO);
178		}
179		break;
180	case SIO_NORMAL_8:
181		// TODO
182		sio->normalControl.start = 0;
183		if (node->id) {
184			sio->normalControl.si = node->p->players[node->id - 1]->d.p->normalControl.idleSo;
185			node->d.p->p->memory.io[REG_SIODATA8 >> 1] = node->p->normalRecv[node->id - 1] & 0xFF;
186		} else {
187			node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF;
188		}
189		if (sio->multiplayerControl.irq) {
190			GBARaiseIRQ(sio->p, IRQ_SIO);
191		}
192		break;
193	case SIO_NORMAL_32:
194		// TODO
195		sio->normalControl.start = 0;
196		if (node->id) {
197			sio->normalControl.si = node->p->players[node->id - 1]->d.p->normalControl.idleSo;
198			node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = node->p->normalRecv[node->id - 1];
199			node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] |= node->p->normalRecv[node->id - 1] >> 16;
200		} else {
201			node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0xFFFF;
202			node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0xFFFF;
203		}
204		if (sio->multiplayerControl.irq) {
205			GBARaiseIRQ(sio->p, IRQ_SIO);
206		}
207		break;
208	default:
209		break;
210	}
211	node->transferFinished = true;
212#ifndef NDEBUG
213	++node->transferId;
214#endif
215}
216
217static int32_t _masterUpdate(struct GBASIOLockstepNode* node) {
218	bool needsToWait = false;
219	int i;
220	switch (node->p->transferActive) {
221	case TRANSFER_IDLE:
222		// If the master hasn't initiated a transfer, it can keep going.
223		node->nextEvent += LOCKSTEP_INCREMENT;
224		node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached;
225		break;
226	case TRANSFER_STARTING:
227		// Start the transfer, but wait for the other GBAs to catch up
228		node->transferFinished = false;
229		node->p->multiRecv[0] = 0xFFFF;
230		node->p->multiRecv[1] = 0xFFFF;
231		node->p->multiRecv[2] = 0xFFFF;
232		node->p->multiRecv[3] = 0xFFFF;
233		needsToWait = true;
234		ATOMIC_STORE(node->p->transferActive, TRANSFER_STARTED);
235		node->nextEvent += 512;
236		break;
237	case TRANSFER_STARTED:
238		// All the other GBAs have caught up and are sleeping, we can all continue now
239		node->p->multiRecv[0] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
240		node->nextEvent += 512;
241		ATOMIC_STORE(node->p->transferActive, TRANSFER_FINISHING);
242		break;
243	case TRANSFER_FINISHING:
244		// Finish the transfer
245		// We need to make sure the other GBAs catch up so they don't get behind
246		node->nextEvent += node->p->transferCycles - 1024; // Split the cycles to avoid waiting too long
247#ifndef NDEBUG
248		ATOMIC_ADD(node->p->transferId, 1);
249#endif
250		needsToWait = true;
251		ATOMIC_STORE(node->p->transferActive, TRANSFER_FINISHED);
252		break;
253	case TRANSFER_FINISHED:
254		// Everything's settled. We're done.
255		_finishTransfer(node);
256		node->nextEvent += LOCKSTEP_INCREMENT;
257		ATOMIC_STORE(node->p->transferActive, TRANSFER_IDLE);
258		break;
259	}
260	int mask = 0;
261	for (i = 1; i < node->p->attached; ++i) {
262		if (node->p->players[i]->mode == node->mode) {
263			mask |= 1 << i;
264		}
265	}
266	if (mask) {
267		if (needsToWait) {
268			if (!node->p->wait(node->p, mask)) {
269				abort();
270			}
271		} else {
272			node->p->signal(node->p, mask);
273		}
274	}
275	// Tell the other GBAs they can continue up to where we were
276	node->p->addCycles(node->p, 0, node->eventDiff);
277#ifndef NDEBUG
278	node->phase = node->p->transferActive;
279#endif
280	if (needsToWait) {
281		return 0;
282	}
283	return node->nextEvent;
284}
285
286static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
287	node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached;
288	bool signal = false;
289	switch (node->p->transferActive) {
290	case TRANSFER_IDLE:
291		if (!node->d.p->multiplayerControl.ready) {
292			node->p->addCycles(node->p, node->id, LOCKSTEP_INCREMENT);
293		}
294		break;
295	case TRANSFER_STARTING:
296	case TRANSFER_FINISHING:
297		break;
298	case TRANSFER_STARTED:
299		node->transferFinished = false;
300		switch (node->mode) {
301		case SIO_MULTI:
302			node->d.p->rcnt &= ~1;
303			node->p->multiRecv[node->id] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
304			node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;
305			node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
306			node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
307			node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
308			node->d.p->multiplayerControl.busy = 1;
309			break;
310		case SIO_NORMAL_8:
311			node->p->multiRecv[node->id] = 0xFFFF;
312			node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA8 >> 1] & 0xFF;
313			break;
314		case SIO_NORMAL_32:
315			node->p->multiRecv[node->id] = 0xFFFF;
316			node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA32_LO >> 1];
317			node->p->normalRecv[node->id] |= node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] << 16;
318			break;
319		default:
320			node->p->multiRecv[node->id] = 0xFFFF;
321			break;
322		}
323		signal = true;
324		break;
325	case TRANSFER_FINISHED:
326		_finishTransfer(node);
327		signal = true;
328		break;
329	}
330#ifndef NDEBUG
331	node->phase = node->p->transferActive;
332#endif
333	if (signal) {
334		node->p->signal(node->p, 1 << node->id);
335	}
336	return 0;
337}
338
339static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles) {
340	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
341	if (node->p->attached < 2) {
342		return INT_MAX;
343	}
344	node->eventDiff += cycles;
345	node->nextEvent -= cycles;
346	if (node->nextEvent <= 0) {
347		if (!node->id) {
348			cycles = _masterUpdate(node);
349		} else {
350			cycles = _slaveUpdate(node);
351			node->nextEvent += node->p->useCycles(node->p, node->id, node->eventDiff);
352		}
353		node->eventDiff = 0;
354	} else {
355		cycles = node->nextEvent;
356	}
357	if (cycles < 0) {
358		return 0;
359	}
360	return cycles;
361}
362
363static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
364	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
365	if (address == REG_SIOCNT) {
366		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
367		value &= 0xFF8B;
368		if (!node->id) {
369			driver->p->normalControl.si = 1;
370		}
371		if (value & 0x0080 && !node->id) {
372			// Internal shift clock
373			if (value & 1) {
374				node->p->transferActive = TRANSFER_STARTING;
375			}
376			// Frequency
377			if (value & 2) {
378				node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 1024;
379			} else {
380				node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 8192;
381			}
382		}
383	} else if (address == REG_SIODATA32_LO) {
384		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04x", node->id, value);
385	} else if (address == REG_SIODATA32_HI) {
386		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04x", node->id, value);
387	}
388	return value;
389}