From 4384e1abb657f44d6942ded7cc96de3b6badb24b Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 27 Apr 2025 21:56:51 +1000 Subject: [PATCH] wip blah --- src/core/bus.cpp | 2 +- src/core/core.vcxproj | 2 + src/core/core.vcxproj.filters | 2 + src/core/imgui_overlays.cpp | 6 +- src/core/sio.cpp | 525 ++++++++++++++++++++++++++++-- src/core/sio.h | 6 +- src/core/sio_connection.cpp | 499 ++++++++++++++++++++++++++++ src/core/sio_connection.h | 109 +++++++ src/duckstation-qt/mainwindow.cpp | 1 + src/duckstation-qt/mainwindow.ui | 9 + 10 files changed, 1120 insertions(+), 41 deletions(-) create mode 100644 src/core/sio_connection.cpp create mode 100644 src/core/sio_connection.h diff --git a/src/core/bus.cpp b/src/core/bus.cpp index 5771d5c5a..133793fd5 100644 --- a/src/core/bus.cpp +++ b/src/core/bus.cpp @@ -1809,7 +1809,7 @@ template u32 Bus::HWHandlers::SIORead(PhysicalMemoryAddress address) { const u32 offset = address & SIO_MASK; - u32 value = SIO::ReadRegister(FIXUP_HALFWORD_OFFSET(size, offset)); + u32 value = SIO::ReadRegister(FIXUP_HALFWORD_OFFSET(size, offset), 1u << static_cast(size)); value = FIXUP_HALFWORD_READ_VALUE(size, offset, value); BUS_CYCLES(2); return value; diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index 383a19c8b..2bb8642a2 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -75,6 +75,7 @@ + @@ -159,6 +160,7 @@ + diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index 0f07ee604..75b02b173 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -68,6 +68,7 @@ + @@ -146,6 +147,7 @@ + diff --git a/src/core/imgui_overlays.cpp b/src/core/imgui_overlays.cpp index 294de2212..2842af20e 100644 --- a/src/core/imgui_overlays.cpp +++ b/src/core/imgui_overlays.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "imgui_overlays.h" @@ -18,6 +18,7 @@ #include "settings.h" #include "spu.h" #include "system.h" +#include "sio.h" #include "util/gpu_device.h" #include "util/imgui_animated.h" @@ -108,7 +109,7 @@ static void UpdateInputOverlay(void* buffer); #ifndef __ANDROID__ -static constexpr size_t NUM_DEBUG_WINDOWS = 7; +static constexpr size_t NUM_DEBUG_WINDOWS = 8; static constexpr const char* DEBUG_WINDOW_CONFIG_SECTION = "DebugWindows"; static constexpr const std::array s_debug_window_info = {{ {"Freecam", "Free Camera", ":icons/applications-system.png", >E::DrawFreecamWindow, 500, 425}, @@ -118,6 +119,7 @@ static constexpr const std::array s_debug_wi {"DMA", "DMA State", ":icons/applications-system.png", &DMA::DrawDebugStateWindow, 860, 180}, {"MDEC", "MDEC State", ":icons/applications-system.png", &MDEC::DrawDebugStateWindow, 300, 350}, {"Timers", "Timers State", ":icons/applications-system.png", &Timers::DrawDebugStateWindow, 800, 95}, + {"SIO", "SIO State", ":icons/applications-system.png", &SIO::DrawDebugStateWindow, 600, 400}, }}; static std::array s_debug_window_state = {}; diff --git a/src/core/sio.cpp b/src/core/sio.cpp index fb1b1ad87..b0e91495d 100644 --- a/src/core/sio.cpp +++ b/src/core/sio.cpp @@ -1,8 +1,11 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "sio.h" -#include "controller.h" +#include "interrupt_controller.h" +#include "sio_connection.h" +#include "system.h" +#include "timing_event.h" #include "util/state_wrapper.h" @@ -10,6 +13,8 @@ #include "common/bitutils.h" #include "common/log.h" +#include "imgui.h" + #include #include @@ -18,6 +23,12 @@ LOG_CHANNEL(SIO); namespace SIO { namespace { +enum : u32 +{ + // Actually 8 bytes, but we allow a bit more due to network latency. + RX_FIFO_SIZE = 32 +}; + union SIO_CTRL { u16 bits; @@ -32,7 +43,7 @@ union SIO_CTRL BitField RXIMODE; BitField TXINTEN; BitField RXINTEN; - BitField ACKINTEN; + BitField DTRINTEN; }; union SIO_STAT @@ -41,7 +52,7 @@ union SIO_STAT BitField TXRDY; BitField RXFIFONEMPTY; - BitField TXDONE; + BitField TXIDLE; BitField RXPARITY; BitField RXFIFOOVERRUN; BitField RXBADSTOPBIT; @@ -62,24 +73,75 @@ union SIO_MODE BitField parity_type; BitField stop_bit_length; }; + } // namespace static void SoftReset(); -static SIO_CTRL s_SIO_CTRL = {}; -static SIO_STAT s_SIO_STAT = {}; -static SIO_MODE s_SIO_MODE = {}; -static u16 s_SIO_BAUD = 0; +static TickCount GetTicksBetweenTransfers(); + +static void UpdateTXRX(); +static void SetInterrupt(); + +static void UpdateEvent(); +static void TransferEvent(void* param, TickCount ticks, TickCount ticks_late); +static void TransferWithoutSync(); +static void TransferWithSync(); + +namespace { + +struct ALIGN_TO_CACHE_LINE SIOState +{ + SIO_CTRL ctrl = {}; + SIO_STAT stat = {}; + SIO_MODE mode = {}; + u16 baud_rate = 0; + + InlineFIFOQueue data_in; + + u8 data_out = 0; + bool data_out_full = false; + bool latched_txen = false; + + bool sync_mode = true; + bool sync_last_dtr = false; + bool sync_last_rts = false; + u8 sync_last_fifo_size = 0; + u8 sync_remote_rx_fifo_size = 0; + + bool is_server = false; + bool needs_initial_sync = false; + + TimingEvent transfer_event{"SIO Transfer", 1, 1, &SIO::TransferEvent, nullptr}; + + std::unique_ptr connection; +}; +} // namespace + +static constexpr std::array s_mul_factors = {{1, 16, 64, 0}}; + +static SIOState s_state; } // namespace SIO void SIO::Initialize() { + s_state.is_server = !IsDebuggerPresent(); + if (s_state.is_server) + s_state.connection = SIOConnection::CreateSocketServer("0.0.0.0", 1337); + else + s_state.connection = SIOConnection::CreateSocketClient("127.0.0.1", 1337); + + s_state.stat.bits = 0; + s_state.stat.CTSINPUTLEVEL = false; + s_state.needs_initial_sync = true; Reset(); } void SIO::Shutdown() { + s_state.connection.reset(); + s_state.transfer_event.Deactivate(); } void SIO::Reset() @@ -89,41 +151,111 @@ void SIO::Reset() bool SIO::DoState(StateWrapper& sw) { - sw.Do(&s_SIO_CTRL.bits); - sw.Do(&s_SIO_STAT.bits); - sw.Do(&s_SIO_MODE.bits); - sw.Do(&s_SIO_BAUD); + const bool dtr = s_state.stat.DSRINPUTLEVEL; + const bool rts = s_state.stat.CTSINPUTLEVEL; + + sw.Do(&s_state.ctrl.bits); + sw.Do(&s_state.stat.bits); + sw.Do(&s_state.mode.bits); + sw.Do(&s_state.baud_rate); + + s_state.stat.DSRINPUTLEVEL = dtr; + s_state.stat.CTSINPUTLEVEL = rts; + + UpdateEvent(); + UpdateTXRX(); return !sw.HasError(); } -u32 SIO::ReadRegister(u32 offset) +void SIO::SoftReset() +{ + s_state.ctrl.bits = 0; + s_state.stat.RXPARITY = false; + s_state.stat.RXFIFOOVERRUN = false; + s_state.stat.RXBADSTOPBIT = false; + s_state.stat.INTR = false; + s_state.mode.bits = 0; + s_state.baud_rate = 0xDC; + s_state.data_in.Clear(); + s_state.data_out = 0; + s_state.data_out_full = false; + + UpdateEvent(); + UpdateTXRX(); +} + +void SIO::UpdateTXRX() +{ + s_state.stat.TXRDY = !s_state.data_out_full && s_state.stat.CTSINPUTLEVEL; + s_state.stat.TXIDLE = !s_state.data_out_full; + s_state.stat.RXFIFONEMPTY = !s_state.data_in.IsEmpty(); +} + +void SIO::SetInterrupt() +{ + DEV_LOG("Set SIO IRQ"); + s_state.stat.INTR = true; + InterruptController::SetLineState(InterruptController::IRQ::SIO, true); +} + +u32 SIO::ReadRegister(u32 offset, u32 read_size) { switch (offset) { case 0x00: // SIO_DATA { - ERROR_LOG("Read SIO_DATA"); + s_state.transfer_event.InvokeEarly(false); - const u8 value = 0xFF; - return (ZeroExtend32(value) | (ZeroExtend32(value) << 8) | (ZeroExtend32(value) << 16) | - (ZeroExtend32(value) << 24)); + const u32 data_in_size = std::min(s_state.data_in.GetSize(), 4u); + u32 res = 0; + switch (data_in_size) + { + case 4: + res = ZeroExtend32(s_state.data_in.Peek(3)) << 24; + [[fallthrough]]; + + case 3: + res |= ZeroExtend32(s_state.data_in.Peek(2)) << 16; + [[fallthrough]]; + + case 2: + res |= ZeroExtend32(s_state.data_in.Peek(1)) << 8; + [[fallthrough]]; + + case 1: + res |= ZeroExtend32(s_state.data_in.Peek(0)); + s_state.data_in.Remove(std::min(s_state.data_in.GetSize(), read_size)); + break; + + case 0: + default: + res = 0xFFFFFFFFu; + break; + } + + WARNING_LOG("Read SIO_DATA -> 0x{:08X}", res); + UpdateTXRX(); + return res; } case 0x04: // SIO_STAT { - const u32 bits = s_SIO_STAT.bits; + s_state.transfer_event.InvokeEarly(false); + + const u32 bits = s_state.stat.bits; + DEBUG_LOG("Read SIO_STAT -> 0x{:08X}", bits); return bits; } case 0x08: // SIO_MODE - return ZeroExtend32(s_SIO_MODE.bits); + return ZeroExtend32(s_state.mode.bits); case 0x0A: // SIO_CTRL - return ZeroExtend32(s_SIO_CTRL.bits); + return ZeroExtend32(s_state.ctrl.bits); case 0x0E: // SIO_BAUD - return ZeroExtend32(s_SIO_BAUD); + return ZeroExtend32(s_state.baud_rate); [[unlikely]] default: ERROR_LOG("Unknown register read: 0x{:X}", offset); @@ -138,31 +270,65 @@ void SIO::WriteRegister(u32 offset, u32 value) case 0x00: // SIO_DATA { WARNING_LOG("SIO_DATA (W) <- 0x{:02X}", value); + s_state.transfer_event.InvokeEarly(false); + + if (s_state.data_out_full) + WARNING_LOG("SIO TX buffer overflow, lost 0x{:02X} when writing 0x{:02X}", s_state.data_out, value); + + s_state.data_out = Truncate8(value); + s_state.data_out_full = true; + UpdateTXRX(); + + s_state.transfer_event.InvokeEarly(false); return; } case 0x0A: // SIO_CTRL { - DEBUG_LOG("SIO_CTRL <- 0x{:04X}", value); + DEV_LOG("SIO_CTRL <- 0x{:04X}", value); + s_state.transfer_event.InvokeEarly(false); - s_SIO_CTRL.bits = Truncate16(value); - if (s_SIO_CTRL.RESET) + s_state.ctrl.bits = Truncate16(value); + if (s_state.ctrl.RESET) SoftReset(); + if (s_state.ctrl.ACK) + { + s_state.stat.RXPARITY = false; + s_state.stat.RXFIFOOVERRUN = false; + s_state.stat.RXBADSTOPBIT = false; + s_state.stat.INTR = false; + InterruptController::SetLineState(InterruptController::IRQ::SIO, false); + } + + if (!s_state.ctrl.RXEN) + { + WARNING_LOG("Clearing Input FIFO"); + s_state.data_in.Clear(); + s_state.stat.RXFIFOOVERRUN = false; + UpdateTXRX(); + } + /*if (!m_ctrl.TXEN) + { + WARNING_LOG("Clearing output fifo"); + m_data_out_full = false; + UpdateTXRX(); + }*/ + return; } case 0x08: // SIO_MODE { - DEBUG_LOG("SIO_MODE <- 0x{:08X}", value); - s_SIO_MODE.bits = Truncate16(value); + DEV_LOG("SIO_MODE <- 0x{:08X}", value); + s_state.mode.bits = Truncate16(value); return; } case 0x0E: { DEBUG_LOG("SIO_BAUD <- 0x{:08X}", value); - s_SIO_BAUD = Truncate16(value); + s_state.baud_rate = Truncate16(value); return; } @@ -172,14 +338,301 @@ void SIO::WriteRegister(u32 offset, u32 value) } } -void SIO::SoftReset() +void SIO::DrawDebugStateWindow(float scale) { - s_SIO_CTRL.bits = 0; - s_SIO_STAT.bits = 0; - s_SIO_STAT.DSRINPUTLEVEL = true; - s_SIO_STAT.CTSINPUTLEVEL = true; - s_SIO_STAT.TXDONE = true; - s_SIO_STAT.TXRDY = true; - s_SIO_MODE.bits = 0; - s_SIO_BAUD = 0xDC; + static const ImVec4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; + static const ImVec4 inactive_color{0.4f, 0.4f, 0.4f, 1.0f}; + + ImGui::Text("Connected: "); + ImGui::SameLine(); + if (s_state.connection && s_state.connection->IsConnected()) + ImGui::TextColored(active_color, "Yes (%s)", s_state.is_server ? "server" : "client"); + else + ImGui::TextColored(inactive_color, "No"); + + ImGui::Text("Status: "); + ImGui::SameLine(); + + float pos = ImGui::GetCursorPosX(); + ImGui::TextColored(s_state.stat.TXRDY ? active_color : inactive_color, "TXRDY"); + ImGui::SameLine(); + ImGui::TextColored(s_state.stat.RXFIFONEMPTY ? active_color : inactive_color, "RXFIFONEMPTY"); + ImGui::SameLine(); + ImGui::TextColored(s_state.stat.TXIDLE ? active_color : inactive_color, "TXIDLE"); + ImGui::SameLine(); + ImGui::TextColored(s_state.stat.RXPARITY ? active_color : inactive_color, "RXPARITY"); + ImGui::SameLine(); + ImGui::TextColored(s_state.stat.RXFIFOOVERRUN ? active_color : inactive_color, "RXFIFOOVERRUN"); + ImGui::SetCursorPosX(pos); + ImGui::TextColored(s_state.stat.RXBADSTOPBIT ? active_color : inactive_color, "RXBADSTOPBIT"); + ImGui::SameLine(); + ImGui::TextColored(s_state.stat.RXINPUTLEVEL ? active_color : inactive_color, "RXINPUTLEVEL"); + ImGui::SameLine(); + ImGui::TextColored(s_state.stat.DSRINPUTLEVEL ? active_color : inactive_color, "DSRINPUTLEVEL"); + ImGui::SameLine(); + ImGui::TextColored(s_state.stat.CTSINPUTLEVEL ? active_color : inactive_color, "CTSINPUTLEVEL"); + ImGui::SameLine(); + ImGui::TextColored(s_state.stat.INTR ? active_color : inactive_color, "INTR"); + + ImGui::NewLine(); + + ImGui::Text("Control: "); + ImGui::SameLine(); + + pos = ImGui::GetCursorPosX(); + ImGui::TextColored(s_state.ctrl.TXEN ? active_color : inactive_color, "TXEN"); + ImGui::SameLine(); + ImGui::TextColored(s_state.ctrl.DTROUTPUT ? active_color : inactive_color, "DTROUTPUT"); + ImGui::SameLine(); + ImGui::TextColored(s_state.ctrl.RXEN ? active_color : inactive_color, "RXEN"); + ImGui::SameLine(); + ImGui::TextColored(s_state.ctrl.TXOUTPUT ? active_color : inactive_color, "TXOUTPUT"); + ImGui::SameLine(); + ImGui::TextColored(s_state.ctrl.RTSOUTPUT ? active_color : inactive_color, "RTSOUTPUT"); + ImGui::SetCursorPosX(pos); + ImGui::TextColored(s_state.ctrl.TXINTEN ? active_color : inactive_color, "TXINTEN"); + ImGui::SameLine(); + ImGui::TextColored(s_state.ctrl.RXINTEN ? active_color : inactive_color, "RXINTEN"); + ImGui::SameLine(); + ImGui::TextColored(s_state.ctrl.RXINTEN ? active_color : inactive_color, "RXIMODE: %u", + s_state.ctrl.RXIMODE.GetValue()); + + ImGui::NewLine(); + + ImGui::Text("Mode: "); + ImGui::Text(" Reload Factor: %u", s_mul_factors[s_state.mode.reload_factor]); + ImGui::Text(" Character Length: %u", s_state.mode.character_length.GetValue()); + ImGui::Text(" Parity Enable: %s", s_state.mode.parity_enable ? "Yes" : "No"); + ImGui::Text(" Parity Type: %u", s_state.mode.parity_type.GetValue()); + ImGui::Text(" Stop Bit Length: %u", s_state.mode.stop_bit_length.GetValue()); + + ImGui::NewLine(); + + ImGui::Text("Baud Rate: %u", s_state.baud_rate); + + ImGui::NewLine(); + + ImGui::TextColored(s_state.data_out_full ? active_color : inactive_color, "Output buffer: 0x%02X", s_state.data_out); + + ImGui::Text("Input buffer: "); + for (u32 i = 0; i < s_state.data_in.GetSize(); i++) + { + ImGui::SameLine(); + ImGui::Text("0x%02X ", s_state.data_in.Peek(i)); + } } + +TickCount SIO::GetTicksBetweenTransfers() +{ + const u32 factor = s_mul_factors[s_state.mode.reload_factor]; + const u32 ticks = std::max((s_state.baud_rate * factor) & ~u32(1), factor); + + return static_cast(ticks); +} + +void SIO::UpdateEvent() +{ + if (!s_state.connection) + { + s_state.transfer_event.Deactivate(); + s_state.stat.CTSINPUTLEVEL = true; + s_state.stat.DSRINPUTLEVEL = false; + s_state.sync_last_dtr = false; + s_state.sync_last_rts = false; + s_state.sync_remote_rx_fifo_size = 0; + return; + } + + TickCount ticks = GetTicksBetweenTransfers(); + if (ticks == 0) + ticks = System::GetMaxSliceTicks(); + + if (s_state.transfer_event.GetPeriod() == ticks && s_state.transfer_event.IsActive()) + return; + + s_state.transfer_event.Deactivate(); + s_state.transfer_event.SetPeriodAndSchedule(ticks); +} + +void SIO::TransferEvent(void* param, TickCount ticks, TickCount ticks_late) +{ + if (s_state.sync_mode) + TransferWithSync(); + else + TransferWithoutSync(); +} + +void SIO::TransferWithoutSync() +{ + // bytes aren't transmitted when CTS isn't set (i.e. there's nothing on the other side) + if (s_state.connection && s_state.connection->IsConnected()) + { + s_state.stat.CTSINPUTLEVEL = true; + s_state.stat.DSRINPUTLEVEL = true; + + if (s_state.ctrl.RXEN) + { + u8 data_in; + u32 data_in_size = s_state.connection->Read(&data_in, sizeof(data_in), 0); + if (data_in_size > 0) + { + if (s_state.data_in.IsFull()) + { + WARNING_LOG("FIFO overrun"); + s_state.data_in.RemoveOne(); + s_state.stat.RXFIFOOVERRUN = true; + } + + s_state.data_in.Push(data_in); + + if (s_state.ctrl.RXINTEN) + SetInterrupt(); + } + } + + if (s_state.ctrl.TXEN && s_state.data_out_full) + { + const u8 data_out = s_state.data_out; + s_state.data_out_full = false; + + const u32 data_sent = s_state.connection->Write(&data_out, sizeof(data_out)); + if (data_sent != sizeof(data_out)) + WARNING_LOG("Failed to send 0x{:02X} to connection", data_out); + + if (s_state.ctrl.TXINTEN) + SetInterrupt(); + } + } + else + { + s_state.stat.CTSINPUTLEVEL = false; + s_state.stat.DSRINPUTLEVEL = false; + } + + UpdateTXRX(); +} + +void SIO::TransferWithSync() +{ + union SyncByte + { + BitField has_data; + BitField dtr_level; + BitField rts_level; + BitField rx_fifo_size; + + u8 bits; + }; + + if (!s_state.connection || !s_state.connection->IsConnected()) + { + s_state.stat.CTSINPUTLEVEL = true; + s_state.stat.DSRINPUTLEVEL = false; + s_state.sync_last_dtr = false; + s_state.sync_last_rts = false; + UpdateTXRX(); + return; + } + + u8 buf[2] = {}; + bool send_reply = std::exchange(s_state.needs_initial_sync, false); + if (s_state.connection->HasData()) + { + while (s_state.connection->Read(buf, sizeof(buf), sizeof(buf)) != 0) + { + // INFO_LOG("In: {:02X} {:02X}", buf[0], buf[1]); + + const SyncByte sb{buf[0]}; + + if (sb.has_data) + { + WARNING_LOG("Received: {:02X}", buf[1]); + send_reply = true; + + if (s_state.data_in.IsFull()) + { + WARNING_LOG("RX OVERRUN"); + s_state.stat.RXFIFOOVERRUN = true; + } + else + { + s_state.data_in.Push(buf[1]); + } + + const u32 rx_threshold = 1u << s_state.ctrl.RXIMODE; + if (s_state.ctrl.RXINTEN && !s_state.stat.INTR && s_state.data_in.GetSize() >= rx_threshold) + { + WARNING_LOG("Setting RX interrupt"); + SetInterrupt(); + } + } + + if (!s_state.stat.DSRINPUTLEVEL && sb.dtr_level) + WARNING_LOG("DSR active"); + else if (s_state.stat.DSRINPUTLEVEL && !sb.dtr_level) + WARNING_LOG("DSR inactive"); + if (!s_state.stat.CTSINPUTLEVEL && sb.rts_level) + WARNING_LOG("Remote RTS active, setting CTS"); + else if (s_state.stat.CTSINPUTLEVEL && !sb.rts_level) + WARNING_LOG("Remote RTS inactive, clearing CTS"); + + if (sb.dtr_level && !s_state.stat.DSRINPUTLEVEL && s_state.ctrl.DTRINTEN) + { + WARNING_LOG("Setting DSR interrupt"); + SetInterrupt(); + } + + s_state.stat.DSRINPUTLEVEL = sb.dtr_level; + s_state.stat.CTSINPUTLEVEL = sb.rts_level; + s_state.sync_remote_rx_fifo_size = sb.rx_fifo_size; + } + } + + const bool dtr_level = s_state.ctrl.DTROUTPUT; + const bool rts_level = s_state.ctrl.RTSOUTPUT; + const bool tx = (s_state.ctrl.TXEN || s_state.latched_txen) && s_state.stat.CTSINPUTLEVEL && s_state.data_out_full && + s_state.sync_remote_rx_fifo_size < RX_FIFO_SIZE; + s_state.latched_txen = s_state.ctrl.TXEN; + if (dtr_level != s_state.sync_last_dtr || rts_level != s_state.sync_last_rts || + s_state.data_in.GetSize() != s_state.sync_last_fifo_size || tx || send_reply) + { + if (s_state.sync_last_dtr != dtr_level) + WARNING_LOG("OUR DTR level => {}", dtr_level); + + if (s_state.sync_last_rts != rts_level) + WARNING_LOG("OUR RTS level => {}", rts_level); + + s_state.sync_last_dtr = dtr_level; + s_state.sync_last_rts = rts_level; + s_state.sync_last_fifo_size = Truncate8(s_state.data_in.GetSize()); + + SyncByte sb{0}; + sb.has_data = tx; + sb.dtr_level = dtr_level; + sb.rts_level = rts_level; + sb.rx_fifo_size = Truncate8(s_state.data_in.GetSize()); + + buf[0] = sb.bits; + buf[1] = 0; + if (tx) + { + s_state.sync_remote_rx_fifo_size++; + + WARNING_LOG("Sending: {:02X}, remote fifo now {} bytes", s_state.data_out, s_state.sync_remote_rx_fifo_size); + buf[1] = s_state.data_out; + s_state.data_out_full = false; + + if (s_state.ctrl.TXINTEN) + { + WARNING_LOG("Setting TX interrupt"); + SetInterrupt(); + } + } + + // INFO_LOG("Out: {:02X} {:02X}", buf[0], buf[1]); + if (s_state.connection->Write(buf, sizeof(buf)) != sizeof(buf)) + WARNING_LOG("Write failed"); + } + + UpdateTXRX(); +} \ No newline at end of file diff --git a/src/core/sio.h b/src/core/sio.h index 7a53118a3..abe240fef 100644 --- a/src/core/sio.h +++ b/src/core/sio.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #pragma once @@ -14,7 +14,9 @@ void Shutdown(); void Reset(); bool DoState(StateWrapper& sw); -u32 ReadRegister(u32 offset); +u32 ReadRegister(u32 offset, u32 read_size); void WriteRegister(u32 offset, u32 value); +void DrawDebugStateWindow(float scale); + } // namespace SIO diff --git a/src/core/sio_connection.cpp b/src/core/sio_connection.cpp new file mode 100644 index 000000000..a840d41af --- /dev/null +++ b/src/core/sio_connection.cpp @@ -0,0 +1,499 @@ +#include "sio_connection.h" +#include "common/log.h" +#include "common/small_string.h" +#include +LOG_CHANNEL(SIO); + +#ifdef _WIN32 +#include +#pragma comment(lib, "ws2_32.lib") +#define SOCKET_ERROR_WOULD_BLOCK WSAEWOULDBLOCK +#else +#define SOCKET_ERROR_WOULD_BLOCK EWOULDBLOCK +#define INVALID_SOCKET -1 +#endif + +static void CloseSocket(SocketType fd) +{ +#ifdef _WIN32 + closesocket(fd); +#else + close(fd); +#endif +} + +static int GetSocketError() +{ +#ifdef _WIN32 + return WSAGetLastError(); +#else + return errno; +#endif +} + +static void PrintSocketError(const char* format, ...) +{ + std::va_list ap; + va_start(ap, format); + + SmallString str; + str.vsprintf(format, ap); + va_end(ap); + + ERROR_LOG("{}: {}", str, GetSocketError()); +} + +static bool SetSocketNonblocking(SocketType socket, bool nonblocking) +{ +#ifdef WIN32 + u_long value = nonblocking ? 1 : 0; + if (ioctlsocket(socket, FIONBIO, &value) != 0) + { + PrintSocketError("ioctlsocket(%s)", nonblocking ? "nonblocking" : "blocking"); + return false; + } + + return true; +#else + return false; +#endif +} + +SIOSocketConnection::SIOSocketConnection(std::string hostname, u32 port) + : m_hostname(std::move(hostname)), m_port(port), m_client_fd(INVALID_SOCKET) +{ +} + +SIOSocketConnection::~SIOSocketConnection() +{ + if (m_client_fd != INVALID_SOCKET) + CloseSocket(m_client_fd); + +#ifdef WIN32 + if (m_client_event != NULL) + WSACloseEvent(m_client_event); + + if (m_want_write_event != NULL) + CloseHandle(m_want_write_event); + + if (m_sockets_initialized) + WSACleanup(); +#endif +} + +bool SIOSocketConnection::Initialize() +{ +#ifdef _WIN32 + WSADATA wd = {}; + if (WSAStartup(MAKEWORD(2, 0), &wd) != 0) + { + PrintSocketError("WSAStartup() failed"); + return false; + } + + m_sockets_initialized = true; +#endif + +#ifdef _WIN32 + m_client_event = WSACreateEvent(); + m_want_write_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (m_client_event == NULL || m_want_write_event == NULL) + return false; +#endif + + return true; +} + +u32 SIOSocketConnection::Read(void* buffer, u32 buffer_size, u32 min_size) +{ + std::unique_lock lock(m_buffer_mutex); + if (m_read_buffer.empty() || m_client_fd < 0) + { + m_data_ready.store(false); + return 0; + } + + if (m_read_buffer.size() < min_size) + return 0; + + const u32 to_read = std::min(static_cast(m_read_buffer.size()), buffer_size); + if (to_read > 0) + { + std::memcpy(buffer, m_read_buffer.data(), to_read); + if (to_read == m_read_buffer.size()) + { + m_read_buffer.clear(); + } + else + { + const size_t new_size = m_read_buffer.size() - to_read; + std::memmove(&m_read_buffer[0], &m_read_buffer[to_read], new_size); + m_read_buffer.resize(new_size); + } + } + + m_data_ready.store(!m_read_buffer.empty()); + return to_read; +} + +u32 SIOSocketConnection::Write(const void* buffer, u32 buffer_size) +{ + std::unique_lock lock(m_buffer_mutex); + if (m_client_fd < 0) + return 0; + + // TODO: Max buffer size + const u32 to_write = buffer_size; + const size_t current_size = m_write_buffer.size(); + m_write_buffer.resize(m_write_buffer.size() + buffer_size); + std::memcpy(&m_write_buffer[current_size], buffer, buffer_size); + +#ifdef _WIN32 + SetEvent(m_want_write_event); +#else +#endif + + return to_write; +} + +void SIOSocketConnection::StartThread() +{ + m_thread = std::thread([this]() { SocketThread(); }); +} + +void SIOSocketConnection::ShutdownThread() +{ + if (!m_thread.joinable()) + return; + + m_thread_shutdown.store(true); + +#ifdef _WIN32 + SetEvent(m_want_write_event); +#endif + + m_thread.join(); +} + +void SIOSocketConnection::HandleRead() +{ + std::unique_lock lock(m_buffer_mutex); + + size_t current_size = m_read_buffer.size(); + size_t buffer_size = std::max(m_read_buffer.size() * 2, 128); + m_read_buffer.resize(buffer_size); + + int nbytes = recv(m_client_fd, reinterpret_cast(&m_read_buffer[current_size]), + static_cast(buffer_size - current_size), 0); + if (nbytes <= 0) + { + m_read_buffer.resize(current_size); + if (GetSocketError() == SOCKET_ERROR_WOULD_BLOCK) + return; + + PrintSocketError("recv() failed"); + Disconnect(); + return; + } + else if (nbytes == 0) + { + INFO_LOG("Client disconnected."); + Disconnect(); + return; + } + + m_read_buffer.resize(current_size + static_cast(nbytes)); + m_data_ready.store(true); +} + +void SIOSocketConnection::HandleWrite() +{ + std::unique_lock lock(m_buffer_mutex); + if (m_write_buffer.empty()) + return; + + int nbytes = + send(m_client_fd, reinterpret_cast(m_write_buffer.data()), static_cast(m_write_buffer.size()), 0); + if (nbytes < 0) + { + if (GetSocketError() == SOCKET_ERROR_WOULD_BLOCK) + return; + + PrintSocketError("send() failed"); + Disconnect(); + return; + } + + if (nbytes == static_cast(m_write_buffer.size())) + { + m_write_buffer.clear(); + return; + } + + const size_t new_size = m_write_buffer.size() - static_cast(nbytes); + std::memmove(&m_write_buffer[0], &m_write_buffer[static_cast(nbytes)], new_size); + m_write_buffer.resize(new_size); +} + +void SIOSocketConnection::HandleClose() +{ + INFO_LOG("Client disconnected."); + Disconnect(); +} + +void SIOSocketConnection::Disconnect() +{ + CloseSocket(m_client_fd); + m_client_fd = INVALID_SOCKET; + m_read_buffer.clear(); + m_write_buffer.clear(); + m_connected.store(false); + m_data_ready.store(false); +} + +std::unique_ptr SIOConnection::CreateSocketServer(std::string hostname, u32 port) +{ + std::unique_ptr server(new SIOSocketServerConnection(std::move(hostname), port)); + if (!server->Initialize()) + return {}; + + return server; +} + +SIOSocketServerConnection::SIOSocketServerConnection(std::string hostname, u32 port) + : SIOSocketConnection(std::move(hostname), port), m_accept_fd(INVALID_SOCKET) +{ +} + +SIOSocketServerConnection::~SIOSocketServerConnection() +{ + ShutdownThread(); + + if (m_accept_fd != INVALID_SOCKET) + CloseSocket(m_accept_fd); + + if (m_accept_event != NULL) + WSACloseEvent(m_accept_event); +} + +bool SIOSocketServerConnection::Initialize() +{ + if (!SIOSocketConnection::Initialize()) + return false; + + sockaddr_in addr = {}; + addr.sin_family = AF_INET; + addr.sin_port = htons(static_cast(m_port)); + + m_accept_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (m_accept_fd == INVALID_SOCKET) + { + PrintSocketError("socket() failed"); + return false; + } + + if (bind(m_accept_fd, reinterpret_cast(&addr), sizeof(addr)) != 0) + { + PrintSocketError("bind() failed"); + return false; + } + + if (listen(m_accept_fd, 1) != 0) + { + PrintSocketError("listen() failed"); + return false; + } + +#ifdef _WIN32 + SetSocketNonblocking(m_accept_fd, true); + + m_accept_event = WSACreateEvent(); + if (m_accept_event == NULL) + return false; + + if (WSAEventSelect(m_accept_fd, m_accept_event, FD_ACCEPT) != 0) + { + PrintSocketError("WSAEventSelect(FD_ACCEPT) failed"); + return false; + } +#endif + + StartThread(); + return true; +} + +void SIOSocketServerConnection::SocketThread() +{ + while (!m_thread_shutdown.load()) + { +#ifdef _WIN32 + const HANDLE event_handles[] = {m_want_write_event, m_accept_event, m_client_event}; + const DWORD res = WSAWaitForMultipleEvents(countof(event_handles), event_handles, FALSE, 1000, FALSE); + if (res == WAIT_TIMEOUT) + continue; + + WSANETWORKEVENTS ev; + if (WSAEnumNetworkEvents(m_accept_fd, m_accept_event, &ev) == 0) + { + if (ev.lNetworkEvents & FD_ACCEPT) + HandleAccept(); + } + + if (m_client_fd != INVALID_SOCKET) + { + if (WSAEnumNetworkEvents(m_client_fd, m_client_event, &ev) == 0) + { + if (ev.lNetworkEvents & FD_READ) + HandleRead(); + if (ev.lNetworkEvents & FD_WRITE) + HandleWrite(); + if (ev.lNetworkEvents & FD_CLOSE) + HandleClose(); + } + } + + if (m_client_fd != INVALID_SOCKET && res == WSA_WAIT_EVENT_0) + HandleWrite(); +#else +#endif + } +} + +void SIOSocketServerConnection::HandleAccept() +{ + sockaddr client_address = {}; + int client_address_len = sizeof(client_address); + SocketType new_socket = accept(m_accept_fd, &client_address, &client_address_len); + if (new_socket == INVALID_SOCKET) + { + if (GetSocketError() != SOCKET_ERROR_WOULD_BLOCK) + PrintSocketError("accept() failed"); + + return; + } + + if (m_client_fd != INVALID_SOCKET) + { + static const char error[] = "Client already connected."; + WARNING_LOG("Dropping client connection because we're already connected"); + + // we already have a client + SetSocketNonblocking(new_socket, false); + send(new_socket, error, sizeof(error) - 1, 0); + CloseSocket(new_socket); + return; + } + + SetSocketNonblocking(new_socket, true); + +#ifdef _WIN32 + if (WSAEventSelect(new_socket, m_client_event, FD_READ | FD_WRITE | FD_CLOSE) != 0) + { + PrintSocketError("WSAEventSelect(FD_READ | FD_WRITE | FD_CLOSE) failed"); + CloseSocket(new_socket); + } +#endif + + std::unique_lock lock(m_buffer_mutex); + INFO_LOG("Client connection accepted: {}", new_socket); + m_client_fd = new_socket; + m_connected.store(true); +} + +SIOSocketClientConnection::SIOSocketClientConnection(std::string hostname, u32 port) + : SIOSocketConnection(std::move(hostname), port) +{ +} + +SIOSocketClientConnection::~SIOSocketClientConnection() +{ + ShutdownThread(); +} + +bool SIOSocketClientConnection::Initialize() +{ + if (!SIOSocketConnection::Initialize()) + return false; + + struct addrinfo hints = {}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + struct addrinfo* ai; + int err = getaddrinfo(m_hostname.c_str(), TinyString::from_format("{}", m_port), &hints, &ai); + if (err != 0) + { + ERROR_LOG("getaddrinfo({}:{}) failed: {}", m_hostname, m_port, err); + return false; + } + + m_client_fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (m_client_fd == INVALID_SOCKET) + { + PrintSocketError("socket() failed"); + freeaddrinfo(ai); + return false; + } + + err = connect(m_client_fd, ai->ai_addr, static_cast(ai->ai_addrlen)); + freeaddrinfo(ai); + if (err != 0) + { + PrintSocketError("connect() failed"); + return false; + } + + SetSocketNonblocking(m_client_fd, true); + +#ifdef _WIN32 + if (WSAEventSelect(m_client_fd, m_client_event, FD_READ | FD_WRITE | FD_CLOSE) != 0) + { + PrintSocketError("WSAEventSelect(FD_READ | FD_WRITE | FD_CLOSE) failed"); + CloseSocket(m_client_fd); + } +#endif + + m_connected.store(true); + StartThread(); + return true; +} + +void SIOSocketClientConnection::SocketThread() +{ + while (!m_thread_shutdown.load()) + { +#ifdef _WIN32 + HANDLE event_handles[] = {m_want_write_event, m_client_event}; + DWORD res = WSAWaitForMultipleEvents(countof(event_handles), event_handles, FALSE, 1000, FALSE); + if (res == WAIT_TIMEOUT) + continue; + + WSANETWORKEVENTS ev; + if (m_client_fd != INVALID_SOCKET) + { + if (WSAEnumNetworkEvents(m_client_fd, m_client_event, &ev) == 0) + { + if (ev.lNetworkEvents & FD_READ) + HandleRead(); + if (ev.lNetworkEvents & FD_WRITE) + HandleWrite(); + if (ev.lNetworkEvents & FD_CLOSE) + HandleClose(); + } + } + + if (m_client_fd != INVALID_SOCKET && res == WSA_WAIT_EVENT_0) + HandleWrite(); +#else +#endif + } +} + +std::unique_ptr SIOConnection::CreateSocketClient(std::string hostname, u32 port) +{ + std::unique_ptr server(new SIOSocketClientConnection(std::move(hostname), port)); + if (!server->Initialize()) + return {}; + + return server; +} diff --git a/src/core/sio_connection.h b/src/core/sio_connection.h new file mode 100644 index 000000000..64f7c2b99 --- /dev/null +++ b/src/core/sio_connection.h @@ -0,0 +1,109 @@ +#pragma once +#include "sio.h" +#include "types.h" +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include "common/windows_headers.h" +#include +#endif + +#ifdef _WIN32 +using SocketType = SOCKET; +#else +using SocketType = int; +#endif + +class SIOConnection +{ +public: + virtual ~SIOConnection() = default; + + static std::unique_ptr CreateSocketServer(std::string hostname, u32 port); + static std::unique_ptr CreateSocketClient(std::string hostname, u32 port); + + ALWAYS_INLINE bool HasData() const { return m_data_ready.load(); } + ALWAYS_INLINE bool IsConnected() const { return m_connected.load(); } + + virtual u32 Read(void* buffer, u32 buffer_size, u32 min_size) = 0; + virtual u32 Write(const void* buffer, u32 buffer_size) = 0; + +protected: + std::atomic_bool m_connected{false}; + std::atomic_bool m_data_ready{false}; +}; + +class SIOSocketConnection : public SIOConnection +{ +public: + SIOSocketConnection(std::string hostname, u32 port); + ~SIOSocketConnection() override; + + virtual bool Initialize(); + + u32 Read(void* buffer, u32 buffer_size, u32 min_size) override; + u32 Write(const void* buffer, u32 buffer_size) override; + +protected: + virtual void SocketThread() = 0; + + void StartThread(); + void ShutdownThread(); + + void HandleRead(); + void HandleWrite(); + void HandleClose(); + void Disconnect(); + + std::string m_hostname; + std::thread m_thread; + std::atomic_bool m_thread_shutdown{false}; + u32 m_port = 0; + SocketType m_client_fd; + + std::mutex m_buffer_mutex; + std::vector m_read_buffer; + std::vector m_write_buffer; + +#ifdef _WIN32 + HANDLE m_client_event = NULL; + HANDLE m_want_write_event = NULL; + bool m_sockets_initialized = false; +#endif +}; + +class SIOSocketServerConnection : public SIOSocketConnection +{ +public: + SIOSocketServerConnection(std::string hostname, u32 port); + ~SIOSocketServerConnection() override; + + bool Initialize() override; + +protected: + void SocketThread() override; + + void HandleAccept(); + + SocketType m_accept_fd; + +#ifdef _WIN32 + HANDLE m_accept_event = NULL; +#endif +}; + +class SIOSocketClientConnection : public SIOSocketConnection +{ +public: + SIOSocketClientConnection(std::string hostname, u32 port); + ~SIOSocketClientConnection() override; + + bool Initialize() override; + +protected: + void SocketThread() override; +}; diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 638e9025f..289bb5c80 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -2212,6 +2212,7 @@ void MainWindow::connectSignals() false); SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionDebugShowMDECState, "DebugWindows", "MDEC", false); SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionDebugShowDMAState, "DebugWindows", "DMA", false); + SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionDebugShowSIOState, "DebugWindows", "SIO", false); } void MainWindow::updateTheme() diff --git a/src/duckstation-qt/mainwindow.ui b/src/duckstation-qt/mainwindow.ui index 49e7e4014..84fe3b1c4 100644 --- a/src/duckstation-qt/mainwindow.ui +++ b/src/duckstation-qt/mainwindow.ui @@ -192,6 +192,7 @@ + @@ -702,6 +703,14 @@ Show DMA State + + + true + + + Show SIO State + +