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 mLOG(GBA_SIO, DEBUG, "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 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
146 if (value & 0x0080) {
147 if (!node->id) {
148 mLOG(GBA_SIO, DEBUG, "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 mLOG(GBA_SIO, DEBUG, "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 mLOG(GBA_SIO, DEBUG, "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 mLOG(GBA_SIO, DEBUG, "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 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04x", node->id, value);
255 } else if (address == REG_SIODATA32_HI) {
256 mLOG(GBA_SIO, DEBUG, "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}