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