all repos — mgba @ 463ce997397850e6ea5ca27fb5c806e2edca4e89

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