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