all repos — mgba @ c4b38790f211b65cb15af26cebe9fc25e3c19914

mGBA Game Boy Advance Emulator

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}