diff --git a/libraries/libstratosphere/include/stratosphere/tipc/tipc_allocators.hpp b/libraries/libstratosphere/include/stratosphere/tipc/tipc_allocators.hpp index 48c34c93a..ea9ba03ba 100644 --- a/libraries/libstratosphere/include/stratosphere/tipc/tipc_allocators.hpp +++ b/libraries/libstratosphere/include/stratosphere/tipc/tipc_allocators.hpp @@ -44,12 +44,12 @@ namespace ams::tipc { util::TypedStorage storage; }; private: - os::SdkMutex m_mutex; Entry m_entries[N]; + os::SdkMutex m_mutex; public: - constexpr ALWAYS_INLINE SlabAllocator() : m_entries() { /* ... */ } + constexpr ALWAYS_INLINE SlabAllocator() : m_entries(), m_mutex() { /* ... */ } - ServiceObjectBase *Allocate() { + T *Allocate() { std::scoped_lock lk(m_mutex); for (size_t i = 0; i < N; ++i) { diff --git a/libraries/libstratosphere/include/stratosphere/tipc/tipc_deferral_manager.hpp b/libraries/libstratosphere/include/stratosphere/tipc/tipc_deferral_manager.hpp new file mode 100644 index 000000000..08c5faed6 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/tipc/tipc_deferral_manager.hpp @@ -0,0 +1,182 @@ +/* + * 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 +#include + +namespace ams::tipc { + + template + concept IsResumeKey = util::is_pod::value && (0 < sizeof(T) && sizeof(T) <= sizeof(uintptr_t)); + + template + static constexpr ALWAYS_INLINE uintptr_t ConvertToInternalResumeKey(ResumeKey key) { + if constexpr (std::same_as) { + return key; + } else if constexpr (sizeof(key) == sizeof(uintptr_t)) { + return std::bit_cast(key); + } else { + uintptr_t converted = 0; + std::memcpy(std::addressof(converted), std::addressof(key), sizeof(key)); + return converted; + } + } + + class DeferralManagerBase; + + namespace impl { + + class DeferrableBaseTag{}; + + } + + class DeferrableBase : public impl::DeferrableBaseTag { + private: + DeferralManagerBase *m_deferral_manager; + ObjectHolder m_object_holder; + uintptr_t m_resume_key; + u8 m_message_buffer[svc::ipc::MessageBufferSize]; + public: + ALWAYS_INLINE DeferrableBase() : m_deferral_manager(nullptr), m_object_holder(), m_resume_key() { /* ... */ } + + ~DeferrableBase(); + + ALWAYS_INLINE void SetDeferralManager(DeferralManagerBase *manager, os::NativeHandle reply_target, ServiceObjectBase *object) { + m_deferral_manager = manager; + m_object_holder.InitializeForDeferralManager(reply_target, object); + } + + ALWAYS_INLINE bool TestResume(uintptr_t key) const { + return m_resume_key == key; + } + + template + ALWAYS_INLINE void RegisterRetry(ResumeKey key) { + m_resume_key = ConvertToInternalResumeKey(key); + std::memcpy(m_message_buffer, svc::ipc::GetMessageBuffer(), sizeof(m_message_buffer)); + } + + template + ALWAYS_INLINE Result RegisterRetryIfDeferred(ResumeKey key, F f) { + const Result result = f(); + if (tipc::ResultRequestDeferred::Includes(result)) { + this->RegisterRetry(key); + } + return result; + } + + template + ALWAYS_INLINE void TriggerResume(PortManager *port_manager) { + /* Clear resume key. */ + m_resume_key = 0; + + /* Restore message buffer. */ + std::memcpy(svc::ipc::GetMessageBuffer(), m_message_buffer, sizeof(m_message_buffer)); + + /* Process the request. */ + return port_manager->ProcessDeferredRequest(m_object_holder); + } + }; + + template + concept IsDeferrable = std::derived_from; + + class DeferralManagerBase { + NON_COPYABLE(DeferralManagerBase); + NON_MOVEABLE(DeferralManagerBase); + private: + size_t m_object_count; + DeferrableBase *m_objects_base[0]; + public: + ALWAYS_INLINE DeferralManagerBase() : m_object_count(0) { /* ... */ } + + void AddObject(DeferrableBase &object, os::NativeHandle reply_target, ServiceObjectBase *service_object) { + /* Set ourselves as the manager for the object. */ + object.SetDeferralManager(this, reply_target, service_object); + + /* Add the object to our entries. */ + AMS_ASSERT(m_object_count < N); + m_objects_base[m_object_count++] = std::addressof(object); + } + + void RemoveObject(DeferrableBase *object) { + /* If the object is present, remove it. */ + for (size_t i = 0; i < m_object_count; ++i) { + if (m_objects_base[i] == object) { + std::swap(m_objects_base[i], m_objects_base[--m_object_count]); + break; + } + } + } + + ALWAYS_INLINE bool TestResume(uintptr_t resume_key) const { + /* Try to resume all entries. */ + for (size_t i = 0; i < m_object_count; ++i) { + if (m_objects_base[i]->TestResume(resume_key)) { + return true; + } + } + + return false; + } + + template + ALWAYS_INLINE void TriggerResume(PortManager *port_manager, uintptr_t resume_key) const { + /* Try to resume all entries. */ + for (size_t i = 0; i < m_object_count; ++i) { + if (m_objects_base[i]->TestResume(resume_key)) { + m_objects_base[i]->TriggerResume(port_manager); + } + } + } + protected: + static consteval size_t GetObjectPointersOffsetBase(); + }; + static_assert(std::is_standard_layout::value); + + inline DeferrableBase::~DeferrableBase() { + AMS_ASSUME(m_deferral_manager != nullptr); + m_deferral_manager->RemoveObject(this); + } + + template requires (N > 0) + class DeferralManager final : public DeferralManagerBase { + private: + DeferrableBase *m_objects[N]; + public: + DeferralManager(); + private: + static consteval size_t GetObjectPointersOffset(); + }; + + consteval size_t DeferralManagerBase::GetObjectPointersOffsetBase() { + return OFFSETOF(DeferralManagerBase, m_objects_base); + } + + template + consteval size_t DeferralManager::GetObjectPointersOffset() { + return OFFSETOF(DeferralManager, m_objects); + } + + template + inline DeferralManager::DeferralManager() : DeferralManagerBase() { + static_assert(GetObjectPointersOffset() == GetObjectPointersOffsetBase()); + static_assert(sizeof(DeferralManager) == sizeof(DeferralManagerBase) + N * sizeof(DeferrableBase *)); + } + +} + diff --git a/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_holder.hpp b/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_holder.hpp index 9e3ebdb9f..003376c6b 100644 --- a/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_holder.hpp +++ b/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_holder.hpp @@ -16,7 +16,7 @@ #pragma once #include #include -#include +#include namespace ams::tipc { @@ -26,6 +26,8 @@ namespace ams::tipc { ObjectType_Invalid = 0, ObjectType_Port = 1, ObjectType_Session = 2, + + ObjectType_Deferral = ObjectType_Invalid, }; private: os::NativeHandle m_handle; @@ -57,6 +59,10 @@ namespace ams::tipc { this->InitializeImpl(ObjectType_Session, handle, managed, object); } + void InitializeForDeferralManager(os::NativeHandle handle, tipc::ServiceObjectBase *object) { + this->InitializeImpl(ObjectType_Deferral, handle, false, object); + } + void Destroy() { /* Validate that the object is constructed. */ AMS_ASSERT(m_type != ObjectType_Invalid); diff --git a/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_manager.hpp b/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_manager.hpp index 4a4044f3a..4103cb051 100644 --- a/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_manager.hpp +++ b/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_manager.hpp @@ -125,7 +125,7 @@ namespace ams::tipc { } } - Result Reply(os::NativeHandle reply_target) { + void Reply(os::NativeHandle reply_target) { /* Perform the reply. */ s32 dummy; R_TRY_CATCH(svc::ReplyAndReceive(std::addressof(dummy), nullptr, 0, reply_target, 0)) { @@ -136,8 +136,6 @@ namespace ams::tipc { /* It's okay if we couldn't reply to a closed session. */ } } R_END_TRY_CATCH_WITH_ABORT_UNLESS; - - return ResultSuccess(); } Result ProcessRequest(ObjectHolder &object) { diff --git a/libraries/libstratosphere/include/stratosphere/tipc/tipc_server_manager.hpp b/libraries/libstratosphere/include/stratosphere/tipc/tipc_server_manager.hpp index 1e898c173..4ae593f94 100644 --- a/libraries/libstratosphere/include/stratosphere/tipc/tipc_server_manager.hpp +++ b/libraries/libstratosphere/include/stratosphere/tipc/tipc_server_manager.hpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace ams::tipc { @@ -25,69 +26,51 @@ namespace ams::tipc { struct PortMeta { static constexpr inline size_t MaxSessions = NumSessions; + static constexpr bool CanDeferInvokeRequest = IsDeferrable; + using ServiceObject = tipc::ServiceObject; using Allocator = _Allocator; }; - struct DummyDeferralManager{ - struct Key{}; - }; + struct DummyDeferralManagerBase{}; - class PortManagerInterface { - public: - virtual Result ProcessRequest(ObjectHolder &object) = 0; - }; + template + struct DummyDeferralManager : public DummyDeferralManagerBase {}; - template + template class ServerManagerImpl { private: static constexpr inline size_t NumPorts = sizeof...(PortInfos); static constexpr inline size_t MaxSessions = (PortInfos::MaxSessions + ...); + /* Verify that we have at least one port. */ + static_assert(NumPorts > 0); + /* Verify that it's possible to service this many sessions, with our port manager count. */ static_assert(MaxSessions <= NumPorts * svc::ArgumentHandleCountMax); static_assert(util::IsAligned(ThreadStackSize, os::ThreadStackAlignment)); alignas(os::ThreadStackAlignment) static constinit inline u8 s_port_stacks[ThreadStackSize * (NumPorts - 1)]; - static constexpr inline bool IsDeferralSupported = !std::same_as; - using ResumeKey = typename DeferralManagerType::Key; - - static constexpr ALWAYS_INLINE uintptr_t ConvertKeyToMessage(ResumeKey key) { - static_assert(sizeof(key) <= sizeof(uintptr_t)); - static_assert(std::is_trivial::value); - - if constexpr (sizeof(key) == sizeof(uintptr_t)) { - return std::bit_cast(key); - } else { - uintptr_t converted = 0; - std::memcpy(std::addressof(converted), std::addressof(key), sizeof(key)); - return converted; - } - } - - static constexpr ALWAYS_INLINE ResumeKey ConvertMessageToKey(uintptr_t message) { - static_assert(sizeof(ResumeKey) <= sizeof(uintptr_t)); - static_assert(std::is_trivial::value); - - if constexpr (sizeof(ResumeKey) == sizeof(uintptr_t)) { - return std::bit_cast(message); - } else { - ResumeKey converted = {}; - std::memcpy(std::addressof(converted), std::addressof(message), sizeof(converted)); - return converted; - } - } - 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; + + static constexpr inline bool IsDeferralSupported = (PortInfos::CanDeferInvokeRequest || ...); + + template + using DeferralManagerImplType = typename std::conditional, DummyDeferralManager>::type; + + using DeferralManagerBaseType = typename std::conditional::type; + + template requires (Ix < NumPorts) + static constexpr inline bool IsPortDeferrable = PortInfo::CanDeferInvokeRequest; public: - class PortManagerBase : public PortManagerInterface { + class PortManagerBase { public: enum MessageType : u8 { MessageType_AddSession = 0, @@ -98,14 +81,14 @@ namespace ams::tipc { std::atomic m_num_sessions; s32 m_port_number; os::MultiWaitType m_multi_wait; - DeferralManagerType m_deferral_manager; os::MessageQueueType m_message_queue; os::MultiWaitHolderType m_message_queue_holder; uintptr_t m_message_queue_storage[MaxSessions]; - ObjectManagerBase *m_object_manager; ServerManagerImpl *m_server_manager; + ObjectManagerBase *m_object_manager; + DeferralManagerBaseType *m_deferral_manager; public: - PortManagerBase() : m_id(), m_num_sessions(), m_port_number(), m_multi_wait(), m_deferral_manager(), m_message_queue(), m_message_queue_holder(), m_message_queue_storage(), m_object_manager(), m_server_manager() { + PortManagerBase() : m_id(), m_num_sessions(), m_port_number(), m_multi_wait(), m_message_queue(), m_message_queue_holder(), m_message_queue_storage(), m_server_manager(), m_object_manager(), m_deferral_manager() { /* Setup our message queue. */ os::InitializeMessageQueue(std::addressof(m_message_queue), m_message_queue_storage, util::size(m_message_queue_storage)); os::InitializeMultiWaitHolder(std::addressof(m_message_queue_holder), std::addressof(m_message_queue), os::MessageQueueWaitType::ForNotEmpty); @@ -119,11 +102,7 @@ namespace ams::tipc { return m_num_sessions; } - ObjectManagerBase *GetObjectManager() const { - return m_object_manager; - } - - void InitializeBase(s32 id, ServerManagerImpl *sm, ObjectManagerBase *manager) { + void InitializeBase(s32 id, ServerManagerImpl *sm, DeferralManagerBaseType *dm, ObjectManagerBase *om) { /* Set our id. */ m_id = id; @@ -138,7 +117,10 @@ namespace ams::tipc { os::LinkMultiWaitHolder(std::addressof(m_multi_wait), std::addressof(m_message_queue_holder)); /* Initialize our object manager. */ - m_object_manager = manager; + m_object_manager = om; + + /* Initialize our deferral manager. */ + m_deferral_manager = dm; } void RegisterPort(s32 index, os::NativeHandle port_handle) { @@ -155,21 +137,57 @@ namespace ams::tipc { m_object_manager->AddObject(object); } - virtual Result ProcessRequest(ObjectHolder &object) override { - /* Process the request, this must succeed because we succeeded when deferring earlier. */ - R_ABORT_UNLESS(m_object_manager->ProcessRequest(object)); + os::NativeHandle ProcessRequest(ObjectHolder &object) { + /* Acquire exclusive server manager access. */ + std::scoped_lock lk(m_server_manager->GetMutex()); - /* NOTE: We support nested deferral, where Nintendo does not. */ - if constexpr (IsDeferralSupported) { - R_UNLESS(!PortManagerBase::IsRequestDeferred(), tipc::ResultRequestDeferred()); + /* Process the request. */ + const Result result = m_object_manager->ProcessRequest(object); + if (R_SUCCEEDED(result)) { + /* We should reply only if the request isn't deferred. */ + return !IsRequestDeferred() ? object.GetHandle() : os::InvalidNativeHandle; + } else { + /* Processing failed, so note the session as closed (or close it). */ + this->CloseSessionIfNecessary(object, !tipc::ResultSessionClosed::Includes(result)); + + /* We shouldn't reply on failure. */ + return os::InvalidNativeHandle; } - - /* Reply to the request. */ - return m_object_manager->Reply(object.GetHandle()); } - Result ReplyAndReceive(os::MultiWaitHolderType **out_holder, ObjectHolder *out_object, os::NativeHandle reply_target) { - return m_object_manager->ReplyAndReceive(out_holder, out_object, reply_target, std::addressof(m_multi_wait)); + template::type> + void ProcessDeferredRequest(ObjectHolder &object) { + static_assert(Enable == IsDeferralSupported); + + if (const auto reply_target = this->ProcessRequest(object); reply_target != os::InvalidNativeHandle) { + m_object_manager->Reply(reply_target); + } + } + + bool ReplyAndReceive(os::MultiWaitHolderType **out_holder, ObjectHolder *out_object, os::NativeHandle reply_target) { + /* If we don't have a reply target, clear our message buffer. */ + if (reply_target == os::InvalidNativeHandle) { + svc::ipc::MessageBuffer(svc::ipc::GetMessageBuffer()).SetNull(); + } + + /* Try to reply/receive. */ + const Result result = m_object_manager->ReplyAndReceive(out_holder, out_object, reply_target, std::addressof(m_multi_wait)); + + /* Acquire exclusive access to the server manager. */ + std::scoped_lock lk(m_server_manager->GetMutex()); + + /* Handle the result. */ + R_TRY_CATCH(result) { + R_CATCH(os::ResultSessionClosedForReceive, os::ResultReceiveListBroken) { + /* Close the object. */ + this->CloseSession(*out_object); + + /* We don't have anything to process. */ + return false; + } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + return true; } void AddSession(os::NativeHandle session_handle, tipc::ServiceObjectBase *service_object) { @@ -198,7 +216,7 @@ namespace ams::tipc { const os::NativeHandle session_handle = static_cast(message_type >> BITSIZEOF(u32)); /* Allocate a service object for the port. */ - auto *service_object = m_server_manager->AllocateObject(static_cast(message_data)); + auto *service_object = m_server_manager->AllocateObject(static_cast(message_data), session_handle, *m_deferral_manager); /* Add the newly-created service object. */ this->AddSession(session_handle, service_object); @@ -206,12 +224,8 @@ namespace ams::tipc { break; case MessageType_TriggerResume: if constexpr (IsDeferralSupported) { - /* Acquire exclusive server manager access. */ - std::scoped_lock lk(m_server_manager->GetMutex()); - /* Perform the resume. */ - const auto resume_key = ConvertMessageToKey(message_data); - m_deferral_manager.Resume(resume_key, this); + this->OnTriggerResume(message_data); } break; AMS_UNREACHABLE_DEFAULT_CASE(); @@ -249,53 +263,28 @@ namespace ams::tipc { --m_num_sessions; } - Result StartRegisterRetry(ResumeKey key) { - if constexpr (IsDeferralSupported) { - /* Acquire exclusive server manager access. */ - std::scoped_lock lk(m_server_manager->GetMutex()); - - /* Begin the retry. */ - return m_deferral_manager.StartRegisterRetry(key); - } else { - return ResultSuccess(); - } - } - - void ProcessRegisterRetry(ObjectHolder &object) { - if constexpr (IsDeferralSupported) { - /* Acquire exclusive server manager access. */ - std::scoped_lock lk(m_server_manager->GetMutex()); - - /* Process the retry. */ - m_deferral_manager.ProcessRegisterRetry(object); - } - } - - bool TestResume(ResumeKey key) { + bool TestResume(uintptr_t key) { if constexpr (IsDeferralSupported) { /* Acquire exclusive server manager access. */ std::scoped_lock lk(m_server_manager->GetMutex()); /* Check to see if the key corresponds to some deferred message. */ - return m_deferral_manager.TestResume(key); + return m_deferral_manager->TestResume(key); } else { return false; } } - void TriggerResume(ResumeKey key) { + void TriggerResume(uintptr_t key) { /* Acquire exclusive server manager access. */ std::scoped_lock lk(m_server_manager->GetMutex()); /* 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)); + os::SendMessageQueue(std::addressof(m_message_queue), key); } void TriggerAddSession(os::NativeHandle session_handle, size_t port_index) { - /* Acquire exclusive server manager access. */ - std::scoped_lock lk(m_server_manager->GetMutex()); - /* Increment our session count. */ ++m_num_sessions; @@ -303,6 +292,14 @@ namespace ams::tipc { os::SendMessageQueue(std::addressof(m_message_queue), static_cast(MessageType_AddSession) | (static_cast(session_handle) << BITSIZEOF(u32))); os::SendMessageQueue(std::addressof(m_message_queue), static_cast(port_index)); } + private: + void OnTriggerResume(uintptr_t key) { + /* Acquire exclusive server manager access. */ + std::scoped_lock lk(m_server_manager->GetMutex()); + + /* Trigger the resume. */ + m_deferral_manager->TriggerResume(this, key); + } public: static bool IsRequestDeferred() { if constexpr (IsDeferralSupported) { @@ -331,15 +328,16 @@ namespace ams::tipc { template class PortManagerImpl final : public PortManagerBase { private: + DeferralManagerImplType m_deferral_manager_impl; tipc::ObjectManager<1 + PortSessions> m_object_manager_impl; public: - PortManagerImpl() : PortManagerBase(), m_object_manager_impl() { + PortManagerImpl() : PortManagerBase(), m_deferral_manager_impl(), m_object_manager_impl() { /* ... */ } void Initialize(s32 id, ServerManagerImpl *sm) { /* Initialize our base. */ - this->InitializeBase(id, sm, std::addressof(m_object_manager_impl)); + this->InitializeBase(id, sm, std::addressof(m_deferral_manager_impl), std::addressof(m_object_manager_impl)); /* Initialize our object manager. */ m_object_manager_impl.Initialize(std::addressof(this->m_multi_wait)); @@ -356,7 +354,6 @@ namespace ams::tipc { using PortAllocatorTuple = std::tuple; private: os::SdkRecursiveMutex m_mutex; - os::TlsSlot m_tls_slot; PortManagerTuple m_port_managers; PortAllocatorTuple m_port_allocators; os::ThreadType m_port_threads[NumPorts - 1]; @@ -390,18 +387,11 @@ namespace ams::tipc { 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; } + ServerManagerImpl() : m_mutex(), m_port_managers(), m_port_allocators() { /* ... */ } os::SdkRecursiveMutex &GetMutex() { return m_mutex; } 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), this), ...); @@ -438,14 +428,14 @@ namespace ams::tipc { this->LoopAutoForPort(); } - tipc::ServiceObjectBase *AllocateObject(size_t port_index) { + tipc::ServiceObjectBase *AllocateObject(size_t port_index, os::NativeHandle handle, DeferralManagerBaseType &deferral_manager) { /* Check that the port index is valid. */ AMS_ABORT_UNLESS(port_index < NumPorts); /* Try to allocate from each port, in turn. */ tipc::ServiceObjectBase *allocated = nullptr; - [this, port_index, &allocated](std::index_sequence) ALWAYS_INLINE_LAMBDA { - (this->TryAllocateObject(port_index, allocated), ...); + [this, port_index, handle, &deferral_manager, &allocated](std::index_sequence) ALWAYS_INLINE_LAMBDA { + (this->TryAllocateObject(port_index, handle, deferral_manager, allocated), ...); }(std::make_index_sequence()); /* Return the allocated object. */ @@ -453,13 +443,17 @@ namespace ams::tipc { return allocated; } - void TriggerResume(ResumeKey resume_key) { + template + void TriggerResume(const ResumeKey &resume_key) { /* Acquire exclusive access to ourselves. */ std::scoped_lock lk(m_mutex); + /* Convert to internal resume key. */ + const auto internal_resume_key = ConvertToInternalResumeKey(resume_key); + /* Check/trigger resume on each of our ports. */ - [this, resume_key](std::index_sequence) ALWAYS_INLINE_LAMBDA { - (this->TriggerResumeImpl(resume_key), ...); + [this, internal_resume_key](std::index_sequence) ALWAYS_INLINE_LAMBDA { + (this->TriggerResumeImpl(internal_resume_key), ...); }(std::make_index_sequence()); } @@ -485,50 +479,45 @@ namespace ams::tipc { } private: template requires (Ix < NumPorts) - void TryAllocateObject(size_t port_index, tipc::ServiceObjectBase *&allocated) { + void TryAllocateObject(size_t port_index, os::NativeHandle handle, DeferralManagerBaseType &deferral_manager, tipc::ServiceObjectBase *&allocated) { /* Check that the port index matches. */ if (port_index == Ix) { + /* Check that we haven't already allocated. */ + AMS_ABORT_UNLESS(allocated == nullptr); + /* Get the allocator. */ auto &allocator = std::get(m_port_allocators); /* Allocate the object. */ - AMS_ABORT_UNLESS(allocated == nullptr); - allocated = allocator.Allocate(); - AMS_ABORT_UNLESS(allocated != nullptr); + auto * const new_object = allocator.Allocate(); + AMS_ABORT_UNLESS(new_object != nullptr); /* If we should, set the object's deleter. */ if constexpr (IsServiceObjectDeleter::type>) { - allocated->SetDeleter(std::addressof(allocator)); + new_object->SetDeleter(std::addressof(allocator)); } + + /* If we should, set the object's deferral manager. */ + if constexpr (IsPortDeferrable) { + deferral_manager.AddObject(new_object->GetImpl(), handle, new_object); + } + + /* Set the allocated object. */ + allocated = new_object; } } 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. */ os::NativeHandle reply_target = os::InvalidNativeHandle; while (true) { - /* Reply to our pending request, and receive a new one. */ + /* Reply to our pending request, and wait to receive a new one. */ os::MultiWaitHolderType *signaled_holder = nullptr; tipc::ObjectHolder signaled_object{}; - R_TRY_CATCH(port_manager.ReplyAndReceive(std::addressof(signaled_holder), std::addressof(signaled_object), reply_target)) { - R_CATCH(os::ResultSessionClosedForReceive, os::ResultReceiveListBroken) { - /* Close the object and continue. */ - port_manager.CloseSession(signaled_object); - - /* We have nothing to reply to. */ - reply_target = os::InvalidNativeHandle; - continue; - } - } R_END_TRY_CATCH; + while (!port_manager.ReplyAndReceive(std::addressof(signaled_holder), std::addressof(signaled_object), reply_target)) { + reply_target = os::InvalidNativeHandle; + signaled_object = {}; + } if (signaled_holder == nullptr) { /* A session was signaled, accessible via signaled_object. */ @@ -548,31 +537,7 @@ namespace ams::tipc { case ObjectHolder::ObjectType_Session: { /* Process the request */ - const Result process_result = port_manager.GetObjectManager()->ProcessRequest(signaled_object); - if (R_SUCCEEDED(process_result)) { - if constexpr (IsDeferralSupported) { - /* Check if the request is deferred. */ - if (PortManagerBase::IsRequestDeferred()) { - /* Process the retry that we began. */ - port_manager.ProcessRegisterRetry(signaled_object); - - /* We have nothing to reply to. */ - reply_target = os::InvalidNativeHandle; - } else { - /* We're done processing, so we should reply. */ - reply_target = signaled_object.GetHandle(); - } - } else { - /* We're done processing, so we should reply. */ - reply_target = signaled_object.GetHandle(); - } - } else { - /* We failed to process, so note the session as closed (or close it). */ - port_manager.CloseSessionIfNecessary(signaled_object, !tipc::ResultSessionClosed::Includes(process_result)); - - /* We have nothing to reply to. */ - reply_target = os::InvalidNativeHandle; - } + reply_target = port_manager.ProcessRequest(signaled_object); } break; AMS_UNREACHABLE_DEFAULT_CASE(); @@ -625,7 +590,7 @@ namespace ams::tipc { } template - void TriggerResumeImpl(ResumeKey resume_key) { + void TriggerResumeImpl(uintptr_t resume_key) { /* Get the port manager. */ auto &port_manager = this->GetPortManager(); @@ -636,17 +601,11 @@ namespace ams::tipc { } }; - template - using ServerManagerWithDeferral = ServerManagerImpl; - - template - using ServerManagerWithDeferralAndThreadStack = ServerManagerImpl; - template - using ServerManager = ServerManagerImpl; + using ServerManager = ServerManagerImpl; template - using ServerManagerWithThreadStack = ServerManagerImpl; + using ServerManagerWithThreadStack = ServerManagerImpl; } diff --git a/stratosphere/sm/source/impl/sm_service_manager.cpp b/stratosphere/sm/source/impl/sm_service_manager.cpp index 099c23cc5..2ae192cac 100644 --- a/stratosphere/sm/source/impl/sm_service_manager.cpp +++ b/stratosphere/sm/source/impl/sm_service_manager.cpp @@ -604,11 +604,10 @@ namespace ams::sm::impl { bool has_service = false; R_TRY(impl::HasService(std::addressof(has_service), service)); - /* If we do, we can succeed immediately. */ - R_SUCCEED_IF(has_service); + /* If we don't, we want to wait until the service is registered. */ + R_UNLESS(has_service, tipc::ResultRequestDeferred()); - /* Otherwise, we want to wait until the service is registered. */ - return StartRegisterRetry(service); + return ResultSuccess(); } Result GetServiceHandle(os::NativeHandle *out, os::ProcessId process_id, ServiceName service) { @@ -628,9 +627,10 @@ namespace ams::sm::impl { /* Get service info. Check to see if we need to defer this until later. */ ServiceInfo *service_info = GetServiceInfo(service); MitmInfo *mitm_info = GetMitmInfo(service_info); - if (service_info == nullptr || ShouldDeferForInit(service) || HasFutureMitmDeclaration(service) || (mitm_info != nullptr && mitm_info->waiting_ack)) { - return StartRegisterRetry(service); - } + R_UNLESS(service_info != nullptr, tipc::ResultRequestDeferred()); + R_UNLESS(!ShouldDeferForInit(service), tipc::ResultRequestDeferred()); + R_UNLESS(!HasFutureMitmDeclaration(service), tipc::ResultRequestDeferred()); + R_UNLESS((mitm_info == nullptr || !mitm_info->waiting_ack), tipc::ResultRequestDeferred()); /* Get a handle from the service info. */ R_TRY_CATCH(GetServiceHandleImpl(out, service_info, process_id)) { @@ -715,11 +715,10 @@ namespace ams::sm::impl { bool has_mitm = false; R_TRY(impl::HasMitm(std::addressof(has_mitm), service)); - /* If we do, we can succeed immediately. */ - R_SUCCEED_IF(has_mitm); + /* If we don't, we want to wait until the mitm is installed. */ + R_UNLESS(has_mitm, tipc::ResultRequestDeferred()); - /* Otherwise, we want to wait until the service is registered. */ - return StartRegisterRetry(service); + return ResultSuccess(); } Result InstallMitm(os::NativeHandle *out, os::NativeHandle *out_query, os::ProcessId process_id, ServiceName service) { @@ -740,7 +739,7 @@ namespace ams::sm::impl { ServiceInfo *service_info = GetServiceInfo(service); /* If it doesn't exist, defer until it does. */ - R_UNLESS(service_info != nullptr, StartRegisterRetry(service)); + R_UNLESS(service_info != nullptr, tipc::ResultRequestDeferred()); /* Validate that the service isn't already being mitm'd. */ R_UNLESS(GetMitmInfo(service_info) == nullptr, sm::ResultAlreadyRegistered()); diff --git a/stratosphere/sm/source/sm_tipc_server.cpp b/stratosphere/sm/source/sm_tipc_server.cpp index 892fa91a3..12457cfe3 100644 --- a/stratosphere/sm/source/sm_tipc_server.cpp +++ b/stratosphere/sm/source/sm_tipc_server.cpp @@ -40,99 +40,12 @@ namespace ams::sm { static_assert(MaxSessionsTotal % NumTipcPorts == 0); - /* Define WaitList class. */ - class WaitList { - public: - using Key = sm::ServiceName; - private: - struct Entry { - sm::ServiceName service_name{sm::InvalidServiceName}; - tipc::ObjectHolder object{}; - u8 message_buffer[svc::ipc::MessageBufferSize]; - }; - private: - Entry m_entries[MaxSessionsTotal / NumTipcPorts]{}; - Entry *m_processing_entry{}; - public: - constexpr WaitList() = default; - public: - Result StartRegisterRetry(sm::ServiceName service_name) { - /* Check that we're not already processing a retry. */ - AMS_ABORT_UNLESS(m_processing_entry == nullptr); - - /* Find a free entry. */ - Entry *free_entry = nullptr; - for (auto &entry : m_entries) { - if (entry.service_name == InvalidServiceName) { - free_entry = std::addressof(entry); - break; - } - } - - /* Verify that we found a free entry. */ - R_UNLESS(free_entry != nullptr, sm::ResultOutOfProcesses()); - - /* Populate the entry. */ - free_entry->service_name = service_name; - std::memcpy(free_entry->message_buffer, svc::ipc::GetMessageBuffer(), util::size(free_entry->message_buffer)); - - /* Set the processing entry. */ - m_processing_entry = free_entry; - - /* Return the special request deferral result. */ - return tipc::ResultRequestDeferred(); - } - - void ProcessRegisterRetry(tipc::ObjectHolder &object) { - /* Verify that we have a processing entry. */ - AMS_ABORT_UNLESS(m_processing_entry != nullptr); - - /* Set the entry's object. */ - m_processing_entry->object = object; - - /* Clear our processing entry. */ - m_processing_entry = nullptr; - } - - bool TestResume(sm::ServiceName service_name) { - /* Check that we have a matching service name. */ - for (const auto &entry : m_entries) { - if (entry.service_name == service_name) { - return true; - } - } - - return false; - } - - template - void Resume(sm::ServiceName service_name, PortManagerType *port_manager) { - /* Resume (and clear) all matching entries. */ - for (auto &entry : m_entries) { - if (entry.service_name == service_name) { - /* Copy the saved message buffer. */ - std::memcpy(svc::ipc::GetMessageBuffer(), entry.message_buffer, svc::ipc::MessageBufferSize); - - /* Resume the request. */ - R_TRY_CATCH(port_manager->ProcessRequest(entry.object)) { - R_CATCH(tipc::ResultRequestDeferred) { - this->ProcessRegisterRetry(entry.object); - } - } R_END_TRY_CATCH_WITH_ABORT_UNLESS; - - /* Clear the entry's service name. */ - entry.service_name = sm::InvalidServiceName; - } - } - } - }; - /* Define port metadata. */ - using UserPortMeta = tipc::PortMeta; - using ManagerPortMeta = tipc::PortMeta; + using UserPortMeta = tipc::PortMeta; + using ManagerPortMeta = tipc::PortMeta; /* Define server manager global. */ - using ServerManager = tipc::ServerManagerWithDeferral; + using ServerManager = tipc::ServerManager; ServerManager g_server_manager; @@ -162,14 +75,6 @@ namespace ams::sm { g_server_manager.LoopAuto(); } - Result StartRegisterRetry(sm::ServiceName service_name) { - /* Get the port manager from where it's saved in TLS. */ - auto *port_manager = reinterpret_cast(os::GetTlsValue(g_server_manager.GetTlsSlot())); - - /* Register the retry. */ - return port_manager->StartRegisterRetry(service_name); - } - void TriggerResume(sm::ServiceName service_name) { /* Trigger a resumption. */ g_server_manager.TriggerResume(service_name); diff --git a/stratosphere/sm/source/sm_user_service.cpp b/stratosphere/sm/source/sm_user_service.cpp index 23d178097..cd1fe68bb 100644 --- a/stratosphere/sm/source/sm_user_service.cpp +++ b/stratosphere/sm/source/sm_user_service.cpp @@ -143,7 +143,7 @@ namespace ams::sm { const Result result = this->GetServiceHandle(std::addressof(out_handle), service_name); /* Serialize output. */ - if (R_SUCCEEDED(result) || !tipc::ResultRequestDeferred::Includes(result)) { + if (!tipc::ResultRequestDeferred::Includes(result)) { std::memcpy(out_message_buffer + 0x00, CmifResponseToGetServiceHandleAndRegisterService, sizeof(CmifResponseToGetServiceHandleAndRegisterService)); std::memcpy(out_message_buffer + 0x0C, std::addressof(out_handle), sizeof(out_handle)); std::memcpy(out_message_buffer + 0x18, std::addressof(result), sizeof(result)); @@ -155,12 +155,8 @@ namespace ams::sm { const Result result = this->UnregisterService(service_name); /* Serialize output. */ - if (R_SUCCEEDED(result) || !tipc::ResultRequestDeferred::Includes(result)) { - std::memcpy(out_message_buffer + 0x00, CmifResponseToUnregisterService, sizeof(CmifResponseToUnregisterService)); - std::memcpy(out_message_buffer + 0x18, std::addressof(result), sizeof(result)); - } else { - std::memcpy(out_message_buffer, CmifResponseToForceProcessorDeferral, sizeof(CmifResponseToForceProcessorDeferral)); - } + std::memcpy(out_message_buffer + 0x00, CmifResponseToUnregisterService, sizeof(CmifResponseToUnregisterService)); + std::memcpy(out_message_buffer + 0x18, std::addressof(result), sizeof(result)); } else { return tipc::ResultInvalidMethod(); } @@ -186,13 +182,9 @@ namespace ams::sm { const Result result = this->RegisterService(std::addressof(out_handle), service_name, max_sessions, is_light); /* Serialize output. */ - if (R_SUCCEEDED(result) || !tipc::ResultRequestDeferred::Includes(result)) { - std::memcpy(out_message_buffer + 0x00, CmifResponseToGetServiceHandleAndRegisterService, sizeof(CmifResponseToGetServiceHandleAndRegisterService)); - std::memcpy(out_message_buffer + 0x0C, std::addressof(out_handle), sizeof(out_handle)); - std::memcpy(out_message_buffer + 0x18, std::addressof(result), sizeof(result)); - } else { - std::memcpy(out_message_buffer, CmifResponseToForceProcessorDeferral, sizeof(CmifResponseToForceProcessorDeferral)); - } + std::memcpy(out_message_buffer + 0x00, CmifResponseToGetServiceHandleAndRegisterService, sizeof(CmifResponseToGetServiceHandleAndRegisterService)); + std::memcpy(out_message_buffer + 0x0C, std::addressof(out_handle), sizeof(out_handle)); + std::memcpy(out_message_buffer + 0x18, std::addressof(result), sizeof(result)); } else { return tipc::ResultInvalidMethod(); } diff --git a/stratosphere/sm/source/sm_user_service.hpp b/stratosphere/sm/source/sm_user_service.hpp index f43a7e31f..b73b9ebfe 100644 --- a/stratosphere/sm/source/sm_user_service.hpp +++ b/stratosphere/sm/source/sm_user_service.hpp @@ -20,13 +20,13 @@ namespace ams::sm { /* Service definition. */ - class UserService { + class UserService : public tipc::DeferrableBase { private: os::ProcessId m_process_id; bool m_initialized; public: - constexpr UserService() : m_process_id{os::InvalidProcessId}, m_initialized{false} { /* ... */ } - virtual ~UserService() { + UserService() : m_process_id{os::InvalidProcessId}, m_initialized{false} { /* ... */ } + ~UserService() { if (m_initialized) { impl::OnClientDisconnected(m_process_id); } @@ -41,7 +41,9 @@ namespace ams::sm { Result GetServiceHandle(tipc::OutMoveHandle out_h, ServiceName service) { R_UNLESS(m_initialized, sm::ResultInvalidClient()); - return impl::GetServiceHandle(out_h.GetPointer(), m_process_id, service); + return this->RegisterRetryIfDeferred(service, [&] ALWAYS_INLINE_LAMBDA () -> Result { + return impl::GetServiceHandle(out_h.GetPointer(), m_process_id, service); + }); } Result RegisterService(tipc::OutMoveHandle out_h, ServiceName service, u32 max_sessions, bool is_light) { @@ -64,7 +66,9 @@ namespace ams::sm { /* Atmosphere commands. */ Result AtmosphereInstallMitm(tipc::OutMoveHandle srv_h, tipc::OutMoveHandle qry_h, ServiceName service) { R_UNLESS(m_initialized, sm::ResultInvalidClient()); - return impl::InstallMitm(srv_h.GetPointer(), qry_h.GetPointer(), m_process_id, service); + return this->RegisterRetryIfDeferred(service, [&] ALWAYS_INLINE_LAMBDA () -> Result { + return impl::InstallMitm(srv_h.GetPointer(), qry_h.GetPointer(), m_process_id, service); + }); } Result AtmosphereUninstallMitm(ServiceName service) { @@ -84,7 +88,9 @@ namespace ams::sm { Result AtmosphereWaitMitm(ServiceName service) { R_UNLESS(m_initialized, sm::ResultInvalidClient()); - return impl::WaitMitm(service); + return this->RegisterRetryIfDeferred(service, [&] ALWAYS_INLINE_LAMBDA () -> Result { + return impl::WaitMitm(service); + }); } Result AtmosphereDeclareFutureMitm(ServiceName service) { @@ -104,13 +110,16 @@ namespace ams::sm { Result AtmosphereWaitService(ServiceName service) { R_UNLESS(m_initialized, sm::ResultInvalidClient()); - return impl::WaitService(service); + return this->RegisterRetryIfDeferred(service, [&] ALWAYS_INLINE_LAMBDA () -> Result { + return impl::WaitService(service); + }); } public: /* Backwards compatibility layer for cmif. */ Result ProcessDefaultServiceCommand(const svc::ipc::MessageBuffer &message_buffer); }; static_assert(sm::impl::IsIUserInterface); + static_assert(tipc::IsDeferrable); /* TODO: static assert that this is a tipc interface with default prototyping. */ } diff --git a/stratosphere/sm/source/sm_wait_list.hpp b/stratosphere/sm/source/sm_wait_list.hpp index 9bfbd80b5..5d2da578a 100644 --- a/stratosphere/sm/source/sm_wait_list.hpp +++ b/stratosphere/sm/source/sm_wait_list.hpp @@ -18,8 +18,6 @@ namespace ams::sm { - Result StartRegisterRetry(sm::ServiceName service_name); - void TriggerResume(sm::ServiceName service_name); }