all repos — mgba @ 505157dca00d7b916b00410632eb282f8f7535be

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 GBASIOLockstepNodeWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
 18static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles);
 19
 20void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) {
 21	lockstep->players[0] = 0;
 22	lockstep->players[1] = 0;
 23	lockstep->players[2] = 0;
 24	lockstep->players[3] = 0;
 25	lockstep->multiRecv[0] = 0xFFFF;
 26	lockstep->multiRecv[1] = 0xFFFF;
 27	lockstep->multiRecv[2] = 0xFFFF;
 28	lockstep->multiRecv[3] = 0xFFFF;
 29	lockstep->attached = 0;
 30	lockstep->loaded = 0;
 31	lockstep->transferActive = false;
 32	lockstep->waiting = 0;
 33	lockstep->nextEvent = LOCKSTEP_INCREMENT;
 34	ConditionInit(&lockstep->barrier);
 35	MutexInit(&lockstep->mutex);
 36}
 37
 38void GBASIOLockstepDeinit(struct GBASIOLockstep* lockstep) {
 39	ConditionDeinit(&lockstep->barrier);
 40	MutexDeinit(&lockstep->mutex);
 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 = GBASIOLockstepNodeWriteRegister;
 49	node->d.processEvents = GBASIOLockstepNodeProcessEvents;
 50}
 51
 52bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
 53	if (lockstep->attached == MAX_GBAS) {
 54		return false;
 55	}
 56	lockstep->players[lockstep->attached] = node;
 57	node->p = lockstep;
 58	node->id = lockstep->attached;
 59	++lockstep->attached;
 60	return true;
 61}
 62
 63void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
 64	if (lockstep->attached == 0) {
 65		return;
 66	}
 67	int i;
 68	for (i = 0; i < lockstep->attached; ++i) {
 69		if (lockstep->players[i] != node) {
 70			continue;
 71		}
 72		for (++i; i < lockstep->attached; ++i) {
 73			lockstep->players[i - 1] = lockstep->players[i];
 74			lockstep->players[i - 1]->id = i - 1;
 75		}
 76		--lockstep->attached;
 77		break;
 78	}
 79}
 80
 81bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) {
 82	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
 83	node->nextEvent = LOCKSTEP_INCREMENT;
 84	node->d.p->multiplayerControl.slave = node->id > 0;
 85	GBALog(node->d.p->p, GBA_LOG_SIO, "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->state = LOCKSTEP_IDLE;
 96	MutexLock(&node->p->mutex);
 97	++node->p->loaded;
 98	node->d.p->rcnt |= 3;
 99	if (node->id) {
100		node->d.p->rcnt |= 4;
101		node->d.p->multiplayerControl.slave = 1;
102	}
103	MutexUnlock(&node->p->mutex);
104	return true;
105}
106
107bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
108	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
109	MutexLock(&node->p->mutex);
110	--node->p->loaded;
111	ConditionWake(&node->p->barrier);
112	MutexUnlock(&node->p->mutex);
113	return true;
114}
115
116static uint16_t GBASIOLockstepNodeWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
117	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
118	if (address == REG_SIOCNT) {
119		GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: SIOCNT <- %04x", node->id, value);
120		if (value & 0x0080) {
121			if (!node->id) {
122				GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: Transfer initiated", node->id);
123				MutexLock(&node->p->mutex);
124				node->p->transferActive = true;
125				node->p->transferCycles = GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->attached - 1];
126				node->multiSend = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
127				MutexUnlock(&node->p->mutex);
128			} else {
129				value &= ~0x0080;
130			}
131		}
132		value &= 0xFF83;
133		value |= driver->p->siocnt & 0x00FC;
134	} else if (address == REG_SIOMLT_SEND) {
135		GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: SIOMLT_SEND <- %04x", node->id, value);
136	}
137	return value;
138}
139
140static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles) {
141	struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
142	node->nextEvent -= cycles;
143	while (node->nextEvent <= 0) {
144		MutexLock(&node->p->mutex);
145		++node->p->waiting;
146		if (node->p->waiting < node->p->loaded) {
147			ConditionWait(&node->p->barrier, &node->p->mutex);
148		} else {
149			if (node->p->transferActive) {
150				node->p->transferCycles -= node->p->nextEvent;
151				if (node->p->transferCycles > 0) {
152					if (node->p->transferCycles < LOCKSTEP_INCREMENT) {
153						node->p->nextEvent = node->p->transferCycles;
154					}
155				} else {
156					node->p->nextEvent = LOCKSTEP_INCREMENT;
157					node->p->transferActive = false;
158					int i;
159					for (i = 0; i < node->p->attached; ++i) {
160						node->p->multiRecv[i] = node->p->players[i]->multiSend;
161						node->p->players[i]->state = LOCKSTEP_FINISHED;
162					}
163					for (; i < MAX_GBAS; ++i) {
164						node->p->multiRecv[i] = 0xFFFF;
165					}
166				}
167			}
168			node->p->waiting = 0;
169			ConditionWake(&node->p->barrier);
170		}
171		if (node->state == LOCKSTEP_FINISHED) {
172			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]);
173			node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = node->p->multiRecv[0];
174			node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = node->p->multiRecv[1];
175			node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2];
176			node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3];
177			node->d.p->rcnt |= 1;
178			node->state = LOCKSTEP_IDLE;
179			if (node->d.p->multiplayerControl.irq) {
180				GBARaiseIRQ(node->d.p->p, IRQ_SIO);
181			}
182			node->d.p->multiplayerControl.id = node->id;
183			node->d.p->multiplayerControl.busy = 0;
184		} else if (node->state == LOCKSTEP_IDLE && node->p->transferActive) {
185			node->state = LOCKSTEP_STARTED;
186			node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;
187			node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
188			node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
189			node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
190			node->d.p->rcnt &= ~1;
191			if (node->id) {
192				node->multiSend = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
193				node->d.p->multiplayerControl.busy = 1;
194			}
195		}
196		node->d.p->multiplayerControl.ready = node->p->loaded == node->p->attached;
197		node->nextEvent += node->p->nextEvent;
198		MutexUnlock(&node->p->mutex);
199	}
200	return node->nextEvent;
201}