all repos — mgba @ a89bb0b814072869723edc05cc1dcf8249dd2c9a

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
 11#define LOCKSTEP_INCREMENT 2048
 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 int32_t GBASIOLockstepNodeMultiProcessEvents(struct GBASIODriver* driver, int32_t cycles);
 19static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
 20static int32_t GBASIOLockstepNodeNormalProcessEvents(struct GBASIODriver* driver, int32_t cycles);
 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->loadedMulti = 0;
 33	lockstep->loadedNormal = 0;
 34	lockstep->transferActive = false;
 35	lockstep->waiting = 0;
 36	lockstep->nextEvent = LOCKSTEP_INCREMENT;
 37	ConditionInit(&lockstep->barrier);
 38	MutexInit(&lockstep->mutex);
 39}
 40
 41void GBASIOLockstepDeinit(struct GBASIOLockstep* lockstep) {
 42	ConditionDeinit(&lockstep->barrier);
 43	MutexDeinit(&lockstep->mutex);
 44}
 45
 46void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) {
 47	node->d.init = GBASIOLockstepNodeInit;
 48	node->d.deinit = GBASIOLockstepNodeDeinit;
 49	node->d.load = GBASIOLockstepNodeLoad;
 50	node->d.unload = GBASIOLockstepNodeUnload;
 51	node->d.writeRegister = 0;
 52	node->d.processEvents = 0;
 53}
 54
 55bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
 56	if (lockstep->attached == MAX_GBAS) {
 57		return false;
 58	}
 59	lockstep->players[lockstep->attached] = node;
 60	node->p = lockstep;
 61	node->id = lockstep->attached;
 62	++lockstep->attached;
 63	return true;
 64}
 65
 66void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
 67	if (lockstep->attached == 0) {
 68		return;
 69	}
 70	int i;
 71	for (i = 0; i < lockstep->attached; ++i) {
 72		if (lockstep->players[i] != node) {
 73			continue;
 74		}
 75		for (++i; i < lockstep->attached; ++i) {
 76			lockstep->players[i - 1] = lockstep->players[i];
 77			lockstep->players[i - 1]->id = i - 1;
 78		}
 79		--lockstep->attached;
 80		break;
 81	}
 82}
 83
 84bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) {
 85	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
 86	node->nextEvent = LOCKSTEP_INCREMENT;
 87	node->d.p->multiplayerControl.slave = node->id > 0;
 88	GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: Node init", node->id);
 89	return true;
 90}
 91
 92void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver) {
 93	UNUSED(driver);
 94}
 95
 96bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
 97	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
 98	node->state = LOCKSTEP_IDLE;
 99	node->mode = driver->p->mode;
