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