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