mgba-ps3/src/gba/sio/dolphin.c

218 lines
5.9 KiB
C

/* Copyright (c) 2013-2017 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/internal/gba/sio/dolphin.h>
#include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/io.h>
#define BITS_PER_SECOND 115200 // This is wrong, but we need to maintain compat for the time being
#define CYCLES_PER_BIT (GBA_ARM7TDMI_FREQUENCY / BITS_PER_SECOND)
#define CLOCK_GRAIN (CYCLES_PER_BIT * 8)
#define CLOCK_WAIT 500
const uint16_t DOLPHIN_CLOCK_PORT = 49420;
const uint16_t DOLPHIN_DATA_PORT = 54970;
enum {
WAIT_FOR_FIRST_CLOCK = 0,
WAIT_FOR_CLOCK,
WAIT_FOR_COMMAND,
};
static bool GBASIODolphinInit(struct GBASIODriver* driver);
static void GBASIODolphinReset(struct GBASIODriver* driver);
static void GBASIODolphinSetMode(struct GBASIODriver* driver, enum GBASIOMode mode);
static bool GBASIODolphinHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode);
static int GBASIODolphinConnectedDevices(struct GBASIODriver* driver);
static void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate);
static int32_t _processCommand(struct GBASIODolphin* dol, uint32_t cyclesLate);
static void _flush(struct GBASIODolphin* dol);
void GBASIODolphinCreate(struct GBASIODolphin* dol) {
memset(&dol->d, 0, sizeof(dol->d));
dol->d.init = GBASIODolphinInit;
dol->d.reset = GBASIODolphinReset;
dol->d.setMode = GBASIODolphinSetMode;
dol->d.handlesMode = GBASIODolphinHandlesMode;
dol->d.connectedDevices = GBASIODolphinConnectedDevices;
dol->event.context = dol;
dol->event.name = "GB SIO Lockstep";
dol->event.callback = GBASIODolphinProcessEvents;
dol->event.priority = 0x80;
dol->data = INVALID_SOCKET;
dol->clock = INVALID_SOCKET;
dol->active = false;
}
void GBASIODolphinDestroy(struct GBASIODolphin* dol) {
if (!SOCKET_FAILED(dol->data)) {
SocketClose(dol->data);
dol->data = INVALID_SOCKET;
}
if (!SOCKET_FAILED(dol->clock)) {
SocketClose(dol->clock);
dol->clock = INVALID_SOCKET;
}
}
bool GBASIODolphinConnect(struct GBASIODolphin* dol, const struct Address* address, short dataPort, short clockPort) {
if (!SOCKET_FAILED(dol->data)) {
SocketClose(dol->data);
dol->data = INVALID_SOCKET;
}
if (!dataPort) {
dataPort = DOLPHIN_DATA_PORT;
}
if (!SOCKET_FAILED(dol->clock)) {
SocketClose(dol->clock);
dol->clock = INVALID_SOCKET;
}
if (!clockPort) {
clockPort = DOLPHIN_CLOCK_PORT;
}
dol->data = SocketConnectTCP(dataPort, address);
if (SOCKET_FAILED(dol->data)) {
return false;
}
dol->clock = SocketConnectTCP(clockPort, address);
if (SOCKET_FAILED(dol->clock)) {
SocketClose(dol->data);
dol->data = INVALID_SOCKET;
return false;
}
SocketSetBlocking(dol->data, false);
SocketSetBlocking(dol->clock, false);
SocketSetTCPPush(dol->data, true);
return true;
}
static bool GBASIODolphinInit(struct GBASIODriver* driver) {
struct GBASIODolphin* dol = (struct GBASIODolphin*) driver;
dol->clockSlice = 0;
dol->state = WAIT_FOR_FIRST_CLOCK;
GBASIODolphinReset(driver);
return true;
}
static void GBASIODolphinReset(struct GBASIODriver* driver) {
struct GBASIODolphin* dol = (struct GBASIODolphin*) driver;
dol->active = false;
_flush(dol);
mTimingDeschedule(&dol->d.p->p->timing, &dol->event);
mTimingSchedule(&dol->d.p->p->timing, &dol->event, 0);
}
static void GBASIODolphinSetMode(struct GBASIODriver* driver, enum GBASIOMode mode) {
struct GBASIODolphin* dol = (struct GBASIODolphin*) driver;
dol->active = mode == GBA_SIO_JOYBUS;
}
static bool GBASIODolphinHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode) {
UNUSED(driver);
return mode == GBA_SIO_JOYBUS;
}
static int GBASIODolphinConnectedDevices(struct GBASIODriver* driver) {
UNUSED(driver);
return 1;
}
void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate) {
struct GBASIODolphin* dol = context;
if (SOCKET_FAILED(dol->data)) {
return;
}
dol->clockSlice -= cyclesLate;
int32_t clockSlice;
int32_t nextEvent = CLOCK_GRAIN;
switch (dol->state) {
case WAIT_FOR_FIRST_CLOCK:
dol->clockSlice = 0;
// Fall through
case WAIT_FOR_CLOCK:
if (dol->clockSlice < 0) {
Socket r = dol->clock;
SocketPoll(1, &r, 0, 0, CLOCK_WAIT);
}
if (SocketRecv(dol->clock, &clockSlice, 4) == 4) {
clockSlice = ntohl(clockSlice);
dol->clockSlice += clockSlice;
dol->state = WAIT_FOR_COMMAND;
nextEvent = 0;
}
// Fall through
case WAIT_FOR_COMMAND:
if (dol->clockSlice < -VIDEO_TOTAL_LENGTH * 4) {
Socket r = dol->data;
SocketPoll(1, &r, 0, 0, CLOCK_WAIT);
}
if (_processCommand(dol, cyclesLate) >= 0) {
dol->state = WAIT_FOR_CLOCK;
nextEvent = CLOCK_GRAIN;
}
break;
}
dol->clockSlice -= nextEvent;
mTimingSchedule(timing, &dol->event, nextEvent);
}
void _flush(struct GBASIODolphin* dol) {
uint8_t buffer[32];
while (SocketRecv(dol->clock, buffer, sizeof(buffer)) == sizeof(buffer));
while (SocketRecv(dol->data, buffer, sizeof(buffer)) == sizeof(buffer));
}
int32_t _processCommand(struct GBASIODolphin* dol, uint32_t cyclesLate) {
// This does not include the stop bits due to compatibility reasons
int bitsOnLine = 8;
uint8_t buffer[6];
int gotten = SocketRecv(dol->data, buffer, 1);
if (gotten < 1) {
return -1;
}
switch (buffer[0]) {
case JOY_RESET:
case JOY_POLL:
bitsOnLine += 24;
break;
case JOY_RECV:
gotten = SocketRecv(dol->data, &buffer[1], 4);
if (gotten < 4) {
return -1;
}
mLOG(GBA_SIO, DEBUG, "DOL recv: %02X%02X%02X%02X", buffer[1], buffer[2], buffer[3], buffer[4]);
// Fall through
case JOY_TRANS:
bitsOnLine += 40;
break;
}
if (!dol->active) {
return 0;
}
int sent = GBASIOJOYSendCommand(&dol->d, buffer[0], &buffer[1]);
SocketSend(dol->data, &buffer[1], sent);
return bitsOnLine * CYCLES_PER_BIT - cyclesLate;
}
bool GBASIODolphinIsConnected(struct GBASIODolphin* dol) {
return dol->data != INVALID_SOCKET;
}