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