100	MutexLock(&node->p->mutex);
101	switch (node->mode) {
102	case SIO_MULTI:
103		node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;
104		node->d.processEvents = GBASIOLockstepNodeMultiProcessEvents;
105		++node->p->loadedMulti;
106		node->d.p->rcnt |= 3;
107		if (node->id) {
108			node->d.p->rcnt |= 4;
109			node->d.p->multiplayerControl.slave = 1;
110		}
111		break;
112	case SIO_NORMAL_32:
113		node->d.writeRegister = GBASIOLockstepNodeNormalWriteRegister;
114		node->d.processEvents = GBASIOLockstepNodeNormalProcessEvents;
115		++node->p->loadedNormal;
116		break;
117	default:
118		break;
119	}
120	MutexUnlock(&node->p->mutex);
121	return true;
122}
123
124bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
125	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
126	MutexLock(&node->p->mutex);
127	switch (node->mode) {
128	case SIO_MULTI:
129		--node->p->loadedMulti;
130		break;
131	case SIO_NORMAL_32:
132		--node->p->loadedNormal;
133		break;
134	default:
135		break;
136	}
137	ConditionWake(&node->p->barrier);
138	MutexUnlock(&node->p->mutex);
139	return true;
140}
141
142static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
143	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
144	if (address == REG_SIOCNT) {
145		GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: SIOCNT <- %04x", node->id, value);
146		if (value & 0x0080) {
147			if (!node->id) {
148				GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: Transfer initiated", node->id);
149				MutexLock(&node->p->mutex);
150				node->p->transferActive = true;
151				node->p->transferCycles = GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->attached - 1];
152				node->multiSend = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
153				MutexUnlock(&node->p->mutex);
154			} else {
155				value &= ~0x0080;
156			}
157		}
158		value &= 0xFF83;
159		value |= driver->p->siocnt & 0x00FC;
160	} else if (address == REG_SIOMLT_SEND) {
161		GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: SIOMLT_SEND <- %04x", node->id, value);
162	}
163	return value;
164}
165
166static int32_t GBASIOLockstepNodeMultiProcessEvents(struct GBASIODriver* driver, int32_t cycles) {
167	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
168	node->nextEvent -= cycles;
169	while (node->nextEvent <= 0) {
170		MutexLock(&node->p->mutex);
171		++node->p->waiting;
172		if (node->p->waiting < node->p->loadedMulti) {
173			ConditionWait(&node->p->barrier, &node->p->mutex);
174		} else {
175			if (node->p->transferActive) {
176				node->p->transferCycles -= node->p->nextEvent;
177				if (node->p->transferCycles > 0) {
178					if (node->p->transferCycles < LOCKSTEP_INCREMENT) {
179						node->p->nextEvent = node->p->transferCycles;
180					}
181				} else {
182					node->p->nextEvent = LOCKSTEP_INCREMENT;
183					node->p->transferActive = false;
184					int i;
185					for (i = 0; i < node->p->attached; ++i) {
186						node->p->multiRecv[i] = node->p->players[i]->multiSend;
187						node->p->players[i]->state = LOCKSTEP_FINISHED;
188					}
189					for (; i < MAX_GBAS; ++i) {
190						node->p->multiRecv[i] = 0xFFFF;
191					}
192				}
193			}
194			node->p->waiting = 0;
195			ConditionWake(&node->p->barrier);
196		}
197		if (node->state == LOCKSTEP_FINISHED) {
198			GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: Finishing transfer: %04x %04x %04x %04x", node->id, node->p->multiRecv[0], node->p->multiRecv[1], node->p->multiRecv[2], node->p->multiRecv[3]);
199			node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = node->p->multiRecv[0];
200			node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = node->p->multiRecv[1];
201			node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2];
202			node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3];
203			node->d.p->rcnt |= 1;
204			node->state = LOCKSTEP_IDLE;
205			if (node->d.p->multiplayerControl.irq) {
206				GBARaiseIRQ(node->d.p->p, IRQ_SIO);
207			}
208			node->d.p->multiplayerControl.id = node->id;
209			node->d.p->multiplayerControl.busy = 0;
210		} else if (node->state == LOCKSTEP_IDLE && node->p->transferActive) {
211			node->state = LOCKSTEP_STARTED;
212			node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;
213			node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
214			node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
215			node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
216			node->d.p->rcnt &= ~1;
217			if (node->id) {
218				node->multiSend = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
219				node->d.p->multiplayerControl.busy = 1;
220			}
221		}
222		node->d.p->multiplayerControl.ready = node->p->loadedMulti == node->p->attached;
223		node->nextEvent += node->p->nextEvent;
224		MutexUnlock(&node->p->mutex);
225	}
226	return node->nextEvent;
227}
228
229static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
230	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
231	if (address == REG_SIOCNT) {
232		GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: SIOCNT <- %04x", node->id, value);
233		value &= 0xFF8B;
234		MutexLock(&node->p->mutex);
235		if (value & 0x0080) {
236			// Internal shift clock
237			if (value & 1) {
238				node->p->transferActive = true;
239			}
240			// Frequency
241			if (value & 2) {
242				node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 1024;
243			} else {
244				node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 8192;
245			}
246			node->normalSO = !!(value & 8);
247			// Opponent's SO
248			if (node->id) {
249				value |= node->p->players[node->id - 1]->normalSO << 2;
250			}
251		}
252		MutexUnlock(&node->p->mutex);
253	} else if (address == REG_SIODATA32_LO) {
254		GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: SIODATA32_LO <- %04x", node->id, value);
255	} else if (address == REG_SIODATA32_HI) {
256		GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: SIODATA32_HI <- %04x", node->id, value);
257	}
258	return value;
259}
260
261static int32_t GBASIOLockstepNodeNormalProcessEvents(struct GBASIODriver* driver, int32_t cycles) {
262	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
263	node->nextEvent -= cycles;
264	while (node->nextEvent <= 0) {
265		MutexLock(&node->p->mutex);
266		++node->p->waiting;
267		if (node->p->waiting < node->p->loadedNormal) {
268			ConditionWait(&node->p->barrier, &node->p->mutex);
269		} else {
270			if (node->p->transferActive) {
271				node->p->transferCycles -= node->p->nextEvent;
272				if (node->p->transferCycles > 0) {
273					if (node->p->transferCycles < LOCKSTEP_INCREMENT) {
274						node->p->nextEvent = node->p->transferCycles;
275					}
276				} else {
277					node->p->nextEvent = LOCKSTEP_INCREMENT;
278					node->p->transferActive = false;
279					int i;
280					for (i = 0; i < node->p->attached; ++i) {
281						node->p->players[i]->state = LOCKSTEP_FINISHED;
282					}
283				}
284			}
285			node->p->waiting = 0;
286			ConditionWake(&node->p->barrier);
287		}
288		if (node->state == LOCKSTEP_FINISHED) {
289			int i;
290			for (i = 1; i < node->p->loadedNormal; ++i) {
291				node->p->players[i]->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = node->p->players[i - 1]->d.p->p->memory.io[REG_SIODATA32_LO >> 1];
292				node->p->players[i]->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = node->p->players[i - 1]->d.p->p->memory.io[REG_SIODATA32_HI >> 1];
293			}
294			node->state = LOCKSTEP_IDLE;
295			if (node->d.p->normalControl.irq) {
296				GBARaiseIRQ(node->d.p->p, IRQ_SIO);
297			}
298			node->d.p->multiplayerControl.id = node->id;
299			node->d.p->normalControl.start = 0;
300		} else if (node->state == LOCKSTEP_IDLE && node->p->transferActive) {
301			node->state = LOCKSTEP_STARTED;
302		}
303		node->nextEvent += node->p->nextEvent;
304		MutexUnlock(&node->p->mutex);
305	}
306	return node->nextEvent;
307}