all repos — mgba @ b1828dbc59b8a9cc080c8c7f9472c46a3d39e90c

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	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}