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