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 <mgba/internal/gba/sio/lockstep.h>
7
8#include <mgba/internal/gba/gba.h>
9#include <mgba/internal/gba/io.h>
10
11#define LOCKSTEP_INCREMENT 2000
12#define LOCKSTEP_TRANSFER 512
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);
21static void _finishTransfer(struct GBASIOLockstepNode* node);
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->attachedMulti = 0;
33}
34
35void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) {
36 node->d.init = GBASIOLockstepNodeInit;
37 node->d.deinit = GBASIOLockstepNodeDeinit;
38 node->d.load = GBASIOLockstepNodeLoad;
39 node->d.unload = GBASIOLockstepNodeUnload;
40 node->d.writeRegister = 0;
41}
42
43bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
44 if (lockstep->d.attached == MAX_GBAS) {
45 return false;
46 }
47 lockstep->players[lockstep->d.attached] = node;
48 node->p = lockstep;
49 node->id = lockstep->d.attached;
50 node->transferFinished = true;
51 ++lockstep->d.attached;
52 return true;
53}
54
55void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
56 if (lockstep->d.attached == 0) {
57 return;
58 }
59 int i;
60 for (i = 0; i < lockstep->d.attached; ++i) {
61 if (lockstep->players[i] != node) {
62 continue;
63 }
64 for (++i; i < lockstep->d.attached; ++i) {
65 lockstep->players[i - 1] = lockstep->players[i];
66 lockstep->players[i - 1]->id = i - 1;
67 }
68 --lockstep->d.attached;
69 break;
70 }
71}
72
73bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) {
74 struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
75 node->d.p->siocnt = GBASIOMultiplayerSetSlave(node->d.p->siocnt, node->id > 0);
76 mLOG(GBA_SIO, DEBUG, "Lockstep %i: Node init", node->id);
77 node->event.context = node;
78 node->event.name = "GBA SIO Lockstep";
79 node->event.callback = _GBASIOLockstepNodeProcessEvents;
80 node->event.priority = 0x80;
81 return true;
82}
83
84void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver) {
85 UNUSED(driver);
86}
87
88bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
89 struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
90 node->nextEvent = 0;
91 node->eventDiff = 0;
92 mTimingSchedule(&driver->p->p->timing, &node->event, 0);
93
94 mLockstepLock(&node->p->d);
95
96 node->mode = driver->p->mode;
97
98 switch (node->mode) {
99 case SIO_MULTI:
100 node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;
101 node->d.p->rcnt |= 3;
102 ATOMIC_ADD(node->p->attachedMulti, 1);
103 node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, node->p->attachedMulti == node->p->d.attached);
104 if (node->id) {
105 node->d.p->rcnt |= 4;
106 node->d.p->siocnt = GBASIOMultiplayerFillSlave(node->d.p->siocnt);
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->d.transferActive;
117 node->transferId = node->p->d.transferId;
118#endif
119
120 mLockstepUnlock(&node->p->d);
121
122 return true;
123}
124
125bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
126 struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
127
128 mLockstepLock(&node->p->d);
129
130 node->mode = driver->p->mode;
131 switch (node->mode) {
132 case SIO_MULTI:
133 ATOMIC_SUB(node->p->attachedMulti, 1);
134 break;
135 default:
136 break;
137 }
138
139 // Flush ongoing transfer
140 if (mTimingIsScheduled(&driver->p->p->timing, &node->event)) {
141 int oldWhen = node->event.when;
142
143 mTimingDeschedule(&driver->p->p->timing, &node->event);
144 mTimingSchedule(&driver->p->p->timing, &node->event, 0);
145 node->eventDiff -= oldWhen - node->event.when;
146 mTimingDeschedule(&driver->p->p->timing, &node->event);
147 }
148
149 node->p->d.unload(&node->p->d, node->id);
150
151 node->p->multiRecv[0] = 0xFFFF;
152 node->p->multiRecv[1] = 0xFFFF;
153 node->p->multiRecv[2] = 0xFFFF;
154 node->p->multiRecv[3] = 0xFFFF;
155
156 _finishTransfer(node);
157
158 if (!node->id) {
159 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE);
160 }
161
162 // Invalidate SIO mode
163 node->mode = SIO_GPIO;
164
165 mLockstepUnlock(&node->p->d);
166
167 return true;
168}
169
170static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
171 struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
172
173 mLockstepLock(&node->p->d);
174
175 if (address == REG_SIOCNT) {
176 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
177
178 enum mLockstepPhase transferActive;
179 ATOMIC_LOAD(transferActive, node->p->d.transferActive);
180
181 if (value & 0x0080 && transferActive == TRANSFER_IDLE) {
182 if (!node->id && GBASIOMultiplayerIsReady(node->d.p->siocnt)) {
183 mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
184 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
185 ATOMIC_STORE(node->p->d.transferCycles, GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][node->p->d.attached - 1]);
186
187 bool scheduled = mTimingIsScheduled(&driver->p->p->timing, &node->event);
188 int oldWhen = node->event.when;
189
190 mTimingDeschedule(&driver->p->p->timing, &node->event);
191 mTimingSchedule(&driver->p->p->timing, &node->event, 0);
192
193 if (scheduled) {
194 node->eventDiff -= oldWhen - node->event.when;
195 }
196 } else {
197 value &= ~0x0080;
198 }
199 }
200 value &= 0xFF83;
201 value |= driver->p->siocnt & 0x00FC;
202 } else if (address == REG_SIOMLT_SEND) {
203 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOMLT_SEND <- %04x", node->id, value);
204 }
205
206 mLockstepUnlock(&node->p->d);
207
208 return value;
209}
210
211static void _finishTransfer(struct GBASIOLockstepNode* node) {
212 if (node->transferFinished) {
213 return;
214 }
215
216 struct GBASIO* sio = node->d.p;
217 switch (node->mode) {
218 case SIO_MULTI:
219 sio->p->memory.io[REG_SIOMULTI0 >> 1] = node->p->multiRecv[0];
220 sio->p->memory.io[REG_SIOMULTI1 >> 1] = node->p->multiRecv[1];
221 sio->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2];
222 sio->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3];
223 sio->rcnt |= 1;
224 sio->siocnt = GBASIOMultiplayerClearBusy(sio->siocnt);
225 sio->siocnt = GBASIOMultiplayerSetId(sio->siocnt, node->id);
226 if (GBASIOMultiplayerIsIrq(sio->siocnt)) {
227 GBARaiseIRQ(sio->p, IRQ_SIO, 0);
228 }
229 break;
230 case SIO_NORMAL_8:
231 // TODO
232 sio->siocnt = GBASIONormalClearStart(sio->siocnt);
233 if (node->id) {
234 sio->siocnt = GBASIONormalSetSi(sio->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt));
235 node->d.p->p->memory.io[REG_SIODATA8 >> 1] = node->p->normalRecv[node->id - 1] & 0xFF;
236 } else {
237 node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF;
238 }
239 if (GBASIONormalIsIrq(sio->siocnt)) {
240 GBARaiseIRQ(sio->p, IRQ_SIO, 0);
241 }
242 break;
243 case SIO_NORMAL_32:
244 // TODO
245 sio->siocnt = GBASIONormalClearStart(sio->siocnt);
246 if (node->id) {
247 sio->siocnt = GBASIONormalSetSi(sio->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt));
248 node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = node->p->normalRecv[node->id - 1];
249 node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] |= node->p->normalRecv[node->id - 1] >> 16;
250 } else {
251 node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0xFFFF;
252 node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0xFFFF;
253 }
254 if (GBASIONormalIsIrq(sio->siocnt)) {
255 GBARaiseIRQ(sio->p, IRQ_SIO, 0);
256 }
257 break;
258 default:
259 break;
260 }
261 node->transferFinished = true;
262#ifndef NDEBUG
263 ++node->transferId;
264#endif
265}
266
267static int32_t _masterUpdate(struct GBASIOLockstepNode* node) {
268 bool needsToWait = false;
269 int i;
270
271 enum mLockstepPhase transferActive;
272 int attachedMulti, attached;
273
274 ATOMIC_LOAD(transferActive, node->p->d.transferActive);
275 ATOMIC_LOAD(attachedMulti, node->p->attachedMulti);
276 ATOMIC_LOAD(attached, node->p->d.attached);
277
278 switch (transferActive) {
279 case TRANSFER_IDLE:
280 // If the master hasn't initiated a transfer, it can keep going.
281 node->nextEvent += LOCKSTEP_INCREMENT;
282 if (node->mode == SIO_MULTI) {
283 node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached);
284 }
285 break;
286 case TRANSFER_STARTING:
287 // Start the transfer, but wait for the other GBAs to catch up
288 node->transferFinished = false;
289 switch (node->mode) {
290 case SIO_MULTI:
291 node->p->multiRecv[0] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
292 node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;
293 node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
294 node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
295 node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
296 node->p->multiRecv[1] = 0xFFFF;
297 node->p->multiRecv[2] = 0xFFFF;
298 node->p->multiRecv[3] = 0xFFFF;
299 break;
300 case SIO_NORMAL_8:
301 node->p->multiRecv[0] = 0xFFFF;
302 node->p->normalRecv[0] = node->d.p->p->memory.io[REG_SIODATA8 >> 1] & 0xFF;
303 break;
304 case SIO_NORMAL_32:
305 node->p->multiRecv[0] = 0xFFFF;
306 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04x", node->id, node->d.p->p->memory.io[REG_SIODATA32_LO >> 1]);
307 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04x", node->id, node->d.p->p->memory.io[REG_SIODATA32_HI >> 1]);
308 node->p->normalRecv[0] = node->d.p->p->memory.io[REG_SIODATA32_LO >> 1];
309 node->p->normalRecv[0] |= node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] << 16;
310 break;
311 default:
312 node->p->multiRecv[0] = 0xFFFF;
313 break;
314 }
315 needsToWait = true;
316 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTED);
317 node->nextEvent += LOCKSTEP_TRANSFER;
318 break;
319 case TRANSFER_STARTED:
320 // All the other GBAs have caught up and are sleeping, we can all continue now
321 node->nextEvent += LOCKSTEP_TRANSFER;
322 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHING);
323 break;
324 case TRANSFER_FINISHING:
325 // Finish the transfer
326 // We need to make sure the other GBAs catch up so they don't get behind
327 node->nextEvent += node->p->d.transferCycles - 1024; // Split the cycles to avoid waiting too long
328#ifndef NDEBUG
329 ATOMIC_ADD(node->p->d.transferId, 1);
330#endif
331 needsToWait = true;
332 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHED);
333 break;
334 case TRANSFER_FINISHED:
335 // Everything's settled. We're done.
336 _finishTransfer(node);
337 node->nextEvent += LOCKSTEP_INCREMENT;
338 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE);
339 break;
340 }
341 int mask = 0;
342 for (i = 1; i < node->p->d.attached; ++i) {
343 if (node->p->players[i]->mode == node->mode) {
344 mask |= 1 << i;
345 }
346 }
347 if (mask) {
348 if (needsToWait) {
349 if (!node->p->d.wait(&node->p->d, mask)) {
350 abort();
351 }
352 } else {
353 node->p->d.signal(&node->p->d, mask);
354 }
355 }
356 // Tell the other GBAs they can continue up to where we were
357 node->p->d.addCycles(&node->p->d, 0, node->eventDiff);
358#ifndef NDEBUG
359 node->phase = node->p->d.transferActive;
360#endif
361
362 if (needsToWait) {
363 return 0;
364 }
365 return node->nextEvent;
366}
367
368static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
369 enum mLockstepPhase transferActive;
370 int attachedMulti, attached;
371
372 ATOMIC_LOAD(transferActive, node->p->d.transferActive);
373 ATOMIC_LOAD(attachedMulti, node->p->attachedMulti);
374 ATOMIC_LOAD(attached, node->p->d.attached);
375
376 node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached);
377 bool signal = false;
378 switch (transferActive) {
379 case TRANSFER_IDLE:
380 if (!GBASIOMultiplayerIsReady(node->d.p->siocnt)) {
381 node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT);
382 }
383 break;
384 case TRANSFER_STARTING:
385 case TRANSFER_FINISHING:
386 break;
387 case TRANSFER_STARTED:
388 if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
389 break;
390 }
391 node->transferFinished = false;
392 switch (node->mode) {
393 case SIO_MULTI:
394 node->d.p->rcnt &= ~1;
395 node->p->multiRecv[node->id] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
396 node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;
397 node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
398 node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
399 node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
400 node->d.p->siocnt = GBASIOMultiplayerFillBusy(node->d.p->siocnt);
401 break;
402 case SIO_NORMAL_8:
403 node->p->multiRecv[node->id] = 0xFFFF;
404 node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA8 >> 1] & 0xFF;
405 break;
406 case SIO_NORMAL_32:
407 node->p->multiRecv[node->id] = 0xFFFF;
408 node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA32_LO >> 1];
409 node->p->normalRecv[node->id] |= node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] << 16;
410 break;
411 default:
412 node->p->multiRecv[node->id] = 0xFFFF;
413 break;
414 }
415 signal = true;
416 break;
417 case TRANSFER_FINISHED:
418 if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
419 break;
420 }
421 _finishTransfer(node);
422 signal = true;
423 break;
424 }
425#ifndef NDEBUG
426 node->phase = node->p->d.transferActive;
427#endif
428 if (signal) {
429 node->p->d.signal(&node->p->d, 1 << node->id);
430 }
431
432 return 0;
433}
434
435static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
436 struct GBASIOLockstepNode* node = user;
437 mLockstepLock(&node->p->d);
438 if (node->p->d.attached < 2) {
439 mLockstepUnlock(&node->p->d);
440 return;
441 }
442 int32_t cycles = 0;
443 node->nextEvent -= cyclesLate;
444 node->eventDiff += cyclesLate;
445 if (node->nextEvent <= 0) {
446 if (!node->id) {
447 cycles = _masterUpdate(node);
448 } else {
449 cycles = _slaveUpdate(node);
450 cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff);
451 }
452 node->eventDiff = 0;
453 } else {
454 cycles = node->nextEvent;
455 }
456 if (cycles > 0) {
457 node->nextEvent = 0;
458 node->eventDiff += cycles;
459 mTimingDeschedule(timing, &node->event);
460 mTimingSchedule(timing, &node->event, cycles);
461 } else {
462 node->d.p->p->earlyExit = true;
463 node->eventDiff += 1;
464 mTimingSchedule(timing, &node->event, 1);
465 }
466
467 mLockstepUnlock(&node->p->d);
468}
469
470static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
471 struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
472
473 mLockstepLock(&node->p->d);
474
475 if (address == REG_SIOCNT) {
476 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
477 value &= 0xFF8B;
478 if (!node->id) {
479 value = GBASIONormalFillSi(value);
480 }
481 if (value & 0x0080 && !node->id) {
482 // Internal shift clock
483 if (value & 1) {
484 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
485 }
486 // Frequency
487 if (value & 2) {
488 node->p->d.transferCycles = GBA_ARM7TDMI_FREQUENCY / 1024;
489 } else {
490 node->p->d.transferCycles = GBA_ARM7TDMI_FREQUENCY / 8192;
491 }
492 }
493 } else if (address == REG_SIODATA32_LO) {
494 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04x", node->id, value);
495 } else if (address == REG_SIODATA32_HI) {
496 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04x", node->id, value);
497 }
498
499 mLockstepUnlock(&node->p->d);
500
501 return value;
502}