From 822875ecf5a441edd93a7338342611d9465739c8 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Fri, 9 Apr 2021 00:06:03 -0700 Subject: [PATCH] tipc: implement framework/server support logic (except for actual processing) --- .../include/stratosphere/tipc.hpp | 4 + .../impl/tipc_autogen_interface_macros.hpp | 2 +- .../stratosphere/tipc/tipc_allocators.hpp | 3 +- .../stratosphere/tipc/tipc_object_manager.hpp | 187 ++++++++++ .../stratosphere/tipc/tipc_server_manager.hpp | 323 ++++++++++++++++++ .../tipc/tipc_waitable_object.hpp | 92 +++++ .../source/_test/test_tipc_serializer.cpp | 1 - 7 files changed, 609 insertions(+), 3 deletions(-) create mode 100644 libraries/libstratosphere/include/stratosphere/tipc/tipc_object_manager.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/tipc/tipc_server_manager.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/tipc/tipc_waitable_object.hpp diff --git a/libraries/libstratosphere/include/stratosphere/tipc.hpp b/libraries/libstratosphere/include/stratosphere/tipc.hpp index 8102c877d..bd139cfef 100644 --- a/libraries/libstratosphere/include/stratosphere/tipc.hpp +++ b/libraries/libstratosphere/include/stratosphere/tipc.hpp @@ -19,3 +19,7 @@ #include #include +#include + +#include +#include diff --git a/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_autogen_interface_macros.hpp b/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_autogen_interface_macros.hpp index 1247ee30d..5c0ac16d9 100644 --- a/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_autogen_interface_macros.hpp +++ b/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_autogen_interface_macros.hpp @@ -127,7 +127,7 @@ namespace ams::tipc::impl { static_assert(::NAMESPACE::Is##INTERFACE); \ \ /* Get accessor to the message buffer. */ \ - svc::ipc::MessageBuffer message_buffer(svc::ipc::GetMessageBuffer()); \ + const svc::ipc::MessageBuffer message_buffer(svc::ipc::GetMessageBuffer()); \ \ /* Get decision variables. */ \ const auto tag = svc::ipc::MessageBuffer::MessageHeader(message_buffer).GetTag(); \ diff --git a/libraries/libstratosphere/include/stratosphere/tipc/tipc_allocators.hpp b/libraries/libstratosphere/include/stratosphere/tipc/tipc_allocators.hpp index 8600499ec..0cf758d1e 100644 --- a/libraries/libstratosphere/include/stratosphere/tipc/tipc_allocators.hpp +++ b/libraries/libstratosphere/include/stratosphere/tipc/tipc_allocators.hpp @@ -25,8 +25,9 @@ namespace ams::tipc { { t.Allocate() } -> std::convertible_to; }; - template requires IsServiceObject + template requires IsServiceObject class SingletonAllocator final { + static_assert(N >= 1); private: T m_singleton; public: diff --git a/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_manager.hpp b/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_manager.hpp new file mode 100644 index 000000000..c14942e9f --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_manager.hpp @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2018-2020 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 +#include +#include + +namespace ams::tipc { + + /* TODO: Put this in a better header. */ + constexpr inline u16 MethodId_Invalid = 0x0; + constexpr inline u16 MethodId_CloseSession = 0xF; + + class ObjectManagerBase { + private: + struct Entry { + util::TypedStorage object; + os::WaitableHolderType waitable_holder; + }; + private: + os::SdkMutex m_mutex{}; + Entry *m_entries_start{}; + Entry *m_entries_end{}; + os::WaitableManagerType *m_waitable_manager{}; + private: + Entry *FindEntry(svc::Handle handle) { + for (Entry *cur = m_entries_start; cur != m_entries_end; ++cur) { + if (GetReference(cur->object).GetHandle() == handle) { + return cur; + } + } + return nullptr; + } + + Entry *FindEntry(os::WaitableHolderType *holder) { + for (Entry *cur = m_entries_start; cur != m_entries_end; ++cur) { + if (std::addressof(cur->waitable_holder) == holder) { + return cur; + } + } + return nullptr; + } + public: + constexpr ObjectManagerBase() = default; + + void Initialize(os::WaitableManagerType *manager, Entry *entries, size_t max_objects) { + /* Set our waitable manager. */ + m_waitable_manager = manager; + + /* Setup entry pointers. */ + m_entries_start = entries; + m_entries_end = entries + max_objects; + + /* Construct all entries. */ + for (size_t i = 0; i < max_objects; ++i) { + util::ConstructAt(m_entries_start[i].object); + } + } + + void AddObject(WaitableObject &object) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Find an empty entry. */ + auto *entry = this->FindEntry(svc::InvalidHandle); + AMS_ABORT_UNLESS(entry != nullptr); + + /* Set the entry's object. */ + GetReference(entry->object) = object; + + /* Setup the entry's holder. */ + os::InitializeWaitableHolder(std::addressof(entry->waitable_holder), object.GetHandle()); + os::LinkWaitableHolder(m_waitable_manager, std::addressof(entry->waitable_holder)); + } + + void CloseObject(svc::Handle handle) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Find the matching entry. */ + auto *entry = this->FindEntry(handle); + AMS_ABORT_UNLESS(entry != nullptr); + + /* Finalize the entry's holder. */ + os::UnlinkWaitableHolder(std::addressof(entry->waitable_holder)); + os::FinalizeWaitableHolder(std::addressof(entry->waitable_holder)); + + /* Destroy the object. */ + GetReference(entry->object).Destroy(); + } + + Result ReplyAndReceive(os::WaitableHolderType **out_holder, WaitableObject *out_object, svc::Handle reply_target, os::WaitableManagerType *manager) { + /* Declare signaled holder for processing ahead of time. */ + os::WaitableHolderType *signaled_holder; + + /* Reply and receive until we get a newly signaled target. */ + Result result = os::SdkReplyAndReceive(out_holder, reply_target, manager); + for (signaled_holder = *out_holder; signaled_holder == nullptr; signaled_holder = *out_holder) { + result = os::SdkReplyAndReceive(out_holder, svc::InvalidHandle, manager); + } + + /* Find the entry matching the signaled holder. */ + if (auto *entry = this->FindEntry(signaled_holder); entry != nullptr) { + /* Get the output object. */ + *out_object = GetReference(entry->object); + *out_holder = nullptr; + + return result; + } else { + return ResultSuccess(); + } + } + + Result Reply(svc::Handle reply_target) { + /* Perform the reply. */ + s32 dummy; + R_TRY_CATCH(svc::ReplyAndReceive(std::addressof(dummy), nullptr, 0, reply_target, 0)) { + R_CATCH(svc::ResultTimedOut) { + /* Timing out is acceptable. */ + return R_CURRENT_RESULT; + } + R_CATCH(svc::ResultSessionClosed) { + /* It's okay if we couldn't reply to a closed session. */ + return R_CURRENT_RESULT; + } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + return ResultSuccess(); + } + + Result ProcessRequest(WaitableObject &object) { + /* Get the method id. */ + const auto method_id = svc::ipc::MessageBuffer::MessageHeader(svc::ipc::MessageBuffer(svc::ipc::GetMessageBuffer())).GetTag(); + + /* Check that the method id is valid. */ + R_UNLESS(method_id != MethodId_Invalid, tipc::ResultInvalidMethod()); + + /* If we're closing the object, do so. */ + if (method_id == MethodId_CloseSession) { + const auto handle = object.GetHandle(); + + /* Close the object itself. */ + this->CloseObject(handle); + + /* Close the object's handle. */ + /* NOTE: Nintendo does not check that this succeeds. */ + R_ABORT_UNLESS(svc::CloseHandle(handle)); + + /* Return result to signify we closed the object. */ + return tipc::ResultSessionClosed(); + } + + /* Process the generic method for the object. */ + R_TRY(object.GetObject()->ProcessRequest()); + + return ResultSuccess(); + } + }; + + template + class ObjectManager : public ObjectManagerBase { + private: + Entry m_entries_storage[MaxObjects]{}; + public: + constexpr ObjectManager() = default; + + void Initialize(os::WaitableManagerType *manager) { + this->Initialize(manager, m_entries_storage, MaxObjects); + } + }; + +} + diff --git a/libraries/libstratosphere/include/stratosphere/tipc/tipc_server_manager.hpp b/libraries/libstratosphere/include/stratosphere/tipc/tipc_server_manager.hpp new file mode 100644 index 000000000..6454ab129 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/tipc/tipc_server_manager.hpp @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2018-2020 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 +#include +#include + +namespace ams::tipc { + + template typename _Allocator> + struct PortMeta { + static constexpr inline size_t MaxSessions = NumSessions; + + using ServiceObject = tipc::ServiceObject; + + using Allocator = _Allocator; + }; + + struct DummyDeferralManager{ + struct Key{}; + }; + + class PortManagerInterface { + public: + virtual Result ProcessRequest(WaitableObject &object) = 0; + }; + + template + class ServerManagerImpl { + private: + static_assert(util::IsAligned(ThreadStackSize, os::ThreadStackAlignment)); + + static constexpr inline bool IsDeferralSupported = !std::same_as; + using ResumeKey = typename DeferralManagerType::Key; + + static ALWAYS_INLINE uintptr_t ConvertKeyToMessage(ResumeKey key) { + static_assert(sizeof(key) <= sizeof(uintptr_t)); + static_assert(std::is_trivial::value); + + /* TODO: std::bit_cast */ + uintptr_t converted = 0; + std::memcpy(std::addressof(converted), std::addressof(key), sizeof(key)); + return converted; + } + + static ALWAYS_INLINE ResumeKey ConvertMessageToKey(uintptr_t message) { + static_assert(sizeof(ResumeKey) <= sizeof(uintptr_t)); + static_assert(std::is_trivial::value); + + /* TODO: std::bit_cast */ + ResumeKey converted = {}; + std::memcpy(std::addressof(converted), std::addressof(message), sizeof(converted)); + return converted; + } + + static constexpr inline size_t NumPorts = sizeof...(PortInfos); + static constexpr inline size_t MaxSessions = (PortInfos::MaxSessions + ...); + + /* Verify that it's possible to service this many sessions, with our port manager count. */ + static_assert(MaxSessions <= NumPorts * svc::ArgumentHandleCountMax); + + template requires (Ix < NumPorts) + static constexpr inline size_t SessionsPerPortManager = (Ix == NumPorts - 1) ? ((MaxSessions / NumPorts) + MaxSessions % NumPorts) + : ((MaxSessions / NumPorts)); + + template requires (Ix < NumPorts) + using PortInfo = typename std::tuple_element>::type; + public: + class PortManagerBase : public PortManagerInterface { + public: + enum MessageType { + MessageType_AddSession = 0, + MessageType_TriggerResume = 1, + }; + protected: + s32 m_id; + std::atomic m_num_sessions; + s32 m_port_number; + os::WaitableManagerType m_waitable_manager; + DeferralManagerType m_deferral_manager; + os::MessageQueueType m_message_queue; + os::WaitableHolderType m_message_queue_holder; + uintptr_t m_message_queue_storage[MaxSessions]; + ObjectManagerBase *m_object_manager; + public: + PortManagerBase() : m_id(), m_num_sessions(), m_port_number(), m_waitable_manager(), m_deferral_manager(), m_message_queue(), m_message_queue_holder(), m_message_queue_storage(), m_object_manager() { + /* Setup our message queue. */ + os::InitializeMessageQueue(std::addressof(m_message_queue), m_message_queue_storage, util::size(m_message_queue_storage)); + os::InitializeWaitableHolder(std::addressof(m_message_queue_holder), std::addressof(m_message_queue), os::MessageQueueWaitType::ForNotEmpty); + } + + void InitializeBase(s32 id, ObjectManagerBase *manager) { + /* Set our id. */ + m_id = id; + + /* Reset our session count. */ + m_num_sessions = 0; + + /* Initialize our waitable manager. */ + os::InitializeWaitableManager(std::addressof(m_waitable_manager)); + os::LinkWaitableHolder(std::addressof(m_waitable_manager), std::addressof(m_message_queue_holder)); + + /* Initialize our object manager. */ + m_object_manager = manager; + } + + void RegisterPort(s32 index, svc::Handle port_handle) { + /* Set our port number. */ + this->m_port_number = index; + + /* Create a waitable object for the port. */ + tipc::WaitableObject object; + + /* Setup the object. */ + object.InitializeAsPort(port_handle); + + /* Register the object. */ + m_object_manager->AddObject(object); + } + + virtual Result ProcessRequest(WaitableObject &object) override { + /* Process the request, this must succeed because we succeeded when deferring earlier. */ + R_ABORT_UNLESS(m_object_manager->ProcessRequest(object)); + + /* NOTE: We support nested deferral, where Nintendo does not. */ + if constexpr (IsDeferralSupported) { + R_UNLESS(!PortManagerBase::IsRequestDeferred(), tipc::ResultRequestDeferred()); + } + + /* Reply to the request. */ + return m_object_manager->Reply(object.GetHandle()); + } + + Result ReplyAndReceive(os::WaitableHolderType **out_holder, WaitableObject *out_object, svc::Handle reply_target) { + return m_object_manager->ReplyAndReceive(out_holder, out_object, reply_target, std::addressof(m_waitable_manager)); + } + + void StartRegisterRetry(ResumeKey key) { + /* Begin the retry. */ + m_deferral_manager.StartRegisterRetry(key); + } + + bool TestResume(ResumeKey key) { + /* Check to see if the key corresponds to some deferred message. */ + return m_deferral_manager.TestResume(key); + } + + void TriggerResume(ResumeKey key) { + /* Send the key as a message. */ + os::SendMessageQueue(std::addressof(m_message_queue), static_cast(MessageType_TriggerResume)); + os::SendMessageQueue(std::addressof(m_message_queue), ConvertKeyToMessage(key)); + } + private: + static bool IsRequestDeferred() { + if constexpr (IsDeferralSupported) { + /* Get the message buffer. */ + const svc::ipc::MessageBuffer message_buffer(svc::ipc::GetMessageBuffer()); + + /* Parse the hipc headers. */ + const svc::ipc::MessageBuffer::MessageHeader message_header(message_buffer); + const svc::ipc::MessageBuffer::SpecialHeader special_header(message_buffer, message_header); + + /* Determine raw data index and extents. */ + const auto raw_data_offset = message_buffer.GetRawDataIndex(message_header, special_header); + const auto raw_data_count = message_header.GetRawCount(); + + /* Result is the last raw data word. */ + const Result method_result = message_buffer.GetRaw(raw_data_offset + raw_data_count - 1); + + /* Check that the result is the special deferral result. */ + return tipc::ResultRequestDeferred::Includes(method_result); + } else { + /* If deferral isn't supported, requests are never deferred. */ + return false; + } + } + }; + + template + class PortManagerImpl final : public PortManagerBase { + private: + tipc::ObjectManager m_object_manager_impl; + public: + PortManagerImpl() : PortManagerBase(), m_object_manager_impl() { + /* ... */ + } + + void Initialize(s32 id) { + /* Initialize our base. */ + this->InitializeBase(id, std::addressof(m_object_manager_impl)); + + /* Initialize our object manager. */ + m_object_manager_impl->Initialize(std::addressof(this->m_waitable_manager)); + } + }; + + template + using PortManager = PortManagerImpl, SessionsPerPortManager>; + + using PortManagerTuple = decltype([](std::index_sequence) { + return std::tuple...>{}; + }(std::make_index_sequence(NumPorts))); + + using PortAllocatorTuple = std::tuple; + private: + os::SdkMutex m_mutex; + os::TlsSlot m_tls_slot; + PortManagerTuple m_port_managers; + PortAllocatorTuple m_port_allocators; + os::ThreadType m_port_threads[NumPorts - 1]; + alignas(os::ThreadStackAlignment) u8 m_port_stacks[ThreadStackSize * (NumPorts - 1)]; + private: + template + ALWAYS_INLINE auto &GetPortManager() { + return std::get(m_port_managers); + } + + template + ALWAYS_INLINE const auto &GetPortManager() const { + return std::get(m_port_managers); + } + + template + void LoopAutoForPort() { + R_ABORT_UNLESS(this->LoopProcess(this->GetPortManager())); + } + + template + static void LoopAutoForPortThreadFunction(void *_this) { + static_cast(_this)->LoopAutoForPort(); + } + + template + void InitializePortThread(s32 priority) { + /* Create the thread. */ + R_ABORT_UNLESS(os::CreateThread(m_port_threads + Ix, LoopAutoForPortThreadFunction, this, m_port_stacks + Ix, ThreadStackSize, priority)); + + /* Start the thread. */ + os::StartThread(m_port_threads + Ix); + } + public: + ServerManagerImpl() : m_mutex(), m_tls_slot(), m_port_managers(), m_port_allocators() { /* ... */ } + + os::TlsSlot GetTlsSlot() const { return m_tls_slot; } + + void Initialize() { + /* Initialize our tls slot. */ + if constexpr (IsDeferralSupported) { + R_ABORT_UNLESS(os::SdkAllocateTlsSlot(std::addressof(m_tls_slot), nullptr)); + } + + /* Initialize our port managers. */ + [this](std::index_sequence) ALWAYS_INLINE_LAMBDA { + (this->GetPortManager().Initialize(static_cast(Ix)), ...); + }(std::make_index_sequence(NumPorts)); + } + + template + void RegisterPort(svc::Handle port_handle) { + this->GetPortManager().RegisterPort(static_cast(Ix), port_handle); + } + + void LoopAuto() { + /* If we have additional threads, create and start them. */ + if constexpr (NumPorts > 1) { + const auto thread_priority = os::GetThreadPriority(os::GetCurrentThread()); + + [thread_priority, this](std::index_sequence) ALWAYS_INLINE_LAMBDA { + /* Create all threads. */ + (this->InitializePortThread(thread_priority), ...); + }(std::make_index_sequence(NumPorts - 1)); + } + + /* Process for the last port. */ + this->LoopAutoForPort(); + } + private: + Result LoopProcess(PortManagerBase &port_manager) { + /* Set our tls slot's value to be the port manager we're processing for. */ + if constexpr (IsDeferralSupported) { + os::SetTlsValue(this->GetTlsSlot(), reinterpret_cast(std::addressof(port_manager))); + } + + /* Clear the message buffer. */ + /* NOTE: Nintendo only clears the hipc header. */ + std::memset(svc::ipc::GetMessageBuffer(), 0, svc::ipc::MessageBufferSize); + + /* Process requests forever. */ + svc::Handle reply_target = svc::InvalidHandle; + while (true) { + /* TODO */ + } + } + }; + + template + using ServerManagerWithDeferral = ServerManagerImpl; + + template + using ServerManagerWithDeferralAndThreadStack = ServerManagerImpl; + + template + using ServerManager = ServerManagerImpl; + + template + using ServerManagerWithThreadStack = ServerManagerImpl; + +} + diff --git a/libraries/libstratosphere/include/stratosphere/tipc/tipc_waitable_object.hpp b/libraries/libstratosphere/include/stratosphere/tipc/tipc_waitable_object.hpp new file mode 100644 index 000000000..cddce48fa --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/tipc/tipc_waitable_object.hpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2018-2020 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 +#include + +namespace ams::tipc { + + class WaitableObject { + public: + enum ObjectType : u8 { + ObjectType_Invalid = 0, + ObjectType_Port = 1, + ObjectType_Session = 2, + }; + private: + svc::Handle m_handle; + ObjectType m_type; + bool m_managed; + tipc::ServiceObjectBase *m_object; + private: + void InitializeImpl(ObjectType type, svc::Handle handle, bool managed, tipc::ServiceObjectBase *object) { + /* Validate that the object isn't already constructed. */ + AMS_ASSERT(m_type == ObjectType_Invalid); + + /* Set all fields. */ + m_handle = handle; + m_type = type; + m_managed = managed; + m_object = object; + } + public: + constexpr inline WaitableObject() : m_handle(svc::InvalidHandle), m_type(ObjectType_Invalid), m_managed(false), m_object(nullptr) { /* ... */ } + + void InitializeAsPort(svc::Handle handle) { + /* NOTE: Nintendo sets ports as managed, but this will cause a nullptr-deref if one is ever closed. */ + /* This is theoretically a non-issue, as ports can't be closed, but we will set ours as unmanaged, */ + /* just in case. */ + this->InitializeImpl(ObjectType_Port, handle, false, nullptr); + } + + void InitializeAsSession(svc::Handle handle, bool managed, tipc::ServiceObjectBase *object) { + this->InitializeImpl(ObjectType_Session, handle, managed, object); + } + + void Destroy() { + /* Validate that the object is constructed. */ + AMS_ASSERT(m_type != ObjectType_Invalid); + + /* If we're managed, destroy the associated object. */ + if (m_managed) { + if (auto * const deleter = m_object->GetDeleter(); deleter != nullptr) { + deleter->DeleteServiceObject(m_object); + } + } + + /* Reset all fields. */ + m_handle = svc::InvalidHandle; + m_type = ObjectType_Invalid; + m_managed = false; + m_object = nullptr; + } + + constexpr svc::Handle GetHandle() const { + return m_handle; + } + + constexpr ObjectType GetType() const { + return m_type; + } + + constexpr tipc::ServiceObjectBase *GetObject() const { + return m_object; + } + }; + +} + diff --git a/libraries/libstratosphere/source/_test/test_tipc_serializer.cpp b/libraries/libstratosphere/source/_test/test_tipc_serializer.cpp index 7fe1dd36c..7675990d7 100644 --- a/libraries/libstratosphere/source/_test/test_tipc_serializer.cpp +++ b/libraries/libstratosphere/source/_test/test_tipc_serializer.cpp @@ -14,7 +14,6 @@ * along with this program. If not, see . */ #include -#include #define AMS_TEST_I_USER_INTERFACE_INTERFACE_INFO(C, H) \ AMS_TIPC_METHOD_INFO(C, H, 0, Result, RegisterClient, (const tipc::ClientProcessId &client_process_id), (client_process_id)) \