GBA SIO: Dolphin connectivity
Vicki Pfau vi@endrift.com
Fri, 14 Jul 2017 09:04:40 -0700
8 files changed,
282 insertions(+),
3 deletions(-)
M
include/mgba/gba/interface.h
→
include/mgba/gba/interface.h
@@ -78,9 +78,6 @@ bool (*unload)(struct GBASIODriver* driver);
uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value); }; -void GBASIOJOYCreate(struct GBASIODriver* sio); -int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data); - enum GBASIOBattleChipGateFlavor { GBA_FLAVOR_BATTLECHIP_GATE = 4, GBA_FLAVOR_PROGRESS_GATE = 5,
M
include/mgba/internal/gba/sio.h
→
include/mgba/internal/gba/sio.h
@@ -78,6 +78,9 @@ void GBASIOWriteRCNT(struct GBASIO* sio, uint16_t value);
void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value); uint16_t GBASIOWriteRegister(struct GBASIO* sio, uint32_t address, uint16_t value); +void GBASIOJOYCreate(struct GBASIODriver* sio); +int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data); + CXX_GUARD_END #endif
A
include/mgba/internal/gba/sio/dolphin.h
@@ -0,0 +1,40 @@
+/* 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/. */ +#ifndef SIO_DOLPHIN_H +#define SIO_DOLPHIN_H + +#include <mgba-util/common.h> + +CXX_GUARD_START + +#include <mgba/core/timing.h> +#include <mgba/internal/gba/sio.h> + +#include <mgba-util/socket.h> + +extern const uint16_t DOLPHIN_CLOCK_PORT; +extern const uint16_t DOLPHIN_DATA_PORT; + +struct GBASIODolphin { + struct GBASIODriver d; + struct mTimingEvent event; + + Socket data; + Socket clock; + + int32_t clockSlice; + int state; +}; + +void GBASIODolphinCreate(struct GBASIODolphin*); +void GBASIODolphinDestroy(struct GBASIODolphin*); + +bool GBASIODolphinConnect(struct GBASIODolphin*, const struct Address* address, short dataPort, short clockPort); +bool GBASIODolphinIsConnected(struct GBASIODolphin*); + +CXX_GUARD_END + +#endif
M
src/gba/CMakeLists.txt
→
src/gba/CMakeLists.txt
@@ -34,6 +34,7 @@ vfame.c
video.c) set(SIO_FILES + sio/dolphin.c sio/joybus.c sio/lockstep.c)
A
src/gba/sio/dolphin.c
@@ -0,0 +1,190 @@
+/* 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 { + CMD_RESET = 0xFF, + CMD_POLL = 0x00, + CMD_TRANS = 0x14, + CMD_RECV = 0x15, + + CMD_NONE = 0x80 +}; + +enum { + WAIT_FOR_FIRST_CLOCK = 0, + WAIT_FOR_CLOCK, + WAIT_FOR_COMMAND, +}; + +static bool GBASIODolphinLoad(struct GBASIODriver* driver); +static void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate); + +static int32_t _processCommand(struct GBASIODolphin* dol, uint32_t cyclesLate); + +void GBASIODolphinCreate(struct GBASIODolphin* dol) { + GBASIOJOYCreate(&dol->d); + dol->d.load = GBASIODolphinLoad; + 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; +} + +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 GBASIODolphinLoad(struct GBASIODriver* driver) { + struct GBASIODolphin* dol = (struct GBASIODolphin*) driver; + dol->clockSlice = 0; + dol->state = WAIT_FOR_FIRST_CLOCK; + mTimingDeschedule(&dol->d.p->p->timing, &dol->event); + mTimingSchedule(&dol->d.p->p->timing, &dol->event, 0); + return true; +} + +void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate) { + struct GBASIODolphin* dol = context; + 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)) { + dol->state = WAIT_FOR_CLOCK; + nextEvent = CLOCK_GRAIN; + } + break; + } + + dol->clockSlice -= nextEvent; + mTimingSchedule(timing, &dol->event, nextEvent); +} + +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, 5); + if (gotten < 1) { + return 0; + } + + switch (buffer[0]) { + case CMD_RESET: + case CMD_POLL: + bitsOnLine += 24; + break; + case CMD_RECV: + mLOG(GBA_SIO, DEBUG, "JOY <: %02X%02X%02X%02X", buffer[1], buffer[2], buffer[3], buffer[4]); + // Fall through + case CMD_TRANS: + bitsOnLine += 40; + break; + } + + int sent = GBASIOJOYSendCommand(&dol->d, buffer[0], &buffer[1]); + SocketSend(dol->data, &buffer[1], sent); + switch (buffer[0]) { + case CMD_TRANS: + mLOG(GBA_SIO, DEBUG, "JOY >: %02X%02X%02X%02X:%02X", buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]); + break; + case CMD_RECV: + mLOG(GBA_SIO, DEBUG, "JOY <: %02X", buffer[1]); + break; + case CMD_RESET: + mLOG(GBA_SIO, DEBUG, "JOY !: %02X", buffer[3]); + break; + case CMD_POLL: + mLOG(GBA_SIO, DEBUG, "JOY ?: %02X", buffer[3]); + break; + } + + return bitsOnLine * CYCLES_PER_BIT - cyclesLate; +} + +bool GBASIODolphinIsConnected(struct GBASIODolphin* dol) { + return dol->data != INVALID_SOCKET; +}
M
src/platform/qt/CoreController.cpp
→
src/platform/qt/CoreController.cpp
@@ -42,6 +42,10 @@ m_threadContext.core = core;
m_threadContext.userData = this; updateROMInfo(); +#ifdef M_CORE_GBA + GBASIODolphinCreate(&m_dolphin); +#endif + m_resetActions.append([this]() { if (m_autoload) { mCoreLoadState(m_threadContext.core, 0, m_loadStateFlags);@@ -113,6 +117,7 @@ mCoreSaveState(context->core, 0, controller->m_saveStateFlags);
} controller->clearMultiplayerController(); + controller->detachDolphin(); QMetaObject::invokeMethod(controller, "stopping"); };@@ -355,6 +360,29 @@ default:
return nullptr; } return m_cacheSet.get(); +} + +bool CoreController::connectDolphin(uint32_t ipv4) { + if (platform() != mPLATFORM_GBA) { + return false; + } + Address ipaddr; + ipaddr.version = IPV4; + ipaddr.ipv4 = htonl(ipv4); + if (GBASIODolphinConnect(&m_dolphin, &ipaddr, 0, 0)) { + GBA* gba = static_cast<GBA*>(m_threadContext.core->board); + GBASIOSetDriver(&gba->sio, &m_dolphin.d, SIO_JOYBUS); + return true; + } + return false; +} + +void CoreController::detachDolphin() { + if (platform() == mPLATFORM_GBA) { + GBA* gba = static_cast<GBA*>(m_threadContext.core->board); + GBASIOSetDriver(&gba->sio, nullptr, SIO_JOYBUS); + } + GBASIODolphinDestroy(&m_dolphin); } void CoreController::setOverride(std::unique_ptr<Override> override) {
M
src/platform/qt/CoreController.h
→
src/platform/qt/CoreController.h
@@ -25,6 +25,9 @@
#ifdef M_CORE_GB #include <mgba/internal/gb/sio/printer.h> #endif +#ifdef M_CORE_GBA +#include <mgba/internal/gba/sio/dolphin.h> +#endif #ifdef M_CORE_GBA #include <mgba/gba/interface.h>@@ -104,6 +107,9 @@
void setMultiplayerController(MultiplayerController*); void clearMultiplayerController(); MultiplayerController* multiplayerController() { return m_multiplayer; } + + bool connectDolphin(uint32_t ipv4); + void detachDolphin(); mCacheSet* graphicCaches(); int stateSlot() const { return m_stateSlot; }@@ -269,6 +275,9 @@
InputController* m_inputController = nullptr; LogController* m_log = nullptr; MultiplayerController* m_multiplayer = nullptr; +#ifdef M_CORE_GBA + GBASIODolphin m_dolphin; +#endif mVideoLogContext* m_vl = nullptr; VFile* m_vlVf = nullptr;
M
src/platform/qt/Window.cpp
→
src/platform/qt/Window.cpp
@@ -1192,6 +1192,17 @@ m_multiWindow = m_actions.addAction(tr("New multiplayer window"), "multiWindow", [this]() {
GBAApp::app()->newWindow(); }, "file"); + Action* dolphin = m_actions.addAction(tr("Connect to Dolphin"), "connectDolphin", [this]() { + CoreController::Interrupter interrupter; + if (m_controller) { + interrupter.interrupt(m_controller); + } else { + setController(m_manager->loadBIOS(mPLATFORM_GBA, m_config->getOption("gba.bios")), QString()); + } + m_controller->connectDolphin(0x0100007F); + }, "file"); + m_platformActions.insert(mPLATFORM_GBA, dolphin); + #ifndef Q_OS_MAC m_actions.addSeparator("file"); #endif