diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj
index 30d622791..71363446c 100644
--- a/src/core/core.vcxproj
+++ b/src/core/core.vcxproj
@@ -148,6 +148,7 @@
+
@@ -228,6 +229,7 @@
+
diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters
index 03e5d4196..4861037ed 100644
--- a/src/core/core.vcxproj.filters
+++ b/src/core/core.vcxproj.filters
@@ -58,6 +58,7 @@
+
@@ -118,5 +119,6 @@
+
\ No newline at end of file
diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp
index 50f949e64..d873853a9 100644
--- a/src/core/host_interface.cpp
+++ b/src/core/host_interface.cpp
@@ -610,6 +610,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
si.SetBoolValue("Debug", "ShowTimersState", false);
si.SetBoolValue("Debug", "ShowMDECState", false);
si.SetBoolValue("Debug", "ShowDMAState", false);
+ si.SetBoolValue("Debug", "ShowSIOState", false);
si.SetIntValue("Hacks", "DMAMaxSliceTicks", static_cast(Settings::DEFAULT_DMA_MAX_SLICE_TICKS));
si.SetIntValue("Hacks", "DMAHaltTicks", static_cast(Settings::DEFAULT_DMA_HALT_TICKS));
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 81ae46a27..49968345a 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -308,6 +308,7 @@ void Settings::Load(SettingsInterface& si)
debugging.show_timers_state = si.GetBoolValue("Debug", "ShowTimersState");
debugging.show_mdec_state = si.GetBoolValue("Debug", "ShowMDECState");
debugging.show_dma_state = si.GetBoolValue("Debug", "ShowDMAState");
+ debugging.show_sio_state = si.GetBoolValue("Debug", "ShowSIOState");
texture_replacements.enable_vram_write_replacements =
si.GetBoolValue("TextureReplacements", "EnableVRAMWriteReplacements", false);
@@ -468,6 +469,7 @@ void Settings::Save(SettingsInterface& si) const
si.SetBoolValue("Debug", "ShowTimersState", debugging.show_timers_state);
si.SetBoolValue("Debug", "ShowMDECState", debugging.show_mdec_state);
si.SetBoolValue("Debug", "ShowDMAState", debugging.show_dma_state);
+ si.SetBoolValue("Debug", "ShowSIOState", debugging.show_sio_state);
si.SetBoolValue("TextureReplacements", "EnableVRAMWriteReplacements",
texture_replacements.enable_vram_write_replacements);
diff --git a/src/core/settings.h b/src/core/settings.h
index 352ab4d5b..654fd4850 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -185,6 +185,7 @@ struct Settings
mutable bool show_timers_state = false;
mutable bool show_mdec_state = false;
mutable bool show_dma_state = false;
+ mutable bool show_sio_state = false;
} debugging;
// texture replacements
diff --git a/src/core/sio.cpp b/src/core/sio.cpp
index b3c7c79f0..3db395176 100644
--- a/src/core/sio.cpp
+++ b/src/core/sio.cpp
@@ -1,12 +1,16 @@
#include "sio.h"
#include "common/log.h"
#include "common/state_wrapper.h"
-#include "controller.h"
#include "host_interface.h"
+#include "imgui.h"
#include "interrupt_controller.h"
-#include "memory_card.h"
+#include "sio_connection.h"
+#include "system.h"
+#include "timing_event.h"
Log_SetChannel(SIO);
+static constexpr std::array s_mul_factors = {{1, 16, 64, 0}};
+
SIO g_sio;
SIO::SIO() = default;
@@ -15,10 +19,23 @@ SIO::~SIO() = default;
void SIO::Initialize()
{
+ m_transfer_event = TimingEvents::CreateTimingEvent(
+ "SIO Transfer", 1, 1, [](void* param, TickCount ticks, TickCount ticks_late) { g_sio.TransferEvent(); }, nullptr,
+ false);
+
+ if (true)
+ m_connection = SIOConnection::CreateSocketServer("0.0.0.0", 1337);
+ //m_connection = SIOConnection::CreateSocketClient("127.0.0.1", 1337);
+
+ m_stat.bits = 0;
Reset();
}
-void SIO::Shutdown() {}
+void SIO::Shutdown()
+{
+ m_connection.reset();
+ m_transfer_event.reset();
+}
void SIO::Reset()
{
@@ -27,41 +44,112 @@ void SIO::Reset()
bool SIO::DoState(StateWrapper& sw)
{
- sw.Do(&m_SIO_CTRL.bits);
- sw.Do(&m_SIO_STAT.bits);
- sw.Do(&m_SIO_MODE.bits);
- sw.Do(&m_SIO_BAUD);
+ const bool dtr = m_stat.DTRINPUTLEVEL;
+ const bool rts = m_stat.CTSINPUTLEVEL;
+
+ sw.Do(&m_ctrl.bits);
+ sw.Do(&m_stat.bits);
+ sw.Do(&m_mode.bits);
+ sw.Do(&m_baud_rate);
+
+ m_stat.DTRINPUTLEVEL = dtr;
+ m_stat.CTSINPUTLEVEL = rts;
return !sw.HasError();
}
+void SIO::SoftReset()
+{
+ m_ctrl.bits = 0;
+ m_stat.RXPARITY = false;
+ m_stat.RXFIFOOVERRUN = false;
+ m_stat.RXBADSTOPBIT = false;
+ m_stat.INTR = false;
+ m_mode.bits = 0;
+ m_baud_rate = 0xDC;
+ m_data_in.Clear();
+ m_data_out = 0;
+ m_data_out_full = false;
+
+ UpdateEvent();
+ UpdateTXRX();
+}
+
+void SIO::UpdateTXRX()
+{
+ m_stat.TXRDY = !m_data_out_full && m_ctrl.TXEN;
+ m_stat.TXDONE = !m_data_out_full;
+ m_stat.RXFIFONEMPTY = !m_data_in.IsEmpty();
+}
+
+void SIO::SetInterrupt()
+{
+ Log_DevPrintf("Set SIO IRQ");
+ m_stat.INTR = true;
+ g_interrupt_controller.InterruptRequest(InterruptController::IRQ::SIO);
+}
+
u32 SIO::ReadRegister(u32 offset)
{
switch (offset)
{
case 0x00: // SIO_DATA
{
- Log_ErrorPrintf("Read SIO_DATA");
+ m_transfer_event->InvokeEarly(false);
+
+ const u32 data_in_size = m_data_in.GetSize();
+ u32 res = 0;
+ switch (data_in_size)
+ {
+ case 8:
+ case 7:
+ case 6:
+ case 5:
+ case 4:
+ res = ZeroExtend32(m_data_in.Peek(3)) << 24;
+ [[fallthrough]];
+
+ case 3:
+ res |= ZeroExtend32(m_data_in.Peek(2)) << 16;
+ [[fallthrough]];
+
+ case 2:
+ res |= ZeroExtend32(m_data_in.Peek(1)) << 8;
+ [[fallthrough]];
- const u8 value = 0xFF;
- return (ZeroExtend32(value) | (ZeroExtend32(value) << 8) | (ZeroExtend32(value) << 16) |
- (ZeroExtend32(value) << 24));
+ case 1:
+ res |= ZeroExtend32(m_data_in.Peek(0));
+ m_data_in.RemoveOne();
+ break;
+
+ case 0:
+ default:
+ res = 0xFFFFFFFFu;
+ break;
+ }
+
+ Log_WarningPrintf("Read SIO_DATA -> 0x%08X", res);
+ UpdateTXRX();
+ return res;
}
case 0x04: // SIO_STAT
{
- const u32 bits = m_SIO_STAT.bits;
+ m_transfer_event->InvokeEarly(false);
+
+ const u32 bits = m_stat.bits;
+ Log_DevPrintf("Read SIO_STAT -> 0x%08X", bits);
return bits;
}
case 0x08: // SIO_MODE
- return ZeroExtend32(m_SIO_MODE.bits);
+ return ZeroExtend32(m_mode.bits);
case 0x0A: // SIO_CTRL
- return ZeroExtend32(m_SIO_CTRL.bits);
+ return ZeroExtend32(m_ctrl.bits);
case 0x0E: // SIO_BAUD
- return ZeroExtend32(m_SIO_BAUD);
+ return ZeroExtend32(m_baud_rate);
default:
Log_ErrorPrintf("Unknown register read: 0x%X", offset);
@@ -76,31 +164,61 @@ void SIO::WriteRegister(u32 offset, u32 value)
case 0x00: // SIO_DATA
{
Log_WarningPrintf("SIO_DATA (W) <- 0x%02X", value);
+ m_transfer_event->InvokeEarly(false);
+
+ if (m_data_out_full)
+ Log_WarningPrintf("SIO TX buffer overflow, lost 0x%02X when writing 0x%02X", m_data_out, value);
+
+ m_data_out = Truncate8(value);
+ m_data_out_full = true;
+ UpdateTXRX();
return;
}
case 0x0A: // SIO_CTRL
{
- Log_DebugPrintf("SIO_CTRL <- 0x%04X", value);
+ Log_DevPrintf("SIO_CTRL <- 0x%04X", value);
+ m_transfer_event->InvokeEarly(false);
- m_SIO_CTRL.bits = Truncate16(value);
- if (m_SIO_CTRL.RESET)
+ m_ctrl.bits = Truncate16(value);
+ if (m_ctrl.RESET)
SoftReset();
+ if (m_ctrl.ACK)
+ {
+ m_stat.RXPARITY = false;
+ m_stat.RXFIFOOVERRUN = false;
+ m_stat.RXBADSTOPBIT = false;
+ m_stat.INTR = false;
+ }
+
+ if (!m_ctrl.RXEN)
+ {
+ Log_WarningPrintf("Clearing Input FIFO");
+ m_data_in.Clear();
+ UpdateTXRX();
+ }
+ /*if (!m_ctrl.TXEN)
+ {
+ Log_WarningPrintf("Clearing output fifo");
+ m_data_out_full = false;
+ UpdateTXRX();
+ }*/
+
return;
}
case 0x08: // SIO_MODE
{
- Log_DebugPrintf("SIO_MODE <- 0x%08X", value);
- m_SIO_MODE.bits = Truncate16(value);
+ Log_DevPrintf("SIO_MODE <- 0x%08X", value);
+ m_mode.bits = Truncate16(value);
return;
}
case 0x0E:
{
- Log_DebugPrintf("SIO_BAUD <- 0x%08X", value);
- m_SIO_BAUD = Truncate16(value);
+ Log_DevPrintf("SIO_BAUD <- 0x%08X", value);
+ m_baud_rate = Truncate16(value);
return;
}
@@ -110,12 +228,288 @@ void SIO::WriteRegister(u32 offset, u32 value)
}
}
-void SIO::SoftReset()
+void SIO::DrawDebugStateWindow()
{
- m_SIO_CTRL.bits = 0;
- m_SIO_STAT.bits = 0;
- m_SIO_STAT.TXDONE = true;
- m_SIO_STAT.TXRDY = true;
- m_SIO_MODE.bits = 0;
- m_SIO_BAUD = 0xDC;
+#ifdef WITH_IMGUI
+ const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x;
+
+ ImGui::SetNextWindowSize(ImVec2(600.0f * framebuffer_scale, 400.0f * framebuffer_scale), ImGuiCond_FirstUseEver);
+ if (!ImGui::Begin("SIO", nullptr))
+ {
+ ImGui::End();
+ return;
+ }
+
+ 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();
+ ImGui::TextColored((m_connection && m_connection->IsConnected()) ? active_color : inactive_color,
+ (m_connection && m_connection->IsConnected()) ? "Yes" : "No");
+
+ ImGui::Text("Status: ");
+ ImGui::SameLine();
+
+ float pos = ImGui::GetCursorPosX();
+ ImGui::TextColored(m_stat.TXRDY ? active_color : inactive_color, "TXRDY");
+ ImGui::SameLine();
+ ImGui::TextColored(m_stat.RXFIFONEMPTY ? active_color : inactive_color, "RXFIFONEMPTY");
+ ImGui::SameLine();
+ ImGui::TextColored(m_stat.TXDONE ? active_color : inactive_color, "TXDONE");
+ ImGui::SameLine();
+ ImGui::TextColored(m_stat.RXPARITY ? active_color : inactive_color, "RXPARITY");
+ ImGui::SameLine();
+ ImGui::TextColored(m_stat.RXFIFOOVERRUN ? active_color : inactive_color, "RXFIFOOVERRUN");
+ ImGui::SetCursorPosX(pos);
+ ImGui::TextColored(m_stat.RXBADSTOPBIT ? active_color : inactive_color, "RXBADSTOPBIT");
+ ImGui::SameLine();
+ ImGui::TextColored(m_stat.RXINPUTLEVEL ? active_color : inactive_color, "RXINPUTLEVEL");
+ ImGui::SameLine();
+ ImGui::TextColored(m_stat.DTRINPUTLEVEL ? active_color : inactive_color, "DTRINPUTLEVEL");
+ ImGui::SameLine();
+ ImGui::TextColored(m_stat.CTSINPUTLEVEL ? active_color : inactive_color, "CTSINPUTLEVEL");
+ ImGui::SameLine();
+ ImGui::TextColored(m_stat.INTR ? active_color : inactive_color, "INTR");
+
+ ImGui::NewLine();
+
+ ImGui::Text("Control: ");
+ ImGui::SameLine();
+
+ pos = ImGui::GetCursorPosX();
+ ImGui::TextColored(m_ctrl.TXEN ? active_color : inactive_color, "TXEN");
+ ImGui::SameLine();
+ ImGui::TextColored(m_ctrl.DTROUTPUT ? active_color : inactive_color, "DTROUTPUT");
+ ImGui::SameLine();
+ ImGui::TextColored(m_ctrl.RXEN ? active_color : inactive_color, "RXEN");
+ ImGui::SameLine();
+ ImGui::TextColored(m_ctrl.TXOUTPUT ? active_color : inactive_color, "TXOUTPUT");
+ ImGui::SameLine();
+ ImGui::TextColored(m_ctrl.RTSOUTPUT ? active_color : inactive_color, "RTSOUTPUT");
+ ImGui::SetCursorPosX(pos);
+ ImGui::TextColored(m_ctrl.TXINTEN ? active_color : inactive_color, "TXINTEN");
+ ImGui::SameLine();
+ ImGui::TextColored(m_ctrl.RXINTEN ? active_color : inactive_color, "RXINTEN");
+ ImGui::SameLine();
+ ImGui::TextColored(m_ctrl.RXINTEN ? active_color : inactive_color, "RXIMODE: %u", m_ctrl.RXIMODE.GetValue());
+
+ ImGui::NewLine();
+
+ ImGui::Text("Mode: ");
+ ImGui::Text(" Reload Factor: %u", s_mul_factors[m_mode.reload_factor]);
+ ImGui::Text(" Character Length: %u", m_mode.character_length.GetValue());
+ ImGui::Text(" Parity Enable: %s", m_mode.parity_enable ? "Yes" : "No");
+ ImGui::Text(" Parity Type: %u", m_mode.parity_type.GetValue());
+ ImGui::Text(" Stop Bit Length: %u", m_mode.stop_bit_length.GetValue());
+
+ ImGui::NewLine();
+
+ ImGui::Text("Baud Rate: %u", m_baud_rate);
+
+ ImGui::NewLine();
+
+ ImGui::TextColored(m_data_out_full ? active_color : inactive_color, "Output buffer: 0x%02X", m_data_out);
+
+ ImGui::Text("Input buffer: ");
+ for (u32 i = 0; i < m_data_in.GetSize(); i++)
+ {
+ ImGui::SameLine();
+ ImGui::Text("0x%02X ", m_data_in.Peek(i));
+ }
+
+ ImGui::End();
+#endif
+}
+
+TickCount SIO::GetTicksBetweenTransfers() const
+{
+ const u32 factor = s_mul_factors[m_mode.reload_factor];
+ const u32 ticks = std::max((m_baud_rate * factor) & ~u32(1), factor);
+
+ return static_cast(ticks);
}
+
+void SIO::UpdateEvent()
+{
+ if (!m_connection)
+ {
+ m_transfer_event->Deactivate();
+ m_stat.CTSINPUTLEVEL = false;
+ m_stat.DTRINPUTLEVEL = false;
+ m_sync_last_cts = false;
+ m_sync_last_dtr = false;
+ m_sync_last_rts = false;
+ m_sync_remote_rts = false;
+ return;
+ }
+
+ TickCount ticks = GetTicksBetweenTransfers();
+ if (ticks == 0)
+ ticks = System::GetMaxSliceTicks();
+
+ if (m_transfer_event->GetPeriod() == ticks && m_transfer_event->IsActive())
+ return;
+
+ m_transfer_event->Deactivate();
+ m_transfer_event->SetPeriodAndSchedule(ticks);
+}
+
+void SIO::TransferEvent()
+{
+ if (m_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 (m_connection && m_connection->IsConnected())
+ {
+ m_stat.CTSINPUTLEVEL = true;
+ m_stat.DTRINPUTLEVEL = true;
+
+ if (m_ctrl.RXEN)
+ {
+ u8 data_in;
+ u32 data_in_size = m_connection->Read(&data_in, sizeof(data_in), 0);
+ if (data_in_size > 0)
+ {
+ if (m_data_in.IsFull())
+ {
+ Log_WarningPrintf("FIFO overrun");
+ m_data_in.RemoveOne();
+ m_stat.RXFIFOOVERRUN = true;
+ }
+
+ m_data_in.Push(data_in);
+
+ if (m_ctrl.RXINTEN)
+ SetInterrupt();
+ }
+ }
+
+ if (m_ctrl.TXEN && m_data_out_full)
+ {
+ const u8 data_out = m_data_out;
+ m_data_out_full = false;
+
+ const u32 data_sent = m_connection->Write(&data_out, sizeof(data_out));
+ if (data_sent != sizeof(data_out))
+ Log_WarningPrintf("Failed to send 0x%02X to connection", data_out);
+
+ if (m_ctrl.TXINTEN)
+ SetInterrupt();
+ }
+ }
+ else
+ {
+ m_stat.CTSINPUTLEVEL = false;
+ m_stat.DTRINPUTLEVEL = false;
+ }
+
+ UpdateTXRX();
+}
+
+void SIO::TransferWithSync()
+{
+ enum : u8
+ {
+ STATE_HAS_DATA = (1 << 0),
+ STATE_DTR_LEVEL = (1 << 1),
+ STATE_CTS_LEVEL = (1 << 2),
+ STATE_RTS_LEVEL = (1 << 3),
+ };
+
+ if (!m_connection || !m_connection->IsConnected())
+ {
+ m_stat.CTSINPUTLEVEL = false;
+ m_stat.DTRINPUTLEVEL = false;
+ m_sync_last_cts = false;
+ m_sync_last_dtr = false;
+ m_sync_last_rts = false;
+ m_sync_remote_rts = false;
+ UpdateTXRX();
+ return;
+ }
+
+ u8 buf[2] = {};
+ if (m_connection->HasData())
+ {
+ while (m_connection->Read(buf, sizeof(buf), sizeof(buf)) != 0)
+ {
+ Log_InfoPrintf("In: %02X %02X", buf[0], buf[1]);
+
+ if (buf[0] & STATE_HAS_DATA)
+ {
+ Log_WarningPrintf("Received: %02X", buf[1]);
+ if (m_data_in.IsFull())
+ m_stat.RXFIFOOVERRUN = true;
+ else
+ m_data_in.Push(buf[1]);
+
+ if (m_ctrl.RXINTEN)
+ {
+ Log_WarningPrintf("Setting RX interrupt");
+ SetInterrupt();
+ }
+ }
+
+ if (!m_stat.DTRINPUTLEVEL && buf[0] & STATE_DTR_LEVEL)
+ Log_WarningPrintf("DTR active");
+ else if (m_stat.DTRINPUTLEVEL && !(buf[0] & STATE_DTR_LEVEL))
+ Log_WarningPrintf("DTR inactive");
+ if (!m_stat.CTSINPUTLEVEL && buf[0] & STATE_CTS_LEVEL)
+ Log_WarningPrintf("CTS active");
+ else if (m_stat.CTSINPUTLEVEL && !(buf[0] & STATE_CTS_LEVEL))
+ Log_WarningPrintf("CTS inactive");
+ if (!m_sync_remote_rts && buf[0] & STATE_RTS_LEVEL)
+ Log_WarningPrintf("Remote RTS active");
+ else if (m_sync_remote_rts && !(buf[0] & STATE_RTS_LEVEL))
+ Log_WarningPrintf("Remote RTS inactive");
+
+ m_stat.DTRINPUTLEVEL = (buf[0] & STATE_DTR_LEVEL) != 0;
+ m_stat.CTSINPUTLEVEL = (buf[0] & STATE_CTS_LEVEL) != 0;
+ m_sync_remote_rts = (buf[0] & STATE_RTS_LEVEL) != 0;
+ }
+ }
+
+ const bool cts_level = m_sync_remote_rts && !m_data_in.IsFull();
+ const bool dtr_level = m_ctrl.DTROUTPUT;
+ const bool rts_level = m_ctrl.RTSOUTPUT;
+ const bool tx = (m_ctrl.TXEN || m_latched_txen) && m_stat.CTSINPUTLEVEL && m_data_out_full;
+ m_latched_txen = m_ctrl.TXEN;
+ if (cts_level != m_sync_last_cts || dtr_level != m_sync_last_dtr || rts_level != m_sync_last_rts || tx)
+ {
+ m_sync_last_cts = cts_level;
+ m_sync_last_dtr = dtr_level;
+ m_sync_last_rts = rts_level;
+
+ buf[0] = cts_level ? STATE_CTS_LEVEL : 0;
+ buf[0] |= dtr_level ? STATE_DTR_LEVEL : 0;
+ buf[0] |= rts_level ? STATE_RTS_LEVEL : 0;
+
+ buf[1] = 0;
+ if (tx)
+ {
+ Log_WarningPrintf("Sending: %02X", m_data_out);
+ buf[0] |= STATE_HAS_DATA;
+ buf[1] = m_data_out;
+ m_data_out_full = false;
+
+ if (m_ctrl.TXINTEN)
+ {
+ Log_WarningPrintf("Setting TX interrupt");
+ SetInterrupt();
+ }
+ }
+
+ Log_InfoPrintf("Out: %02X %02X", buf[0], buf[1]);
+ if (m_connection->Write(buf, sizeof(buf)) != sizeof(buf))
+ Log_WarningPrintf("Write failed");
+ }
+
+ UpdateTXRX();
+}
\ No newline at end of file
diff --git a/src/core/sio.h b/src/core/sio.h
index cc49b0891..a08783924 100644
--- a/src/core/sio.h
+++ b/src/core/sio.h
@@ -3,12 +3,14 @@
#include "common/fifo_queue.h"
#include "types.h"
#include
+#include
#include
+#include
class StateWrapper;
+class TimingEvent;
-class Controller;
-class MemoryCard;
+class SIOConnection;
class SIO
{
@@ -24,7 +26,14 @@ public:
u32 ReadRegister(u32 offset);
void WriteRegister(u32 offset, u32 value);
+ void DrawDebugStateWindow();
+
private:
+ enum : u32
+ {
+ RX_FIFO_SIZE = 8
+ };
+
union SIO_CTRL
{
u16 bits;
@@ -39,7 +48,7 @@ private:
BitField RXIMODE;
BitField TXINTEN;
BitField RXINTEN;
- BitField ACKINTEN;
+ BitField DTRINTEN;
};
union SIO_STAT
@@ -53,7 +62,7 @@ private:
BitField RXFIFOOVERRUN;
BitField RXBADSTOPBIT;
BitField RXINPUTLEVEL;
- BitField DSRINPUTLEVEL;
+ BitField DTRINPUTLEVEL;
BitField CTSINPUTLEVEL;
BitField INTR;
BitField TMR;
@@ -70,12 +79,56 @@ private:
BitField stop_bit_length;
};
+ TickCount GetTicksBetweenTransfers() const;
+
void SoftReset();
- SIO_CTRL m_SIO_CTRL = {};
- SIO_STAT m_SIO_STAT = {};
- SIO_MODE m_SIO_MODE = {};
- u16 m_SIO_BAUD = 0;
+ void UpdateTXRX();
+ void SetInterrupt();
+
+ void UpdateEvent();
+ void TransferEvent();
+ void TransferWithoutSync();
+ void TransferWithSync();
+
+ std::unique_ptr m_connection;
+ std::unique_ptr m_transfer_event;
+
+ SIO_CTRL m_ctrl = {};
+ SIO_STAT m_stat = {};
+ SIO_MODE m_mode = {};
+ u16 m_baud_rate = 0;
+
+ InlineFIFOQueue m_data_in;
+
+ u8 m_data_out = 0;
+ bool m_data_out_full = false;
+ bool m_latched_txen = false;
+
+ bool m_sync_mode = true;
+ bool m_sync_last_cts = false;
+ bool m_sync_last_dtr = false;
+ bool m_sync_last_rts = false;
+ bool m_sync_remote_rts = false;
+};
+
+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};
};
extern SIO g_sio;
\ No newline at end of file
diff --git a/src/core/sio_connection.cpp b/src/core/sio_connection.cpp
new file mode 100644
index 000000000..7092e5a52
--- /dev/null
+++ b/src/core/sio_connection.cpp
@@ -0,0 +1,499 @@
+#include "sio_connection.h"
+#include "common/log.h"
+#include "common/string.h"
+#include
+Log_SetChannel(SIOConnection);
+
+#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.FormatVA(format, ap);
+ va_end(ap);
+
+ Log_ErrorPrintf("%s: %d", str.GetCharArray(), 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)
+ {
+ Log_InfoPrint("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()
+{
+ Log_InfoPrint("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.";
+ Log_WarningPrint("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);
+ Log_InfoPrintf("Client connection accepted: %d", 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::FromFormat("%u", m_port), &hints, &ai);
+ if (err != 0)
+ {
+ Log_ErrorPrintf("getaddrinfo(%s:%u) failed: %d", m_hostname.c_str(), 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..009f19ac8
--- /dev/null
+++ b/src/core/sio_connection.h
@@ -0,0 +1,90 @@
+#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 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 569daf1ae..6033dc463 100644
--- a/src/duckstation-qt/mainwindow.cpp
+++ b/src/duckstation-qt/mainwindow.cpp
@@ -1133,6 +1133,7 @@ void MainWindow::connectSignals()
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.actionDebugShowMDECState, "Debug",
"ShowMDECState");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.actionDebugShowDMAState, "Debug", "ShowDMAState");
+ SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.actionDebugShowSIOState, "Debug", "ShowSIOState");
addThemeToMenu(tr("Default"), QStringLiteral("default"));
addThemeToMenu(tr("Fusion"), QStringLiteral("fusion"));
diff --git a/src/duckstation-qt/mainwindow.ui b/src/duckstation-qt/mainwindow.ui
index 6147859b4..b46f6529d 100644
--- a/src/duckstation-qt/mainwindow.ui
+++ b/src/duckstation-qt/mainwindow.ui
@@ -193,6 +193,7 @@
+