src/gba/sio/dolphin.c (view raw)
1/* Copyright (c) 2013-2017 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/dolphin.h>
7
8#include <mgba/internal/gba/gba.h>
9#include <mgba/internal/gba/io.h>
10
11#define BITS_PER_SECOND 115200 // This is wrong, but we need to maintain compat for the time being
12#define CYCLES_PER_BIT (GBA_ARM7TDMI_FREQUENCY / BITS_PER_SECOND)
13#define CLOCK_GRAIN (CYCLES_PER_BIT * 8)
14#define CLOCK_WAIT 500
15
16const uint16_t DOLPHIN_CLOCK_PORT = 49420;
17const uint16_t DOLPHIN_DATA_PORT = 54970;
18
19enum {
20 WAIT_FOR_FIRST_CLOCK = 0,
21 WAIT_FOR_CLOCK,
22 WAIT_FOR_COMMAND,
23};
24
25static bool GBASIODolphinInit(struct GBASIODriver* driver);
26static bool GBASIODolphinLoad(struct GBASIODriver* driver);
27static bool GBASIODolphinUnload(struct GBASIODriver* driver);
28static void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate);
29
30static int32_t _processCommand(struct GBASIODolphin* dol, uint32_t cyclesLate);
31static void _flush(struct GBASIODolphin* dol);
32
33void GBASIODolphinCreate(struct GBASIODolphin* dol) {
34 GBASIOJOYCreate(&dol->d);
35 dol->d.init = GBASIODolphinInit;
36 dol->d.load = GBASIODolphinLoad;
37 dol->d.unload = GBASIODolphinUnload;
38 dol->event.context = dol;
39 dol->event.name = "GB SIO Lockstep";
40 dol->event.callback = GBASIODolphinProcessEvents;
41 dol->event.priority = 0x80;
42
43 dol->data = INVALID_SOCKET;
44 dol->clock = INVALID_SOCKET;
45 dol->active = false;
46}
47
48void GBASIODolphinDestroy(struct GBASIODolphin* dol) {
49 if (!SOCKET_FAILED(dol->data)) {
50 SocketClose(dol->data);
51 dol->data = INVALID_SOCKET;
52 }
53
54 if (!SOCKET_FAILED(dol->clock)) {
55 SocketClose(dol->clock);
56 dol->clock = INVALID_SOCKET;
57 }
58}
59
60bool GBASIODolphinConnect(struct GBASIODolphin* dol, const struct Address* address, short dataPort, short clockPort) {
61 if (!SOCKET_FAILED(dol->data)) {
62 SocketClose(dol->data);
63 dol->data = INVALID_SOCKET;
64 }
65 if (!dataPort) {
66 dataPort = DOLPHIN_DATA_PORT;
67 }
68
69 if (!SOCKET_FAILED(dol->clock)) {
70 SocketClose(dol->clock);
71 dol->clock = INVALID_SOCKET;
72 }
73 if (!clockPort) {
74 clockPort = DOLPHIN_CLOCK_PORT;
75 }
76
77 dol->data = SocketConnectTCP(dataPort, address);
78 if (SOCKET_FAILED(dol->data)) {
79 return false;
80 }
81
82 dol->clock = SocketConnectTCP(clockPort, address);
83 if (SOCKET_FAILED(dol->clock)) {
84 SocketClose(dol->data);
85 dol->data = INVALID_SOCKET;
86 return false;
87 }
88
89 SocketSetBlocking(dol->data, false);
90 SocketSetBlocking(dol->clock, false);
91 SocketSetTCPPush(dol->data, true);
92 return true;
93}
94
95static bool GBASIODolphinInit(struct GBASIODriver* driver) {
96 struct GBASIODolphin* dol = (struct GBASIODolphin*) driver;
97 dol->active = false;
98 dol->clockSlice = 0;
99 dol->state = WAIT_FOR_FIRST_CLOCK;
100 _flush(dol);
101 return true;
102}
103
104static bool GBASIODolphinLoad(struct GBASIODriver* driver) {
105 struct GBASIODolphin* dol = (struct GBASIODolphin*) driver;
106 dol->active = true;
107 _flush(dol);
108 mTimingDeschedule(&dol->d.p->p->timing, &dol->event);
109 mTimingSchedule(&dol->d.p->p->timing, &dol->event, 0);
110 return true;
111}
112
113static bool GBASIODolphinUnload(struct GBASIODriver* driver) {
114 struct GBASIODolphin* dol = (struct GBASIODolphin*) driver;
115 dol->active = false;
116 return true;
117}
118
119void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate) {
120 struct GBASIODolphin* dol = context;
121 if (SOCKET_FAILED(dol->data)) {
122 return;
123 }
124
125 dol->clockSlice -= cyclesLate;
126
127 int32_t clockSlice;
128
129 int32_t nextEvent = CLOCK_GRAIN;
130 switch (dol->state) {
131 case WAIT_FOR_FIRST_CLOCK:
132 dol->clockSlice = 0;
133 // Fall through
134 case WAIT_FOR_CLOCK:
135 if (dol->clockSlice < 0) {
136 Socket r = dol->clock;
137 SocketPoll(1, &r, 0, 0, CLOCK_WAIT);
138 }
139 if (SocketRecv(dol->clock, &clockSlice, 4) == 4) {
140 clockSlice = ntohl(clockSlice);
141 dol->clockSlice += clockSlice;
142 dol->state = WAIT_FOR_COMMAND;
143 nextEvent = 0;
144 }
145 // Fall through
146 case WAIT_FOR_COMMAND:
147 if (dol->clockSlice < -VIDEO_TOTAL_LENGTH * 4) {
148 Socket r = dol->data;
149 SocketPoll(1, &r, 0, 0, CLOCK_WAIT);
150 }
151 if (_processCommand(dol, cyclesLate) >= 0) {
152 dol->state = WAIT_FOR_CLOCK;
153 nextEvent = CLOCK_GRAIN;
154 }
155 break;
156 }
157
158 dol->clockSlice -= nextEvent;
159 mTimingSchedule(timing, &dol->event, nextEvent);
160}
161
162void _flush(struct GBASIODolphin* dol) {
163 uint8_t buffer[32];
164 while (SocketRecv(dol->clock, buffer, sizeof(buffer)) == sizeof(buffer));
165 while (SocketRecv(dol->data, buffer, sizeof(buffer)) == sizeof(buffer));
166}
167
168int32_t _processCommand(struct GBASIODolphin* dol, uint32_t cyclesLate) {
169 // This does not include the stop bits due to compatibility reasons
170 int bitsOnLine = 8;
171 uint8_t buffer[6];
172 int gotten = SocketRecv(dol->data, buffer, 1);
173 if (gotten < 1) {
174 return -1;
175 }
176
177 switch (buffer[0]) {
178 case JOY_RESET:
179 case JOY_POLL:
180 bitsOnLine += 24;
181 break;
182 case JOY_RECV:
183 gotten = SocketRecv(dol->data, &buffer[1], 4);
184 if (gotten < 4) {
185 return -1;
186 }
187 mLOG(GBA_SIO, DEBUG, "DOL recv: %02X%02X%02X%02X", buffer[1], buffer[2], buffer[3], buffer[4]);
188 // Fall through
189 case JOY_TRANS:
190 bitsOnLine += 40;
191 break;
192 }
193
194 if (!dol->active) {
195 return 0;
196 }
197
198 int sent = GBASIOJOYSendCommand(&dol->d, buffer[0], &buffer[1]);
199 SocketSend(dol->data, &buffer[1], sent);
200
201 return bitsOnLine * CYCLES_PER_BIT - cyclesLate;
202}
203
204bool GBASIODolphinIsConnected(struct GBASIODolphin* dol) {
205 return dol->data != INVALID_SOCKET;
206}