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 CMD_RESET = 0xFF,
21 CMD_POLL = 0x00,
22 CMD_TRANS = 0x14,
23 CMD_RECV = 0x15,
24
25 CMD_NONE = 0x80
26};
27
28enum {
29 WAIT_FOR_FIRST_CLOCK = 0,
30 WAIT_FOR_CLOCK,
31 WAIT_FOR_COMMAND,
32};
33
34static bool GBASIODolphinLoad(struct GBASIODriver* driver);
35static void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate);
36
37static int32_t _processCommand(struct GBASIODolphin* dol, uint32_t cyclesLate);
38
39void GBASIODolphinCreate(struct GBASIODolphin* dol) {
40 GBASIOJOYCreate(&dol->d);
41 dol->d.load = GBASIODolphinLoad;
42 dol->event.context = dol;
43 dol->event.name = "GB SIO Lockstep";
44 dol->event.callback = GBASIODolphinProcessEvents;
45 dol->event.priority = 0x80;
46
47 dol->data = INVALID_SOCKET;
48 dol->clock = INVALID_SOCKET;
49}
50
51void GBASIODolphinDestroy(struct GBASIODolphin* dol) {
52 if (!SOCKET_FAILED(dol->data)) {
53 SocketClose(dol->data);
54 dol->data = INVALID_SOCKET;
55 }
56
57 if (!SOCKET_FAILED(dol->clock)) {
58 SocketClose(dol->clock);
59 dol->clock = INVALID_SOCKET;
60 }
61}
62
63bool GBASIODolphinConnect(struct GBASIODolphin* dol, const struct Address* address, short dataPort, short clockPort) {
64 if (!SOCKET_FAILED(dol->data)) {
65 SocketClose(dol->data);
66 dol->data = INVALID_SOCKET;
67 }
68 if (!dataPort) {
69 dataPort = DOLPHIN_DATA_PORT;
70 }
71
72 if (!SOCKET_FAILED(dol->clock)) {
73 SocketClose(dol->clock);
74 dol->clock = INVALID_SOCKET;
75 }
76 if (!clockPort) {
77 clockPort = DOLPHIN_CLOCK_PORT;
78 }
79
80 dol->data = SocketConnectTCP(dataPort, address);
81 if (SOCKET_FAILED(dol->data)) {
82 return false;
83 }
84
85 dol->clock = SocketConnectTCP(clockPort, address);
86 if (SOCKET_FAILED(dol->clock)) {
87 SocketClose(dol->data);
88 dol->data = INVALID_SOCKET;
89 return false;
90 }
91
92 SocketSetBlocking(dol->data, false);
93 SocketSetBlocking(dol->clock, false);
94 SocketSetTCPPush(dol->data, true);
95 return true;
96}
97
98static bool GBASIODolphinLoad(struct GBASIODriver* driver) {
99 struct GBASIODolphin* dol = (struct GBASIODolphin*) driver;
100 dol->clockSlice = 0;
101 dol->state = WAIT_FOR_FIRST_CLOCK;
102 mTimingDeschedule(&dol->d.p->p->timing, &dol->event);
103 mTimingSchedule(&dol->d.p->p->timing, &dol->event, 0);
104 return true;
105}
106
107void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate) {
108 struct GBASIODolphin* dol = context;
109 dol->clockSlice -= cyclesLate;
110
111 int32_t clockSlice;
112
113 int32_t nextEvent = CLOCK_GRAIN;
114 switch (dol->state) {
115 case WAIT_FOR_FIRST_CLOCK:
116 dol->clockSlice = 0;
117 // Fall through
118 case WAIT_FOR_CLOCK:
119 if (dol->clockSlice < 0) {
120 Socket r = dol->clock;
121 SocketPoll(1, &r, 0, 0, CLOCK_WAIT);
122 }
123 if (SocketRecv(dol->clock, &clockSlice, 4) == 4) {
124 clockSlice = ntohl(clockSlice);
125 dol->clockSlice += clockSlice;
126 dol->state = WAIT_FOR_COMMAND;
127 nextEvent = 0;
128 }
129 // Fall through
130 case WAIT_FOR_COMMAND:
131 if (dol->clockSlice < -VIDEO_TOTAL_LENGTH * 4) {
132 Socket r = dol->data;
133 SocketPoll(1, &r, 0, 0, CLOCK_WAIT);
134 }
135 if (_processCommand(dol, cyclesLate)) {
136 dol->state = WAIT_FOR_CLOCK;
137 nextEvent = CLOCK_GRAIN;
138 }
139 break;
140 }
141
142 dol->clockSlice -= nextEvent;
143 mTimingSchedule(timing, &dol->event, nextEvent);
144}
145
146int32_t _processCommand(struct GBASIODolphin* dol, uint32_t cyclesLate) {
147 // This does not include the stop bits due to compatibility reasons
148 int bitsOnLine = 8;
149 uint8_t buffer[6];
150 int gotten = SocketRecv(dol->data, &buffer, 5);
151 if (gotten < 1) {
152 return 0;
153 }
154
155 switch (buffer[0]) {
156 case CMD_RESET:
157 case CMD_POLL:
158 bitsOnLine += 24;
159 break;
160 case CMD_RECV:
161 mLOG(GBA_SIO, DEBUG, "JOY <: %02X%02X%02X%02X", buffer[1], buffer[2], buffer[3], buffer[4]);
162 // Fall through
163 case CMD_TRANS:
164 bitsOnLine += 40;
165 break;
166 }
167
168 int sent = GBASIOJOYSendCommand(&dol->d, buffer[0], &buffer[1]);
169 SocketSend(dol->data, &buffer[1], sent);
170 switch (buffer[0]) {
171 case CMD_TRANS:
172 mLOG(GBA_SIO, DEBUG, "JOY >: %02X%02X%02X%02X:%02X", buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]);
173 break;
174 case CMD_RECV:
175 mLOG(GBA_SIO, DEBUG, "JOY <: %02X", buffer[1]);
176 break;
177 case CMD_RESET:
178 mLOG(GBA_SIO, DEBUG, "JOY !: %02X", buffer[3]);
179 break;
180 case CMD_POLL:
181 mLOG(GBA_SIO, DEBUG, "JOY ?: %02X", buffer[3]);
182 break;
183 }
184
185 return bitsOnLine * CYCLES_PER_BIT - cyclesLate;
186}
187
188bool GBASIODolphinIsConnected(struct GBASIODolphin* dol) {
189 return dol->data != INVALID_SOCKET;
190}