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