diff --git a/Makefile b/Makefile index a6d747a..80b4e1e 100644 --- a/Makefile +++ b/Makefile @@ -112,6 +112,7 @@ mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000042 mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000420 mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000B240 + mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000D609 mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000D623 cp stratosphere/boot2/boot2.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000008/exefs.nsp cp stratosphere/dmnt/dmnt.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000000D/exefs.nsp @@ -125,6 +126,7 @@ cp stratosphere/pgl/pgl.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000042/exefs.nsp cp stratosphere/LogManager/LogManager.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000420/exefs.nsp cp stratosphere/htc/htc.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000B240/exefs.nsp + cp stratosphere/dmnt.gen2/dmnt.gen2.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000D609/exefs.nsp cp stratosphere/TioServer/TioServer.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000D623/exefs.nsp @build_romfs atmosphere-$(AMSVER)/stratosphere_romfs atmosphere-$(AMSVER)/atmosphere/stratosphere.romfs rm -r atmosphere-$(AMSVER)/stratosphere_romfs diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_utility.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_utility.hpp index 0255d62..827f094 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_utility.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_utility.hpp @@ -118,12 +118,12 @@ /* Copy API. */ Result CopyFile(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const char *dst_parent_path, const char *src_path, const fs::DirectoryEntry *dir_ent, void *work_buf, size_t work_buf_size); - NX_INLINE Result CopyFile(fs::fsa::IFileSystem *fs, const char *dst_parent_path, const char *src_path, const fs::DirectoryEntry *dir_ent, void *work_buf, size_t work_buf_size) { + ALWAYS_INLINE Result CopyFile(fs::fsa::IFileSystem *fs, const char *dst_parent_path, const char *src_path, const fs::DirectoryEntry *dir_ent, void *work_buf, size_t work_buf_size) { return CopyFile(fs, fs, dst_parent_path, src_path, dir_ent, work_buf, work_buf_size); } Result CopyDirectoryRecursively(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const char *dst_path, const char *src_path, void *work_buf, size_t work_buf_size); - NX_INLINE Result CopyDirectoryRecursively(fs::fsa::IFileSystem *fs, const char *dst_path, const char *src_path, void *work_buf, size_t work_buf_size) { + ALWAYS_INLINE Result CopyDirectoryRecursively(fs::fsa::IFileSystem *fs, const char *dst_path, const char *src_path, void *work_buf, size_t work_buf_size) { return CopyDirectoryRecursively(fs, fs, dst_path, src_path, work_buf, work_buf_size); } @@ -148,11 +148,11 @@ Result EnsureDirectoryRecursively(fs::fsa::IFileSystem *fs, const char *path); Result EnsureParentDirectoryRecursively(fs::fsa::IFileSystem *fs, const char *path); - template - NX_INLINE Result RetryFinitelyForTargetLocked(F f) { + template + ALWAYS_INLINE Result RetryFinitelyForTargetLocked(auto f) { /* Retry up to 10 times, 100ms between retries. */ constexpr s32 MaxRetryCount = 10; - constexpr TimeSpan RetryWaitTime = TimeSpan::FromMilliSeconds(100); + constexpr TimeSpan RetryWaitTime = TimeSpan::FromMilliSeconds(RetryMilliSeconds); s32 remaining_retries = MaxRetryCount; while (true) { diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp index 30b5d2d..07acdf9 100644 --- a/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp @@ -43,6 +43,7 @@ s32 Shutdown(s32 desc, ShutdownMethod how); + s32 Socket(Family domain, Type type, Protocol protocol); s32 SocketExempt(Family domain, Type type, Protocol protocol); s32 Accept(s32 desc, SockAddr *out_address, SockLenT *out_addr_len); diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_system_config.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_system_config.hpp index b13018a..c6bac93 100644 --- a/libraries/libstratosphere/include/stratosphere/socket/socket_system_config.hpp +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_system_config.hpp @@ -57,4 +57,42 @@ } }; + class SystemConfigLightDefault : public Config { + public: + static constexpr size_t DefaultTcpInitialSendBufferSize = 16_KB; + static constexpr size_t DefaultTcpInitialReceiveBufferSize = 16_KB; + static constexpr size_t DefaultTcpAutoSendBufferSizeMax = 0_KB; + static constexpr size_t DefaultTcpAutoReceiveBufferSizeMax = 0_KB; + static constexpr size_t DefaultUdpSendBufferSize = 9_KB; + static constexpr size_t DefaultUdpReceiveBufferSize = 42240; + static constexpr auto DefaultSocketBufferEfficiency = 2; + static constexpr auto DefaultConcurrency = 2; + static constexpr size_t DefaultAllocatorPoolSize = 64_KB; + + static constexpr size_t PerTcpSocketWorstCaseMemoryPoolSize = [] { + constexpr size_t WorstCaseTcpSendBufferSize = AlignMss(std::max(DefaultTcpInitialSendBufferSize, DefaultTcpAutoSendBufferSizeMax)); + constexpr size_t WorstCaseTcpReceiveBufferSize = AlignMss(std::max(DefaultTcpInitialReceiveBufferSize, DefaultTcpAutoReceiveBufferSizeMax)); + + return util::AlignUp(WorstCaseTcpSendBufferSize * DefaultSocketBufferEfficiency + WorstCaseTcpReceiveBufferSize * DefaultSocketBufferEfficiency, os::MemoryPageSize); + }(); + + static constexpr size_t PerUdpSocketWorstCaseMemoryPoolSize = [] { + constexpr size_t WorstCaseUdpSendBufferSize = AlignMss(DefaultUdpSendBufferSize); + constexpr size_t WorstCaseUdpReceiveBufferSize = AlignMss(DefaultUdpReceiveBufferSize); + + return util::AlignUp(WorstCaseUdpSendBufferSize * DefaultSocketBufferEfficiency + WorstCaseUdpReceiveBufferSize * DefaultSocketBufferEfficiency, os::MemoryPageSize); + }(); + public: + constexpr SystemConfigLightDefault(void *mp, size_t mp_sz, size_t ap, int c=DefaultConcurrency) + : Config(mp, mp_sz, ap, + DefaultTcpInitialSendBufferSize, DefaultTcpInitialReceiveBufferSize, + DefaultTcpAutoSendBufferSizeMax, DefaultTcpAutoReceiveBufferSizeMax, + DefaultUdpSendBufferSize, DefaultUdpReceiveBufferSize, + DefaultSocketBufferEfficiency, c) + { + /* Mark as system. */ + m_system = true; + } + }; + } diff --git a/libraries/libstratosphere/source/boot2/boot2_api.cpp b/libraries/libstratosphere/source/boot2/boot2_api.cpp index 17c9029..a40e95f 100644 --- a/libraries/libstratosphere/source/boot2/boot2_api.cpp +++ b/libraries/libstratosphere/source/boot2/boot2_api.cpp @@ -188,6 +188,12 @@ return enable_htc != 0; } + bool IsStandaloneGdbstubEnabled() { + u8 enable_gdbstub = 0; + settings::fwdbg::GetSettingsItemValue(std::addressof(enable_gdbstub), sizeof(enable_gdbstub), "atmosphere", "enable_standalone_gdbstub"); + return enable_gdbstub != 0; + } + bool IsAtmosphereLogManagerEnabled() { /* If htc is enabled, ams log manager is enabled. */ if (IsHtcEnabled()) { @@ -403,6 +409,9 @@ LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Htc, ncm::StorageId::None), 0); LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Cs, ncm::StorageId::None), 0); LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::DmntGen2, ncm::StorageId::None), 0); + } else if (IsStandaloneGdbstubEnabled()) { + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::DmntGen2, ncm::StorageId::None), 0); + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Tma, ncm::StorageId::BuiltInSystem), 0); } else { LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Dmnt, ncm::StorageId::None), 0); LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Tma, ncm::StorageId::BuiltInSystem), 0); diff --git a/libraries/libstratosphere/source/socket/impl/socket_api.hpp b/libraries/libstratosphere/source/socket/impl/socket_api.hpp index f80e467..f1dcc55 100644 --- a/libraries/libstratosphere/source/socket/impl/socket_api.hpp +++ b/libraries/libstratosphere/source/socket/impl/socket_api.hpp @@ -41,6 +41,7 @@ s32 Shutdown(s32 desc, ShutdownMethod how); + s32 Socket(Family domain, Type type, Protocol protocol); s32 SocketExempt(Family domain, Type type, Protocol protocol); s32 Accept(s32 desc, SockAddr *out_address, SockLenT *out_addr_len); diff --git a/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp b/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp index db145ba..299d762 100644 --- a/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp +++ b/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp @@ -206,6 +206,8 @@ /* TODO: socket::resolver::EnableResolverCalls()? Not necessary in our case (htc), but consider calling it. */ + g_initialized = true; + return ResultSuccess(); } @@ -422,6 +424,22 @@ return result; } + s32 Socket(Family domain, Type type, Protocol protocol) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Perform the call. */ + Errno error = Errno::ESuccess; + int result = ::bsdSocket(static_cast(domain), static_cast(type), static_cast(protocol)); + TranslateResultToBsdError(error, result); + + if (result < 0) { + socket::impl::SetLastError(error); + } + + return result; + } + s32 SocketExempt(Family domain, Type type, Protocol protocol) { /* Check pre-conditions. */ AMS_ABORT_UNLESS(IsInitialized()); diff --git a/libraries/libstratosphere/source/socket/socket_api.cpp b/libraries/libstratosphere/source/socket/socket_api.cpp index 30a84a6..bd1949d 100644 --- a/libraries/libstratosphere/source/socket/socket_api.cpp +++ b/libraries/libstratosphere/source/socket/socket_api.cpp @@ -74,6 +74,10 @@ return impl::Shutdown(desc, how); } + s32 Socket(Family domain, Type type, Protocol protocol) { + return impl::Socket(domain, type, protocol); + } + s32 SocketExempt(Family domain, Type type, Protocol protocol) { return impl::SocketExempt(domain, type, protocol); } diff --git a/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp b/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp index d9f370f..3696ddb 100644 --- a/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp +++ b/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp @@ -378,6 +378,12 @@ /* 0 = Disabled, 1 = Enabled */ R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_htc", "u8!0x0")); + /* Controls whether atmosphere's dmnt.gen2 gdbstub should run as a standalone via sockets. */ + /* Note that this setting is ignored (and treated as 0) when htc is enabled. */ + /* Note that this setting may disappear in the future. */ + /* 0 = Disabled, 1 = Enabled */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_standalone_gdbstub", "u8!0x0")); + /* Controls whether atmosphere's log manager is enabled. */ /* Note that this setting is ignored (and treated as 1) when htc is enabled. */ /* 0 = Disabled, 1 = Enabled */ diff --git a/stratosphere/dmnt.gen2/dmnt.gen2.json b/stratosphere/dmnt.gen2/dmnt.gen2.json index 510de62..2d5100f 100644 --- a/stratosphere/dmnt.gen2/dmnt.gen2.json +++ b/stratosphere/dmnt.gen2/dmnt.gen2.json @@ -15,7 +15,7 @@ "filesystem_access": { "permissions": "0xFFFFFFFFFFFFFFFF" }, - "service_access": ["pm:dmnt", "ldr:dmnt", "ro:dmnt", "ns:dev", "lr", "fsp-srv", "fatal:u", "pgl", "htcs"], + "service_access": ["pm:dmnt", "ldr:dmnt", "ro:dmnt", "ns:dev", "lr", "fsp-srv", "fatal:u", "pgl", "htcs", "bsd:s"], "service_host": [], "kernel_capabilities": [{ "type": "kernel_flags", diff --git a/stratosphere/dmnt.gen2/source/dmnt2_debug_log.cpp b/stratosphere/dmnt.gen2/source/dmnt2_debug_log.cpp index 734754d..0d217f9 100644 --- a/stratosphere/dmnt.gen2/source/dmnt2_debug_log.cpp +++ b/stratosphere/dmnt.gen2/source/dmnt2_debug_log.cpp @@ -16,7 +16,8 @@ #include #include "dmnt2_debug_log.hpp" -#define AMS_DMNT2_ENABLE_HTCS_DEBUG_LOG +// TODO: This should be converted to use log manager. */ +//#define AMS_DMNT2_ENABLE_HTCS_DEBUG_LOG #if defined(AMS_DMNT2_ENABLE_HTCS_DEBUG_LOG) @@ -164,15 +165,78 @@ namespace ams::dmnt { + //#define AMS_DMNT2_ENABLE_SD_CARD_DEBUG_LOG + + #if defined(AMS_DMNT2_ENABLE_SD_CARD_DEBUG_LOG) + + namespace { + + alignas(0x40) constinit u8 g_buffer[os::MemoryPageSize * 4]; + constinit lmem::HeapHandle g_debug_log_heap; + constinit fs::FileHandle g_debug_log_file; + + constinit os::SdkMutex g_fs_mutex; + constinit s64 g_fs_offset = 0; + + void *Allocate(size_t size) { + return lmem::AllocateFromExpHeap(g_debug_log_heap, size); + } + + void Deallocate(void *p, size_t size) { + AMS_UNUSED(size); + return lmem::FreeToExpHeap(g_debug_log_heap, p); + } + + } void InitializeDebugLog() { - /* Do nothing. */ + g_debug_log_heap = lmem::CreateExpHeap(g_buffer, sizeof(g_buffer), lmem::CreateOption_ThreadSafe); + + fs::SetAllocator(Allocate, Deallocate); + fs::InitializeForSystem(); + fs::SetEnabledAutoAbort(false); + + R_ABORT_UNLESS(fs::MountSdCard("sdmc")); + + fs::DeleteFile("sdmc:/dmnt2.log"); + R_ABORT_UNLESS(fs::CreateFile("sdmc:/dmnt2.log", 0)); + R_ABORT_UNLESS(fs::OpenFile(std::addressof(g_debug_log_file), "sdmc:/dmnt2.log", fs::OpenMode_Write | fs::OpenMode_AllowAppend)); } void DebugLog(const char *prefix, const char *fmt, ...) { /* Do nothing. */ + char buffer[0x200]; + { + const auto prefix_len = std::strlen(prefix); + std::memcpy(buffer, prefix, prefix_len); + + std::va_list vl; + va_start(vl, fmt); + util::VSNPrintf(buffer + prefix_len, sizeof(buffer) - prefix_len, fmt, vl); + va_end(vl); + } + + const auto len = std::strlen(buffer); + + std::scoped_lock lk(g_fs_mutex); + R_ABORT_UNLESS(fs::WriteFile(g_debug_log_file, g_fs_offset, buffer, len, fs::WriteOption::Flush)); + g_fs_offset += len; } + #else + + void InitializeDebugLog() { + /* ... */ + } + + void DebugLog(const char *prefix, const char *fmt, ...) { + AMS_UNUSED(prefix, fmt); + } + + #endif + + + } diff --git a/stratosphere/dmnt.gen2/source/dmnt2_gdb_packet_io.cpp b/stratosphere/dmnt.gen2/source/dmnt2_gdb_packet_io.cpp index 7eb4a8f..da1e027 100644 --- a/stratosphere/dmnt.gen2/source/dmnt2_gdb_packet_io.cpp +++ b/stratosphere/dmnt.gen2/source/dmnt2_gdb_packet_io.cpp @@ -41,7 +41,7 @@ } - void GdbPacketIo::SendPacket(bool *out_break, const char *src, HtcsSession *session) { + void GdbPacketIo::SendPacket(bool *out_break, const char *src, TransportSession *session) { /* Default to not breaked. */ *out_break = false; @@ -99,7 +99,7 @@ } } - char *GdbPacketIo::ReceivePacket(bool *out_break, char *dst, size_t size, HtcsSession *session) { + char *GdbPacketIo::ReceivePacket(bool *out_break, char *dst, size_t size, TransportSession *session) { /* Default to not breaked. */ *out_break = false; diff --git a/stratosphere/dmnt.gen2/source/dmnt2_gdb_packet_io.hpp b/stratosphere/dmnt.gen2/source/dmnt2_gdb_packet_io.hpp index 9cac7e5..00ca570 100644 --- a/stratosphere/dmnt.gen2/source/dmnt2_gdb_packet_io.hpp +++ b/stratosphere/dmnt.gen2/source/dmnt2_gdb_packet_io.hpp @@ -15,7 +15,7 @@ */ #pragma once #include -#include "dmnt2_htcs_session.hpp" +#include "dmnt2_transport_session.hpp" namespace ams::dmnt { @@ -30,8 +30,8 @@ void SetNoAck() { m_no_ack = true; } - void SendPacket(bool *out_break, const char *src, HtcsSession *session); - char *ReceivePacket(bool *out_break, char *dst, size_t size, HtcsSession *session); + void SendPacket(bool *out_break, const char *src, TransportSession *session); + char *ReceivePacket(bool *out_break, char *dst, size_t size, TransportSession *session); }; } \ No newline at end of file diff --git a/stratosphere/dmnt.gen2/source/dmnt2_gdb_server.cpp b/stratosphere/dmnt.gen2/source/dmnt2_gdb_server.cpp index 1428ce0..82572ba 100644 --- a/stratosphere/dmnt.gen2/source/dmnt2_gdb_server.cpp +++ b/stratosphere/dmnt.gen2/source/dmnt2_gdb_server.cpp @@ -15,6 +15,7 @@ */ #include #include "dmnt2_debug_log.hpp" +#include "dmnt2_transport_layer.hpp" #include "dmnt2_gdb_server.hpp" #include "dmnt2_gdb_server_impl.hpp" @@ -36,34 +37,28 @@ while (true) { /* Get a socket. */ int fd; - while ((fd = htcs::Socket()) == -1) { + while ((fd = transport::Socket()) == -1) { os::SleepThread(TimeSpan::FromSeconds(1)); } /* Ensure we cleanup the socket when we're done with it. */ ON_SCOPE_EXIT { - htcs::Close(fd); + transport::Close(fd); os::SleepThread(TimeSpan::FromSeconds(1)); }; - /* Create a sock addr for our server. */ - htcs::SockAddrHtcs addr; - addr.family = htcs::HTCS_AF_HTCS; - addr.peer_name = htcs::GetPeerNameAny(); - std::strcpy(addr.port_name.name, "iywys@$gdb"); - /* Bind. */ - if (htcs::Bind(fd, std::addressof(addr)) == -1) { + if (transport::Bind(fd, transport::PortName_GdbServer) == -1) { continue; } /* Listen on our port. */ - while (htcs::Listen(fd, 0) == 0) { + while (transport::Listen(fd, 0) == 0) { /* Continue accepting clients, so long as we can. */ int client_fd; while (true) { /* Try to accept a client. */ - if (client_fd = htcs::Accept(fd, std::addressof(addr)); client_fd < 0) { + if (client_fd = transport::Accept(fd); client_fd < 0) { break; } @@ -77,7 +72,7 @@ } /* Close the client socket. */ - htcs::Close(client_fd); + transport::Close(client_fd); } } } diff --git a/stratosphere/dmnt.gen2/source/dmnt2_gdb_server_impl.hpp b/stratosphere/dmnt.gen2/source/dmnt2_gdb_server_impl.hpp index 868552a..0e5de47 100644 --- a/stratosphere/dmnt.gen2/source/dmnt2_gdb_server_impl.hpp +++ b/stratosphere/dmnt.gen2/source/dmnt2_gdb_server_impl.hpp @@ -31,7 +31,7 @@ }; private: int m_socket; - HtcsSession m_session; + TransportSession m_session; GdbPacketIo m_packet_io; char *m_receive_packet{nullptr}; char *m_reply_packet{nullptr}; diff --git a/stratosphere/dmnt.gen2/source/dmnt2_htcs_receive_buffer.cpp b/stratosphere/dmnt.gen2/source/dmnt2_htcs_receive_buffer.cpp deleted file mode 100644 index 5ba81b6..0000000 --- a/stratosphere/dmnt.gen2/source/dmnt2_htcs_receive_buffer.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include -#include "dmnt2_htcs_receive_buffer.hpp" - -namespace ams::dmnt { - - ssize_t HtcsReceiveBuffer::Read(void *dst, size_t size) { - /* Acquire exclusive access to ourselves. */ - std::scoped_lock lk(m_mutex); - - /* Check that we're readable and valid. */ - if (!(this->IsValid() && this->IsReadable())) { - return -1; - } - - /* Check that we have data to read. */ - const size_t readable = std::min(size, m_readable_size); - if (readable <= 0) { - return -1; - } - - /* Copy the data. */ - std::memcpy(dst, m_buffer + m_offset, readable); - - /* Advance our pointers. */ - m_readable_size -= readable; - m_offset += readable; - - /* Handle the case where we're done consuming. */ - if (m_readable_size == 0) { - m_offset = 0; - m_readable_event.Clear(); - m_writable_event.Signal(); - } - - return readable; - } - - ssize_t HtcsReceiveBuffer::Write(const void *src, size_t size) { - /* Acquire exclusive access to ourselves. */ - std::scoped_lock lk(m_mutex); - - /* Check that we're readable and valid. */ - if (!(this->IsValid() && this->IsWritable())) { - return -1; - } - - /* Copy the data to our buffer. */ - std::memcpy(m_buffer, src, size); - - /* Set our fields. */ - m_readable_size = size; - m_offset = 0; - m_writable_event.Clear(); - m_readable_event.Signal(); - - return size; - } - - bool HtcsReceiveBuffer::WaitToBeReadable() { - /* Check if we're already readable. */ - { - std::scoped_lock lk(m_mutex); - - if (this->IsReadable()) { - return true; - } else if (!this->IsValid()) { - return false; - } else { - m_readable_event.Clear(); - } - } - - /* Wait for us to be readable. */ - m_readable_event.Wait(); - - return this->IsValid(); - } - - bool HtcsReceiveBuffer::WaitToBeReadable(TimeSpan timeout) { - /* Check if we're already readable. */ - { - std::scoped_lock lk(m_mutex); - - if (this->IsReadable()) { - return true; - } else if (!this->IsValid()) { - return false; - } else { - m_readable_event.Clear(); - } - } - - /* Wait for us to be readable. */ - const bool res = m_readable_event.TimedWait(timeout); - - return res && this->IsValid(); - } - - bool HtcsReceiveBuffer::WaitToBeWritable() { - /* Check if we're already writable. */ - { - std::scoped_lock lk(m_mutex); - - if (this->IsWritable()) { - return true; - } else if (!this->IsValid()) { - return false; - } else { - m_writable_event.Clear(); - } - } - - /* Wait for us to be writable. */ - m_writable_event.Wait(); - - return this->IsValid(); - } - - void HtcsReceiveBuffer::Invalidate() { - /* Acquire exclusive access to ourselves. */ - std::scoped_lock lk(m_mutex); - - /* Set ourselves as invalid. */ - m_valid = false; - - /* Signal our events. */ - m_readable_event.Signal(); - m_writable_event.Signal(); - } - -} diff --git a/stratosphere/dmnt.gen2/source/dmnt2_htcs_receive_buffer.hpp b/stratosphere/dmnt.gen2/source/dmnt2_htcs_receive_buffer.hpp deleted file mode 100644 index 3901ac4..0000000 --- a/stratosphere/dmnt.gen2/source/dmnt2_htcs_receive_buffer.hpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once -#include - -namespace ams::dmnt { - - class HtcsReceiveBuffer { - public: - static constexpr size_t ReceiveBufferSize = 4_KB; - private: - u8 m_buffer[ReceiveBufferSize]; - os::Event m_readable_event; - os::Event m_writable_event; - os::SdkMutex m_mutex; - size_t m_readable_size; - size_t m_offset; - bool m_valid; - public: - HtcsReceiveBuffer() : m_readable_event(os::EventClearMode_ManualClear), m_writable_event(os::EventClearMode_ManualClear), m_mutex(), m_readable_size(), m_offset(), m_valid(true) { /* ... */ } - - ALWAYS_INLINE bool IsReadable() const { return m_readable_size != 0; } - ALWAYS_INLINE bool IsWritable() const { return m_readable_size == 0; } - ALWAYS_INLINE bool IsValid() const { return m_valid; } - - ssize_t Read(void *dst, size_t size); - ssize_t Write(const void *src, size_t size); - - bool WaitToBeReadable(); - bool WaitToBeReadable(TimeSpan timeout); - bool WaitToBeWritable(); - - void Invalidate(); - }; - -} \ No newline at end of file diff --git a/stratosphere/dmnt.gen2/source/dmnt2_htcs_session.cpp b/stratosphere/dmnt.gen2/source/dmnt2_htcs_session.cpp deleted file mode 100644 index 48bd658..0000000 --- a/stratosphere/dmnt.gen2/source/dmnt2_htcs_session.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include -#include "dmnt2_htcs_session.hpp" -#include "dmnt2_debug_log.hpp" - -namespace ams::dmnt { - - HtcsSession::HtcsSession(int fd) : m_socket(fd), m_valid(true) { - /* Create our thread. */ - R_ABORT_UNLESS(os::CreateThread(std::addressof(m_receive_thread), ReceiveThreadEntry, this, m_receive_thread_stack, sizeof(m_receive_thread_stack), os::HighestThreadPriority - 1)); - - /* Start our thread. */ - os::StartThread(std::addressof(m_receive_thread)); - - /* Note that we connected. */ - AMS_DMNT2_GDB_LOG_INFO("Created Session %d\n", m_socket); - } - - HtcsSession::~HtcsSession() { - /* Note that we connected. */ - AMS_DMNT2_GDB_LOG_INFO("Closing Session %d\n", m_socket); - - /* Invalidate our receive buffer. */ - m_receive_buffer.Invalidate(); - - /* Shutdown our socket. */ - htcs::Shutdown(m_socket, htcs::HTCS_SHUT_RDWR); - - /* Wait for our thread. */ - os::WaitThread(std::addressof(m_receive_thread)); - os::DestroyThread(std::addressof(m_receive_thread)); - - /* Close our socket. */ - htcs::Close(m_socket); - } - - bool HtcsSession::WaitToBeReadable() { - return m_receive_buffer.WaitToBeReadable(); - } - - bool HtcsSession::WaitToBeReadable(TimeSpan timeout) { - return m_receive_buffer.WaitToBeReadable(timeout); - } - - util::optional HtcsSession::GetChar() { - /* Wait for us to have data. */ - m_receive_buffer.WaitToBeReadable(); - - /* Get our data. */ - char c; - if (m_receive_buffer.Read(std::addressof(c), sizeof(c)) > 0) { - return c; - } else { - return util::nullopt; - } - } - - ssize_t HtcsSession::PutChar(char c) { - /* Send the character. */ - const auto sent = htcs::Send(m_socket, std::addressof(c), sizeof(c), 0); - if (sent < 0) { - m_valid = false; - } - - return sent; - } - - ssize_t HtcsSession::PutString(const char *str) { - /* Repeatedly send until all is sent. */ - const size_t len = std::strlen(str); - - size_t remaining = len; - while (remaining > 0) { - const auto sent = htcs::Send(m_socket, str, remaining, 0); - if (sent >= 0) { - remaining -= sent; - str += sent; - } else { - m_valid = false; - return sent; - } - } - - return len; - } - - void HtcsSession::ReceiveThreadFunction() { - /* Create temporary buffer. */ - u8 buffer[HtcsReceiveBuffer::ReceiveBufferSize]; - - /* Loop receiving data. */ - while (true) { - /* Receive data. */ - const auto res = htcs::Recv(m_socket, buffer, sizeof(buffer), 0); - if (res > 0) { - /* Write the data to our buffer. */ - m_receive_buffer.WaitToBeWritable(); - m_receive_buffer.Write(buffer, res); - } else { - /* Otherwise, if we got an error other than "try again", we're done. */ - if (htcs::GetLastError() != htcs::HTCS_EAGAIN) { - AMS_DMNT2_GDB_LOG_INFO("Session %d invalid, res=%ld, err=%d\n", m_socket, res, static_cast(htcs::GetLastError())); - m_valid = false; - break; - } - } - } - - /* Invalidate our receive buffer. */ - m_receive_buffer.Invalidate(); - } - -} diff --git a/stratosphere/dmnt.gen2/source/dmnt2_htcs_session.hpp b/stratosphere/dmnt.gen2/source/dmnt2_htcs_session.hpp deleted file mode 100644 index b109afb..0000000 --- a/stratosphere/dmnt.gen2/source/dmnt2_htcs_session.hpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once -#include -#include "dmnt2_htcs_receive_buffer.hpp" - -namespace ams::dmnt { - - class HtcsSession { - private: - alignas(os::ThreadStackAlignment) u8 m_receive_thread_stack[util::AlignUp(os::MemoryPageSize + HtcsReceiveBuffer::ReceiveBufferSize, os::ThreadStackAlignment)]; - HtcsReceiveBuffer m_receive_buffer; - os::ThreadType m_receive_thread; - int m_socket; - bool m_valid; - public: - HtcsSession(int fd); - ~HtcsSession(); - - ALWAYS_INLINE bool IsValid() const { return m_valid; } - - bool WaitToBeReadable(); - bool WaitToBeReadable(TimeSpan timeout); - - util::optional GetChar(); - ssize_t PutChar(char c); - ssize_t PutString(const char *str); - - private: - static void ReceiveThreadEntry(void *arg) { - static_cast(arg)->ReceiveThreadFunction(); - } - - void ReceiveThreadFunction(); - }; - -} \ No newline at end of file diff --git a/stratosphere/dmnt.gen2/source/dmnt2_main.cpp b/stratosphere/dmnt.gen2/source/dmnt2_main.cpp index 6336cc2..ed8edb7 100644 --- a/stratosphere/dmnt.gen2/source/dmnt2_main.cpp +++ b/stratosphere/dmnt.gen2/source/dmnt2_main.cpp @@ -16,15 +16,22 @@ #include #include "dmnt2_debug_log.hpp" #include "dmnt2_gdb_server.hpp" +#include "dmnt2_transport_layer.hpp" namespace ams { - namespace dmnt { + namespace { - namespace { + bool IsHtcEnabled() { + u8 enable_htc = 0; + settings::fwdbg::GetSettingsItemValue(std::addressof(enable_htc), sizeof(enable_htc), "atmosphere", "enable_htc"); + return enable_htc != 0; + } - alignas(0x40) constinit u8 g_htcs_buffer[4_KB]; - + bool IsStandaloneGdbstubEnabled() { + u8 enable_gdbstub = 0; + settings::fwdbg::GetSettingsItemValue(std::addressof(enable_gdbstub), sizeof(enable_gdbstub), "atmosphere", "enable_standalone_gdbstub"); + return enable_gdbstub != 0; } } @@ -50,11 +57,23 @@ os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(dmnt, Main)); AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(dmnt, Main)); - /* Initialize htcs. */ - constexpr auto HtcsSocketCountMax = 8; - const size_t buffer_size = htcs::GetWorkingMemorySize(HtcsSocketCountMax); - AMS_ABORT_UNLESS(sizeof(dmnt::g_htcs_buffer) >= buffer_size); - htcs::InitializeForSystem(dmnt::g_htcs_buffer, buffer_size, HtcsSocketCountMax); + bool use_htcs = false, use_tcp = false; + { + R_ABORT_UNLESS(::setsysInitialize()); + ON_SCOPE_EXIT { ::setsysExit(); }; + + use_htcs = IsHtcEnabled(); + use_tcp = IsStandaloneGdbstubEnabled(); + } + + /* Initialize transport layer. */ + if (use_htcs) { + dmnt::transport::InitializeByHtcs(); + } else if (use_tcp) { + dmnt::transport::InitializeByTcp(); + } else { + return; + } /* Initialize debug log thread. */ dmnt::InitializeDebugLog(); diff --git a/stratosphere/dmnt.gen2/source/dmnt2_transport_layer.cpp b/stratosphere/dmnt.gen2/source/dmnt2_transport_layer.cpp new file mode 100644 index 0000000..b6d473b --- /dev/null +++ b/stratosphere/dmnt.gen2/source/dmnt2_transport_layer.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "dmnt2_transport_layer.hpp" + +namespace ams::dmnt::transport { + + namespace { + + enum SocketMode { + SocketMode_Invalid, + SocketMode_Htcs, + SocketMode_Tcp, + }; + + constexpr inline const u16 ListenPort_GdbServer = 22225; + constexpr inline const u16 ListenPort_GdbDebugLog = 22227; + + constinit os::SdkMutex g_socket_init_mutex; + constinit SocketMode g_socket_mode = SocketMode_Invalid; + + constexpr inline size_t RequiredAlignment = std::max(os::ThreadStackAlignment, os::MemoryPageSize); + + using SocketConfigType = socket::SystemConfigLightDefault; + + /* TODO: If we ever use resolvers, increase this. */ + constexpr inline size_t SocketAllocatorSize = 4_KB; + constexpr inline size_t SocketMemoryPoolSize = util::AlignUp(SocketConfigType::PerTcpSocketWorstCaseMemoryPoolSize + SocketConfigType::PerUdpSocketWorstCaseMemoryPoolSize, os::MemoryPageSize); + + constexpr inline size_t SocketRequiredSize = util::AlignUp(SocketMemoryPoolSize + SocketAllocatorSize, os::MemoryPageSize); + + /* Declare the memory pool. */ + alignas(RequiredAlignment) constinit u8 g_socket_memory[SocketRequiredSize]; + + constexpr inline const SocketConfigType SocketConfig(g_socket_memory, SocketRequiredSize, SocketAllocatorSize, 2); + + } + + void InitializeByHtcs() { + std::scoped_lock lk(g_socket_init_mutex); + AMS_ABORT_UNLESS(g_socket_mode == SocketMode_Invalid); + + constexpr auto HtcsSocketCountMax = 8; + const size_t buffer_size = htcs::GetWorkingMemorySize(HtcsSocketCountMax); + AMS_ABORT_UNLESS(sizeof(g_socket_memory) >= buffer_size); + htcs::InitializeForSystem(g_socket_memory, sizeof(g_socket_memory), HtcsSocketCountMax); + + g_socket_mode = SocketMode_Htcs; + } + + void InitializeByTcp() { + std::scoped_lock lk(g_socket_init_mutex); + AMS_ABORT_UNLESS(g_socket_mode == SocketMode_Invalid); + + R_ABORT_UNLESS(socket::Initialize(SocketConfig)); + + g_socket_mode = SocketMode_Tcp; + } + + s32 Socket() { + switch (g_socket_mode) { + case SocketMode_Htcs: return htcs::Socket(); + case SocketMode_Tcp: return socket::Socket(socket::Family::Af_Inet, socket::Type::Sock_Stream, socket::Protocol::IpProto_Tcp); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + s32 Close(s32 desc) { + switch (g_socket_mode) { + case SocketMode_Htcs: return htcs::Close(desc); + case SocketMode_Tcp: return socket::Close(desc); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + s32 Bind(s32 desc, PortName port_name) { + switch (g_socket_mode) { + case SocketMode_Htcs: + { + htcs::SockAddrHtcs addr; + addr.family = htcs::HTCS_AF_HTCS; + addr.peer_name = htcs::GetPeerNameAny(); + switch (port_name) { + case PortName_GdbServer: std::strcpy(addr.port_name.name, "iywys@$gdb"); break; + case PortName_GdbDebugLog: std::strcpy(addr.port_name.name, "iywys@$dmnt2_log"); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + return htcs::Bind(desc, std::addressof(addr)); + } + break; + case SocketMode_Tcp: + { + socket::SockAddrIn addr = {}; + addr.sin_family = socket::Family::Af_Inet; + addr.sin_addr.s_addr = socket::InAddr_Any; + + switch (port_name){ + case PortName_GdbServer: addr.sin_port = socket::InetHtons(static_cast(ListenPort_GdbServer)); break; + case PortName_GdbDebugLog: addr.sin_port = socket::InetHtons(static_cast(ListenPort_GdbDebugLog)); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + return socket::Bind(desc, reinterpret_cast(std::addressof(addr)), sizeof(addr)); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + s32 Listen(s32 desc, s32 backlog_count) { + switch (g_socket_mode) { + case SocketMode_Htcs: return htcs::Listen(desc, backlog_count); + case SocketMode_Tcp: return socket::Listen(desc, backlog_count); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + s32 Accept(s32 desc) { + switch (g_socket_mode) { + case SocketMode_Htcs: + { + htcs::SockAddrHtcs addr; + addr.family = htcs::HTCS_AF_HTCS; + addr.peer_name = htcs::GetPeerNameAny(); + addr.port_name.name[0] = '\x00'; + + return htcs::Accept(desc, std::addressof(addr)); + } + break; + case SocketMode_Tcp: + { + socket::SockAddrIn addr = {}; + socket::SockLenT addr_len = sizeof(addr); + + return socket::Accept(desc, reinterpret_cast(std::addressof(addr)), std::addressof(addr_len)); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + s32 Shutdown(s32 desc) { + switch (g_socket_mode) { + case SocketMode_Htcs: return htcs::Shutdown(desc, htcs::HTCS_SHUT_RDWR); + case SocketMode_Tcp: return socket::Shutdown(desc, socket::ShutdownMethod::Shut_RdWr); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + ssize_t Recv(s32 desc, void *buffer, size_t buffer_size, s32 flags) { + switch (g_socket_mode) { + case SocketMode_Htcs: return htcs::Recv(desc, buffer, buffer_size, flags); + case SocketMode_Tcp: return socket::Recv(desc, buffer, buffer_size, static_cast(flags)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, s32 flags) { + switch (g_socket_mode) { + case SocketMode_Htcs: return htcs::Send(desc, buffer, buffer_size, flags); + case SocketMode_Tcp: return socket::Send(desc, buffer, buffer_size, static_cast(flags)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + s32 GetLastError() { + switch (g_socket_mode) { + case SocketMode_Htcs: return htcs::GetLastError(); + case SocketMode_Tcp: return static_cast(socket::GetLastError()); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + bool IsLastErrorEAgain() { + switch (g_socket_mode) { + case SocketMode_Htcs: return htcs::GetLastError() == htcs::HTCS_EAGAIN; + case SocketMode_Tcp: return socket::GetLastError() == socket::Errno::EAgain; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + +} diff --git a/stratosphere/dmnt.gen2/source/dmnt2_transport_layer.hpp b/stratosphere/dmnt.gen2/source/dmnt2_transport_layer.hpp new file mode 100644 index 0000000..364b728 --- /dev/null +++ b/stratosphere/dmnt.gen2/source/dmnt2_transport_layer.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::dmnt::transport { + + void InitializeByHtcs(); + void InitializeByTcp(); + + enum PortName { + PortName_GdbServer, + PortName_GdbDebugLog, + }; + + s32 Socket(); + s32 Close(s32 desc); + s32 Bind(s32 desc, PortName port_name); + s32 Listen(s32 desc, s32 backlog_count); + s32 Accept(s32 desc); + s32 Shutdown(s32 desc); + + ssize_t Recv(s32 desc, void *buffer, size_t buffer_size, s32 flags); + ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, s32 flags); + + s32 GetLastError(); + bool IsLastErrorEAgain(); + +} \ No newline at end of file diff --git a/stratosphere/dmnt.gen2/source/dmnt2_transport_receive_buffer.cpp b/stratosphere/dmnt.gen2/source/dmnt2_transport_receive_buffer.cpp new file mode 100644 index 0000000..93ebffe --- /dev/null +++ b/stratosphere/dmnt.gen2/source/dmnt2_transport_receive_buffer.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "dmnt2_transport_receive_buffer.hpp" + +namespace ams::dmnt { + + ssize_t TransportReceiveBuffer::Read(void *dst, size_t size) { + /* Acquire exclusive access to ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're readable and valid. */ + if (!(this->IsValid() && this->IsReadable())) { + return -1; + } + + /* Check that we have data to read. */ + const size_t readable = std::min(size, m_readable_size); + if (readable <= 0) { + return -1; + } + + /* Copy the data. */ + std::memcpy(dst, m_buffer + m_offset, readable); + + /* Advance our pointers. */ + m_readable_size -= readable; + m_offset += readable; + + /* Handle the case where we're done consuming. */ + if (m_readable_size == 0) { + m_offset = 0; + m_readable_event.Clear(); + m_writable_event.Signal(); + } + + return readable; + } + + ssize_t TransportReceiveBuffer::Write(const void *src, size_t size) { + /* Acquire exclusive access to ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're readable and valid. */ + if (!(this->IsValid() && this->IsWritable())) { + return -1; + } + + /* Copy the data to our buffer. */ + std::memcpy(m_buffer, src, size); + + /* Set our fields. */ + m_readable_size = size; + m_offset = 0; + m_writable_event.Clear(); + m_readable_event.Signal(); + + return size; + } + + bool TransportReceiveBuffer::WaitToBeReadable() { + /* Check if we're already readable. */ + { + std::scoped_lock lk(m_mutex); + + if (this->IsReadable()) { + return true; + } else if (!this->IsValid()) { + return false; + } else { + m_readable_event.Clear(); + } + } + + /* Wait for us to be readable. */ + m_readable_event.Wait(); + + return this->IsValid(); + } + + bool TransportReceiveBuffer::WaitToBeReadable(TimeSpan timeout) { + /* Check if we're already readable. */ + { + std::scoped_lock lk(m_mutex); + + if (this->IsReadable()) { + return true; + } else if (!this->IsValid()) { + return false; + } else { + m_readable_event.Clear(); + } + } + + /* Wait for us to be readable. */ + const bool res = m_readable_event.TimedWait(timeout); + + return res && this->IsValid(); + } + + bool TransportReceiveBuffer::WaitToBeWritable() { + /* Check if we're already writable. */ + { + std::scoped_lock lk(m_mutex); + + if (this->IsWritable()) { + return true; + } else if (!this->IsValid()) { + return false; + } else { + m_writable_event.Clear(); + } + } + + /* Wait for us to be writable. */ + m_writable_event.Wait(); + + return this->IsValid(); + } + + void TransportReceiveBuffer::Invalidate() { + /* Acquire exclusive access to ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Set ourselves as invalid. */ + m_valid = false; + + /* Signal our events. */ + m_readable_event.Signal(); + m_writable_event.Signal(); + } + +} diff --git a/stratosphere/dmnt.gen2/source/dmnt2_transport_receive_buffer.hpp b/stratosphere/dmnt.gen2/source/dmnt2_transport_receive_buffer.hpp new file mode 100644 index 0000000..363ff79 --- /dev/null +++ b/stratosphere/dmnt.gen2/source/dmnt2_transport_receive_buffer.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::dmnt { + + class TransportReceiveBuffer { + public: + static constexpr size_t ReceiveBufferSize = 4_KB; + private: + u8 m_buffer[ReceiveBufferSize]; + os::Event m_readable_event; + os::Event m_writable_event; + os::SdkMutex m_mutex; + size_t m_readable_size; + size_t m_offset; + bool m_valid; + public: + TransportReceiveBuffer() : m_readable_event(os::EventClearMode_ManualClear), m_writable_event(os::EventClearMode_ManualClear), m_mutex(), m_readable_size(), m_offset(), m_valid(true) { /* ... */ } + + ALWAYS_INLINE bool IsReadable() const { return m_readable_size != 0; } + ALWAYS_INLINE bool IsWritable() const { return m_readable_size == 0; } + ALWAYS_INLINE bool IsValid() const { return m_valid; } + + ssize_t Read(void *dst, size_t size); + ssize_t Write(const void *src, size_t size); + + bool WaitToBeReadable(); + bool WaitToBeReadable(TimeSpan timeout); + bool WaitToBeWritable(); + + void Invalidate(); + }; + +} \ No newline at end of file diff --git a/stratosphere/dmnt.gen2/source/dmnt2_transport_session.cpp b/stratosphere/dmnt.gen2/source/dmnt2_transport_session.cpp new file mode 100644 index 0000000..a500f71 --- /dev/null +++ b/stratosphere/dmnt.gen2/source/dmnt2_transport_session.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "dmnt2_transport_layer.hpp" +#include "dmnt2_transport_session.hpp" +#include "dmnt2_debug_log.hpp" + +namespace ams::dmnt { + + TransportSession::TransportSession(int fd) : m_socket(fd), m_valid(true) { + /* Create our thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_receive_thread), ReceiveThreadEntry, this, m_receive_thread_stack, sizeof(m_receive_thread_stack), os::HighestThreadPriority - 1)); + + /* Start our thread. */ + os::StartThread(std::addressof(m_receive_thread)); + + /* Note that we connected. */ + AMS_DMNT2_GDB_LOG_INFO("Created Session %d\n", m_socket); + } + + TransportSession::~TransportSession() { + /* Note that we connected. */ + AMS_DMNT2_GDB_LOG_INFO("Closing Session %d\n", m_socket); + + /* Invalidate our receive buffer. */ + m_receive_buffer.Invalidate(); + + /* Shutdown our socket. */ + transport::Shutdown(m_socket); + + /* Wait for our thread. */ + os::WaitThread(std::addressof(m_receive_thread)); + os::DestroyThread(std::addressof(m_receive_thread)); + + /* Close our socket. */ + transport::Close(m_socket); + } + + bool TransportSession::WaitToBeReadable() { + return m_receive_buffer.WaitToBeReadable(); + } + + bool TransportSession::WaitToBeReadable(TimeSpan timeout) { + return m_receive_buffer.WaitToBeReadable(timeout); + } + + util::optional TransportSession::GetChar() { + /* Wait for us to have data. */ + m_receive_buffer.WaitToBeReadable(); + + /* Get our data. */ + char c; + if (m_receive_buffer.Read(std::addressof(c), sizeof(c)) > 0) { + return c; + } else { + return util::nullopt; + } + } + + ssize_t TransportSession::PutChar(char c) { + /* Send the character. */ + const auto sent = transport::Send(m_socket, std::addressof(c), sizeof(c), 0); + if (sent < 0) { + m_valid = false; + } + + return sent; + } + + ssize_t TransportSession::PutString(const char *str) { + /* Repeatedly send until all is sent. */ + const size_t len = std::strlen(str); + + size_t remaining = len; + while (remaining > 0) { + const auto sent = transport::Send(m_socket, str, remaining, 0); + if (sent >= 0) { + remaining -= sent; + str += sent; + } else { + m_valid = false; + return sent; + } + } + + return len; + } + + void TransportSession::ReceiveThreadFunction() { + /* Create temporary buffer. */ + u8 buffer[TransportReceiveBuffer::ReceiveBufferSize]; + + /* Loop receiving data. */ + while (true) { + /* Receive data. */ + const auto res = transport::Recv(m_socket, buffer, sizeof(buffer), 0); + if (res > 0) { + /* Write the data to our buffer. */ + m_receive_buffer.WaitToBeWritable(); + m_receive_buffer.Write(buffer, res); + } else { + /* Otherwise, if we got an error other than "try again", we're done. */ + if (!transport::IsLastErrorEAgain()) { + AMS_DMNT2_GDB_LOG_INFO("Session %d invalid, res=%ld, err=%d\n", m_socket, res, static_cast(transport::GetLastError())); + m_valid = false; + break; + } + } + } + + /* Invalidate our receive buffer. */ + m_receive_buffer.Invalidate(); + } + +} diff --git a/stratosphere/dmnt.gen2/source/dmnt2_transport_session.hpp b/stratosphere/dmnt.gen2/source/dmnt2_transport_session.hpp new file mode 100644 index 0000000..e19fec3 --- /dev/null +++ b/stratosphere/dmnt.gen2/source/dmnt2_transport_session.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "dmnt2_transport_receive_buffer.hpp" + +namespace ams::dmnt { + + class TransportSession { + private: + alignas(os::ThreadStackAlignment) u8 m_receive_thread_stack[util::AlignUp(os::MemoryPageSize + TransportReceiveBuffer::ReceiveBufferSize, os::ThreadStackAlignment)]; + TransportReceiveBuffer m_receive_buffer; + os::ThreadType m_receive_thread; + int m_socket; + bool m_valid; + public: + TransportSession(int fd); + ~TransportSession(); + + ALWAYS_INLINE bool IsValid() const { return m_valid; } + + bool WaitToBeReadable(); + bool WaitToBeReadable(TimeSpan timeout); + + util::optional GetChar(); + ssize_t PutChar(char c); + ssize_t PutString(const char *str); + + private: + static void ReceiveThreadEntry(void *arg) { + static_cast(arg)->ReceiveThreadFunction(); + } + + void ReceiveThreadFunction(); + }; + +} \ No newline at end of file