all repos — mgba @ a6e747add45fc8fd3b959d647634bee1f8dee0b1

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