all repos — mgba @ 327c3e78c6f3b4efb60a79c77e36c376754d2085

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	return true;
 88}
 89
 90void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver) {
 91	UNUSED(driver);
 92}
 93
 94bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
 95	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
 96	node->nextEvent = 0;
 97	node->eventDiff = 0;
 98	mTimingSchedule(&driver->p->p->timing, &node->event, 0);
 99	node->mode = driver->p->mode;
100	switch (node->mode) {
101	case SIO_MULTI:
102		node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;
103		node->d.p->rcnt |= 3;
104		++node->p->attachedMulti;
105		node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached;
106		if (node->id) {
107			node->d.p->rcnt |= 4;
108			node->d.p->multiplayerControl.slave = 1;
109		}
110		break;
111	case SIO_NORMAL_32:
112		node->d.writeRegister = GBASIOLockstepNodeNormalWriteRegister;
113		break;
114	default:
115		break;
116	}
117#ifndef NDEBUG
118	node->phase = node->p->transferActive;
119	node->transferId = node->p->transferId;
120#endif
121	return true;
122}
123
124bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
125	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
126	node->mode = driver->p->mode;
127	switch (node->mode) {
128	case SIO_MULTI:
129		--node->p->attachedMulti;
130		break;
131	default:
132		break;
133	}
134	node->p->unload(node->p, node->id);
135	mTimingDeschedule(&driver->p->p->timing, &node->event);
136	return true;
137}
138
139static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
140	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
141	if (address == REG_SIOCNT) {
142		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
143		if (value & 0x0080 && node->p->transferActive == TRANSFER_IDLE) {
144			if (!node->id && node->d.p->multiplayerControl.ready) {
145				mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
146				node->p->transferActive = TRANSFER_STARTING;
147				node->p->transferCycles = GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->attached - 1];
148				mTimingDeschedule(&driver->p->p->timing, &node->event);
149				mTimingSchedule(&driver->p->p->timing, &node->event, 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 void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
340	struct GBASIOLockstepNode* node = user;
341	if (node->p->attached < 2) {
342		return;
343	}
344	int32_t cycles = 0;
345	node->nextEvent -= cyclesLate;
346	if (node->nextEvent <= 0) {
347		if (!node->id) {
348			cycles = _masterUpdate(node);
349		} else {
350			cycles = _slaveUpdate(node);
351			cycles += 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		node->nextEvent = 0;
359		node->eventDiff += cycles;
360		mTimingDeschedule(timing, &node->event);
361		mTimingSchedule(timing, &node->event, cycles);
362	} else {
363		node->d.p->p->earlyExit = true;
364		mTimingSchedule(timing, &node->event, cyclesLate + 1);
365	}
366}
367
368static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
369	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
370	if (address == REG_SIOCNT) {
371		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
372		value &= 0xFF8B;
373		if (!node->id) {
374			driver->p->normalControl.si = 1;
375		}
376		if (value & 0x0080 && !node->id) {
377			// Internal shift clock
378			if (value & 1) {
379				node->p->transferActive = TRANSFER_STARTING;
380			}
381			// Frequency
382			if (value & 2) {
383				node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 1024;
384			} else {
385				node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 8192;
386			}
387		}
388	} else if (address == REG_SIODATA32_LO) {
389		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04x", node->id, value);
390	} else if (address == REG_SIODATA32_HI) {
391		mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04x", node->id, value);
392	}
393	return value;
394}