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}