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 int oldWhen = node->event.when;
153
154 mTimingDeschedule(&driver->p->p->timing, &node->event);
155 mTimingSchedule(&driver->p->p->timing, &node->event, 0);
156 node->eventDiff -= oldWhen - node->event.when;
157 mTimingDeschedule(&driver->p->p->timing, &node->event);
158 }
159
160 node->p->d.unload(&node->p->d, node->id);
161
162 _finishTransfer(node);
163
164 if (!node->id) {
165 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE);
166 }
167
168 // Invalidate SIO mode
169 node->mode = SIO_GPIO;
170
171 mLockstepUnlock(&node->p->d);
172
173 return true;
174}
175
176static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
177 struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
178
179 mLockstepLock(&node->p->d);
180
181 if (address == REG_SIOCNT) {
182 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04X", node->id, value);
183
184 enum mLockstepPhase transferActive;
185 ATOMIC_LOAD(transferActive, node->p->d.transferActive);
186
187 if (value & 0x0080 && transferActive == TRANSFER_IDLE) {
188 if (!node->id && GBASIOMultiplayerIsReady(node->d.p->siocnt)) {
189 mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
190 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
191 ATOMIC_STORE(node->p->d.transferCycles, GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][node->p->d.attached - 1]);
192
193 bool scheduled = mTimingIsScheduled(&driver->p->p->timing, &node->event);
194 int oldWhen = node->event.when;
195
196 mTimingDeschedule(&driver->p->p->timing, &node->event);
197 mTimingSchedule(&driver->p->p->timing, &node->event, 0);
198
199 if (scheduled) {
200 node->eventDiff -= oldWhen - node->event.when;
201 }
202 } else {
203 value &= ~0x0080;
204 }
205 }
206 value &= 0xFF83;
207 value |= driver->p->siocnt & 0x00FC;
208 } else if (address == REG_SIOMLT_SEND) {
209 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOMLT_SEND <- %04X", node->id, value);
210 } else {
211 mLOG(GBA_SIO, STUB, "Lockstep %i: Unknown reg %03X <- %04X", node->id, address, value);
212 }
213
214 mLockstepUnlock(&node->p->d);
215
216 return value;
217}
218
219static void _finishTransfer(struct GBASIOLockstepNode* node) {
220 if (node->transferFinished) {
221 return;
222 }
223
224 struct GBASIO* sio = node->d.p;
225 switch (node->mode) {
226 case SIO_MULTI:
227 sio->p->memory.io[REG_SIOMULTI0 >> 1] = node->p->multiRecv[0];
228 sio->p->memory.io[REG_SIOMULTI1 >> 1] = node->p->multiRecv[1];
229 sio->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2];
230 sio->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3];
231 sio->rcnt |= 1;
232 sio->siocnt = GBASIOMultiplayerClearBusy(sio->siocnt);
233 sio->siocnt = GBASIOMultiplayerSetId(sio->siocnt, node->id);
234 if (GBASIOMultiplayerIsIrq(sio->siocnt)) {
235 GBARaiseIRQ(sio->p, IRQ_SIO, 0);
236 }
237 break;
238 case SIO_NORMAL_8:
239 // TODO
240 sio->siocnt = GBASIONormalClearStart(sio->siocnt);
241 if (node->id) {
242 sio->siocnt = GBASIONormalSetSi(sio->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt));
243 node->d.p->p->memory.io[REG_SIODATA8 >> 1] = node->p->normalRecv[node->id - 1] & 0xFF;
244 } else {
245 node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF;
246 }
247 if (GBASIONormalIsIrq(sio->siocnt)) {
248 GBARaiseIRQ(sio->p, IRQ_SIO, 0);
249 }
250 break;
251 case SIO_NORMAL_32:
252 // TODO
253 sio->siocnt = GBASIONormalClearStart(sio->siocnt);
254 if (node->id) {
255 sio->siocnt = GBASIONormalSetSi(sio->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt));
256 node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = node->p->normalRecv[node->id - 1];
257 node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = node->p->normalRecv[node->id - 1] >> 16;
258 } else {
259 node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0xFFFF;
260 node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0xFFFF;
261 }
262 if (GBASIONormalIsIrq(sio->siocnt)) {
263 GBARaiseIRQ(sio->p, IRQ_SIO, 0);
264 }
265 break;
266 default:
267 break;
268 }
269 node->transferFinished = true;
270#ifndef NDEBUG
271 ++node->transferId;
272#endif
273}
274
275static int32_t _masterUpdate(struct GBASIOLockstepNode* node) {
276 bool needsToWait = false;
277 int i;
278
279 enum mLockstepPhase transferActive;
280 int attachedMulti, attached;
281
282 ATOMIC_LOAD(transferActive, node->p->d.transferActive);
283 ATOMIC_LOAD(attachedMulti, node->p->attachedMulti);
284 ATOMIC_LOAD(attached, node->p->d.attached);
285
286 switch (transferActive) {
287 case TRANSFER_IDLE:
288 // If the master hasn't initiated a transfer, it can keep going.
289 node->nextEvent += LOCKSTEP_INCREMENT;
290 if (node->mode == SIO_MULTI) {
291 node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached);
292 }
293 break;
294 case TRANSFER_STARTING:
295 // Start the transfer, but wait for the other GBAs to catch up
296 node->transferFinished = false;
297 switch (node->mode) {
298 case SIO_MULTI:
299 node->p->multiRecv[0] = 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->p->multiRecv[1] = 0xFFFF;
305 node->p->multiRecv[2] = 0xFFFF;
306 node->p->multiRecv[3] = 0xFFFF;
307 break;
308 case SIO_NORMAL_8:
309 node->p->multiRecv[0] = 0xFFFF;
310 node->p->normalRecv[0] = node->d.p->p->memory.io[REG_SIODATA8 >> 1] & 0xFF;
311 break;
312 case SIO_NORMAL_32:
313 node->p->multiRecv[0] = 0xFFFF;
314 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04X", node->id, node->d.p->p->memory.io[REG_SIODATA32_LO >> 1]);
315 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04X", node->id, node->d.p->p->memory.io[REG_SIODATA32_HI >> 1]);
316 node->p->normalRecv[0] = node->d.p->p->memory.io[REG_SIODATA32_LO >> 1];
317 node->p->normalRecv[0] |= node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] << 16;
318 break;
319 default:
320 node->p->multiRecv[0] = 0xFFFF;
321 break;
322 }
323 needsToWait = true;
324 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTED);
325 node->nextEvent += LOCKSTEP_TRANSFER;
326 break;
327 case TRANSFER_STARTED:
328 // All the other GBAs have caught up and are sleeping, we can all continue now
329 node->nextEvent += LOCKSTEP_TRANSFER;
330 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHING);
331 break;
332 case TRANSFER_FINISHING:
333 // Finish the transfer
334 // We need to make sure the other GBAs catch up so they don't get behind
335 node->nextEvent += node->p->d.transferCycles - 1024; // Split the cycles to avoid waiting too long
336#ifndef NDEBUG
337 ATOMIC_ADD(node->p->d.transferId, 1);
338#endif
339 needsToWait = true;
340 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHED);
341 break;
342 case TRANSFER_FINISHED:
343 // Everything's settled. We're done.
344 _finishTransfer(node);
345 node->nextEvent += LOCKSTEP_INCREMENT;
346 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE);
347 break;
348 }
349 int mask = 0;
350 for (i = 1; i < node->p->d.attached; ++i) {
351 if (node->p->players[i]->mode == node->mode) {
352 mask |= 1 << i;
353 }
354 }
355 if (mask) {
356 if (needsToWait) {
357 if (!node->p->d.wait(&node->p->d, mask)) {
358 abort();
359 }
360 } else {
361 node->p->d.signal(&node->p->d, mask);
362 }
363 }
364 // Tell the other GBAs they can continue up to where we were
365 node->p->d.addCycles(&node->p->d, 0, node->eventDiff);
366#ifndef NDEBUG
367 node->phase = node->p->d.transferActive;
368#endif
369
370 if (needsToWait) {
371 return 0;
372 }
373 return node->nextEvent;
374}
375
376static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
377 enum mLockstepPhase transferActive;
378 int attachedMulti, attached;
379
380 ATOMIC_LOAD(transferActive, node->p->d.transferActive);
381 ATOMIC_LOAD(attachedMulti, node->p->attachedMulti);
382 ATOMIC_LOAD(attached, node->p->d.attached);
383
384 node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached);
385 bool signal = false;
386 switch (transferActive) {
387 case TRANSFER_IDLE:
388 if (!GBASIOMultiplayerIsReady(node->d.p->siocnt)) {
389 node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT);
390 }
391 break;
392 case TRANSFER_STARTING:
393 case TRANSFER_FINISHING:
394 break;
395 case TRANSFER_STARTED:
396 if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
397 break;
398 }
399 node->transferFinished = false;
400 switch (node->mode) {
401 case SIO_MULTI:
402 node->d.p->rcnt &= ~1;
403 node->p->multiRecv[node->id] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
404 node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;
405 node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
406 node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
407 node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
408 node->d.p->siocnt = GBASIOMultiplayerFillBusy(node->d.p->siocnt);
409 break;
410 case SIO_NORMAL_8:
411 node->p->multiRecv[node->id] = 0xFFFF;
412 node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA8 >> 1] & 0xFF;
413 break;
414 case SIO_NORMAL_32:
415 node->p->multiRecv[node->id] = 0xFFFF;
416 node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA32_LO >> 1];
417 node->p->normalRecv[node->id] |= node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] << 16;
418 break;
419 default:
420 node->p->multiRecv[node->id] = 0xFFFF;
421 break;
422 }
423 signal = true;
424 break;
425 case TRANSFER_FINISHED:
426 if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
427 break;
428 }
429 _finishTransfer(node);
430 signal = true;
431 break;
432 }
433#ifndef NDEBUG
434 node->phase = node->p->d.transferActive;
435#endif
436 if (signal) {
437 node->p->d.signal(&node->p->d, 1 << node->id);
438 }
439
440 return 0;
441}
442
443static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
444 struct GBASIOLockstepNode* node = user;
445 mLockstepLock(&node->p->d);
446 if (node->p->d.attached < 2) {
447 mLockstepUnlock(&node->p->d);
448 return;
449 }
450 int32_t cycles = 0;
451 node->nextEvent -= cyclesLate;
452 node->eventDiff += cyclesLate;
453 if (node->nextEvent <= 0) {
454 if (!node->id) {
455 cycles = _masterUpdate(node);
456 } else {
457 cycles = _slaveUpdate(node);
458 cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff);
459 }
460 node->eventDiff = 0;
461 } else {
462 cycles = node->nextEvent;
463 }
464 if (cycles > 0) {
465 node->nextEvent = 0;
466 node->eventDiff += cycles;
467 mTimingDeschedule(timing, &node->event);
468 mTimingSchedule(timing, &node->event, cycles);
469 } else {
470 node->d.p->p->earlyExit = true;
471 node->eventDiff += 1;
472 mTimingSchedule(timing, &node->event, 1);
473 }
474
475 mLockstepUnlock(&node->p->d);
476}
477
478static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
479 struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
480
481 mLockstepLock(&node->p->d);
482
483 if (address == REG_SIOCNT) {
484 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04X", node->id, value);
485 value &= 0xFF8B;
486 if (!node->id) {
487 value = GBASIONormalClearSi(value);
488 }
489 if (value & 0x0080) {
490 if (!node->id) {
491 // Internal shift clock
492 if (value & 1) {
493 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
494 }
495 // Frequency
496 if (value & 2) {
497 node->p->d.transferCycles = GBA_ARM7TDMI_FREQUENCY / 1024;
498 } else {
499 node->p->d.transferCycles = GBA_ARM7TDMI_FREQUENCY / 8192;
500 }
501 } else {
502
503 }
504 }
505 } else if (address == REG_SIODATA32_LO) {
506 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04X", node->id, value);
507 } else if (address == REG_SIODATA32_HI) {
508 mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04X", node->id, value);
509 }
510
511 mLockstepUnlock(&node->p->d);
512
513 return value;
514}