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 lockstep->attachedNormal = 0;
34}
35
36void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) {
37 node->d.init = GBASIOLockstepNodeInit;
38 node->d.deinit = GBASIOLockstepNodeDeinit;
39 node->d.load = GBASIOLockstepNodeLoad;
40 node->d.unload = GBASIOLockstepNodeUnload;
41 node->d.writeRegister = 0;
42}
43
44bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
45 if (lockstep->d.attached == MAX_GBAS) {
46 return false;
47 }
48 mLockstepLock(&lockstep->d);
49 lockstep->players[lockstep->d.attached] = node;
50 node->p = lockstep;
51 node->id = lockstep->d.attached;
52 node->normalSO = true;
53 node->transferFinished = true;
54 ++lockstep->d.attached;
55 mLockstepUnlock(&lockstep->d);
56 return true;
57}
58
59void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
60 if (lockstep->d.attached == 0) {
61 return;
62 }
63 mLockstepLock(&lockstep->d);
64 int i;
65 for (i = 0; i < lockstep->d.attached; ++i) {
66 if (lockstep->players[i] != node) {
67 continue;
68 }
69 for (++i; i < lockstep->d.attached; ++i) {
70 lockstep->players[i - 1] = lockstep->players[i];
71 lockstep->players[i - 1]->id = i - 1;
72 }
73 --lockstep->d.attached;
74 lockstep->players[lockstep->d.attached] = NULL;
75 break;
76 }
77 mLockstepUnlock(&lockstep->d);
78}
79
80bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) {
81 struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
82 node->d.p->siocnt = GBASIOMultiplayerSetSlave(node->d.p->siocnt, 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 node->event.priority = 0x80;
88 return true;
89}
90
91void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver) {
92 UNUSED(driver);
93}
94
95bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
96 struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
97 node->nextEvent = 0;
98 node->eventDiff = 0;
99 mTimingSchedule(&driver->p->p->timing, &node->event, 0);
100
101 mLockstepLock(&node->p->d);
102
103 node->mode = driver->p->mode;
104
105 switch (node->mode) {
106 case SIO_MULTI:
107 node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;
108 node->d.p->rcnt |= 3;
109 ATOMIC_ADD(node->p->attachedMulti, 1);
110 node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, node->p->attachedMulti == node->p->d.attached);
111 if (node->id) {
112 node->d.p->rcnt |= 4;
113 node->d.p->siocnt = GBASIOMultiplayerFillSlave(node->d.p->siocnt);
114 }
115 break;
116 case SIO_NORMAL_32:
117 ATOMIC_ADD(node->p->attachedNormal, 1);
118 node->d.writeRegister = GBASIOLockstepNodeNormalWriteRegister;
119 break;
120 default:
121 break;
122 }
123#ifndef NDEBUG
124 node->phase = node->p->d.transferActive;
125 node->transferId = node->p->d.transferId;
126#endif
127
128 mLockstepUnlock(&node->p->d);
129
130 return true;
131}
132
133bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
134 struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
135
136 mLockstepLock(&node->p->d);
137
138 node->mode = driver->p->mode;
139 switch (node->mode) {
140 case SIO_MULTI:
141 ATOMIC_SUB(node->p->attachedMulti, 1);
142 break;
143 case SIO_NORMAL_32:
144 ATOMIC_SUB(node->p->attachedNormal, 1);
145 break;
146 default:
147 break;
148 }
149
150 // Flush ongoing transfer
151 if (mTimingIsScheduled(&driver->p->p->timing, &node->event)) {
152 node->eventDiff -= node->event.when - mTimingCurrentTime(&driver->p->p->timing);
153 mTimingDeschedule(&driver->p->p->timing, &node->event);
154 }
155
156 node->p->d.unload(&node->p->d, node->id);
157
158 _finishTransfer(node);
159
160 if (!node->id) {
161 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE);
162 }
163
164 // Invalidate SIO mode
165 node->mode = SIO_GPIO;
166
167 mLockstepUnlock(&node->p->d);
168
169 return true;
170}
171
172static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
173 struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
174
175 mLockstepLock(&node->p->d);
176
177 if (address == REG_SIOCNT) {
178 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04X", node->id, value);
179
180 enum mLockstepPhase transferActive;
181 ATOMIC_LOAD(transferActive, node->p->d.transferActive);
182
183 if (value & 0x0080 && transferActive == TRANSFER_IDLE) {
184 if (!node->id && GBASIOMultiplayerIsReady(node->d.p->siocnt)) {
185 mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
186 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
187 ATOMIC_STORE(node->p->d.transferCycles, GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][node->p->d.attached - 1]);
188
189 if (mTimingIsScheduled(&driver->p->p->timing, &node->event)) {
190 node->eventDiff -= node->event.when - mTimingCurrentTime(&driver->p->p->timing);
191 mTimingDeschedule(&driver->p->p->timing, &node->event);
192 }
193 mTimingSchedule(&driver->p->p->timing, &node->event, 0);
194 } else {
195 value &= ~0x0080;
196 }
197 }
198 value &= 0xFF83;
199 value |= driver->p->siocnt & 0x00FC;
200 } else if (address == REG_SIOMLT_SEND) {
201 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOMLT_SEND <- %04X", node->id, value);
202 } else {
203 mLOG(GBA_SIO, STUB, "Lockstep %i: Unknown reg %03X <- %04X", node->id, address, 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 attached;
371 int attachedMode;
372
373 ATOMIC_LOAD(transferActive, node->p->d.transferActive);
374 ATOMIC_LOAD(attached, node->p->d.attached);
375
376 if (node->mode == SIO_MULTI) {
377 ATOMIC_LOAD(attachedMode, node->p->attachedMulti);
378 node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMode == attached);
379 } else {
380 ATOMIC_LOAD(attachedMode, node->p->attachedNormal);
381 }
382 bool signal = false;
383 switch (transferActive) {
384 case TRANSFER_IDLE:
385 if (attachedMode != attached) {
386 node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT);
387 }
388 break;
389 case TRANSFER_STARTING:
390 case TRANSFER_FINISHING:
391 break;
392 case TRANSFER_STARTED:
393 if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
394 break;
395 }
396 node->transferFinished = false;
397 switch (node->mode) {
398 case SIO_MULTI:
399 node->d.p->rcnt &= ~1;
400 node->p->multiRecv[node->id] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
401 node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;
402 node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
403 node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
404 node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
405 node->d.p->siocnt = GBASIOMultiplayerFillBusy(node->d.p->siocnt);
406 break;
407 case SIO_NORMAL_8:
408 node->p->multiRecv[node->id] = 0xFFFF;
409 node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA8 >> 1] & 0xFF;
410 break;
411 case SIO_NORMAL_32:
412 node->p->multiRecv[node->id] = 0xFFFF;
413 node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA32_LO >> 1];
414 node->p->normalRecv[node->id] |= node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] << 16;
415 break;
416 default:
417 node->p->multiRecv[node->id] = 0xFFFF;
418 break;
419 }
420 signal = true;
421 break;
422 case TRANSFER_FINISHED:
423 if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
424 break;
425 }
426 _finishTransfer(node);
427 signal = true;
428 break;
429 }
430#ifndef NDEBUG
431 node->phase = node->p->d.transferActive;
432#endif
433 if (signal) {
434 node->p->d.signal(&node->p->d, 1 << node->id);
435 }
436
437 return 0;
438}
439
440static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
441 struct GBASIOLockstepNode* node = user;
442 mLockstepLock(&node->p->d);
443
444 int32_t cycles = 0;
445 node->nextEvent -= cyclesLate;
446 node->eventDiff += cyclesLate;
447 if (node->p->d.attached < 2) {
448 cycles = GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][0];
449 } else if (node->nextEvent <= 0) {
450 if (!node->id) {
451 cycles = _masterUpdate(node);
452 } else {
453 cycles = _slaveUpdate(node);
454 cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff);
455 }
456 node->eventDiff = 0;
457 } else {
458 cycles = node->nextEvent;
459 }
460 if (cycles > 0) {
461 node->nextEvent = 0;
462 node->eventDiff += cycles;
463 mTimingDeschedule(timing, &node->event);
464 mTimingSchedule(timing, &node->event, cycles);
465 } else {
466 node->d.p->p->earlyExit = true;
467 node->eventDiff += 1;
468 mTimingSchedule(timing, &node->event, 1);
469 }
470
471 mLockstepUnlock(&node->p->d);
472}
473
474static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
475 struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
476
477 mLockstepLock(&node->p->d);
478
479 if (address == REG_SIOCNT) {
480 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04X", node->id, value);
481 value &= 0xFF8B;
482 if (!node->id) {
483 value = GBASIONormalClearSi(value);
484 }
485 if (value & 0x0080) {
486 if (!node->id) {
487 // Frequency
488 int32_t cycles;
489 if (value & 2) {
490 cycles = 8 * 8;
491 } else {
492 cycles = 64 * 8;
493 }
494 if (value & 0x1000) {
495 cycles *= 4;
496 }
497
498 enum mLockstepPhase transferActive;
499 ATOMIC_LOAD(transferActive, node->p->d.transferActive);
500
501 if (transferActive == TRANSFER_IDLE) {
502 mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
503 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
504 ATOMIC_STORE(node->p->d.transferCycles, cycles);
505
506 if (mTimingIsScheduled(&driver->p->p->timing, &node->event)) {
507 node->eventDiff -= node->event.when - mTimingCurrentTime(&driver->p->p->timing);
508 mTimingDeschedule(&driver->p->p->timing, &node->event);
509 }
510 mTimingSchedule(&driver->p->p->timing, &node->event, 0);
511 } else {
512 value &= ~0x0080;
513 }
514 } else {
515
516 }
517 }
518 } else if (address == REG_SIODATA32_LO) {
519 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04X", node->id, value);
520 } else if (address == REG_SIODATA32_HI) {
521 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04X", node->id, value);
522 }
523
524 mLockstepUnlock(&node->p->d);
525
526 return value;
527}