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