/* * 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 #include namespace ams::kern { constexpr ALWAYS_INLINE util::BitPack32 GetHandleBitPack(ams::svc::Handle handle) { return util::BitPack32{handle}; } class KProcess; class KThread; class KHandleTable { NON_COPYABLE(KHandleTable); NON_MOVEABLE(KHandleTable); public: static constexpr size_t MaxTableSize = 1024; private: using HandleRawValue = util::BitPack32::Field<0, BITSIZEOF(u32), u32>; using HandleEncoded = util::BitPack32::Field<0, BITSIZEOF(ams::svc::Handle), ams::svc::Handle>; using HandleIndex = util::BitPack32::Field<0, 15, u16>; using HandleLinearId = util::BitPack32::Field; using HandleReserved = util::BitPack32::Field; static constexpr u16 MinLinearId = 1; static constexpr u16 MaxLinearId = util::BitPack32{std::numeric_limits::max()}.Get(); static constexpr ALWAYS_INLINE ams::svc::Handle EncodeHandle(u16 index, u16 linear_id) { util::BitPack32 pack = {0}; pack.Set(index); pack.Set(linear_id); pack.Set(0); return pack.Get(); } union EntryInfo { struct { u16 linear_id; u16 type; } info; s32 next_free_index; constexpr ALWAYS_INLINE u16 GetLinearId() const { return info.linear_id; } constexpr ALWAYS_INLINE u16 GetType() const { return info.type; } constexpr ALWAYS_INLINE s32 GetNextFreeIndex() const { return next_free_index; } }; private: EntryInfo m_entry_infos[MaxTableSize]; KAutoObject *m_objects[MaxTableSize]; s32 m_free_head_index; u16 m_table_size; u16 m_max_count; u16 m_next_linear_id; u16 m_count; mutable KSpinLock m_lock; public: constexpr KHandleTable() : m_entry_infos(), m_objects(), m_free_head_index(-1), m_table_size(0), m_max_count(0), m_next_linear_id(MinLinearId), m_count(0), m_lock() { MESOSPHERE_ASSERT_THIS(); } constexpr NOINLINE Result Initialize(s32 size) { MESOSPHERE_ASSERT_THIS(); R_UNLESS(size <= static_cast(MaxTableSize), svc::ResultOutOfMemory()); /* Initialize all fields. */ m_max_count = 0; m_table_size = (size <= 0) ? MaxTableSize : size; m_next_linear_id = MinLinearId; m_count = 0; m_free_head_index = -1; /* Free all entries. */ for (s32 i = 0; i < static_cast(m_table_size); ++i) { m_objects[i] = nullptr; m_entry_infos[i].next_free_index = i - 1; m_free_head_index = i; } return ResultSuccess(); } constexpr ALWAYS_INLINE size_t GetTableSize() const { return m_table_size; } constexpr ALWAYS_INLINE size_t GetCount() const { return m_count; } constexpr ALWAYS_INLINE size_t GetMaxCount() const { return m_max_count; } NOINLINE Result Finalize(); NOINLINE bool Remove(ams::svc::Handle handle); template ALWAYS_INLINE KScopedAutoObject GetObjectWithoutPseudoHandle(ams::svc::Handle handle) const { /* Lock and look up in table. */ KScopedDisableDispatch dd; KScopedSpinLock lk(m_lock); if constexpr (std::is_same::value) { return this->GetObjectImpl(handle); } else { if (auto *obj = this->GetObjectImpl(handle); AMS_LIKELY(obj != nullptr)) { return obj->DynamicCast(); } else { return nullptr; } } } template ALWAYS_INLINE KScopedAutoObject GetObject(ams::svc::Handle handle) const { MESOSPHERE_ASSERT_THIS(); /* Handle pseudo-handles. */ if constexpr (std::derived_from) { if (handle == ams::svc::PseudoHandle::CurrentProcess) { auto * const cur_process = GetCurrentProcessPointer(); AMS_ASSUME(cur_process != nullptr); return cur_process; } } else if constexpr (std::derived_from) { if (handle == ams::svc::PseudoHandle::CurrentThread) { auto * const cur_thread = GetCurrentThreadPointer(); AMS_ASSUME(cur_thread != nullptr); return cur_thread; } } return this->template GetObjectWithoutPseudoHandle(handle); } KScopedAutoObject GetObjectForIpcWithoutPseudoHandle(ams::svc::Handle handle) const { /* Lock and look up in table. */ KScopedDisableDispatch dd; KScopedSpinLock lk(m_lock); KAutoObject *obj = this->GetObjectImpl(handle); if (AMS_LIKELY(obj != nullptr)) { if (AMS_UNLIKELY(obj->DynamicCast() != nullptr)) { return nullptr; } } return obj; } ALWAYS_INLINE KScopedAutoObject GetObjectForIpc(ams::svc::Handle handle, KThread *cur_thread) const { /* Handle pseudo-handles. */ AMS_ASSUME(cur_thread != nullptr); if (handle == ams::svc::PseudoHandle::CurrentProcess) { auto * const cur_process = static_cast(static_cast(cur_thread->GetOwnerProcess())); AMS_ASSUME(cur_process != nullptr); return cur_process; } if (handle == ams::svc::PseudoHandle::CurrentThread) { return static_cast(cur_thread); } return GetObjectForIpcWithoutPseudoHandle(handle); } ALWAYS_INLINE KScopedAutoObject GetObjectByIndex(ams::svc::Handle *out_handle, size_t index) const { MESOSPHERE_ASSERT_THIS(); KScopedDisableDispatch dd; KScopedSpinLock lk(m_lock); return this->GetObjectByIndexImpl(out_handle, index); } NOINLINE Result Reserve(ams::svc::Handle *out_handle); NOINLINE void Unreserve(ams::svc::Handle handle); template ALWAYS_INLINE Result Add(ams::svc::Handle *out_handle, T *obj) { static_assert(std::is_base_of::value); return this->Add(out_handle, obj, obj->GetTypeObj().GetClassToken()); } template ALWAYS_INLINE void Register(ams::svc::Handle handle, T *obj) { static_assert(std::is_base_of::value); return this->Register(handle, obj, obj->GetTypeObj().GetClassToken()); } template ALWAYS_INLINE bool GetMultipleObjects(T **out, const ams::svc::Handle *handles, size_t num_handles) const { /* Try to convert and open all the handles. */ size_t num_opened; { /* Lock the table. */ KScopedDisableDispatch dd; KScopedSpinLock lk(m_lock); for (num_opened = 0; num_opened < num_handles; num_opened++) { /* Get the current handle. */ const auto cur_handle = handles[num_opened]; /* Get the object for the current handle. */ KAutoObject *cur_object = this->GetObjectImpl(cur_handle); if (AMS_UNLIKELY(cur_object == nullptr)) { break; } /* Cast the current object to the desired type. */ T *cur_t = cur_object->DynamicCast(); if (AMS_UNLIKELY(cur_t == nullptr)) { break; } /* Open a reference to the current object. */ cur_t->Open(); out[num_opened] = cur_t; } } /* If we converted every object, succeed. */ if (AMS_LIKELY(num_opened == num_handles)) { return true; } /* If we didn't convert entry object, close the ones we opened. */ for (size_t i = 0; i < num_opened; i++) { out[i]->Close(); } return false; } private: NOINLINE Result Add(ams::svc::Handle *out_handle, KAutoObject *obj, u16 type); NOINLINE void Register(ams::svc::Handle handle, KAutoObject *obj, u16 type); constexpr ALWAYS_INLINE s32 AllocateEntry() { MESOSPHERE_ASSERT_THIS(); MESOSPHERE_ASSERT(m_count < m_table_size); const auto index = m_free_head_index; m_free_head_index = m_entry_infos[index].GetNextFreeIndex(); m_max_count = std::max(m_max_count, ++m_count); return index; } constexpr ALWAYS_INLINE void FreeEntry(s32 index) { MESOSPHERE_ASSERT_THIS(); MESOSPHERE_ASSERT(m_count > 0); m_objects[index] = nullptr; m_entry_infos[index].next_free_index = m_free_head_index; m_free_head_index = index; --m_count; } constexpr ALWAYS_INLINE u16 AllocateLinearId() { const u16 id = m_next_linear_id++; if (m_next_linear_id > MaxLinearId) { m_next_linear_id = MinLinearId; } return id; } constexpr ALWAYS_INLINE bool IsValidHandle(ams::svc::Handle handle) const { MESOSPHERE_ASSERT_THIS(); /* Unpack the handle. */ const auto handle_pack = GetHandleBitPack(handle); const auto raw_value = handle_pack.Get(); const auto index = handle_pack.Get(); const auto linear_id = handle_pack.Get(); const auto reserved = handle_pack.Get(); MESOSPHERE_ASSERT(reserved == 0); MESOSPHERE_UNUSED(reserved); /* Validate our indexing information. */ if (AMS_UNLIKELY(raw_value == 0)) { return false; } if (AMS_UNLIKELY(linear_id == 0)) { return false; } if (AMS_UNLIKELY(index >= m_table_size)) { return false; } /* Check that there's an object, and our serial id is correct. */ if (AMS_UNLIKELY(m_objects[index] == nullptr)) { return false; } if (AMS_UNLIKELY(m_entry_infos[index].GetLinearId() != linear_id)) { return false; } return true; } constexpr ALWAYS_INLINE KAutoObject *GetObjectImpl(ams::svc::Handle handle) const { MESOSPHERE_ASSERT_THIS(); /* Handles must not have reserved bits set. */ const auto handle_pack = GetHandleBitPack(handle); if (AMS_UNLIKELY(handle_pack.Get() != 0)) { return nullptr; } if (AMS_LIKELY(this->IsValidHandle(handle))) { return m_objects[handle_pack.Get()]; } else { return nullptr; } } constexpr ALWAYS_INLINE KAutoObject *GetObjectByIndexImpl(ams::svc::Handle *out_handle, size_t index) const { MESOSPHERE_ASSERT_THIS(); /* Index must be in bounds. */ if (AMS_UNLIKELY(index >= m_table_size)) { return nullptr; } /* Ensure entry has an object. */ if (KAutoObject *obj = m_objects[index]; obj != nullptr) { *out_handle = EncodeHandle(index, m_entry_infos[index].GetLinearId()); return obj; } else { return nullptr; } } }; }