From 9cf8131b239e1704a0898cd3805400357ecf8ef2 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 28 Jan 2024 17:26:57 -0800 Subject: [PATCH] respond to review feedback on tapserver implementation --- Source/Core/Core/CMakeLists.txt | 5 +- Source/Core/Core/HW/EXI/BBA/TAPServer.cpp | 305 ------------------ Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp | 88 +++++ .../TAPServerConnection.cpp} | 139 ++++---- .../Core/HW/EXI/BBA/TAPServerConnection.h | 66 ++++ Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h | 27 +- Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp | 75 +++-- Source/Core/Core/HW/EXI/EXI_DeviceModem.h | 56 ++-- .../Core/Core/HW/EXI/Modem/TAPServerModem.cpp | 79 +++++ Source/Core/DolphinLib.props | 5 +- 10 files changed, 389 insertions(+), 456 deletions(-) delete mode 100644 Source/Core/Core/HW/EXI/BBA/TAPServer.cpp create mode 100644 Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp rename Source/Core/Core/HW/EXI/{Modem/TAPServer.cpp => BBA/TAPServerConnection.cpp} (57%) create mode 100644 Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h create mode 100644 Source/Core/Core/HW/EXI/Modem/TAPServerModem.cpp diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index f36235d7d4..e87983e15b 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -189,11 +189,12 @@ add_library(core HW/DVD/DVDThread.h HW/DVD/FileMonitor.cpp HW/DVD/FileMonitor.h - HW/EXI/BBA/TAPServer.cpp + HW/EXI/BBA/TAPServerConnection.cpp + HW/EXI/BBA/TAPServerBBA.cpp HW/EXI/BBA/XLINK_KAI_BBA.cpp HW/EXI/BBA/BuiltIn.cpp HW/EXI/BBA/BuiltIn.h - HW/EXI/Modem/TAPServer.cpp + HW/EXI/Modem/TAPServerModem.cpp HW/EXI/EXI_Channel.cpp HW/EXI/EXI_Channel.h HW/EXI/EXI_Device.cpp diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServer.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServer.cpp deleted file mode 100644 index f81c1f479f..0000000000 --- a/Source/Core/Core/HW/EXI/BBA/TAPServer.cpp +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright 2020 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "Core/HW/EXI/EXI_DeviceEthernet.h" - -#ifdef _WIN32 -#include -#include -#else -#include -#include -#include -#include -#include -#endif - -#include "Common/CommonFuncs.h" -#include "Common/Logging/Log.h" -#include "Common/StringUtil.h" -#include "Core/HW/EXI/EXI_Device.h" - -namespace ExpansionInterface -{ - -#ifdef _WIN32 -static constexpr auto pi_close = &closesocket; -using ws_ssize_t = int; -#else -static constexpr auto pi_close = &close; -using ws_ssize_t = ssize_t; -#endif - -#ifdef __LINUX__ -#define SEND_FLAGS MSG_NOSIGNAL -#else -#define SEND_FLAGS 0 -#endif - -static int ConnectToDestination(const std::string& destination) -{ - if (destination.empty()) - { - ERROR_LOG_FMT(SP1, "Cannot connect: destination is empty\n"); - return -1; - } - - int ss_size; - struct sockaddr_storage ss; - memset(&ss, 0, sizeof(ss)); - if (destination[0] != '/') - { - // IP address or hostname - size_t colon_offset = destination.find(':'); - if (colon_offset == std::string::npos) - { - ERROR_LOG_FMT(SP1, "Destination IP address does not include port\n"); - return -1; - } - - struct sockaddr_in* sin = reinterpret_cast(&ss); - sin->sin_addr.s_addr = htonl(sf::IpAddress(destination.substr(0, colon_offset)).toInteger()); - sin->sin_family = AF_INET; - sin->sin_port = htons(stoul(destination.substr(colon_offset + 1))); - ss_size = sizeof(*sin); -#ifndef _WIN32 - } - else - { - // UNIX socket - struct sockaddr_un* sun = reinterpret_cast(&ss); - if (destination.size() + 1 > sizeof(sun->sun_path)) - { - ERROR_LOG_FMT(SP1, "Socket path is too long, unable to init BBA\n"); - return -1; - } - sun->sun_family = AF_UNIX; - strcpy(sun->sun_path, destination.c_str()); - ss_size = sizeof(*sun); -#else - } - else - { - ERROR_LOG_FMT(SP1, "UNIX sockets are not supported on Windows\n"); - return -1; -#endif - } - - int fd = socket(ss.ss_family, SOCK_STREAM, (ss.ss_family == AF_INET) ? IPPROTO_TCP : 0); - if (fd == -1) - { - ERROR_LOG_FMT(SP1, "Couldn't create socket; unable to init BBA\n"); - return -1; - } - -#ifdef __APPLE__ - int opt_no_sigpipe = 1; - if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &opt_no_sigpipe, sizeof(opt_no_sigpipe)) < 0) - INFO_LOG_FMT(SP1, "Failed to set SO_NOSIGPIPE on socket\n"); -#endif - - if (connect(fd, reinterpret_cast(&ss), ss_size) == -1) - { - std::string s = Common::LastStrerrorString(); - INFO_LOG_FMT(SP1, "Couldn't connect socket ({}), unable to init BBA\n", s.c_str()); - pi_close(fd); - return -1; - } - - return fd; -} - -bool CEXIETHERNET::TAPServerNetworkInterface::Activate() -{ - if (IsActivated()) - return true; - - m_fd = ConnectToDestination(m_destination); - - INFO_LOG_FMT(SP1, "BBA initialized."); - return RecvInit(); -} - -void CEXIETHERNET::TAPServerNetworkInterface::Deactivate() -{ - pi_close(m_fd); - m_fd = -1; - - m_read_enabled.Clear(); - m_read_shutdown.Set(); - if (m_read_thread.joinable()) - m_read_thread.join(); -} - -bool CEXIETHERNET::TAPServerNetworkInterface::IsActivated() -{ - return (m_fd >= 0); -} - -bool CEXIETHERNET::TAPServerNetworkInterface::RecvInit() -{ - m_read_thread = std::thread(&CEXIETHERNET::TAPServerNetworkInterface::ReadThreadHandler, this); - return true; -} - -void CEXIETHERNET::TAPServerNetworkInterface::RecvStart() -{ - m_read_enabled.Set(); -} - -void CEXIETHERNET::TAPServerNetworkInterface::RecvStop() -{ - m_read_enabled.Clear(); -} - -bool CEXIETHERNET::TAPServerNetworkInterface::SendFrame(const u8* frame, u32 size) -{ - { - const std::string s = ArrayToString(frame, size, 0x10); - INFO_LOG_FMT(SP1, "SendFrame {}\n{}", size, s); - } - - // On Windows, the data pointer is of type const char*; on other systems it is - // of type const void*. This is the reason for the reinterpret_cast here and - // in the other send/recv calls in this file. - u8 size_bytes[2] = {static_cast(size), static_cast(size >> 8)}; - if (send(m_fd, reinterpret_cast(size_bytes), 2, SEND_FLAGS) != 2) - { - ERROR_LOG_FMT(SP1, "SendFrame(): could not write size field"); - return false; - } - int written_bytes = send(m_fd, reinterpret_cast(frame), size, SEND_FLAGS); - if (u32(written_bytes) != size) - { - ERROR_LOG_FMT(SP1, "SendFrame(): expected to write {} bytes, instead wrote {}", size, - written_bytes); - return false; - } - else - { - m_eth_ref->SendComplete(); - return true; - } -} - -void CEXIETHERNET::TAPServerNetworkInterface::ReadThreadHandler() -{ - while (!m_read_shutdown.IsSet()) - { - fd_set rfds; - FD_ZERO(&rfds); - FD_SET(m_fd, &rfds); - - timeval timeout; - timeout.tv_sec = 0; - timeout.tv_usec = 50000; - if (select(m_fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0) - continue; - - // The tapserver protocol is very simple: there is a 16-bit little-endian - // size field, followed by that many bytes of packet data - switch (m_read_state) - { - case ReadState::Size: - { - u8 size_bytes[2]; - ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast(size_bytes), 2, 0); - if (bytes_read == 1) - { - m_read_state = ReadState::SizeHigh; - m_read_packet_bytes_remaining = size_bytes[0]; - } - else if (bytes_read == 2) - { - m_read_packet_bytes_remaining = size_bytes[0] | (size_bytes[1] << 8); - if (m_read_packet_bytes_remaining > BBA_RECV_SIZE) - { - ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", - m_read_packet_bytes_remaining); - m_read_state = ReadState::Skip; - } - else - { - m_read_state = ReadState::Data; - } - } - else - { - ERROR_LOG_FMT(SP1, "Failed to read size field from BBA: {}", Common::LastStrerrorString()); - } - break; - } - case ReadState::SizeHigh: - { - // This handles the annoying case where only one byte of the size field - // was available earlier. - u8 size_high = 0; - ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast(&size_high), 1, 0); - if (bytes_read == 1) - { - m_read_packet_bytes_remaining |= (size_high << 8); - if (m_read_packet_bytes_remaining > BBA_RECV_SIZE) - { - ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", - m_read_packet_bytes_remaining); - m_read_state = ReadState::Skip; - } - else - { - m_read_state = ReadState::Data; - } - } - else - { - ERROR_LOG_FMT(SP1, "Failed to read split size field from BBA: {}", - Common::LastStrerrorString()); - } - break; - } - case ReadState::Data: - { - ws_ssize_t bytes_read = - recv(m_fd, reinterpret_cast(m_eth_ref->mRecvBuffer.get() + m_read_packet_offset), - m_read_packet_bytes_remaining, 0); - if (bytes_read <= 0) - { - ERROR_LOG_FMT(SP1, "Failed to read data from BBA: {}", Common::LastStrerrorString()); - } - else - { - m_read_packet_offset += bytes_read; - m_read_packet_bytes_remaining -= bytes_read; - if (m_read_packet_bytes_remaining == 0) - { - m_eth_ref->mRecvBufferLength = m_read_packet_offset; - m_eth_ref->RecvHandlePacket(); - m_read_state = ReadState::Size; - m_read_packet_offset = 0; - } - } - break; - } - case ReadState::Skip: - { - ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast(m_eth_ref->mRecvBuffer.get()), - std::min(m_read_packet_bytes_remaining, BBA_RECV_SIZE), 0); - if (bytes_read <= 0) - { - ERROR_LOG_FMT(SP1, "Failed to read data from BBA: {}", Common::LastStrerrorString()); - } - else - { - m_read_packet_bytes_remaining -= bytes_read; - if (m_read_packet_bytes_remaining == 0) - { - m_read_state = ReadState::Size; - m_read_packet_offset = 0; - } - } - break; - } - } - } -} - -} // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp new file mode 100644 index 0000000000..f0c0a4ce0b --- /dev/null +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp @@ -0,0 +1,88 @@ +// Copyright 2020 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/EXI/EXI_DeviceEthernet.h" + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#include "Common/CommonFuncs.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "Core/HW/EXI/EXI_Device.h" + +namespace ExpansionInterface +{ + +CEXIETHERNET::TAPServerNetworkInterface::TAPServerNetworkInterface(CEXIETHERNET* eth_ref, + const std::string& destination) + : NetworkInterface(eth_ref), + m_tapserver_if( + destination, + std::bind(&TAPServerNetworkInterface::HandleReceivedFrame, this, std::placeholders::_1), + BBA_RECV_SIZE) +{ +} + +bool CEXIETHERNET::TAPServerNetworkInterface::Activate() +{ + return m_tapserver_if.Activate(); +} + +void CEXIETHERNET::TAPServerNetworkInterface::Deactivate() +{ + m_tapserver_if.Deactivate(); +} + +bool CEXIETHERNET::TAPServerNetworkInterface::IsActivated() +{ + return m_tapserver_if.IsActivated(); +} + +bool CEXIETHERNET::TAPServerNetworkInterface::RecvInit() +{ + return m_tapserver_if.RecvInit(); +} + +void CEXIETHERNET::TAPServerNetworkInterface::RecvStart() +{ + m_tapserver_if.RecvStart(); +} + +void CEXIETHERNET::TAPServerNetworkInterface::RecvStop() +{ + m_tapserver_if.RecvStop(); +} + +bool CEXIETHERNET::TAPServerNetworkInterface::SendFrame(const u8* frame, u32 size) +{ + bool ret = m_tapserver_if.SendFrame(frame, size); + if (ret) + m_eth_ref->SendComplete(); + return ret; +} + +void CEXIETHERNET::TAPServerNetworkInterface::HandleReceivedFrame(std::string&& data) +{ + if (data.size() > BBA_RECV_SIZE) + { + ERROR_LOG_FMT(SP1, "Received BBA frame of size {}, which is larger than maximum size {}", + data.size(), BBA_RECV_SIZE); + } + else + { + memcpy(m_eth_ref->mRecvBuffer.get(), data.data(), data.size()); + m_eth_ref->mRecvBufferLength = data.size(); + m_eth_ref->RecvHandlePacket(); + } +} + +} // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/Modem/TAPServer.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp similarity index 57% rename from Source/Core/Core/HW/EXI/Modem/TAPServer.cpp rename to Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp index 9db6e5e76e..13b03a9349 100644 --- a/Source/Core/Core/HW/EXI/Modem/TAPServer.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp @@ -1,7 +1,7 @@ // Copyright 2020 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "Core/HW/EXI/EXI_DeviceModem.h" +#include "Core/HW/EXI/EXI_DeviceEthernet.h" #ifdef _WIN32 #include @@ -23,10 +23,9 @@ namespace ExpansionInterface { #ifdef _WIN32 -static constexpr auto pi_close = &closesocket; using ws_ssize_t = int; #else -static constexpr auto pi_close = &close; +#define closesocket close using ws_ssize_t = ssize_t; #endif @@ -36,6 +35,13 @@ using ws_ssize_t = ssize_t; #define SEND_FLAGS 0 #endif +TAPServerConnection::TAPServerConnection(const std::string& destination, + std::function recv_cb, + std::size_t max_frame_size) + : m_destination(destination), m_recv_cb(recv_cb), m_max_frame_size(max_frame_size) +{ +} + static int ConnectToDestination(const std::string& destination) { if (destination.empty()) @@ -45,32 +51,33 @@ static int ConnectToDestination(const std::string& destination) } int ss_size; - struct sockaddr_storage ss; + sockaddr_storage ss; memset(&ss, 0, sizeof(ss)); if (destination[0] != '/') { // IP address or hostname - size_t colon_offset = destination.find(':'); + const std::size_t colon_offset = destination.find(':'); if (colon_offset == std::string::npos) { ERROR_LOG_FMT(SP1, "Destination IP address does not include port\n"); return -1; } - struct sockaddr_in* sin = reinterpret_cast(&ss); + sockaddr_in* sin = reinterpret_cast(&ss); sin->sin_addr.s_addr = htonl(sf::IpAddress(destination.substr(0, colon_offset)).toInteger()); sin->sin_family = AF_INET; - sin->sin_port = htons(stoul(destination.substr(colon_offset + 1))); + std::string port_str = destination.substr(colon_offset + 1); + sin->sin_port = htons(atoi(port_str.c_str())); ss_size = sizeof(*sin); #ifndef _WIN32 } else { // UNIX socket - struct sockaddr_un* sun = reinterpret_cast(&ss); + sockaddr_un* sun = reinterpret_cast(&ss); if (destination.size() + 1 > sizeof(sun->sun_path)) { - ERROR_LOG_FMT(SP1, "Socket path is too long, unable to init BBA\n"); + ERROR_LOG_FMT(SP1, "Socket path is too long; unable to create tapserver connection\n"); return -1; } sun->sun_family = AF_UNIX; @@ -85,10 +92,10 @@ static int ConnectToDestination(const std::string& destination) #endif } - int fd = socket(ss.ss_family, SOCK_STREAM, (ss.ss_family == AF_INET) ? IPPROTO_TCP : 0); + const int fd = socket(ss.ss_family, SOCK_STREAM, (ss.ss_family == AF_INET) ? IPPROTO_TCP : 0); if (fd == -1) { - ERROR_LOG_FMT(SP1, "Couldn't create socket; unable to init BBA\n"); + ERROR_LOG_FMT(SP1, "Couldn't create socket; unable to create tapserver connection\n"); return -1; } @@ -100,109 +107,129 @@ static int ConnectToDestination(const std::string& destination) if (connect(fd, reinterpret_cast(&ss), ss_size) == -1) { - std::string s = Common::LastStrerrorString(); - INFO_LOG_FMT(SP1, "Couldn't connect socket ({}), unable to init BBA\n", s.c_str()); - pi_close(fd); + std::string s = Common::StrNetworkError(); + INFO_LOG_FMT(SP1, "Couldn't connect socket ({}), unable to create tapserver connection\n", s); + closesocket(fd); return -1; } return fd; } -bool CEXIModem::TAPServerNetworkInterface::Activate() +bool TAPServerConnection::Activate() { if (IsActivated()) return true; m_fd = ConnectToDestination(m_destination); if (m_fd < 0) - { return false; - } - INFO_LOG_FMT(SP1, "Modem initialized."); return RecvInit(); } -void CEXIModem::TAPServerNetworkInterface::Deactivate() +void TAPServerConnection::Deactivate() { if (m_fd >= 0) - { - pi_close(m_fd); - } + closesocket(m_fd); m_fd = -1; m_read_enabled.Clear(); m_read_shutdown.Set(); if (m_read_thread.joinable()) - { m_read_thread.join(); - } m_read_shutdown.Clear(); } -bool CEXIModem::TAPServerNetworkInterface::IsActivated() +bool TAPServerConnection::IsActivated() { return (m_fd >= 0); } -bool CEXIModem::TAPServerNetworkInterface::RecvInit() +bool TAPServerConnection::RecvInit() { - m_read_thread = std::thread(&CEXIModem::TAPServerNetworkInterface::ReadThreadHandler, this); + m_read_thread = std::thread(&TAPServerConnection::ReadThreadHandler, this); return true; } -void CEXIModem::TAPServerNetworkInterface::RecvStart() +void TAPServerConnection::RecvStart() { m_read_enabled.Set(); } -void CEXIModem::TAPServerNetworkInterface::RecvStop() +void TAPServerConnection::RecvStop() { m_read_enabled.Clear(); } -bool CEXIModem::TAPServerNetworkInterface::SendFrames() +bool TAPServerConnection::SendAndRemoveAllHDLCFrames(std::string& send_buf) { - while (!m_modem_ref->m_send_buffer.empty()) + while (!send_buf.empty()) { - size_t start_offset = m_modem_ref->m_send_buffer.find(0x7E); + std::size_t start_offset = send_buf.find(0x7E); if (start_offset == std::string::npos) { break; } - size_t end_sentinel_offset = m_modem_ref->m_send_buffer.find(0x7E, start_offset + 1); + std::size_t end_sentinel_offset = send_buf.find(0x7E, start_offset + 1); if (end_sentinel_offset == std::string::npos) { break; } - size_t end_offset = end_sentinel_offset + 1; - size_t size = end_offset - start_offset; + std::size_t end_offset = end_sentinel_offset + 1; + std::size_t size = end_offset - start_offset; - uint8_t size_bytes[2] = {static_cast(size), static_cast(size >> 8)}; - if (send(m_fd, size_bytes, 2, SEND_FLAGS) != 2) + u8 size_bytes[2] = {static_cast(size), static_cast(size >> 8)}; + if (send(m_fd, reinterpret_cast(size_bytes), 2, SEND_FLAGS) != 2) { - ERROR_LOG_FMT(SP1, "SendFrames(): could not write size field"); + ERROR_LOG_FMT(SP1, "SendAndRemoveAllHDLCFrames(): could not write size field"); return false; } - int written_bytes = - send(m_fd, m_modem_ref->m_send_buffer.data() + start_offset, size, SEND_FLAGS); + const int written_bytes = + send(m_fd, send_buf.data() + start_offset, static_cast(size), SEND_FLAGS); if (u32(written_bytes) != size) { - ERROR_LOG_FMT(SP1, "SendFrames(): expected to write {} bytes, instead wrote {}", size, - written_bytes); + ERROR_LOG_FMT(SP1, + "SendAndRemoveAllHDLCFrames(): expected to write {} bytes, instead wrote {}", + size, written_bytes); return false; } else { - m_modem_ref->m_send_buffer = m_modem_ref->m_send_buffer.substr(end_offset); - m_modem_ref->SendComplete(); + send_buf = send_buf.substr(end_offset); } } return true; } -void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler() +bool TAPServerConnection::SendFrame(const u8* frame, u32 size) +{ + { + const std::string s = ArrayToString(frame, size, 0x10); + INFO_LOG_FMT(SP1, "SendFrame {}\n{}", size, s); + } + + // On Windows, the data pointer is of type const char*; on other systems it is + // of type const void*. This is the reason for the reinterpret_cast here and + // in the other send/recv calls in this file. + u8 size_bytes[2] = {static_cast(size), static_cast(size >> 8)}; + if (send(m_fd, reinterpret_cast(size_bytes), 2, SEND_FLAGS) != 2) + { + ERROR_LOG_FMT(SP1, "SendFrame(): could not write size field"); + return false; + } + int written_bytes = + send(m_fd, reinterpret_cast(frame), static_cast(size), SEND_FLAGS); + if (u32(written_bytes) != size) + { + ERROR_LOG_FMT(SP1, "SendFrame(): expected to write {} bytes, instead wrote {}", size, + written_bytes); + return false; + } + return true; +} + +void TAPServerConnection::ReadThreadHandler() { enum class ReadState { @@ -213,8 +240,8 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler() }; ReadState read_state = ReadState::SIZE; - size_t frame_bytes_received = 0; - size_t frame_bytes_expected = 0; + std::size_t frame_bytes_received = 0; + std::size_t frame_bytes_expected = 0; std::string frame_data; while (!m_read_shutdown.IsSet()) @@ -246,7 +273,7 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler() { frame_bytes_expected = size_bytes[0] | (size_bytes[1] << 8); frame_data.resize(frame_bytes_expected, '\0'); - if (frame_bytes_expected > MODEM_RECV_SIZE) + if (frame_bytes_expected > m_max_frame_size) { ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", frame_bytes_expected); read_state = ReadState::SKIP; @@ -259,7 +286,7 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler() else { ERROR_LOG_FMT(SP1, "Failed to read size field from destination: {}", - Common::LastStrerrorString()); + Common::StrNetworkError()); } break; } @@ -273,7 +300,7 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler() { frame_bytes_expected |= (size_high << 8); frame_data.resize(frame_bytes_expected, '\0'); - if (frame_bytes_expected > MODEM_RECV_SIZE) + if (frame_bytes_expected > m_max_frame_size) { ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", frame_bytes_expected); read_state = ReadState::SKIP; @@ -286,19 +313,19 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler() else { ERROR_LOG_FMT(SP1, "Failed to read split size field from destination: {}", - Common::LastStrerrorString()); + Common::StrNetworkError()); } break; } case ReadState::DATA: case ReadState::SKIP: { - ws_ssize_t bytes_read = recv(m_fd, frame_data.data() + frame_bytes_received, - frame_data.size() - frame_bytes_received, 0); + ws_ssize_t bytes_to_read = frame_data.size() - frame_bytes_received; + ws_ssize_t bytes_read = + recv(m_fd, frame_data.data() + frame_bytes_received, bytes_to_read, 0); if (bytes_read <= 0) { - ERROR_LOG_FMT(SP1, "Failed to read data from destination: {}", - Common::LastStrerrorString()); + ERROR_LOG_FMT(SP1, "Failed to read data from destination: {}", Common::StrNetworkError()); } else { @@ -307,7 +334,7 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler() { if (read_state == ReadState::DATA) { - m_modem_ref->AddToReceiveBuffer(std::move(frame_data)); + m_recv_cb(std::move(frame_data)); } frame_data.clear(); frame_bytes_received = 0; diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h new file mode 100644 index 0000000000..ae10fc93a8 --- /dev/null +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h @@ -0,0 +1,66 @@ +// Copyright 2020 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#include +#include + +#include "Common/CommonFuncs.h" +#include "Common/Logging/Log.h" +#include "Common/SocketContext.h" +#include "Common/StringUtil.h" + +namespace ExpansionInterface +{ + +class TAPServerConnection +{ +public: + TAPServerConnection(const std::string& destination, std::function recv_cb, + std::size_t max_frame_size); + + bool Activate(); + void Deactivate(); + bool IsActivated(); + bool RecvInit(); + void RecvStart(); + void RecvStop(); + bool SendAndRemoveAllHDLCFrames(std::string& send_buf); + bool SendFrame(const u8* frame, u32 size); + +private: + enum class ReadState + { + Size, + SizeHigh, + Data, + Skip, + }; + + std::string m_destination; + std::function m_recv_cb; + std::size_t m_max_frame_size; + Common::SocketContext m_socket_context; + + int m_fd = -1; + std::thread m_read_thread; + Common::Flag m_read_enabled; + Common::Flag m_read_shutdown; + + bool StartReadThread(); + void ReadThreadHandler(); +}; + +} // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h index 067f5d59c0..465f050521 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h @@ -19,6 +19,7 @@ #include "Common/Network.h" #include "Common/SocketContext.h" #include "Core/HW/EXI/BBA/BuiltIn.h" +#include "Core/HW/EXI/BBA/TAPServerConnection.h" #include "Core/HW/EXI/EXI_Device.h" class PointerWrap; @@ -366,10 +367,7 @@ private: class TAPServerNetworkInterface : public NetworkInterface { public: - explicit TAPServerNetworkInterface(CEXIETHERNET* eth_ref, const std::string& destination) - : NetworkInterface(eth_ref), m_destination(destination) - { - } + TAPServerNetworkInterface(CEXIETHERNET* eth_ref, const std::string& destination); public: bool Activate() override; @@ -381,26 +379,9 @@ private: void RecvStop() override; private: - enum class ReadState - { - Size, - SizeHigh, - Data, - Skip, - }; + TAPServerConnection m_tapserver_if; - std::string m_destination; - Common::SocketContext m_socket_context; - - int m_fd = -1; - ReadState m_read_state = ReadState::Size; - u16 m_read_packet_offset; - u16 m_read_packet_bytes_remaining; - std::thread m_read_thread; - Common::Flag m_read_enabled; - Common::Flag m_read_shutdown; - - void ReadThreadHandler(); + void HandleReceivedFrame(std::string&& data); }; class XLinkNetworkInterface : public NetworkInterface diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp index 867044ec89..e488570a42 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp @@ -1,4 +1,4 @@ -// Copyright 2008 Dolphin Emulator Project +// Copyright 2024 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "Core/HW/EXI/EXI_DeviceModem.h" @@ -37,10 +37,6 @@ CEXIModem::CEXIModem(Core::System& system, ModemDeviceType type) : IEXIDevice(sy break; } - for (size_t z = 0; z < m_regs.size(); z++) - { - m_regs[z] = 0; - } m_regs[Register::DEVICE_TYPE] = 0x02; m_regs[Register::INTERRUPT_MASK] = 0x02; } @@ -84,15 +80,19 @@ void CEXIModem::ImmWrite(u32 data, u32 size) } else if (IsModemTransfer(m_transfer_descriptor)) { // Write AT command buffer or packet send buffer - u32 be_data = htonl(data); + const u32 be_data = htonl(data); HandleWriteModemTransfer(&be_data, size); } else { // Write device register - uint8_t reg_num = static_cast((m_transfer_descriptor >> 24) & 0x1F); + u8 reg_num = static_cast((m_transfer_descriptor >> 24) & 0x1F); bool should_update_interrupts = false; for (; size; size--) { + if (reg_num >= m_regs.size()) + { + break; + } should_update_interrupts |= ((reg_num == Register::INTERRUPT_MASK) || (reg_num == Register::PENDING_INTERRUPT_MASK)); m_regs[reg_num++] = (data >> 24); @@ -154,14 +154,18 @@ u32 CEXIModem::ImmRead(u32 size) } else { // Read device register - uint8_t reg_num = static_cast((m_transfer_descriptor >> 24) & 0x1F); + u8 reg_num = static_cast((m_transfer_descriptor >> 24) & 0x1F); if (reg_num == 0) { return 0x02020000; // Device ID (modem) } u32 ret = 0; - for (size_t z = 0; z < size; z++) + for (u8 z = 0; z < size; z++) { + if (reg_num + z >= m_regs.size()) + { + break; + } ret |= (m_regs[reg_num + z] << ((3 - z) * 8)); } m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR; @@ -197,28 +201,31 @@ void CEXIModem::DMARead(u32 addr, u32 size) void CEXIModem::HandleReadModemTransfer(void* data, u32 size) { - u16 bytes_requested = GetModemTransferSize(m_transfer_descriptor); + const u16 bytes_requested = GetModemTransferSize(m_transfer_descriptor); if (size > bytes_requested) { ERROR_LOG_FMT(SP1, "More bytes requested ({}) than originally requested for transfer {:x}", size, m_transfer_descriptor); size = bytes_requested; } - u16 bytes_requested_after_read = bytes_requested - size; + const u16 bytes_requested_after_read = bytes_requested - size; if ((m_transfer_descriptor & 0x0F000000) == 0x03000000) - { // AT command buffer - memcpy(data, m_at_reply_data.data(), std::min(size, m_at_reply_data.size())); - m_at_reply_data = m_at_reply_data.substr(size); - m_regs[Register::AT_REPLY_SIZE] = m_at_reply_data.size(); + { + // AT command buffer + const std::size_t bytes_to_copy = std::min(size, m_at_reply_data.size()); + memcpy(data, m_at_reply_data.data(), bytes_to_copy); + m_at_reply_data = m_at_reply_data.substr(bytes_to_copy); + m_regs[Register::AT_REPLY_SIZE] = static_cast(m_at_reply_data.size()); SetInterruptFlag(Interrupt::AT_REPLY_DATA_AVAILABLE, !m_at_reply_data.empty(), true); } else if ((m_transfer_descriptor & 0x0F000000) == 0x08000000) - { // Packet receive buffer + { + // Packet receive buffer std::lock_guard g(m_receive_buffer_lock); - size_t bytes_to_copy = std::min(size, m_receive_buffer.size()); + const std::size_t bytes_to_copy = std::min(size, m_receive_buffer.size()); memcpy(data, m_receive_buffer.data(), bytes_to_copy); - m_receive_buffer = m_receive_buffer.substr(size); + m_receive_buffer = m_receive_buffer.substr(bytes_to_copy); OnReceiveBufferSizeChangedLocked(true); } else @@ -234,20 +241,20 @@ void CEXIModem::HandleReadModemTransfer(void* data, u32 size) void CEXIModem::HandleWriteModemTransfer(const void* data, u32 size) { - u16 bytes_expected = GetModemTransferSize(m_transfer_descriptor); + const u16 bytes_expected = GetModemTransferSize(m_transfer_descriptor); if (size > bytes_expected) { ERROR_LOG_FMT(SP1, "More bytes received ({}) than expected for transfer {:x}", size, m_transfer_descriptor); return; } - u16 bytes_expected_after_write = bytes_expected - size; + const u16 bytes_expected_after_write = bytes_expected - size; if ((m_transfer_descriptor & 0x0F000000) == 0x03000000) { // AT command buffer m_at_command_data.append(reinterpret_cast(data), size); RunAllPendingATCommands(); - m_regs[Register::AT_COMMAND_SIZE] = m_at_command_data.size(); + m_regs[Register::AT_COMMAND_SIZE] = static_cast(m_at_command_data.size()); } else if ((m_transfer_descriptor & 0x0F000000) == 0x08000000) { // Packet send buffer @@ -257,7 +264,7 @@ void CEXIModem::HandleWriteModemTransfer(const void* data, u32 size) // from the emulated program's perspective, so we always tell it the send // FIFO is empty. SetInterruptFlag(Interrupt::SEND_BUFFER_BELOW_THRESHOLD, true, true); - m_network_interface->SendFrames(); + m_network_interface->SendAndRemoveAllHDLCFrames(m_send_buffer); } else { @@ -289,7 +296,7 @@ u16 CEXIModem::GetRxThreshold() const return (m_regs[Register::RX_THRESHOLD_HIGH] << 8) | m_regs[Register::RX_THRESHOLD_LOW]; } -void CEXIModem::SetInterruptFlag(uint8_t what, bool enabled, bool from_cpu) +void CEXIModem::SetInterruptFlag(u8 what, bool enabled, bool from_cpu) { if (enabled) { @@ -306,7 +313,7 @@ void CEXIModem::SetInterruptFlag(uint8_t what, bool enabled, bool from_cpu) void CEXIModem::OnReceiveBufferSizeChangedLocked(bool from_cpu) { // The caller is expected to hold m_receive_buffer_lock when calling this. - uint16_t bytes_available = std::min(m_receive_buffer.size(), 0x200); + const u16 bytes_available = std::min(m_receive_buffer.size(), 0x200); m_regs[Register::BYTES_AVAILABLE_HIGH] = (bytes_available >> 8) & 0xFF; m_regs[Register::BYTES_AVAILABLE_LOW] = bytes_available & 0xFF; SetInterruptFlag(Interrupt::RECEIVE_BUFFER_ABOVE_THRESHOLD, @@ -343,25 +350,29 @@ void CEXIModem::AddToReceiveBuffer(std::string&& data) void CEXIModem::AddATReply(const std::string& data) { m_at_reply_data += data; - m_regs[Register::AT_REPLY_SIZE] = m_at_reply_data.size(); - SetInterruptFlag(Interrupt::AT_REPLY_DATA_AVAILABLE, !m_at_reply_data.empty(), false); + m_regs[Register::AT_REPLY_SIZE] = static_cast(m_at_reply_data.size()); + SetInterruptFlag(Interrupt::AT_REPLY_DATA_AVAILABLE, !m_at_reply_data.empty(), true); } void CEXIModem::RunAllPendingATCommands() { - for (size_t newline_pos = m_at_command_data.find_first_of("\r\n"); + for (std::size_t newline_pos = m_at_command_data.find_first_of("\r\n"); newline_pos != std::string::npos; newline_pos = m_at_command_data.find_first_of("\r\n")) { std::string command = m_at_command_data.substr(0, newline_pos); m_at_command_data = m_at_command_data.substr(newline_pos + 1); - if (command == "ATZ") - { // Reset + INFO_LOG_FMT(SP1, "Received AT command: {}", command); + + if (command.substr(0, 3) == "ATZ") + { + // Reset m_network_interface->Deactivate(); AddATReply("OK\r"); } else if (command.substr(0, 3) == "ATD") - { // Dial + { + // Dial if (m_network_interface->Activate()) { AddATReply("OK\rCONNECT 115200\r"); // Maximum baud rate @@ -373,7 +384,9 @@ void CEXIModem::RunAllPendingATCommands() } else { - INFO_LOG_FMT(SP1, "Unhandled AT command: {}", command); + // PSO sends several other AT commands during modem setup, but in our + // implementation we don't actually have to do anything in response to + // them, so we just pretend we did. AddATReply("OK\r"); } } diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceModem.h b/Source/Core/Core/HW/EXI/EXI_DeviceModem.h index d6ba896667..7d008f869d 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceModem.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceModem.h @@ -1,24 +1,15 @@ -// Copyright 2008 Dolphin Emulator Project +// Copyright 2024 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include -#include #include #include #include -#ifdef _WIN32 -#include -#endif - -#include - #include "Common/Flag.h" #include "Common/Network.h" -#include "Common/SocketContext.h" -#include "Core/HW/EXI/BBA/BuiltIn.h" +#include "Core/HW/EXI/BBA/TAPServerConnection.h" #include "Core/HW/EXI/EXI_Device.h" class PointerWrap; @@ -86,7 +77,7 @@ private: u16 GetTxThreshold() const; u16 GetRxThreshold() const; - void SetInterruptFlag(uint8_t what, bool enabled, bool from_cpu); + void SetInterruptFlag(u8 what, bool enabled, bool from_cpu); void HandleReadModemTransfer(void* data, u32 size); void HandleWriteModemTransfer(const void* data, u32 size); void OnReceiveBufferSizeChangedLocked(bool from_cpu); @@ -97,19 +88,19 @@ private: static inline bool TransferIsResetCommand(u32 transfer_descriptor) { - return (transfer_descriptor == 0x80000000); + return transfer_descriptor == 0x80000000; } static inline bool IsWriteTransfer(u32 transfer_descriptor) { - return (transfer_descriptor & 0x40000000); + return transfer_descriptor & 0x40000000; } static inline bool IsModemTransfer(u32 transfer_descriptor) { - return (transfer_descriptor & 0x20000000); + return transfer_descriptor & 0x20000000; } static inline u16 GetModemTransferSize(u32 transfer_descriptor) { - return ((transfer_descriptor >> 8) & 0xFFFF); + return (transfer_descriptor >> 8) & 0xFFFF; } static inline u32 SetModemTransferSize(u32 transfer_descriptor, u16 new_size) { @@ -126,7 +117,7 @@ private: virtual bool Activate() { return false; } virtual void Deactivate() {} virtual bool IsActivated() { return false; } - virtual bool SendFrames() { return false; } + virtual bool SendAndRemoveAllHDLCFrames(std::string&) { return false; } virtual bool RecvInit() { return false; } virtual void RecvStart() {} virtual void RecvStop() {} @@ -137,30 +128,21 @@ private: class TAPServerNetworkInterface : public NetworkInterface { public: - explicit TAPServerNetworkInterface(CEXIModem* modem_ref, const std::string& destination) - : NetworkInterface(modem_ref), m_destination(destination) - { - } + TAPServerNetworkInterface(CEXIModem* modem_ref, const std::string& destination); public: - bool Activate() override; - void Deactivate() override; - bool IsActivated() override; - bool SendFrames() override; - bool RecvInit() override; - void RecvStart() override; - void RecvStop() override; + virtual bool Activate() override; + virtual void Deactivate() override; + virtual bool IsActivated() override; + virtual bool SendAndRemoveAllHDLCFrames(std::string& send_buffer) override; + virtual bool RecvInit() override; + virtual void RecvStart() override; + virtual void RecvStop() override; private: - std::string m_destination; - Common::SocketContext m_socket_context; + TAPServerConnection m_tapserver_if; - int m_fd = -1; - std::thread m_read_thread; - Common::Flag m_read_enabled; - Common::Flag m_read_shutdown; - - void ReadThreadHandler(); + void HandleReceivedFrame(std::string&& data); }; std::unique_ptr m_network_interface; @@ -174,6 +156,6 @@ private: std::string m_send_buffer; std::mutex m_receive_buffer_lock; std::string m_receive_buffer; - std::array m_regs; + std::array m_regs{}; }; } // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/Modem/TAPServerModem.cpp b/Source/Core/Core/HW/EXI/Modem/TAPServerModem.cpp new file mode 100644 index 0000000000..4718c34852 --- /dev/null +++ b/Source/Core/Core/HW/EXI/Modem/TAPServerModem.cpp @@ -0,0 +1,79 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/EXI/EXI_DeviceModem.h" + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#include "Common/CommonFuncs.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "Core/HW/EXI/EXI_Device.h" + +namespace ExpansionInterface +{ + +CEXIModem::TAPServerNetworkInterface::TAPServerNetworkInterface(CEXIModem* modem_ref, + const std::string& destination) + : NetworkInterface(modem_ref), + m_tapserver_if( + destination, + std::bind(&TAPServerNetworkInterface::HandleReceivedFrame, this, std::placeholders::_1), + MODEM_RECV_SIZE) +{ +} + +bool CEXIModem::TAPServerNetworkInterface::Activate() +{ + return m_tapserver_if.Activate(); +} + +void CEXIModem::TAPServerNetworkInterface::Deactivate() +{ + m_tapserver_if.Deactivate(); +} + +bool CEXIModem::TAPServerNetworkInterface::IsActivated() +{ + return m_tapserver_if.IsActivated(); +} + +bool CEXIModem::TAPServerNetworkInterface::SendAndRemoveAllHDLCFrames(std::string& send_buffer) +{ + std::size_t orig_size = send_buffer.size(); + bool send_succeeded = m_tapserver_if.SendAndRemoveAllHDLCFrames(send_buffer); + if (send_succeeded && (send_buffer.size() < orig_size)) + m_modem_ref->SendComplete(); + return send_succeeded; +} + +bool CEXIModem::TAPServerNetworkInterface::RecvInit() +{ + return m_tapserver_if.RecvInit(); +} + +void CEXIModem::TAPServerNetworkInterface::RecvStart() +{ + m_tapserver_if.RecvStart(); +} + +void CEXIModem::TAPServerNetworkInterface::RecvStop() +{ + m_tapserver_if.RecvStop(); +} + +void CEXIModem::TAPServerNetworkInterface::HandleReceivedFrame(std::string&& data) +{ + m_modem_ref->AddToReceiveBuffer(std::move(data)); +} + +} // namespace ExpansionInterface diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 72c2f0ba1d..5622bef028 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -938,9 +938,10 @@ - + + - +