src/gb/sio/lockstep.c (view raw)
1/* Copyright (c) 2013-2016 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/gb/sio/lockstep.h>
7
8#include <mgba/internal/gb/gb.h>
9#include <mgba/internal/gb/io.h>
10
11#define LOCKSTEP_INCREMENT 512
12
13static bool GBSIOLockstepNodeInit(struct GBSIODriver* driver);
14static void GBSIOLockstepNodeDeinit(struct GBSIODriver* driver);
15static void GBSIOLockstepNodeWriteSB(struct GBSIODriver* driver, uint8_t value);
16static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t value);
17static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* driver, uint32_t cyclesLate);
18
19void GBSIOLockstepInit(struct GBSIOLockstep* lockstep) {
20 lockstep->players[0] = NULL;
21 lockstep->players[1] = NULL;
22 lockstep->pendingSB[0] = 0xFF;
23 lockstep->pendingSB[1] = 0xFF;
24 lockstep->masterClaimed = false;
25}
26
27void GBSIOLockstepNodeCreate(struct GBSIOLockstepNode* node) {
28 node->d.init = GBSIOLockstepNodeInit;
29 node->d.deinit = GBSIOLockstepNodeDeinit;
30 node->d.writeSB = GBSIOLockstepNodeWriteSB;
31 node->d.writeSC = GBSIOLockstepNodeWriteSC;
32}
33
34bool GBSIOLockstepAttachNode(struct GBSIOLockstep* lockstep, struct GBSIOLockstepNode* node) {
35 if (lockstep->d.attached == MAX_GBS) {
36 return false;
37 }
38 lockstep->players[lockstep->d.attached] = node;
39 node->p = lockstep;
40 node->id = lockstep->d.attached;
41 ++lockstep->d.attached;
42 return true;
43}
44
45void GBSIOLockstepDetachNode(struct GBSIOLockstep* lockstep, struct GBSIOLockstepNode* node) {
46 if (lockstep->d.attached == 0) {
47 return;
48 }
49 int i;
50 for (i = 0; i < lockstep->d.attached; ++i) {
51 if (lockstep->players[i] != node) {
52 continue;
53 }
54 for (++i; i < lockstep->d.attached; ++i) {
55 lockstep->players[i - 1] = lockstep->players[i];
56 lockstep->players[i - 1]->id = i - 1;
57 }
58 --lockstep->d.attached;
59 break;
60 }
61}
62
63bool GBSIOLockstepNodeInit(struct GBSIODriver* driver) {
64 struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
65 mLOG(GB_SIO, DEBUG, "Lockstep %i: Node init", node->id);
66 node->event.context = node;
67 node->event.name = "GB SIO Lockstep";
68 node->event.callback = _GBSIOLockstepNodeProcessEvents;
69 node->event.priority = 0x80;
70
71 node->nextEvent = 0;
72 node->eventDiff = 0;
73 mTimingSchedule(&driver->p->p->timing, &node->event, 0);
74#ifndef NDEBUG
75 node->phase = node->p->d.transferActive;
76 node->transferId = node->p->d.transferId;
77#endif
78 return true;
79}
80
81void GBSIOLockstepNodeDeinit(struct GBSIODriver* driver) {
82 struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
83 node->p->d.unload(&node->p->d, node->id);
84 mTimingDeschedule(&driver->p->p->timing, &node->event);
85}
86
87static void _finishTransfer(struct GBSIOLockstepNode* node) {
88 if (node->transferFinished) {
89 return;
90 }
91 struct GBSIO* sio = node->d.p;
92 sio->pendingSB = node->p->pendingSB[!node->id];
93 if (GBRegisterSCIsEnable(sio->p->memory.io[REG_SC])) {
94 sio->remainingBits = 8;
95 mTimingDeschedule(&sio->p->timing, &sio->event);
96 mTimingSchedule(&sio->p->timing, &sio->event, 0);
97 }
98 node->transferFinished = true;
99#ifndef NDEBUG
100 ++node->transferId;
101#endif
102}
103
104
105static int32_t _masterUpdate(struct GBSIOLockstepNode* node) {
106 enum mLockstepPhase transferActive;
107 ATOMIC_LOAD(transferActive, node->p->d.transferActive);
108
109 bool needsToWait = false;
110 int i;
111 switch (transferActive) {
112 case TRANSFER_IDLE:
113 // If the master hasn't initiated a transfer, it can keep going.
114 node->nextEvent += LOCKSTEP_INCREMENT;
115 break;
116 case TRANSFER_STARTING:
117 // Start the transfer, but wait for the other GBs to catch up
118 node->transferFinished = false;
119 needsToWait = true;
120 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTED);
121 node->nextEvent += 4;
122 break;
123 case TRANSFER_STARTED:
124 // All the other GBs have caught up and are sleeping, we can all continue now
125 node->nextEvent += 4;
126 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHING);
127 break;
128 case TRANSFER_FINISHING:
129 // Finish the transfer
130 // We need to make sure the other GBs catch up so they don't get behind
131 node->nextEvent += node->d.p->period - 8; // Split the cycles to avoid waiting too long
132#ifndef NDEBUG
133 ATOMIC_ADD(node->p->d.transferId, 1);
134#endif
135 needsToWait = true;
136 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHED);
137 break;
138 case TRANSFER_FINISHED:
139 // Everything's settled. We're done.
140 _finishTransfer(node);
141 ATOMIC_STORE(node->p->masterClaimed, false);
142 node->nextEvent += LOCKSTEP_INCREMENT;
143 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE);
144 break;
145 }
146 int mask = 0;
147 for (i = 1; i < node->p->d.attached; ++i) {
148 mask |= 1 << i;
149 }
150 if (mask) {
151 if (needsToWait) {
152 if (!node->p->d.wait(&node->p->d, mask)) {
153 abort();
154 }
155 } else {
156 node->p->d.signal(&node->p->d, mask);
157 }
158 }
159 // Tell the other GBs they can continue up to where we were
160 node->p->d.addCycles(&node->p->d, 0, node->eventDiff);
161#ifndef NDEBUG
162 node->phase = node->p->d.transferActive;
163#endif
164 if (needsToWait) {
165 return 0;
166 }
167 return node->nextEvent;
168}
169
170static uint32_t _slaveUpdate(struct GBSIOLockstepNode* node) {
171 enum mLockstepPhase transferActive;
172
173 ATOMIC_LOAD(transferActive, node->p->d.transferActive);
174
175 bool signal = false;
176 switch (transferActive) {
177 case TRANSFER_IDLE:
178 node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT);
179 break;
180 case TRANSFER_STARTING:
181 case TRANSFER_FINISHING:
182 break;
183 case TRANSFER_STARTED:
184 if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
185 break;
186 }
187 node->transferFinished = false;
188 signal = true;
189 break;
190 case TRANSFER_FINISHED:
191 if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
192 break;
193 }
194 _finishTransfer(node);
195 signal = true;
196 break;
197 }
198#ifndef NDEBUG
199 node->phase = node->p->d.transferActive;
200#endif
201 if (signal) {
202 node->p->d.signal(&node->p->d, 1 << node->id);
203 }
204 return 0;
205}
206
207static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
208 struct GBSIOLockstepNode* node = user;
209 mLockstepLock(&node->p->d);
210 if (node->p->d.attached < 2) {
211 mTimingSchedule(timing, &node->event, (GBSIOCyclesPerTransfer[0] >> 1) - cyclesLate);
212 mLockstepUnlock(&node->p->d);
213 return;
214 }
215 int32_t cycles = 0;
216 node->nextEvent -= cyclesLate;
217 if (node->nextEvent <= 0) {
218 if (!node->id) {
219 cycles = _masterUpdate(node);
220 } else {
221 cycles = _slaveUpdate(node);
222 cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff);
223 }
224 node->eventDiff = 0;
225 } else {
226 cycles = node->nextEvent;
227 }
228 mLockstepUnlock(&node->p->d);
229
230 if (cycles > 0) {
231 node->nextEvent = 0;
232 node->eventDiff += cycles;
233 mTimingDeschedule(timing, &node->event);
234 mTimingSchedule(timing, &node->event, cycles);
235 } else {
236 node->d.p->p->earlyExit = true;
237 mTimingSchedule(timing, &node->event, cyclesLate + 1);
238 }
239}
240
241static void GBSIOLockstepNodeWriteSB(struct GBSIODriver* driver, uint8_t value) {
242 struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
243 node->p->pendingSB[node->id] = value;
244}
245
246static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t value) {
247 struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
248 int attached;
249 ATOMIC_LOAD(attached, node->p->d.attached);
250
251 if ((value & 0x81) == 0x81 && attached > 1) {
252 mLockstepLock(&node->p->d);
253 bool claimed = false;
254 if (ATOMIC_CMPXCHG(node->p->masterClaimed, claimed, true)) {
255 ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
256 ATOMIC_STORE(node->p->d.transferCycles, GBSIOCyclesPerTransfer[(value >> 1) & 1]);
257 mTimingDeschedule(&driver->p->p->timing, &driver->p->event);
258 mTimingDeschedule(&driver->p->p->timing, &node->event);
259 mTimingSchedule(&driver->p->p->timing, &node->event, 0);
260 } else {
261 mLOG(GB_SIO, DEBUG, "GBSIOLockstepNodeWriteSC() failed to write to masterClaimed\n");
262 }
263 mLockstepUnlock(&node->p->d);
264 }
265 return value;
266}