all repos — mgba @ bb7f41e8cca003d57f7470293a9951d9e027a8a5

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