/* * 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 *)); } }