From c3344eaa88e6a919c573299981cdfadc99b9e13e Mon Sep 17 00:00:00 2001 From: skidau Date: Fri, 20 Mar 2015 12:36:46 +1100 Subject: [PATCH] Preliminary update to the GameCube to GBA link cable emulation. Fixes Zelda Wind Waker's Tingle Tuner connection, Pac-Man Vs, Final Fantasy: Crystal Chronicles multiplayer, and most other Gamecube to GBA link cable games. * Changed the SI buffer processing so that transfers do not have to be completed instantly * Added a second socket at port 49420 (0xc10c) which sends clock information to the GBA slaves * Handled disconnections from the GBA and GC * Made the transfers asynchronous * Blocks the socket before the connection times out Requires VBA-M SVN 1235 or later. --- Source/Core/Core/HW/SI.cpp | 65 ++++-- Source/Core/Core/HW/SI_Device.cpp | 4 + Source/Core/Core/HW/SI_Device.h | 1 + Source/Core/Core/HW/SI_DeviceGBA.cpp | 319 +++++++++++++++++++++++---- Source/Core/Core/HW/SI_DeviceGBA.h | 41 ++-- 5 files changed, 353 insertions(+), 77 deletions(-) diff --git a/Source/Core/Core/HW/SI.cpp b/Source/Core/Core/HW/SI.cpp index 513f16da2d..db0a8ec50a 100644 --- a/Source/Core/Core/HW/SI.cpp +++ b/Source/Core/Core/HW/SI.cpp @@ -23,8 +23,9 @@ namespace SerialInterface { static int changeDevice; +static int et_transfer_pending; -void RunSIBuffer(); +void RunSIBuffer(u64 userdata, int cyclesLate); void UpdateInterrupts(); // SI Interrupt Types @@ -274,6 +275,7 @@ void Init() memset(g_SIBuffer, 0, 128); changeDevice = CoreTiming::RegisterEvent("ChangeSIDevice", ChangeDeviceCallback); + et_transfer_pending = CoreTiming::RegisterEvent("SITransferPending", RunSIBuffer); } void Shutdown() @@ -346,8 +348,18 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) if (tmpComCSR.TCINT) g_ComCSR.TCINT = 0; // be careful: run si-buffer after updating the INT flags - if (tmpComCSR.TSTART) RunSIBuffer(); - UpdateInterrupts(); + if (tmpComCSR.TSTART) + { + g_ComCSR.TSTART = 1; + RunSIBuffer(0, 0); + } + else if (g_ComCSR.TSTART) + { + CoreTiming::RemoveEvent(et_transfer_pending); + } + + if (!g_ComCSR.TSTART) + UpdateInterrupts(); }) ); @@ -519,29 +531,40 @@ SIDevices GetDeviceType(int channel) return g_Channel[channel].m_pDevice->GetDeviceType(); } -void RunSIBuffer() +void RunSIBuffer(u64 userdata, int cyclesLate) { - // Math inLength - int inLength = g_ComCSR.INLNGTH; - if (inLength == 0) - inLength = 128; - else - inLength++; + if (g_ComCSR.TSTART) + { + // Math inLength + int inLength = g_ComCSR.INLNGTH; + if (inLength == 0) + inLength = 128; + else + inLength++; - // Math outLength - int outLength = g_ComCSR.OUTLNGTH; - if (outLength == 0) - outLength = 128; - else - outLength++; + // Math outLength + int outLength = g_ComCSR.OUTLNGTH; + if (outLength == 0) + outLength = 128; + else + outLength++; - int numOutput = g_Channel[g_ComCSR.CHANNEL].m_pDevice->RunBuffer(g_SIBuffer, inLength); + int numOutput = 0; - DEBUG_LOG(SERIALINTERFACE, "RunSIBuffer (intLen: %i outLen: %i) (processed: %i)", inLength, outLength, numOutput); + numOutput = g_Channel[g_ComCSR.CHANNEL].m_pDevice->RunBuffer(g_SIBuffer, inLength); - // Transfer completed - GenerateSIInterrupt(INT_TCINT); - g_ComCSR.TSTART = 0; + DEBUG_LOG(SERIALINTERFACE, "RunSIBuffer chan: %d inLen: %i outLen: %i processed: %i", g_ComCSR.CHANNEL, inLength, outLength, numOutput); + + if (numOutput != 0) + { + g_ComCSR.TSTART = 0; + GenerateSIInterrupt(INT_TCINT); + } + else + { + CoreTiming::ScheduleEvent(g_Channel[g_ComCSR.CHANNEL].m_pDevice->TransferInterval() - cyclesLate, et_transfer_pending); + } + } } int GetTicksToNextSIPoll() diff --git a/Source/Core/Core/HW/SI_Device.cpp b/Source/Core/Core/HW/SI_Device.cpp index cdc80574d5..2565c4e54f 100644 --- a/Source/Core/Core/HW/SI_Device.cpp +++ b/Source/Core/Core/HW/SI_Device.cpp @@ -39,6 +39,10 @@ int ISIDevice::RunBuffer(u8* _pBuffer, int _iLength) return 0; } +int ISIDevice::TransferInterval() +{ + return 0; +} // Stub class for saying nothing is attached, and not having to deal with null pointers :) class CSIDevice_Null : public ISIDevice diff --git a/Source/Core/Core/HW/SI_Device.h b/Source/Core/Core/HW/SI_Device.h index 93c8d2eadb..196b1b6ba8 100644 --- a/Source/Core/Core/HW/SI_Device.h +++ b/Source/Core/Core/HW/SI_Device.h @@ -73,6 +73,7 @@ public: // Run the SI Buffer virtual int RunBuffer(u8* _pBuffer, int _iLength); + virtual int TransferInterval(); // Return true on new data virtual bool GetData(u32& _Hi, u32& _Low) = 0; diff --git a/Source/Core/Core/HW/SI_DeviceGBA.cpp b/Source/Core/Core/HW/SI_DeviceGBA.cpp index 7baeb0ffad..c9c7d92ce0 100644 --- a/Source/Core/Core/HW/SI_DeviceGBA.cpp +++ b/Source/Core/Core/HW/SI_DeviceGBA.cpp @@ -5,36 +5,103 @@ #include #include "Common/CommonFuncs.h" +#include "Common/Flag.h" #include "Common/StdMakeUnique.h" #include "Common/Thread.h" #include "Common/Logging/Log.h" +#include "Core/ConfigManager.h" +#include "Core/CoreTiming.h" #include "Core/HW/SI_Device.h" #include "Core/HW/SI_DeviceGBA.h" +#include "Core/HW/SystemTimers.h" +#include "Core/HW/VideoInterface.h" #include "SFML/Network.hpp" static std::thread connectionThread; static std::queue> waiting_socks; +static std::queue> waiting_clocks; static std::mutex cs_gba; -namespace { volatile bool server_running; } +static std::mutex cs_gba_clk; +static u8 num_connected; + +namespace { Common::Flag server_running; } + +enum EJoybusCmds +{ + CMD_RESET = 0xff, + CMD_STATUS = 0x00, + CMD_READ = 0x14, + CMD_WRITE = 0x15 +}; + +const u64 BITS_PER_SECOND = 115200; +const u64 BYTES_PER_SECOND = BITS_PER_SECOND / 8; + +u8 GetNumConnected() +{ + int num_ports_connected = num_connected; + if (num_ports_connected == 0) + num_ports_connected = 1; + + return num_ports_connected; +} // --- GameBoy Advance "Link Cable" --- +int GetTransferTime(u8 cmd) +{ + u64 bytes_transferred = 0; + + switch (cmd) + { + case CMD_RESET: + case CMD_STATUS: + { + bytes_transferred = 4; + break; + } + case CMD_READ: + { + bytes_transferred = 6; + break; + } + case CMD_WRITE: + { + bytes_transferred = 1; + break; + } + default: + { + bytes_transferred = 1; + break; + } + } + return (int)(bytes_transferred * SystemTimers::GetTicksPerSecond() / (GetNumConnected() * BYTES_PER_SECOND)); +} + static void GBAConnectionWaiter() { - server_running = true; + server_running.Set(); Common::SetCurrentThreadName("GBA Connection Waiter"); sf::TcpListener server; + sf::TcpListener clock_server; + // "dolphin gba" if (server.listen(0xd6ba) != sf::Socket::Done) return; + // "clock" + if (clock_server.listen(0xc10c) != sf::Socket::Done) + return; + server.setBlocking(false); + clock_server.setBlocking(false); auto new_client = std::make_unique(); - while (server_running) + while (server_running.IsSet()) { if (server.accept(*new_client) == sf::Socket::Done) { @@ -43,13 +110,21 @@ static void GBAConnectionWaiter() new_client = std::make_unique(); } - SLEEP(1); + if (clock_server.accept(*new_client) == sf::Socket::Done) + { + std::lock_guard lk(cs_gba_clk); + waiting_clocks.push(std::move(new_client)); + + new_client = std::make_unique(); + } + + Common::SleepCurrentThread(1); } } void GBAConnectionWaiter_Shutdown() { - server_running = false; + server_running.Clear(); if (connectionThread.joinable()) connectionThread.join(); } @@ -70,69 +145,229 @@ static bool GetAvailableSock(std::unique_ptr& sock_to_fill) return sock_filled; } -GBASockServer::GBASockServer() +static bool GetNextClock(std::unique_ptr& sock_to_fill) +{ + bool sock_filled = false; + + std::lock_guard lk(cs_gba_clk); + + if (!waiting_clocks.empty()) + { + sock_to_fill = std::move(waiting_clocks.front()); + waiting_clocks.pop(); + sock_filled = true; + } + + return sock_filled; +} + +GBASockServer::GBASockServer(int _iDeviceNumber) { if (!connectionThread.joinable()) connectionThread = std::thread(GBAConnectionWaiter); + + cmd = 0; + num_connected = 0; + last_time_slice = 0; + booted = false; + device_number = _iDeviceNumber; } GBASockServer::~GBASockServer() { + Disconnect(); } -// Blocking, since GBA must always send lower byte of REG_JOYSTAT -void GBASockServer::Transfer(char* si_buffer) +void GBASockServer::Disconnect() { - if (!client || client->getLocalPort() == 0) + if (client) + { + num_connected--; + client->disconnect(); + client = nullptr; + } + if (clock_sync) + { + clock_sync->disconnect(); + clock_sync = nullptr; + } + last_time_slice = 0; + booted = false; +} + +void GBASockServer::ClockSync() +{ + if (!clock_sync) + if (!GetNextClock(clock_sync)) + return; + + u32 time_slice = 0; + + if (last_time_slice == 0) + { + num_connected++; + last_time_slice = CoreTiming::GetTicks(); + time_slice = (u32)(SystemTimers::GetTicksPerSecond() / 60); + } + else + { + time_slice = (u32)(CoreTiming::GetTicks() - last_time_slice); + } + + time_slice = (u32)((u64)time_slice * 16777216 / SystemTimers::GetTicksPerSecond()); + last_time_slice = CoreTiming::GetTicks(); + char bytes[4] = { 0, 0, 0, 0 }; + bytes[0] = (time_slice >> 24) & 0xff; + bytes[1] = (time_slice >> 16) & 0xff; + bytes[2] = (time_slice >> 8) & 0xff; + bytes[3] = time_slice & 0xff; + + sf::Socket::Status status = clock_sync->send(bytes, 4); + if (status == sf::Socket::Disconnected) + { + clock_sync->disconnect(); + clock_sync = nullptr; + } +} + +void GBASockServer::Send(u8* si_buffer) +{ + if (!client) if (!GetAvailableSock(client)) return; for (int i = 0; i < 5; i++) - current_data[i] = si_buffer[i ^ 3]; + send_data[i] = si_buffer[i ^ 3]; - u8 cmd = *current_data; - - if (cmd == CMD_WRITE) - client->send(current_data, sizeof(current_data)); - else - client->send(current_data, 1); - - DEBUG_LOG(SERIALINTERFACE, "> command %02x %02x%02x%02x%02x", - (u8)current_data[0], (u8)current_data[1], (u8)current_data[2], - (u8)current_data[3], (u8)current_data[4]); - - memset(current_data, 0, sizeof(current_data)); - size_t num_received = 0; - if (client->receive(current_data, sizeof(current_data), num_received) == sf::Socket::Disconnected) - client->disconnect(); - - DEBUG_LOG(SERIALINTERFACE, "< %02x%02x%02x%02x%02x", - (u8)current_data[0], (u8)current_data[1], (u8)current_data[2], - (u8)current_data[3], (u8)current_data[4]); + cmd = (u8)send_data[0]; #ifdef _DEBUG - size_t num_expecting = 3; - if (cmd == CMD_READ) - num_expecting = 5; - else if (cmd == CMD_WRITE) - num_expecting = 1; - if (num_received != num_expecting) - ERROR_LOG(SERIALINTERFACE, "%x:%x:%x", (u8)cmd, - (unsigned int)num_received, (unsigned int)num_expecting); + NOTICE_LOG(SERIALINTERFACE, "%01d cmd %02x [> %02x%02x%02x%02x]", + device_number, + (u8)send_data[0], (u8)send_data[1], (u8)send_data[2], + (u8)send_data[3], (u8)send_data[4]); #endif - for (int i = 0; i < 5; i++) - si_buffer[i ^ 3] = current_data[i]; + client->setBlocking(false); + sf::Socket::Status status; + if (cmd == CMD_WRITE) + status = client->send(send_data, sizeof(send_data)); + else + status = client->send(send_data, 1); + + if (cmd != CMD_STATUS) + booted = true; + + if (status == sf::Socket::Disconnected) + Disconnect(); + + time_cmd_sent = CoreTiming::GetTicks(); +} + +int GBASockServer::Receive(u8* si_buffer) +{ + if (!client) + if (!GetAvailableSock(client)) + return 5; + + size_t num_received = 0; + u64 transferTime = GetTransferTime((u8)send_data[0]); + bool block = (CoreTiming::GetTicks() - time_cmd_sent) > transferTime; + if (cmd == CMD_STATUS && !booted) + block = false; + + if (block) + { + sf::SocketSelector Selector; + Selector.add(*client); + Selector.wait(sf::milliseconds(1000)); + } + + sf::Socket::Status recv_stat = client->receive(recv_data, sizeof(recv_data), num_received); + if (recv_stat == sf::Socket::Disconnected) + { + Disconnect(); + return 5; + } + + if (recv_stat == sf::Socket::NotReady) + num_received = 0; + + if (num_received > sizeof(recv_data)) + num_received = sizeof(recv_data); + + if (num_received > 0) + { +#ifdef _DEBUG + if ((u8)send_data[0] == 0x00 || (u8)send_data[0] == 0xff) + { + WARN_LOG(SERIALINTERFACE, "%01d [< %02x%02x%02x%02x%02x] (%d)", + device_number, + (u8)recv_data[0], (u8)recv_data[1], (u8)recv_data[2], + (u8)recv_data[3], (u8)recv_data[4], + num_received); + } + else + { + ERROR_LOG(SERIALINTERFACE, "%01d [< %02x%02x%02x%02x%02x] (%d)", + device_number, + (u8)recv_data[0], (u8)recv_data[1], (u8)recv_data[2], + (u8)recv_data[3], (u8)recv_data[4], + num_received); + } +#endif + + for (int i = 0; i < 5; i++) + si_buffer[i ^ 3] = recv_data[i]; + } + + return (int)num_received; } CSIDevice_GBA::CSIDevice_GBA(SIDevices _device, int _iDeviceNumber) : ISIDevice(_device, _iDeviceNumber) - , GBASockServer() + , GBASockServer(_iDeviceNumber) { + waiting_for_response = false; +} + +CSIDevice_GBA::~CSIDevice_GBA() +{ + GBASockServer::Disconnect(); } int CSIDevice_GBA::RunBuffer(u8* _pBuffer, int _iLength) { - Transfer((char*)_pBuffer); - return _iLength; + if (!waiting_for_response) + { + for (int i = 0; i < 5; i++) + send_data[i] = _pBuffer[i ^ 3]; + + num_data_received = 0; + ClockSync(); + Send(_pBuffer); + timestamp_sent = CoreTiming::GetTicks(); + waiting_for_response = true; + } + + if (waiting_for_response && num_data_received == 0) + { + num_data_received = Receive(_pBuffer); + } + + if ((GetTransferTime(send_data[0])) > (int)(CoreTiming::GetTicks() - timestamp_sent)) + { + return 0; + } + else + { + if (num_data_received != 0) + waiting_for_response = false; + return num_data_received; + } +} + +int CSIDevice_GBA::TransferInterval() +{ + return GetTransferTime(send_data[0]); } diff --git a/Source/Core/Core/HW/SI_DeviceGBA.h b/Source/Core/Core/HW/SI_DeviceGBA.h index 0d0b87b21b..13ec937437 100644 --- a/Source/Core/Core/HW/SI_DeviceGBA.h +++ b/Source/Core/Core/HW/SI_DeviceGBA.h @@ -12,38 +12,51 @@ // GameBoy Advance "Link Cable" +u8 GetNumConnected(); +int GetTransferTime(u8 cmd); void GBAConnectionWaiter_Shutdown(); class GBASockServer { public: - GBASockServer(); + GBASockServer(int _iDeviceNumber); ~GBASockServer(); - void Transfer(char* si_buffer); + void Disconnect(); + + void ClockSync(); + + void Send(u8* si_buffer); + int Receive(u8* si_buffer); private: - enum EJoybusCmds - { - CMD_RESET = 0xff, - CMD_STATUS = 0x00, - CMD_READ = 0x14, - CMD_WRITE = 0x15 - }; - std::unique_ptr client; - char current_data[5]; + std::unique_ptr clock_sync; + char send_data[5]; + char recv_data[5]; + + u64 time_cmd_sent; + u64 last_time_slice; + u8 device_number; + u8 cmd; + bool booted; }; class CSIDevice_GBA : public ISIDevice, private GBASockServer { public: CSIDevice_GBA(SIDevices device, int _iDeviceNumber); - ~CSIDevice_GBA() {} + ~CSIDevice_GBA(); - // Run the SI Buffer virtual int RunBuffer(u8* _pBuffer, int _iLength) override; + virtual int TransferInterval() override; - virtual bool GetData(u32& _Hi, u32& _Low) override { return true; } + virtual bool GetData(u32& _Hi, u32& _Low) override { return false; } virtual void SendCommand(u32 _Cmd, u8 _Poll) override {} + +private: + u8 send_data[5]; + int num_data_received; + u64 timestamp_sent; + bool waiting_for_response; };