mirror of
https://github.com/stenzek/duckstation.git
synced 2025-06-03 02:00:05 +00:00
wip
blah
This commit is contained in:
parent
8d80ae123d
commit
4384e1abb6
@ -1809,7 +1809,7 @@ template<MemoryAccessSize size>
|
||||
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<u32>(size));
|
||||
value = FIXUP_HALFWORD_READ_VALUE(size, offset, value);
|
||||
BUS_CYCLES(2);
|
||||
return value;
|
||||
|
@ -75,6 +75,7 @@
|
||||
<ClCompile Include="psf_loader.cpp" />
|
||||
<ClCompile Include="settings.cpp" />
|
||||
<ClCompile Include="sio.cpp" />
|
||||
<ClCompile Include="sio_connection.cpp" />
|
||||
<ClCompile Include="spu.cpp" />
|
||||
<ClCompile Include="system.cpp" />
|
||||
<ClCompile Include="timers.cpp" />
|
||||
@ -159,6 +160,7 @@
|
||||
<ClInclude Include="settings.h" />
|
||||
<ClInclude Include="shader_cache_version.h" />
|
||||
<ClInclude Include="sio.h" />
|
||||
<ClInclude Include="sio_connection.h" />
|
||||
<ClInclude Include="spu.h" />
|
||||
<ClInclude Include="system.h" />
|
||||
<ClInclude Include="system_private.h" />
|
||||
|
@ -68,6 +68,7 @@
|
||||
<ClCompile Include="gpu_thread.cpp" />
|
||||
<ClCompile Include="gpu_presenter.cpp" />
|
||||
<ClCompile Include="ddgo_controller.cpp" />
|
||||
<ClCompile Include="sio_connection.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="types.h" />
|
||||
@ -146,6 +147,7 @@
|
||||
<ClInclude Include="gpu_thread_commands.h" />
|
||||
<ClInclude Include="gpu_presenter.h" />
|
||||
<ClInclude Include="ddgo_controller.h" />
|
||||
<ClInclude Include="sio_connection.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="gpu_sw_rasterizer.inl" />
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
|
||||
// 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<DebugWindowInfo, NUM_DEBUG_WINDOWS> s_debug_window_info = {{
|
||||
{"Freecam", "Free Camera", ":icons/applications-system.png", >E::DrawFreecamWindow, 500, 425},
|
||||
@ -118,6 +119,7 @@ static constexpr const std::array<DebugWindowInfo, NUM_DEBUG_WINDOWS> 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<ImGuiManager::AuxiliaryRenderWindowState, NUM_DEBUG_WINDOWS> s_debug_window_state = {};
|
||||
|
||||
|
525
src/core/sio.cpp
525
src/core/sio.cpp
@ -1,8 +1,11 @@
|
||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
|
||||
// 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 <array>
|
||||
#include <memory>
|
||||
|
||||
@ -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<u16, u8, 8, 2> RXIMODE;
|
||||
BitField<u16, bool, 10, 1> TXINTEN;
|
||||
BitField<u16, bool, 11, 1> RXINTEN;
|
||||
BitField<u16, bool, 12, 1> ACKINTEN;
|
||||
BitField<u16, bool, 12, 1> DTRINTEN;
|
||||
};
|
||||
|
||||
union SIO_STAT
|
||||
@ -41,7 +52,7 @@ union SIO_STAT
|
||||
|
||||
BitField<u32, bool, 0, 1> TXRDY;
|
||||
BitField<u32, bool, 1, 1> RXFIFONEMPTY;
|
||||
BitField<u32, bool, 2, 1> TXDONE;
|
||||
BitField<u32, bool, 2, 1> TXIDLE;
|
||||
BitField<u32, bool, 3, 1> RXPARITY;
|
||||
BitField<u32, bool, 4, 1> RXFIFOOVERRUN;
|
||||
BitField<u32, bool, 5, 1> RXBADSTOPBIT;
|
||||
@ -62,24 +73,75 @@ union SIO_MODE
|
||||
BitField<u16, u8, 5, 1> parity_type;
|
||||
BitField<u16, u8, 6, 2> 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<u8, RX_FIFO_SIZE> 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<SIOConnection> connection;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static constexpr std::array<u32, 4> 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<u32>((s_state.baud_rate * factor) & ~u32(1), factor);
|
||||
|
||||
return static_cast<TickCount>(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<u8, bool, 0, 1> has_data;
|
||||
BitField<u8, bool, 1, 1> dtr_level;
|
||||
BitField<u8, bool, 2, 1> rts_level;
|
||||
BitField<u8, u8, 3, 5> 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();
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
|
||||
// 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
|
||||
|
499
src/core/sio_connection.cpp
Normal file
499
src/core/sio_connection.cpp
Normal file
@ -0,0 +1,499 @@
|
||||
#include "sio_connection.h"
|
||||
#include "common/log.h"
|
||||
#include "common/small_string.h"
|
||||
#include <cstdarg>
|
||||
LOG_CHANNEL(SIO);
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <WS2tcpip.h>
|
||||
#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<u32>(static_cast<u32>(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<size_t>(m_read_buffer.size() * 2, 128);
|
||||
m_read_buffer.resize(buffer_size);
|
||||
|
||||
int nbytes = recv(m_client_fd, reinterpret_cast<char*>(&m_read_buffer[current_size]),
|
||||
static_cast<int>(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<size_t>(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<const char*>(m_write_buffer.data()), static_cast<int>(m_write_buffer.size()), 0);
|
||||
if (nbytes < 0)
|
||||
{
|
||||
if (GetSocketError() == SOCKET_ERROR_WOULD_BLOCK)
|
||||
return;
|
||||
|
||||
PrintSocketError("send() failed");
|
||||
Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (nbytes == static_cast<int>(m_write_buffer.size()))
|
||||
{
|
||||
m_write_buffer.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t new_size = m_write_buffer.size() - static_cast<size_t>(nbytes);
|
||||
std::memmove(&m_write_buffer[0], &m_write_buffer[static_cast<size_t>(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> SIOConnection::CreateSocketServer(std::string hostname, u32 port)
|
||||
{
|
||||
std::unique_ptr<SIOSocketServerConnection> 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<u16>(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<const sockaddr*>(&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<int>(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> SIOConnection::CreateSocketClient(std::string hostname, u32 port)
|
||||
{
|
||||
std::unique_ptr<SIOSocketClientConnection> server(new SIOSocketClientConnection(std::move(hostname), port));
|
||||
if (!server->Initialize())
|
||||
return {};
|
||||
|
||||
return server;
|
||||
}
|
109
src/core/sio_connection.h
Normal file
109
src/core/sio_connection.h
Normal file
@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
#include "sio.h"
|
||||
#include "types.h"
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "common/windows_headers.h"
|
||||
#include <WinSock2.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
using SocketType = SOCKET;
|
||||
#else
|
||||
using SocketType = int;
|
||||
#endif
|
||||
|
||||
class SIOConnection
|
||||
{
|
||||
public:
|
||||
virtual ~SIOConnection() = default;
|
||||
|
||||
static std::unique_ptr<SIOConnection> CreateSocketServer(std::string hostname, u32 port);
|
||||
static std::unique_ptr<SIOConnection> 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<u8> m_read_buffer;
|
||||
std::vector<u8> 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;
|
||||
};
|
@ -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()
|
||||
|
@ -192,6 +192,7 @@
|
||||
<addaction name="actionDebugShowTimersState"/>
|
||||
<addaction name="actionDebugShowMDECState"/>
|
||||
<addaction name="actionDebugShowDMAState"/>
|
||||
<addaction name="actionDebugShowSIOState"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_View">
|
||||
<property name="title">
|
||||
@ -702,6 +703,14 @@
|
||||
<string>Show DMA State</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDebugShowSIOState">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show SIO State</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionScreenshot">
|
||||
<property name="icon">
|
||||
<iconset theme="screenshot-2-line"/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user