From c1166fd274bc0c19d0291fdc25e1473df5a724b0 Mon Sep 17 00:00:00 2001 From: GPUCode Date: Tue, 5 Dec 2023 22:25:50 +0200 Subject: [PATCH] kernel: Migrate to KAutoObject --- src/common/CMakeLists.txt | 2 + src/common/alignment.h | 6 + src/common/intrusive_list.h | 631 +++++++ src/common/parent_of_member.h | 190 ++ src/core/CMakeLists.txt | 67 +- src/core/hle/applets/applet.cpp | 13 +- src/core/hle/applets/applet.h | 12 +- src/core/hle/applets/erreula.cpp | 6 +- src/core/hle/applets/erreula.h | 11 +- src/core/hle/applets/mii_selector.cpp | 9 +- src/core/hle/applets/mii_selector.h | 11 +- src/core/hle/applets/mint.cpp | 6 +- src/core/hle/applets/mint.h | 11 +- src/core/hle/applets/swkbd.cpp | 9 +- src/core/hle/applets/swkbd.h | 9 +- src/core/hle/ipc_helpers.h | 20 +- src/core/hle/kernel/address_arbiter.h | 100 -- src/core/hle/kernel/client_port.cpp | 52 - src/core/hle/kernel/client_port.h | 81 - src/core/hle/kernel/client_session.cpp | 57 - src/core/hle/kernel/client_session.h | 67 - src/core/hle/kernel/errors.h | 4 + src/core/hle/kernel/event.cpp | 59 - src/core/hle/kernel/event.h | 72 - src/core/hle/kernel/handle_table.cpp | 105 -- src/core/hle/kernel/handle_table.h | 132 -- src/core/hle/kernel/hle_ipc.cpp | 96 +- src/core/hle/kernel/hle_ipc.h | 53 +- src/core/hle/kernel/ipc.cpp | 39 +- src/core/hle/kernel/ipc.h | 4 +- src/core/hle/kernel/ipc_debugger/recorder.cpp | 81 +- src/core/hle/kernel/ipc_debugger/recorder.h | 23 +- ...ress_arbiter.cpp => k_address_arbiter.cpp} | 233 +-- src/core/hle/kernel/k_address_arbiter.h | 79 + src/core/hle/kernel/k_auto_object.h | 22 +- .../hle/kernel/k_auto_object_container.cpp | 31 + src/core/hle/kernel/k_auto_object_container.h | 37 + src/core/hle/kernel/k_client_port.cpp | 69 + src/core/hle/kernel/k_client_port.h | 52 + src/core/hle/kernel/k_client_session.cpp | 40 + src/core/hle/kernel/k_client_session.h | 49 + src/core/hle/kernel/k_code_set.h | 76 + src/core/hle/kernel/k_event.cpp | 78 + src/core/hle/kernel/k_event.h | 67 + src/core/hle/kernel/k_handle_table.cpp | 107 ++ .../hle/kernel/k_handle_table.cpp.autosave | 106 ++ src/core/hle/kernel/k_handle_table.h | 279 +++ src/core/hle/kernel/k_linked_list.h | 237 +++ src/core/hle/kernel/k_mutex.cpp | 151 ++ src/core/hle/kernel/k_mutex.h | 81 + src/core/hle/kernel/k_object_name.cpp | 103 ++ src/core/hle/kernel/k_object_name.h | 81 + src/core/hle/kernel/k_port.cpp | 25 + src/core/hle/kernel/k_port.h | 52 + ...esource_limit.cpp => k_resource_limit.cpp} | 44 +- .../{resource_limit.h => k_resource_limit.h} | 48 +- .../kernel/k_scoped_resource_reservation.h | 50 + src/core/hle/kernel/k_semaphore.cpp | 77 + src/core/hle/kernel/k_semaphore.h | 59 + src/core/hle/kernel/k_server_port.cpp | 63 + src/core/hle/kernel/k_server_port.h | 61 + src/core/hle/kernel/k_server_session.cpp | 145 ++ src/core/hle/kernel/k_server_session.h | 80 + src/core/hle/kernel/k_session.cpp | 68 + src/core/hle/kernel/k_session.h | 76 + src/core/hle/kernel/k_shared_memory.cpp | 238 +++ src/core/hle/kernel/k_shared_memory.h | 107 ++ .../hle/kernel/k_synchronization_object.cpp | 116 ++ .../k_synchronization_object.cpp.autosave | 116 ++ ...it_object.h => k_synchronization_object.h} | 36 +- .../k_synchronization_object.h.autosave | 74 + src/core/hle/kernel/k_timer.cpp | 119 ++ src/core/hle/kernel/k_timer.h | 132 ++ src/core/hle/kernel/kernel.cpp | 85 +- src/core/hle/kernel/kernel.h | 169 +- src/core/hle/kernel/mutex.cpp | 130 -- src/core/hle/kernel/mutex.h | 89 - src/core/hle/kernel/process.cpp | 113 +- src/core/hle/kernel/process.h | 138 +- src/core/hle/kernel/semaphore.cpp | 64 - src/core/hle/kernel/semaphore.h | 67 - src/core/hle/kernel/server_port.cpp | 68 - src/core/hle/kernel/server_port.h | 75 - src/core/hle/kernel/server_session.cpp | 159 -- src/core/hle/kernel/server_session.h | 117 -- src/core/hle/kernel/session.cpp | 23 - src/core/hle/kernel/session.h | 34 - src/core/hle/kernel/shared_memory.cpp | 219 --- src/core/hle/kernel/shared_memory.h | 132 -- src/core/hle/kernel/slab_helpers.h | 95 +- src/core/hle/kernel/svc.cpp | 1588 +++++++++-------- src/core/hle/kernel/thread.cpp | 588 +++--- src/core/hle/kernel/thread.h | 226 +-- src/core/hle/kernel/timer.cpp | 119 -- src/core/hle/kernel/timer.h | 137 -- src/core/hle/kernel/wait_object.cpp | 114 -- src/core/hle/service/am/am.h | 2 +- src/core/hle/service/apt/applet_manager.cpp | 11 +- src/core/hle/service/apt/applet_manager.h | 18 +- src/core/hle/service/apt/apt.h | 8 +- src/core/hle/service/apt/ns.cpp | 6 +- src/core/hle/service/apt/ns.h | 13 +- src/core/hle/service/gsp/gsp_gpu.cpp | 10 +- src/core/hle/service/gsp/gsp_gpu.h | 17 +- src/core/hle/service/kernel_helpers.cpp | 41 + src/core/hle/service/kernel_helpers.h | 63 + src/core/hle/service/service.h | 1 - src/core/hle/service/sm/sm.cpp | 67 +- src/core/hle/service/sm/sm.h | 34 +- src/core/hle/service/sm/srv.cpp | 84 +- src/core/hle/service/sm/srv.h | 8 +- 111 files changed, 6391 insertions(+), 4343 deletions(-) create mode 100644 src/common/intrusive_list.h create mode 100644 src/common/parent_of_member.h delete mode 100644 src/core/hle/kernel/address_arbiter.h delete mode 100644 src/core/hle/kernel/client_port.cpp delete mode 100644 src/core/hle/kernel/client_port.h delete mode 100644 src/core/hle/kernel/client_session.cpp delete mode 100644 src/core/hle/kernel/client_session.h delete mode 100644 src/core/hle/kernel/event.cpp delete mode 100644 src/core/hle/kernel/event.h delete mode 100644 src/core/hle/kernel/handle_table.cpp delete mode 100644 src/core/hle/kernel/handle_table.h rename src/core/hle/kernel/{address_arbiter.cpp => k_address_arbiter.cpp} (51%) create mode 100644 src/core/hle/kernel/k_address_arbiter.h create mode 100644 src/core/hle/kernel/k_auto_object_container.cpp create mode 100644 src/core/hle/kernel/k_auto_object_container.h create mode 100644 src/core/hle/kernel/k_client_port.cpp create mode 100644 src/core/hle/kernel/k_client_port.h create mode 100644 src/core/hle/kernel/k_client_session.cpp create mode 100644 src/core/hle/kernel/k_client_session.h create mode 100644 src/core/hle/kernel/k_code_set.h create mode 100644 src/core/hle/kernel/k_event.cpp create mode 100644 src/core/hle/kernel/k_event.h create mode 100644 src/core/hle/kernel/k_handle_table.cpp create mode 100644 src/core/hle/kernel/k_handle_table.cpp.autosave create mode 100644 src/core/hle/kernel/k_handle_table.h create mode 100644 src/core/hle/kernel/k_linked_list.h create mode 100644 src/core/hle/kernel/k_mutex.cpp create mode 100644 src/core/hle/kernel/k_mutex.h create mode 100644 src/core/hle/kernel/k_object_name.cpp create mode 100644 src/core/hle/kernel/k_object_name.h create mode 100644 src/core/hle/kernel/k_port.cpp create mode 100644 src/core/hle/kernel/k_port.h rename src/core/hle/kernel/{resource_limit.cpp => k_resource_limit.cpp} (82%) rename src/core/hle/kernel/{resource_limit.h => k_resource_limit.h} (61%) create mode 100644 src/core/hle/kernel/k_scoped_resource_reservation.h create mode 100644 src/core/hle/kernel/k_semaphore.cpp create mode 100644 src/core/hle/kernel/k_semaphore.h create mode 100644 src/core/hle/kernel/k_server_port.cpp create mode 100644 src/core/hle/kernel/k_server_port.h create mode 100644 src/core/hle/kernel/k_server_session.cpp create mode 100644 src/core/hle/kernel/k_server_session.h create mode 100644 src/core/hle/kernel/k_session.cpp create mode 100644 src/core/hle/kernel/k_session.h create mode 100644 src/core/hle/kernel/k_shared_memory.cpp create mode 100644 src/core/hle/kernel/k_shared_memory.h create mode 100644 src/core/hle/kernel/k_synchronization_object.cpp create mode 100644 src/core/hle/kernel/k_synchronization_object.cpp.autosave rename src/core/hle/kernel/{wait_object.h => k_synchronization_object.h} (65%) create mode 100644 src/core/hle/kernel/k_synchronization_object.h.autosave create mode 100644 src/core/hle/kernel/k_timer.cpp create mode 100644 src/core/hle/kernel/k_timer.h delete mode 100644 src/core/hle/kernel/mutex.cpp delete mode 100644 src/core/hle/kernel/mutex.h delete mode 100644 src/core/hle/kernel/semaphore.cpp delete mode 100644 src/core/hle/kernel/semaphore.h delete mode 100644 src/core/hle/kernel/server_port.cpp delete mode 100644 src/core/hle/kernel/server_port.h delete mode 100644 src/core/hle/kernel/server_session.cpp delete mode 100644 src/core/hle/kernel/server_session.h delete mode 100644 src/core/hle/kernel/session.cpp delete mode 100644 src/core/hle/kernel/session.h delete mode 100644 src/core/hle/kernel/shared_memory.cpp delete mode 100644 src/core/hle/kernel/shared_memory.h delete mode 100644 src/core/hle/kernel/timer.cpp delete mode 100644 src/core/hle/kernel/timer.h delete mode 100644 src/core/hle/kernel/wait_object.cpp create mode 100644 src/core/hle/service/kernel_helpers.cpp create mode 100644 src/core/hle/service/kernel_helpers.h diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 6b97b6837..d98d76479 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -90,6 +90,7 @@ add_library(citra_common STATIC file_util.cpp file_util.h hash.h + intrusive_list.h linear_disk_cache.h literals.h logging/backend.cpp @@ -113,6 +114,7 @@ add_library(citra_common STATIC misc.cpp param_package.cpp param_package.h + parent_of_member.h polyfill_thread.h precompiled_headers.h quaternion.h diff --git a/src/common/alignment.h b/src/common/alignment.h index 15f981d73..c2b9b98c0 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h @@ -21,4 +21,10 @@ template return static_cast(value - value % size); } +template + requires std::is_unsigned_v +[[nodiscard]] constexpr bool Is4KBAligned(T value) { + return (value & 0xFFF) == 0; +} + } // namespace Common diff --git a/src/common/intrusive_list.h b/src/common/intrusive_list.h new file mode 100644 index 000000000..edc9a9853 --- /dev/null +++ b/src/common/intrusive_list.h @@ -0,0 +1,631 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_funcs.h" +#include "common/parent_of_member.h" + +namespace Common { + +// Forward declare implementation class for Node. +namespace impl { + +class IntrusiveListImpl; + +} + +class IntrusiveListNode { + CITRA_NON_COPYABLE(IntrusiveListNode); + +private: + friend class impl::IntrusiveListImpl; + + IntrusiveListNode* m_prev; + IntrusiveListNode* m_next; + +public: + constexpr IntrusiveListNode() : m_prev(this), m_next(this) {} + + constexpr bool IsLinked() const { + return m_next != this; + } + +private: + constexpr void LinkPrev(IntrusiveListNode* node) { + // We can't link an already linked node. + ASSERT(!node->IsLinked()); + this->SplicePrev(node, node); + } + + constexpr void SplicePrev(IntrusiveListNode* first, IntrusiveListNode* last) { + // Splice a range into the list. + auto last_prev = last->m_prev; + first->m_prev = m_prev; + last_prev->m_next = this; + m_prev->m_next = first; + m_prev = last_prev; + } + + constexpr void LinkNext(IntrusiveListNode* node) { + // We can't link an already linked node. + ASSERT(!node->IsLinked()); + return this->SpliceNext(node, node); + } + + constexpr void SpliceNext(IntrusiveListNode* first, IntrusiveListNode* last) { + // Splice a range into the list. + auto last_prev = last->m_prev; + first->m_prev = this; + last_prev->m_next = m_next; + m_next->m_prev = last_prev; + m_next = first; + } + + constexpr void Unlink() { + this->Unlink(m_next); + } + + constexpr void Unlink(IntrusiveListNode* last) { + // Unlink a node from a next node. + auto last_prev = last->m_prev; + m_prev->m_next = last; + last->m_prev = m_prev; + last_prev->m_next = this; + m_prev = last_prev; + } + + constexpr IntrusiveListNode* GetPrev() { + return m_prev; + } + + constexpr const IntrusiveListNode* GetPrev() const { + return m_prev; + } + + constexpr IntrusiveListNode* GetNext() { + return m_next; + } + + constexpr const IntrusiveListNode* GetNext() const { + return m_next; + } +}; +// DEPRECATED: static_assert(std::is_literal_type::value); + +namespace impl { + +class IntrusiveListImpl { + CITRA_NON_COPYABLE(IntrusiveListImpl); + +private: + IntrusiveListNode m_root_node; + +public: + template + class Iterator; + + using value_type = IntrusiveListNode; + using size_type = size_t; + using difference_type = ptrdiff_t; + using pointer = value_type*; + using const_pointer = const value_type*; + using reference = value_type&; + using const_reference = const value_type&; + using iterator = Iterator; + using const_iterator = Iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + template + class Iterator { + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename IntrusiveListImpl::value_type; + using difference_type = typename IntrusiveListImpl::difference_type; + using pointer = + std::conditional_t; + using reference = std::conditional_t; + + private: + pointer m_node; + + public: + constexpr explicit Iterator(pointer n) : m_node(n) {} + + constexpr bool operator==(const Iterator& rhs) const { + return m_node == rhs.m_node; + } + + constexpr pointer operator->() const { + return m_node; + } + + constexpr reference operator*() const { + return *m_node; + } + + constexpr Iterator& operator++() { + m_node = m_node->m_next; + return *this; + } + + constexpr Iterator& operator--() { + m_node = m_node->m_prev; + return *this; + } + + constexpr Iterator operator++(int) { + const Iterator it{*this}; + ++(*this); + return it; + } + + constexpr Iterator operator--(int) { + const Iterator it{*this}; + --(*this); + return it; + } + + constexpr operator Iterator() const { + return Iterator(m_node); + } + + constexpr Iterator GetNonConstIterator() const { + return Iterator(const_cast(m_node)); + } + }; + +public: + constexpr IntrusiveListImpl() : m_root_node() {} + + // Iterator accessors. + constexpr iterator begin() { + return iterator(m_root_node.GetNext()); + } + + constexpr const_iterator begin() const { + return const_iterator(m_root_node.GetNext()); + } + + constexpr iterator end() { + return iterator(std::addressof(m_root_node)); + } + + constexpr const_iterator end() const { + return const_iterator(std::addressof(m_root_node)); + } + + constexpr iterator iterator_to(reference v) { + // Only allow iterator_to for values in lists. + ASSERT(v.IsLinked()); + return iterator(std::addressof(v)); + } + + constexpr const_iterator iterator_to(const_reference v) const { + // Only allow iterator_to for values in lists. + ASSERT(v.IsLinked()); + return const_iterator(std::addressof(v)); + } + + // Content management. + constexpr bool empty() const { + return !m_root_node.IsLinked(); + } + + constexpr size_type size() const { + return static_cast(std::distance(this->begin(), this->end())); + } + + constexpr reference back() { + return *m_root_node.GetPrev(); + } + + constexpr const_reference back() const { + return *m_root_node.GetPrev(); + } + + constexpr reference front() { + return *m_root_node.GetNext(); + } + + constexpr const_reference front() const { + return *m_root_node.GetNext(); + } + + constexpr void push_back(reference node) { + m_root_node.LinkPrev(std::addressof(node)); + } + + constexpr void push_front(reference node) { + m_root_node.LinkNext(std::addressof(node)); + } + + constexpr void pop_back() { + m_root_node.GetPrev()->Unlink(); + } + + constexpr void pop_front() { + m_root_node.GetNext()->Unlink(); + } + + constexpr iterator insert(const_iterator pos, reference node) { + pos.GetNonConstIterator()->LinkPrev(std::addressof(node)); + return iterator(std::addressof(node)); + } + + constexpr void splice(const_iterator pos, IntrusiveListImpl& o) { + splice_impl(pos, o.begin(), o.end()); + } + + constexpr void splice(const_iterator pos, IntrusiveListImpl&, const_iterator first) { + const_iterator last(first); + std::advance(last, 1); + splice_impl(pos, first, last); + } + + constexpr void splice(const_iterator pos, IntrusiveListImpl&, const_iterator first, + const_iterator last) { + splice_impl(pos, first, last); + } + + constexpr iterator erase(const_iterator pos) { + if (pos == this->end()) { + return this->end(); + } + iterator it(pos.GetNonConstIterator()); + (it++)->Unlink(); + return it; + } + + constexpr void clear() { + while (!this->empty()) { + this->pop_front(); + } + } + +private: + constexpr void splice_impl(const_iterator _pos, const_iterator _first, const_iterator _last) { + if (_first == _last) { + return; + } + iterator pos(_pos.GetNonConstIterator()); + iterator first(_first.GetNonConstIterator()); + iterator last(_last.GetNonConstIterator()); + first->Unlink(std::addressof(*last)); + pos->SplicePrev(std::addressof(*first), std::addressof(*first)); + } +}; + +} // namespace impl + +template +class IntrusiveList { + CITRA_NON_COPYABLE(IntrusiveList); + +private: + impl::IntrusiveListImpl m_impl; + +public: + template + class Iterator; + + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + using pointer = value_type*; + using const_pointer = const value_type*; + using reference = value_type&; + using const_reference = const value_type&; + using iterator = Iterator; + using const_iterator = Iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + template + class Iterator { + public: + friend class Common::IntrusiveList; + + using ImplIterator = + std::conditional_t; + + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename IntrusiveList::value_type; + using difference_type = typename IntrusiveList::difference_type; + using pointer = + std::conditional_t; + using reference = + std::conditional_t; + + private: + ImplIterator m_iterator; + + private: + constexpr explicit Iterator(ImplIterator it) : m_iterator(it) {} + + constexpr ImplIterator GetImplIterator() const { + return m_iterator; + } + + public: + constexpr bool operator==(const Iterator& rhs) const { + return m_iterator == rhs.m_iterator; + } + + constexpr pointer operator->() const { + return std::addressof(Traits::GetParent(*m_iterator)); + } + + constexpr reference operator*() const { + return Traits::GetParent(*m_iterator); + } + + constexpr Iterator& operator++() { + ++m_iterator; + return *this; + } + + constexpr Iterator& operator--() { + --m_iterator; + return *this; + } + + constexpr Iterator operator++(int) { + const Iterator it{*this}; + ++m_iterator; + return it; + } + + constexpr Iterator operator--(int) { + const Iterator it{*this}; + --m_iterator; + return it; + } + + constexpr operator Iterator() const { + return Iterator(m_iterator); + } + }; + +private: + static constexpr IntrusiveListNode& GetNode(reference ref) { + return Traits::GetNode(ref); + } + + static constexpr IntrusiveListNode const& GetNode(const_reference ref) { + return Traits::GetNode(ref); + } + + static constexpr reference GetParent(IntrusiveListNode& node) { + return Traits::GetParent(node); + } + + static constexpr const_reference GetParent(IntrusiveListNode const& node) { + return Traits::GetParent(node); + } + +public: + constexpr IntrusiveList() : m_impl() {} + + // Iterator accessors. + constexpr iterator begin() { + return iterator(m_impl.begin()); + } + + constexpr const_iterator begin() const { + return const_iterator(m_impl.begin()); + } + + constexpr iterator end() { + return iterator(m_impl.end()); + } + + constexpr const_iterator end() const { + return const_iterator(m_impl.end()); + } + + constexpr const_iterator cbegin() const { + return this->begin(); + } + + constexpr const_iterator cend() const { + return this->end(); + } + + constexpr reverse_iterator rbegin() { + return reverse_iterator(this->end()); + } + + constexpr const_reverse_iterator rbegin() const { + return const_reverse_iterator(this->end()); + } + + constexpr reverse_iterator rend() { + return reverse_iterator(this->begin()); + } + + constexpr const_reverse_iterator rend() const { + return const_reverse_iterator(this->begin()); + } + + constexpr const_reverse_iterator crbegin() const { + return this->rbegin(); + } + + constexpr const_reverse_iterator crend() const { + return this->rend(); + } + + constexpr iterator iterator_to(reference v) { + return iterator(m_impl.iterator_to(GetNode(v))); + } + + constexpr const_iterator iterator_to(const_reference v) const { + return const_iterator(m_impl.iterator_to(GetNode(v))); + } + + // Content management. + constexpr bool empty() const { + return m_impl.empty(); + } + + constexpr size_type size() const { + return m_impl.size(); + } + + constexpr reference back() { + return GetParent(m_impl.back()); + } + + constexpr const_reference back() const { + return GetParent(m_impl.back()); + } + + constexpr reference front() { + return GetParent(m_impl.front()); + } + + constexpr const_reference front() const { + return GetParent(m_impl.front()); + } + + constexpr void push_back(reference ref) { + m_impl.push_back(GetNode(ref)); + } + + constexpr void push_front(reference ref) { + m_impl.push_front(GetNode(ref)); + } + + constexpr void pop_back() { + m_impl.pop_back(); + } + + constexpr void pop_front() { + m_impl.pop_front(); + } + + constexpr iterator insert(const_iterator pos, reference ref) { + return iterator(m_impl.insert(pos.GetImplIterator(), GetNode(ref))); + } + + constexpr void splice(const_iterator pos, IntrusiveList& o) { + m_impl.splice(pos.GetImplIterator(), o.m_impl); + } + + constexpr void splice(const_iterator pos, IntrusiveList& o, const_iterator first) { + m_impl.splice(pos.GetImplIterator(), o.m_impl, first.GetImplIterator()); + } + + constexpr void splice(const_iterator pos, IntrusiveList& o, const_iterator first, + const_iterator last) { + m_impl.splice(pos.GetImplIterator(), o.m_impl, first.GetImplIterator(), + last.GetImplIterator()); + } + + constexpr iterator erase(const_iterator pos) { + return iterator(m_impl.erase(pos.GetImplIterator())); + } + + constexpr void clear() { + m_impl.clear(); + } +}; + +template > +class IntrusiveListMemberTraits; + +template +class IntrusiveListMemberTraits { +public: + using ListType = IntrusiveList; + +private: + friend class IntrusiveList; + + static constexpr IntrusiveListNode& GetNode(Derived& parent) { + return parent.*Member; + } + + static constexpr IntrusiveListNode const& GetNode(Derived const& parent) { + return parent.*Member; + } + + static Derived& GetParent(IntrusiveListNode& node) { + return Common::GetParentReference(std::addressof(node)); + } + + static Derived const& GetParent(IntrusiveListNode const& node) { + return Common::GetParentReference(std::addressof(node)); + } +}; + +template > +class IntrusiveListMemberTraitsByNonConstexprOffsetOf; + +template +class IntrusiveListMemberTraitsByNonConstexprOffsetOf { +public: + using ListType = IntrusiveList; + +private: + friend class IntrusiveList; + + static constexpr IntrusiveListNode& GetNode(Derived& parent) { + return parent.*Member; + } + + static constexpr IntrusiveListNode const& GetNode(Derived const& parent) { + return parent.*Member; + } + + static Derived& GetParent(IntrusiveListNode& node) { + return *reinterpret_cast(reinterpret_cast(std::addressof(node)) - + GetOffset()); + } + + static Derived const& GetParent(IntrusiveListNode const& node) { + return *reinterpret_cast( + reinterpret_cast(std::addressof(node)) - GetOffset()); + } + + static uintptr_t GetOffset() { + return reinterpret_cast(std::addressof(reinterpret_cast(0)->*Member)); + } +}; + +template +class IntrusiveListBaseNode : public IntrusiveListNode {}; + +template +class IntrusiveListBaseTraits { +public: + using ListType = IntrusiveList; + +private: + friend class IntrusiveList; + + static constexpr IntrusiveListNode& GetNode(Derived& parent) { + return static_cast( + static_cast&>(parent)); + } + + static constexpr IntrusiveListNode const& GetNode(Derived const& parent) { + return static_cast( + static_cast&>(parent)); + } + + static constexpr Derived& GetParent(IntrusiveListNode& node) { + return static_cast(static_cast&>(node)); + } + + static constexpr Derived const& GetParent(IntrusiveListNode const& node) { + return static_cast( + static_cast&>(node)); + } +}; + +} // namespace Common diff --git a/src/common/parent_of_member.h b/src/common/parent_of_member.h new file mode 100644 index 000000000..8e03f17d8 --- /dev/null +++ b/src/common/parent_of_member.h @@ -0,0 +1,190 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/assert.h" + +namespace Common { +namespace detail { +template +struct TypedStorageImpl { + alignas(Align) u8 storage_[Size]; +}; +} // namespace detail + +template +using TypedStorage = detail::TypedStorageImpl; + +template +static constexpr T* GetPointer(TypedStorage& ts) { + return static_cast(static_cast(std::addressof(ts.storage_))); +} + +template +static constexpr const T* GetPointer(const TypedStorage& ts) { + return static_cast(static_cast(std::addressof(ts.storage_))); +} + +namespace impl { + +template +struct OffsetOfUnionHolder { + template + union UnionImpl { + using PaddingMember = char; + static constexpr size_t GetOffset() { + return Offset; + } + +#pragma pack(push, 1) + struct { + PaddingMember padding[Offset]; + MemberType members[(sizeof(ParentType) / sizeof(MemberType)) + 1]; + } data; +#pragma pack(pop) + UnionImpl next_union; + }; + + template + union UnionImpl { + static constexpr size_t GetOffset() { + return 0; + } + + struct { + MemberType members[(sizeof(ParentType) / sizeof(MemberType)) + 1]; + } data; + UnionImpl next_union; + }; + + template + union UnionImpl {}; +}; + +template +struct OffsetOfCalculator { + using UnionHolder = + typename OffsetOfUnionHolder::template UnionImpl; + union Union { + char c{}; + UnionHolder first_union; + TypedStorage parent; + + constexpr Union() : c() {} + }; + static constexpr Union U = {}; + + static constexpr const MemberType* GetNextAddress(const MemberType* start, + const MemberType* target) { + while (start < target) { + start++; + } + return start; + } + + static constexpr std::ptrdiff_t GetDifference(const MemberType* start, + const MemberType* target) { + return (target - start) * sizeof(MemberType); + } + + template + static constexpr std::ptrdiff_t OffsetOfImpl(MemberType ParentType::*member, + CurUnion& cur_union) { + constexpr size_t Offset = CurUnion::GetOffset(); + const auto target = std::addressof(GetPointer(U.parent)->*member); + const auto start = std::addressof(cur_union.data.members[0]); + const auto next = GetNextAddress(start, target); + + if (next != target) { + if constexpr (Offset < sizeof(MemberType) - 1) { + return OffsetOfImpl(member, cur_union.next_union); + } else { + UNREACHABLE(); + } + } + + return static_cast(static_cast(next - start) * sizeof(MemberType) + + Offset); + } + + static constexpr std::ptrdiff_t OffsetOf(MemberType ParentType::*member) { + return OffsetOfImpl(member, U.first_union); + } +}; + +template +struct GetMemberPointerTraits; + +template +struct GetMemberPointerTraits { + using Parent = P; + using Member = M; +}; + +template +using GetParentType = typename GetMemberPointerTraits::Parent; + +template +using GetMemberType = typename GetMemberPointerTraits::Member; + +template > +constexpr std::ptrdiff_t OffsetOf() { + using DeducedParentType = GetParentType; + using MemberType = GetMemberType; + static_assert(std::is_base_of::value || + std::is_same::value); + + return OffsetOfCalculator::OffsetOf(MemberPtr); +}; + +} // namespace impl + +template > +constexpr RealParentType& GetParentReference(impl::GetMemberType* member) { + std::ptrdiff_t Offset = impl::OffsetOf(); + return *static_cast( + static_cast(static_cast(static_cast(member)) - Offset)); +} + +template > +constexpr RealParentType const& GetParentReference(impl::GetMemberType const* member) { + std::ptrdiff_t Offset = impl::OffsetOf(); + return *static_cast(static_cast( + static_cast(static_cast(member)) - Offset)); +} + +template > +constexpr RealParentType* GetParentPointer(impl::GetMemberType* member) { + return std::addressof(GetParentReference(member)); +} + +template > +constexpr RealParentType const* GetParentPointer(impl::GetMemberType const* member) { + return std::addressof(GetParentReference(member)); +} + +template > +constexpr RealParentType& GetParentReference(impl::GetMemberType& member) { + return GetParentReference(std::addressof(member)); +} + +template > +constexpr RealParentType const& GetParentReference(impl::GetMemberType const& member) { + return GetParentReference(std::addressof(member)); +} + +template > +constexpr RealParentType* GetParentPointer(impl::GetMemberType& member) { + return std::addressof(GetParentReference(member)); +} + +template > +constexpr RealParentType const* GetParentPointer(impl::GetMemberType const& member) { + return std::addressof(GetParentReference(member)); +} + +} // namespace Common diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d228c222a..29ac44ab0 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -128,50 +128,63 @@ add_library(citra_core STATIC hle/applets/swkbd.h hle/ipc.h hle/ipc_helpers.h - hle/kernel/address_arbiter.cpp - hle/kernel/address_arbiter.h - hle/kernel/client_port.cpp - hle/kernel/client_port.h - hle/kernel/client_session.cpp - hle/kernel/client_session.h hle/kernel/config_mem.cpp hle/kernel/config_mem.h hle/kernel/errors.h - hle/kernel/event.cpp - hle/kernel/event.h - hle/kernel/handle_table.cpp - hle/kernel/handle_table.h hle/kernel/hle_ipc.cpp hle/kernel/hle_ipc.h hle/kernel/ipc.cpp hle/kernel/ipc.h hle/kernel/ipc_debugger/recorder.cpp hle/kernel/ipc_debugger/recorder.h + hle/kernel/k_address_arbiter.cpp + hle/kernel/k_address_arbiter.h hle/kernel/k_auto_object.cpp hle/kernel/k_auto_object.h + hle/kernel/k_auto_object_container.cpp + hle/kernel/k_auto_object_container.h + hle/kernel/k_client_port.cpp + hle/kernel/k_client_port.h + hle/kernel/k_client_session.cpp + hle/kernel/k_client_session.h + hle/kernel/k_code_set.h + hle/kernel/k_event.cpp + hle/kernel/k_event.h + hle/kernel/k_handle_table.cpp + hle/kernel/k_handle_table.h + hle/kernel/k_linked_list.h + hle/kernel/k_mutex.cpp + hle/kernel/k_mutex.h + hle/kernel/k_object_name.cpp + hle/kernel/k_object_name.h + hle/kernel/k_port.cpp + hle/kernel/k_port.h + hle/kernel/k_resource_limit.cpp + hle/kernel/k_resource_limit.h + hle/kernel/k_scoped_resource_reservation.h + hle/kernel/k_semaphore.cpp + hle/kernel/k_semaphore.h + hle/kernel/k_server_port.cpp + hle/kernel/k_server_port.h + hle/kernel/k_server_session.cpp + hle/kernel/k_server_session.h + hle/kernel/k_session.cpp + hle/kernel/k_session.h + hle/kernel/k_shared_memory.cpp + hle/kernel/k_shared_memory.h hle/kernel/k_slab_heap.h + hle/kernel/k_synchronization_object.cpp + hle/kernel/k_synchronization_object.h + hle/kernel/k_timer.cpp + hle/kernel/k_timer.h hle/kernel/kernel.cpp hle/kernel/kernel.h hle/kernel/memory.cpp hle/kernel/memory.h - hle/kernel/mutex.cpp - hle/kernel/mutex.h hle/kernel/object.cpp hle/kernel/object.h hle/kernel/process.cpp hle/kernel/process.h - hle/kernel/resource_limit.cpp - hle/kernel/resource_limit.h - hle/kernel/semaphore.cpp - hle/kernel/semaphore.h - hle/kernel/server_port.cpp - hle/kernel/server_port.h - hle/kernel/server_session.cpp - hle/kernel/server_session.h - hle/kernel/session.h - hle/kernel/session.cpp - hle/kernel/shared_memory.cpp - hle/kernel/shared_memory.h hle/kernel/shared_page.cpp hle/kernel/shared_page.h hle/kernel/slab_helpers.h @@ -180,12 +193,8 @@ add_library(citra_core STATIC hle/kernel/svc_wrapper.h hle/kernel/thread.cpp hle/kernel/thread.h - hle/kernel/timer.cpp - hle/kernel/timer.h hle/kernel/vm_manager.cpp hle/kernel/vm_manager.h - hle/kernel/wait_object.cpp - hle/kernel/wait_object.h hle/mii.h hle/mii.cpp hle/result.h @@ -323,6 +332,8 @@ add_library(citra_core STATIC hle/service/ir/ir_u.h hle/service/ir/ir_user.cpp hle/service/ir/ir_user.h + hle/service/kernel_helpers.cpp + hle/service/kernel_helpers.h hle/service/ldr_ro/cro_helper.cpp hle/service/ldr_ro/cro_helper.h hle/service/ldr_ro/ldr_ro.cpp diff --git a/src/core/hle/applets/applet.cpp b/src/core/hle/applets/applet.cpp index d8b03704d..be76f1e5a 100644 --- a/src/core/hle/applets/applet.cpp +++ b/src/core/hle/applets/applet.cpp @@ -26,24 +26,24 @@ static Core::TimingEventType* applet_update_event = nullptr; /// The interval at which the Applet update callback will be called, 16.6ms static const u64 applet_update_interval_us = 16666; -ResultCode Applet::Create(Service::APT::AppletId id, Service::APT::AppletId parent, bool preload, +ResultCode Applet::Create(Core::System& system, Service::APT::AppletId id, Service::APT::AppletId parent, bool preload, const std::shared_ptr& manager) { switch (id) { case Service::APT::AppletId::SoftwareKeyboard1: case Service::APT::AppletId::SoftwareKeyboard2: - applets[id] = std::make_shared(id, parent, preload, manager); + applets[id] = std::make_shared(system, id, parent, preload, manager); break; case Service::APT::AppletId::Ed1: case Service::APT::AppletId::Ed2: - applets[id] = std::make_shared(id, parent, preload, manager); + applets[id] = std::make_shared(system, id, parent, preload, manager); break; case Service::APT::AppletId::Error: case Service::APT::AppletId::Error2: - applets[id] = std::make_shared(id, parent, preload, manager); + applets[id] = std::make_shared(system, id, parent, preload, manager); break; case Service::APT::AppletId::Mint: case Service::APT::AppletId::Mint2: - applets[id] = std::make_shared(id, parent, preload, manager); + applets[id] = std::make_shared(system, id, parent, preload, manager); break; default: LOG_ERROR(Service_APT, "Could not create applet {}", id); @@ -64,7 +64,7 @@ ResultCode Applet::Create(Service::APT::AppletId id, Service::APT::AppletId pare } // Schedule the update event - Core::System::GetInstance().CoreTiming().ScheduleEvent( + system.CoreTiming().ScheduleEvent( usToCycles(applet_update_interval_us), applet_update_event, static_cast(id)); return RESULT_SUCCESS; } @@ -149,4 +149,5 @@ void Init() { void Shutdown() { Core::System::GetInstance().CoreTiming().RemoveEvent(applet_update_event); } + } // namespace HLE::Applets diff --git a/src/core/hle/applets/applet.h b/src/core/hle/applets/applet.h index 8160aeeb2..317a17fd2 100644 --- a/src/core/hle/applets/applet.h +++ b/src/core/hle/applets/applet.h @@ -8,6 +8,10 @@ #include "core/hle/result.h" #include "core/hle/service/apt/applet_manager.h" +namespace Core { +class System; +} + namespace HLE::Applets { class Applet { @@ -22,7 +26,7 @@ public: * @param preload Whether the applet is being preloaded. * @returns ResultCode Whether the operation was successful or not. */ - static ResultCode Create(Service::APT::AppletId id, Service::APT::AppletId parent, bool preload, + static ResultCode Create(Core::System& system, Service::APT::AppletId id, Service::APT::AppletId parent, bool preload, const std::shared_ptr& manager); /** @@ -55,9 +59,9 @@ public: virtual void Update() = 0; protected: - Applet(Service::APT::AppletId id, Service::APT::AppletId parent, bool preload, + Applet(Core::System& system_, Service::APT::AppletId id, Service::APT::AppletId parent, bool preload, std::weak_ptr manager) - : id(id), parent(parent), preload(preload), manager(std::move(manager)) {} + : system(system_), id(id), parent(parent), preload(preload), service_context(system, "Applet"), manager(std::move(manager)) {} /** * Handles a parameter from the application. @@ -79,10 +83,12 @@ protected: */ virtual ResultCode Finalize() = 0; + Core::System& system; Service::APT::AppletId id; ///< Id of this Applet Service::APT::AppletId parent; ///< Id of this Applet's parent bool preload; ///< Whether the Applet is being preloaded. std::shared_ptr> heap_memory; ///< Heap memory for this Applet + Service::KernelHelpers::ServiceContext service_context; /// Whether this applet is running. bool is_running = true; diff --git a/src/core/hle/applets/erreula.cpp b/src/core/hle/applets/erreula.cpp index 66ace0f8a..e3fb6df97 100644 --- a/src/core/hle/applets/erreula.cpp +++ b/src/core/hle/applets/erreula.cpp @@ -5,6 +5,7 @@ #include "common/string_util.h" #include "core/core.h" #include "core/hle/applets/erreula.h" +#include "core/hle/kernel/k_shared_memory.h" #include "core/hle/service/apt/apt.h" namespace HLE::Applets { @@ -28,9 +29,8 @@ ResultCode ErrEula::ReceiveParameterImpl(const Service::APT::MessageParameter& p // TODO: allocated memory never released using Kernel::MemoryPermission; // Create a SharedMemory that directly points to this heap block. - framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet( - 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, - "ErrEula Memory"); + framebuffer_memory = service_context.CreateSharedMemoryForApplet("ErrEula Memory", + 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite); // Send the response message with the newly created SharedMemory SendParameter({ diff --git a/src/core/hle/applets/erreula.h b/src/core/hle/applets/erreula.h index bdb169b13..e0b27d4d4 100644 --- a/src/core/hle/applets/erreula.h +++ b/src/core/hle/applets/erreula.h @@ -5,15 +5,18 @@ #pragma once #include "core/hle/applets/applet.h" -#include "core/hle/kernel/shared_memory.h" + +namespace Kernel { +class KSharedMemory; +} namespace HLE::Applets { class ErrEula final : public Applet { public: - explicit ErrEula(Service::APT::AppletId id, Service::APT::AppletId parent, bool preload, + explicit ErrEula(Core::System& system, Service::APT::AppletId id, Service::APT::AppletId parent, bool preload, std::weak_ptr manager) - : Applet(id, parent, preload, std::move(manager)) {} + : Applet(system, id, parent, preload, std::move(manager)) {} ResultCode ReceiveParameterImpl(const Service::APT::MessageParameter& parameter) override; ResultCode Start(const Service::APT::MessageParameter& parameter) override; @@ -24,7 +27,7 @@ private: /// This SharedMemory will be created when we receive the LibAppJustStarted message. /// It holds the framebuffer info retrieved by the application with /// GSPGPU::ImportDisplayCaptureInfo - std::shared_ptr framebuffer_memory; + Kernel::KSharedMemory* framebuffer_memory; /// Parameter received by the applet on start. std::vector startup_param; diff --git a/src/core/hle/applets/mii_selector.cpp b/src/core/hle/applets/mii_selector.cpp index 7be3e0b46..08779ab4a 100644 --- a/src/core/hle/applets/mii_selector.cpp +++ b/src/core/hle/applets/mii_selector.cpp @@ -12,7 +12,7 @@ #include "core/frontend/applets/mii_selector.h" #include "core/hle/applets/mii_selector.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/shared_memory.h" +#include "core/hle/kernel/k_shared_memory.h" #include "core/hle/result.h" namespace HLE::Applets { @@ -35,9 +35,8 @@ ResultCode MiiSelector::ReceiveParameterImpl(const Service::APT::MessageParamete using Kernel::MemoryPermission; // Create a SharedMemory that directly points to this heap block. - framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet( - 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, - "MiiSelector Memory"); + framebuffer_memory = service_context.CreateSharedMemoryForApplet("MiiSelector Memory", + 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite); // Send the response message with the newly created SharedMemory SendParameter({ @@ -57,7 +56,7 @@ ResultCode MiiSelector::Start(const Service::APT::MessageParameter& parameter) { std::memcpy(&config, parameter.buffer.data(), parameter.buffer.size()); using namespace Frontend; - frontend_applet = Core::System::GetInstance().GetMiiSelector(); + frontend_applet = system.GetMiiSelector(); ASSERT(frontend_applet); MiiSelectorConfig frontend_config = ToFrontendConfig(config); diff --git a/src/core/hle/applets/mii_selector.h b/src/core/hle/applets/mii_selector.h index 63d7298de..c4662e304 100644 --- a/src/core/hle/applets/mii_selector.h +++ b/src/core/hle/applets/mii_selector.h @@ -8,7 +8,6 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "core/hle/applets/applet.h" -#include "core/hle/kernel/shared_memory.h" #include "core/hle/mii.h" #include "core/hle/result.h" #include "core/hle/service/apt/apt.h" @@ -18,6 +17,10 @@ class MiiSelector; struct MiiSelectorConfig; } // namespace Frontend +namespace Kernel { +class KSharedMemory; +} + namespace HLE::Applets { struct MiiConfig { @@ -62,9 +65,9 @@ ASSERT_REG_POSITION(guest_mii_name, 0x6C); class MiiSelector final : public Applet { public: - MiiSelector(Service::APT::AppletId id, Service::APT::AppletId parent, bool preload, + MiiSelector(Core::System& system, Service::APT::AppletId id, Service::APT::AppletId parent, bool preload, std::weak_ptr manager) - : Applet(id, parent, preload, std::move(manager)) {} + : Applet(system, id, parent, preload, std::move(manager)) {} ResultCode ReceiveParameterImpl(const Service::APT::MessageParameter& parameter) override; ResultCode Start(const Service::APT::MessageParameter& parameter) override; @@ -79,7 +82,7 @@ private: /// This SharedMemory will be created when we receive the LibAppJustStarted message. /// It holds the framebuffer info retrieved by the application with /// GSPGPU::ImportDisplayCaptureInfo - std::shared_ptr framebuffer_memory; + Kernel::KSharedMemory* framebuffer_memory; MiiConfig config; diff --git a/src/core/hle/applets/mint.cpp b/src/core/hle/applets/mint.cpp index 1a952d02e..7ceb2ee53 100644 --- a/src/core/hle/applets/mint.cpp +++ b/src/core/hle/applets/mint.cpp @@ -5,6 +5,7 @@ #include "common/string_util.h" #include "core/core.h" #include "core/hle/applets/mint.h" +#include "core/hle/kernel/k_shared_memory.h" #include "core/hle/service/apt/apt.h" namespace HLE::Applets { @@ -28,9 +29,8 @@ ResultCode Mint::ReceiveParameterImpl(const Service::APT::MessageParameter& para // TODO: allocated memory never released using Kernel::MemoryPermission; // Create a SharedMemory that directly points to this heap block. - framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet( - 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, - "Mint Memory"); + framebuffer_memory = service_context.CreateSharedMemoryForApplet("Mint Memory", + 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite); // Send the response message with the newly created SharedMemory SendParameter({ diff --git a/src/core/hle/applets/mint.h b/src/core/hle/applets/mint.h index 4c2f8ffd6..736c2ea5a 100644 --- a/src/core/hle/applets/mint.h +++ b/src/core/hle/applets/mint.h @@ -5,15 +5,18 @@ #pragma once #include "core/hle/applets/applet.h" -#include "core/hle/kernel/shared_memory.h" + +namespace Kernel { +class KSharedMemory; +} namespace HLE::Applets { class Mint final : public Applet { public: - explicit Mint(Service::APT::AppletId id, Service::APT::AppletId parent, bool preload, + explicit Mint(Core::System& system, Service::APT::AppletId id, Service::APT::AppletId parent, bool preload, std::weak_ptr manager) - : Applet(id, parent, preload, std::move(manager)) {} + : Applet(system, id, parent, preload, std::move(manager)) {} ResultCode ReceiveParameterImpl(const Service::APT::MessageParameter& parameter) override; ResultCode Start(const Service::APT::MessageParameter& parameter) override; @@ -24,7 +27,7 @@ private: /// This SharedMemory will be created when we receive the Request message. /// It holds the framebuffer info retrieved by the application with /// GSPGPU::ImportDisplayCaptureInfo - std::shared_ptr framebuffer_memory; + Kernel::KSharedMemory* framebuffer_memory; /// Parameter received by the applet on start. std::vector startup_param; diff --git a/src/core/hle/applets/swkbd.cpp b/src/core/hle/applets/swkbd.cpp index 81e2eedb1..f15dd0b9e 100644 --- a/src/core/hle/applets/swkbd.cpp +++ b/src/core/hle/applets/swkbd.cpp @@ -32,9 +32,8 @@ ResultCode SoftwareKeyboard::ReceiveParameterImpl(Service::APT::MessageParameter using Kernel::MemoryPermission; // Create a SharedMemory that directly points to this heap block. - framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet( - 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, - "SoftwareKeyboard Memory"); + framebuffer_memory = service_context.CreateSharedMemoryForApplet("SoftwareKeyboard Memory", + 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite); // Send the response message with the newly created SharedMemory SendParameter({ @@ -94,12 +93,12 @@ ResultCode SoftwareKeyboard::Start(Service::APT::MessageParameter const& paramet "The size of the parameter (SoftwareKeyboardConfig) is wrong"); std::memcpy(&config, parameter.buffer.data(), parameter.buffer.size()); - text_memory = std::static_pointer_cast(parameter.object); + text_memory = parameter.object->DynamicCast(); DrawScreenKeyboard(); using namespace Frontend; - frontend_applet = Core::System::GetInstance().GetSoftwareKeyboard(); + frontend_applet = system.GetSoftwareKeyboard(); ASSERT(frontend_applet); frontend_applet->Execute(ToFrontendConfig(config)); diff --git a/src/core/hle/applets/swkbd.h b/src/core/hle/applets/swkbd.h index 663693ba6..94a93b61b 100644 --- a/src/core/hle/applets/swkbd.h +++ b/src/core/hle/applets/swkbd.h @@ -9,7 +9,6 @@ #include "common/common_types.h" #include "core/frontend/applets/swkbd.h" #include "core/hle/applets/applet.h" -#include "core/hle/kernel/shared_memory.h" #include "core/hle/result.h" #include "core/hle/service/apt/apt.h" @@ -175,9 +174,9 @@ static_assert(sizeof(SoftwareKeyboardConfig) == 0x400, "Software Keyboard Config class SoftwareKeyboard final : public Applet { public: - SoftwareKeyboard(Service::APT::AppletId id, Service::APT::AppletId parent, bool preload, + SoftwareKeyboard(Core::System& system, Service::APT::AppletId id, Service::APT::AppletId parent, bool preload, std::weak_ptr manager) - : Applet(id, parent, preload, std::move(manager)) {} + : Applet(system, id, parent, preload, std::move(manager)) {} ResultCode ReceiveParameterImpl(const Service::APT::MessageParameter& parameter) override; ResultCode Start(const Service::APT::MessageParameter& parameter) override; @@ -195,10 +194,10 @@ private: /// This SharedMemory will be created when we receive the LibAppJustStarted message. /// It holds the framebuffer info retrieved by the application with /// GSPGPU::ImportDisplayCaptureInfo - std::shared_ptr framebuffer_memory; + Kernel::KSharedMemory* framebuffer_memory; /// SharedMemory where the output text will be stored - std::shared_ptr text_memory; + Kernel::KSharedMemory* text_memory; /// Configuration of this instance of the SoftwareKeyboard, as received from the application SoftwareKeyboardConfig config; diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 724be2434..86e2f32df 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -87,11 +87,11 @@ public: void PushRaw(const T& value); // TODO : ensure that translate params are added after all regular params - template - void PushCopyObjects(std::shared_ptr... pointers); + template + void PushCopyObjects(T... pointers); - template - void PushMoveObjects(std::shared_ptr... pointers); + template + void PushMoveObjects(T... pointers); void PushStaticBuffer(std::vector buffer, u8 buffer_id); @@ -183,14 +183,14 @@ inline void RequestBuilder::PushMoveHLEHandles(H... handles) { Push(static_cast(handles)...); } -template -inline void RequestBuilder::PushCopyObjects(std::shared_ptr... pointers) { - PushCopyHLEHandles(context->AddOutgoingHandle(std::move(pointers))...); +template +inline void RequestBuilder::PushCopyObjects(T... pointers) { + PushCopyHLEHandles(context->AddOutgoingHandle(pointers)...); } -template -inline void RequestBuilder::PushMoveObjects(std::shared_ptr... pointers) { - PushMoveHLEHandles(context->AddOutgoingHandle(std::move(pointers))...); +template +inline void RequestBuilder::PushMoveObjects(T... pointers) { + PushMoveHLEHandles(context->AddOutgoingHandle(pointers)...); } inline void RequestBuilder::PushStaticBuffer(std::vector buffer, u8 buffer_id) { diff --git a/src/core/hle/kernel/address_arbiter.h b/src/core/hle/kernel/address_arbiter.h deleted file mode 100644 index dc8bfbf2d..000000000 --- a/src/core/hle/kernel/address_arbiter.h +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include "common/common_types.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/thread.h" -#include "core/hle/result.h" - -// Address arbiters are an underlying kernel synchronization object that can be created/used via -// supervisor calls (SVCs). They function as sort of a global lock. Typically, games/other CTR -// applications use them as an underlying mechanism to implement thread-safe barriers, events, and -// semaphores. - -namespace Kernel { - -class Thread; -class ResourceLimit; - -enum class ArbitrationType : u32 { - Signal, - WaitIfLessThan, - DecrementAndWaitIfLessThan, - WaitIfLessThanWithTimeout, - DecrementAndWaitIfLessThanWithTimeout, -}; - -class AddressArbiter final : public Object, public WakeupCallback { -public: - explicit AddressArbiter(KernelSystem& kernel); - ~AddressArbiter() override; - - std::string GetTypeName() const override { - return "Arbiter"; - } - std::string GetName() const override { - return name; - } - - static constexpr HandleType HANDLE_TYPE = HandleType::AddressArbiter; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - std::shared_ptr resource_limit; - std::string name; ///< Name of address arbiter object (optional) - - ResultCode ArbitrateAddress(std::shared_ptr thread, ArbitrationType type, VAddr address, - s32 value, u64 nanoseconds); - - class Callback; - -private: - KernelSystem& kernel; - - /// Puts the thread to wait on the specified arbitration address under this address arbiter. - void WaitThread(std::shared_ptr thread, VAddr wait_address); - - /// Resume all threads found to be waiting on the address under this address arbiter - u64 ResumeAllThreads(VAddr address); - - /// Resume one thread found to be waiting on the address under this address arbiter and return - /// the resumed thread. - bool ResumeHighestPriorityThread(VAddr address); - - /// Threads waiting for the address arbiter to be signaled. - std::vector> waiting_threads; - - std::shared_ptr timeout_callback; - - void WakeUp(ThreadWakeupReason reason, std::shared_ptr thread, - std::shared_ptr object) override; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int file_version) { - ar& boost::serialization::base_object(*this); - ar& name; - ar& waiting_threads; - ar& timeout_callback; - ar& resource_limit; - } -}; - -} // namespace Kernel - -BOOST_CLASS_EXPORT_KEY(Kernel::AddressArbiter) -BOOST_CLASS_EXPORT_KEY(Kernel::AddressArbiter::Callback) -BOOST_CLASS_VERSION(Kernel::AddressArbiter, 2) -CONSTRUCT_KERNEL_OBJECT(Kernel::AddressArbiter) diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp deleted file mode 100644 index d12078ce3..000000000 --- a/src/core/hle/kernel/client_port.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/archives.h" -#include "common/assert.h" -#include "core/global.h" -#include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/client_session.h" -#include "core/hle/kernel/errors.h" -#include "core/hle/kernel/hle_ipc.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/server_port.h" -#include "core/hle/kernel/server_session.h" - -SERIALIZE_EXPORT_IMPL(Kernel::ClientPort) - -namespace Kernel { - -ClientPort::ClientPort(KernelSystem& kernel) : Object(kernel), kernel(kernel) {} -ClientPort::~ClientPort() = default; - -ResultVal> ClientPort::Connect() { - // Note: Threads do not wait for the server endpoint to call - // AcceptSession before returning from this call. - - if (active_sessions >= max_sessions) { - return ERR_MAX_CONNECTIONS_REACHED; - } - active_sessions++; - - // Create a new session pair, let the created sessions inherit the parent port's HLE handler. - auto [server, client] = kernel.CreateSessionPair(server_port->GetName(), SharedFrom(this)); - - if (server_port->hle_handler) - server_port->hle_handler->ClientConnected(server); - else - server_port->pending_sessions.push_back(server); - - // Wake the threads waiting on the ServerPort - server_port->WakeupAllWaitingThreads(); - - return client; -} - -void ClientPort::ConnectionClosed() { - ASSERT(active_sessions > 0); - - --active_sessions; -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/client_port.h b/src/core/hle/kernel/client_port.h deleted file mode 100644 index 8d0f50520..000000000 --- a/src/core/hle/kernel/client_port.h +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include -#include "common/common_types.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/server_port.h" -#include "core/hle/result.h" - -namespace Kernel { - -class ClientSession; - -class ClientPort final : public Object { -public: - explicit ClientPort(KernelSystem& kernel); - ~ClientPort() override; - - friend class ServerPort; - std::string GetTypeName() const override { - return "ClientPort"; - } - std::string GetName() const override { - return name; - } - - static constexpr HandleType HANDLE_TYPE = HandleType::ClientPort; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - std::shared_ptr GetServerPort() const { - return server_port; - } - - /** - * Creates a new Session pair, adds the created ServerSession to the associated ServerPort's - * list of pending sessions, and signals the ServerPort, causing any threads - * waiting on it to awake. - * @returns ClientSession The client endpoint of the created Session pair, or error code. - */ - ResultVal> Connect(); - - /** - * Signifies that a previously active connection has been closed, - * decreasing the total number of active connections to this port. - */ - void ConnectionClosed(); - -private: - KernelSystem& kernel; - std::shared_ptr server_port; ///< ServerPort associated with this client port. - u32 max_sessions = 0; ///< Maximum number of simultaneous sessions the port can have - u32 active_sessions = 0; ///< Number of currently open sessions to this port - std::string name; ///< Name of client port (optional) - - friend class KernelSystem; - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int file_version) { - ar& boost::serialization::base_object(*this); - ar& server_port; - ar& max_sessions; - ar& active_sessions; - ar& name; - } -}; - -} // namespace Kernel - -BOOST_CLASS_EXPORT_KEY(Kernel::ClientPort) -CONSTRUCT_KERNEL_OBJECT(Kernel::ClientPort) diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp deleted file mode 100644 index a47e6411b..000000000 --- a/src/core/hle/kernel/client_session.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/archives.h" -#include "common/assert.h" -#include "core/hle/kernel/client_session.h" -#include "core/hle/kernel/errors.h" -#include "core/hle/kernel/hle_ipc.h" -#include "core/hle/kernel/server_session.h" -#include "core/hle/kernel/session.h" -#include "core/hle/kernel/thread.h" - -SERIALIZE_EXPORT_IMPL(Kernel::ClientSession) - -namespace Kernel { - -ClientSession::ClientSession(KernelSystem& kernel) : Object(kernel) {} -ClientSession::~ClientSession() { - // This destructor will be called automatically when the last ClientSession handle is closed by - // the emulated application. - - // Local references to ServerSession and SessionRequestHandler are necessary to guarantee they - // will be kept alive until after ClientDisconnected() returns. - std::shared_ptr server = SharedFrom(parent->server); - if (server) { - std::shared_ptr hle_handler = server->hle_handler; - if (hle_handler) - hle_handler->ClientDisconnected(server); - - // Clean up the list of client threads with pending requests, they are unneeded now that the - // client endpoint is closed. - server->pending_requesting_threads.clear(); - server->currently_handling = nullptr; - } - - parent->client = nullptr; - - if (server) { - // Notify any threads waiting on the ServerSession that the endpoint has been closed. Note - // that this call has to happen after `Session::client` has been set to nullptr to let the - // ServerSession know that the client endpoint has been closed. - server->WakeupAllWaitingThreads(); - } -} - -ResultCode ClientSession::SendSyncRequest(std::shared_ptr thread) { - // Keep ServerSession alive until we're done working with it. - std::shared_ptr server = SharedFrom(parent->server); - if (server == nullptr) - return ERR_SESSION_CLOSED_BY_REMOTE; - - // Signal the server session that new data is available - return server->HandleSyncRequest(std::move(thread)); -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h deleted file mode 100644 index 1943db8e6..000000000 --- a/src/core/hle/kernel/client_session.h +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include "common/common_types.h" -#include "core/hle/kernel/object.h" -#include "core/hle/result.h" - -namespace Kernel { - -class Session; -class Thread; - -class ClientSession final : public Object { -public: - explicit ClientSession(KernelSystem& kernel); - ~ClientSession() override; - - friend class KernelSystem; - - std::string GetTypeName() const override { - return "ClientSession"; - } - - std::string GetName() const override { - return name; - } - - static constexpr HandleType HANDLE_TYPE = HandleType::ClientSession; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - /** - * Sends an SyncRequest from the current emulated thread. - * @param thread Thread that initiated the request. - * @return ResultCode of the operation. - */ - ResultCode SendSyncRequest(std::shared_ptr thread); - - std::string name; ///< Name of client port (optional) - - /// The parent session, which links to the server endpoint. - std::shared_ptr parent; - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int file_version) { - ar& boost::serialization::base_object(*this); - ar& name; - ar& parent; - } -}; - -} // namespace Kernel - -BOOST_CLASS_EXPORT_KEY(Kernel::ClientSession) -CONSTRUCT_KERNEL_OBJECT(Kernel::ClientSession) diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h index 49255dfd1..db0698c6b 100644 --- a/src/core/hle/kernel/errors.h +++ b/src/core/hle/kernel/errors.h @@ -16,6 +16,7 @@ enum { OutOfEvents = 15, OutOfTimers = 16, OutOfHandles = 19, + ProcessNotFound = 24, SessionClosedByRemote = 26, PortNameTooLong = 30, WrongLockingThread = 31, @@ -111,5 +112,8 @@ constexpr ResultCode RESULT_TIMEOUT(ErrorDescription::Timeout, ErrorModule::OS, constexpr ResultCode ERR_NO_PENDING_SESSIONS(ErrCodes::NoPendingSessions, ErrorModule::OS, ErrorSummary::WouldBlock, ErrorLevel::Permanent); // 0xD8401823 +constexpr ResultCode ERR_PROCESS_NOT_FOUND(ErrCodes::ProcessNotFound, ErrorModule::OS, + ErrorSummary::WrongArgument, + ErrorLevel::Permanent); // 0xD9001818 } // namespace Kernel diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/event.cpp deleted file mode 100644 index f1bc7bd12..000000000 --- a/src/core/hle/kernel/event.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/archives.h" -#include "common/assert.h" -#include "core/hle/kernel/event.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/resource_limit.h" -#include "core/hle/kernel/thread.h" - -SERIALIZE_EXPORT_IMPL(Kernel::Event) - -namespace Kernel { - -Event::Event(KernelSystem& kernel) : WaitObject(kernel) {} - -Event::~Event() { - if (resource_limit) { - resource_limit->Release(ResourceLimitType::Event, 1); - } -} - -std::shared_ptr KernelSystem::CreateEvent(ResetType reset_type, std::string name) { - auto event = std::make_shared(*this); - event->signaled = false; - event->reset_type = reset_type; - event->name = std::move(name); - return event; -} - -bool Event::ShouldWait(const Thread* thread) const { - return !signaled; -} - -void Event::Acquire(Thread* thread) { - ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); - - if (reset_type == ResetType::OneShot) - signaled = false; -} - -void Event::Signal() { - signaled = true; - WakeupAllWaitingThreads(); -} - -void Event::Clear() { - signaled = false; -} - -void Event::WakeupAllWaitingThreads() { - WaitObject::WakeupAllWaitingThreads(); - - if (reset_type == ResetType::Pulse) - signaled = false; -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/event.h b/src/core/hle/kernel/event.h deleted file mode 100644 index a1fd8876c..000000000 --- a/src/core/hle/kernel/event.h +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/resource_limit.h" -#include "core/hle/kernel/wait_object.h" - -namespace Kernel { - -class Event final : public WaitObject { -public: - explicit Event(KernelSystem& kernel); - ~Event() override; - - std::string GetTypeName() const override { - return "Event"; - } - std::string GetName() const override { - return name; - } - void SetName(const std::string& name_) { - name = name_; - } - - static constexpr HandleType HANDLE_TYPE = HandleType::Event; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - ResetType GetResetType() const { - return reset_type; - } - - bool ShouldWait(const Thread* thread) const override; - void Acquire(Thread* thread) override; - - void WakeupAllWaitingThreads() override; - - void Signal(); - void Clear(); - - std::shared_ptr resource_limit; - -private: - ResetType reset_type; ///< Current ResetType - - bool signaled; ///< Whether the event has already been signaled - std::string name; ///< Name of event (optional) - - friend class KernelSystem; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int file_version) { - ar& boost::serialization::base_object(*this); - ar& reset_type; - ar& signaled; - ar& name; - ar& resource_limit; - } -}; - -} // namespace Kernel - -BOOST_CLASS_EXPORT_KEY(Kernel::Event) -CONSTRUCT_KERNEL_OBJECT(Kernel::Event) diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp deleted file mode 100644 index 8c7b95f26..000000000 --- a/src/core/hle/kernel/handle_table.cpp +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include "common/assert.h" -#include "common/logging/log.h" -#include "core/hle/kernel/errors.h" -#include "core/hle/kernel/handle_table.h" -#include "core/hle/kernel/process.h" -#include "core/hle/kernel/thread.h" - -namespace Kernel { -namespace { -constexpr u16 GetSlot(Handle handle) { - return handle >> 15; -} - -constexpr u16 GetGeneration(Handle handle) { - return handle & 0x7FFF; -} -} // Anonymous namespace - -HandleTable::HandleTable(KernelSystem& kernel) : kernel(kernel) { - next_generation = 1; - Clear(); -} - -HandleTable::~HandleTable() = default; - -ResultVal HandleTable::Create(std::shared_ptr obj) { - DEBUG_ASSERT(obj != nullptr); - - u16 slot = next_free_slot; - if (slot >= generations.size()) { - LOG_ERROR(Kernel, "Unable to allocate Handle, too many slots in use."); - return ERR_OUT_OF_HANDLES; - } - next_free_slot = generations[slot]; - - u16 generation = next_generation++; - - // Overflow count so it fits in the 15 bits dedicated to the generation in the handle. - // CTR-OS doesn't use generation 0, so skip straight to 1. - if (next_generation >= (1 << 15)) - next_generation = 1; - - generations[slot] = generation; - objects[slot] = std::move(obj); - - Handle handle = generation | (slot << 15); - return handle; -} - -ResultVal HandleTable::Duplicate(Handle handle) { - std::shared_ptr object = GetGeneric(handle); - if (object == nullptr) { - LOG_ERROR(Kernel, "Tried to duplicate invalid handle: {:08X}", handle); - return ERR_INVALID_HANDLE; - } - return Create(std::move(object)); -} - -ResultCode HandleTable::Close(Handle handle) { - if (!IsValid(handle)) - return ERR_INVALID_HANDLE; - - u16 slot = GetSlot(handle); - - objects[slot] = nullptr; - - generations[slot] = next_free_slot; - next_free_slot = slot; - return RESULT_SUCCESS; -} - -bool HandleTable::IsValid(Handle handle) const { - std::size_t slot = GetSlot(handle); - u16 generation = GetGeneration(handle); - - return slot < MAX_COUNT && objects[slot] != nullptr && generations[slot] == generation; -} - -std::shared_ptr HandleTable::GetGeneric(Handle handle) const { - if (handle == CurrentThread) { - return SharedFrom(kernel.GetCurrentThreadManager().GetCurrentThread()); - } else if (handle == CurrentProcess) { - return kernel.GetCurrentProcess(); - } - - if (!IsValid(handle)) { - return nullptr; - } - return objects[GetSlot(handle)]; -} - -void HandleTable::Clear() { - for (u16 i = 0; i < MAX_COUNT; ++i) { - generations[i] = i + 1; - objects[i] = nullptr; - } - next_free_slot = 0; -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/handle_table.h b/src/core/hle/kernel/handle_table.h deleted file mode 100644 index 728ab5fa3..000000000 --- a/src/core/hle/kernel/handle_table.h +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include -#include "common/common_types.h" -#include "core/hle/kernel/object.h" -#include "core/hle/result.h" - -namespace Kernel { - -enum KernelHandle : Handle { - CurrentThread = 0xFFFF8000, - CurrentProcess = 0xFFFF8001, -}; - -/** - * This class allows the creation of Handles, which are references to objects that can be tested - * for validity and looked up. Here they are used to pass references to kernel objects to/from the - * emulated process. it has been designed so that it follows the same handle format and has - * approximately the same restrictions as the handle manager in the CTR-OS. - * - * Handles contain two sub-fields: a slot index (bits 31:15) and a generation value (bits 14:0). - * The slot index is used to index into the arrays in this class to access the data corresponding - * to the Handle. - * - * To prevent accidental use of a freed Handle whose slot has already been reused, a global counter - * is kept and incremented every time a Handle is created. This is the Handle's "generation". The - * value of the counter is stored into the Handle as well as in the handle table (in the - * "generations" array). When looking up a handle, the Handle's generation must match with the - * value stored on the class, otherwise the Handle is considered invalid. - * - * To find free slots when allocating a Handle without needing to scan the entire object array, the - * generations field of unallocated slots is re-purposed as a linked list of indices to free slots. - * When a Handle is created, an index is popped off the list and used for the new Handle. When it - * is destroyed, it is again pushed onto the list to be re-used by the next allocation. It is - * likely that this allocation strategy differs from the one used in CTR-OS, but this hasn't been - * verified and isn't likely to cause any problems. - */ -class HandleTable final : NonCopyable { -public: - explicit HandleTable(KernelSystem& kernel); - ~HandleTable(); - - /** - * Allocates a handle for the given object. - * @return The created Handle or one of the following errors: - * - `ERR_OUT_OF_HANDLES`: the maximum number of handles has been exceeded. - */ - ResultVal Create(std::shared_ptr obj); - - /** - * Returns a new handle that points to the same object as the passed in handle. - * @return The duplicated Handle or one of the following errors: - * - `ERR_INVALID_HANDLE`: an invalid handle was passed in. - * - Any errors returned by `Create()`. - */ - ResultVal Duplicate(Handle handle); - - /** - * Closes a handle, removing it from the table and decreasing the object's ref-count. - * @return `RESULT_SUCCESS` or one of the following errors: - * - `ERR_INVALID_HANDLE`: an invalid handle was passed in. - */ - ResultCode Close(Handle handle); - - /// Checks if a handle is valid and points to an existing object. - bool IsValid(Handle handle) const; - - /** - * Looks up a handle. - * @return Pointer to the looked-up object, or `nullptr` if the handle is not valid. - */ - std::shared_ptr GetGeneric(Handle handle) const; - - /** - * Looks up a handle while verifying its type. - * @return Pointer to the looked-up object, or `nullptr` if the handle is not valid or its - * type differs from the requested one. - */ - template - std::shared_ptr Get(Handle handle) const { - return DynamicObjectCast(GetGeneric(handle)); - } - - /// Closes all handles held in this table. - void Clear(); - -private: - /** - * This is the maximum limit of handles allowed per process in CTR-OS. It can be further - * reduced by ExHeader values, but this is not emulated here. - */ - static const std::size_t MAX_COUNT = 4096; - - /// Stores the Object referenced by the handle or null if the slot is empty. - std::array, MAX_COUNT> objects; - - /** - * The value of `next_generation` when the handle was created, used to check for validity. For - * empty slots, contains the index of the next free slot in the list. - */ - std::array generations; - - /** - * Global counter of the number of created handles. Stored in `generations` when a handle is - * created, and wraps around to 1 when it hits 0x8000. - */ - u16 next_generation; - - /// Head of the free slots linked list. - u16 next_free_slot; - - KernelSystem& kernel; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int file_version) { - ar& objects; - ar& generations; - ar& next_generation; - ar& next_free_slot; - } -}; - -} // namespace Kernel diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 62b993f33..558662f5d 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -8,10 +8,10 @@ #include "common/assert.h" #include "common/common_types.h" #include "core/core.h" -#include "core/hle/kernel/event.h" -#include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/ipc_debugger/recorder.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_handle_table.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" @@ -23,15 +23,13 @@ public: ThreadCallback(std::shared_ptr context_, std::shared_ptr callback_) : callback(std::move(callback_)), context(std::move(context_)) {} - void WakeUp(ThreadWakeupReason reason, std::shared_ptr thread, - std::shared_ptr object) { - ASSERT(thread->status == ThreadStatus::WaitHleEvent); + void WakeUp(ThreadWakeupReason reason, Thread* thread, KSynchronizationObject* object) { + ASSERT(thread->m_status == ThreadStatus::WaitHleEvent); if (callback) { callback->WakeUp(thread, *context, reason); } - auto process = thread->owner_process.lock(); - ASSERT(process); + Process* process = thread->GetOwner(); // We must copy the entire command buffer *plus* the entire static buffers area, since // the translation might need to read from it in order to retrieve the StaticBuffer @@ -60,16 +58,16 @@ private: friend class boost::serialization::access; }; -SessionRequestHandler::SessionInfo::SessionInfo(std::shared_ptr session, +SessionRequestHandler::SessionInfo::SessionInfo(KServerSession* session_, std::unique_ptr data) - : session(std::move(session)), data(std::move(data)) {} + : session(session_), data(std::move(data)) {} -void SessionRequestHandler::ClientConnected(std::shared_ptr server_session) { +void SessionRequestHandler::ClientConnected(KServerSession* server_session) { server_session->SetHleHandler(shared_from_this()); - connected_sessions.emplace_back(std::move(server_session), MakeSessionData()); + connected_sessions.emplace_back(server_session, MakeSessionData()); } -void SessionRequestHandler::ClientDisconnected(std::shared_ptr server_session) { +void SessionRequestHandler::ClientDisconnected(KServerSession* server_session) { server_session->SetHleHandler(nullptr); connected_sessions.erase( std::remove_if(connected_sessions.begin(), connected_sessions.end(), @@ -77,40 +75,40 @@ void SessionRequestHandler::ClientDisconnected(std::shared_ptr se connected_sessions.end()); } -std::shared_ptr HLERequestContext::SleepClientThread( - const std::string& reason, std::chrono::nanoseconds timeout, - std::shared_ptr callback) { +KEvent* HLERequestContext::SleepClientThread(const std::string& reason, + std::chrono::nanoseconds timeout, + std::shared_ptr callback) { // Put the client thread to sleep until the wait event is signaled or the timeout expires. - thread->wakeup_callback = std::make_shared(shared_from_this(), callback); + thread->m_wakeup_callback = std::make_shared(shared_from_this(), callback); auto event = kernel.CreateEvent(Kernel::ResetType::OneShot, "HLE Pause Event: " + reason); - thread->status = ThreadStatus::WaitHleEvent; - thread->wait_objects = {event}; + thread->m_status = ThreadStatus::WaitHleEvent; + thread->m_wait_objects = {event}; event->AddWaitingThread(thread); - if (timeout.count() > 0) + if (timeout.count() > 0) { thread->WakeAfterDelay(timeout.count()); + } return event; } HLERequestContext::HLERequestContext() : kernel(Core::Global()) {} -HLERequestContext::HLERequestContext(KernelSystem& kernel, std::shared_ptr session, - std::shared_ptr thread) - : kernel(kernel), session(std::move(session)), thread(thread) { +HLERequestContext::HLERequestContext(KernelSystem& kernel, KServerSession* session, Thread* thread) + : kernel(kernel), session(session), thread(thread) { cmd_buf[0] = 0; } HLERequestContext::~HLERequestContext() = default; -std::shared_ptr HLERequestContext::GetIncomingHandle(u32 id_from_cmdbuf) const { +KAutoObject* HLERequestContext::GetIncomingHandle(u32 id_from_cmdbuf) const { ASSERT(id_from_cmdbuf < request_handles.size()); return request_handles[id_from_cmdbuf]; } -u32 HLERequestContext::AddOutgoingHandle(std::shared_ptr object) { - request_handles.push_back(std::move(object)); +u32 HLERequestContext::AddOutgoingHandle(KAutoObject* object) { + request_handles.push_back(object); return static_cast(request_handles.size() - 1); } @@ -126,9 +124,8 @@ void HLERequestContext::AddStaticBuffer(u8 buffer_id, std::vector data) { static_buffers[buffer_id] = std::move(data); } -ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer( - const u32_le* src_cmdbuf, std::shared_ptr src_process_) { - auto& src_process = *src_process_; +ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf, + Process* src_process) { IPC::Header header{src_cmdbuf[0]}; std::size_t untranslated_size = 1u + header.normal_params_size; @@ -152,25 +149,32 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer( switch (IPC::GetDescriptorType(descriptor)) { case IPC::DescriptorType::CopyHandle: case IPC::DescriptorType::MoveHandle: { - u32 num_handles = IPC::HandleNumberFromDesc(descriptor); + const u32 num_handles = IPC::HandleNumberFromDesc(descriptor); + auto& src_handle_table = src_process->handle_table; ASSERT(i + num_handles <= command_size); // TODO(yuriks): Return error for (u32 j = 0; j < num_handles; ++j) { - Handle handle = src_cmdbuf[i]; - std::shared_ptr object = nullptr; - if (handle != 0) { - object = src_process.handle_table.GetGeneric(handle); - ASSERT(object != nullptr); // TODO(yuriks): Return error - if (descriptor == IPC::DescriptorType::MoveHandle) { - src_process.handle_table.Close(handle); - } + const Handle handle = src_cmdbuf[i]; + if (!handle) { + cmd_buf[i++] = AddOutgoingHandle(nullptr); + continue; } - cmd_buf[i++] = AddOutgoingHandle(std::move(object)); + // Get object from the handle table. + KScopedAutoObject object = + src_handle_table.GetObjectForIpcWithoutPseudoHandle(handle); + ASSERT(object.IsNotNull()); + + // If we are moving, remove the old handle. + if (descriptor == IPC::DescriptorType::MoveHandle) { + src_handle_table.Remove(handle); + } + + cmd_buf[i++] = AddOutgoingHandle(object.GetPointerUnsafe()); } break; } case IPC::DescriptorType::CallingPid: { - cmd_buf[i++] = src_process.process_id; + cmd_buf[i++] = src_process->process_id; break; } case IPC::DescriptorType::StaticBuffer: { @@ -179,7 +183,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer( // Copy the input buffer into our own vector and store it. std::vector data(buffer_info.size); - kernel.memory.ReadBlock(src_process, source_address, data.data(), data.size()); + kernel.memory.ReadBlock(*src_process, source_address, data.data(), data.size()); AddStaticBuffer(buffer_info.buffer_id, std::move(data)); cmd_buf[i++] = source_address; @@ -187,7 +191,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer( } case IPC::DescriptorType::MappedBuffer: { u32 next_id = static_cast(request_mapped_buffers.size()); - request_mapped_buffers.emplace_back(kernel.memory, src_process_, descriptor, + request_mapped_buffers.emplace_back(kernel.memory, src_process, descriptor, src_cmdbuf[i], next_id); cmd_buf[i++] = next_id; break; @@ -232,14 +236,14 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, case IPC::DescriptorType::CopyHandle: case IPC::DescriptorType::MoveHandle: { // HLE services don't use handles, so we treat both CopyHandle and MoveHandle equally - u32 num_handles = IPC::HandleNumberFromDesc(descriptor); + const u32 num_handles = IPC::HandleNumberFromDesc(descriptor); ASSERT(i + num_handles <= command_size); for (u32 j = 0; j < num_handles; ++j) { - std::shared_ptr object = GetIncomingHandle(cmd_buf[i]); + KAutoObject* object = GetIncomingHandle(cmd_buf[i]); Handle handle = 0; if (object != nullptr) { // TODO(yuriks): Figure out the proper error handling for if this fails - handle = dst_process.handle_table.Create(object).Unwrap(); + dst_process.handle_table.Add(std::addressof(handle), object); } dst_cmdbuf[i++] = handle; } @@ -297,8 +301,8 @@ void HLERequestContext::ReportUnimplemented() const { MappedBuffer::MappedBuffer() : memory(&Core::Global().Memory()) {} -MappedBuffer::MappedBuffer(Memory::MemorySystem& memory, std::shared_ptr process, - u32 descriptor, VAddr address, u32 id) +MappedBuffer::MappedBuffer(Memory::MemorySystem& memory, Process* process, u32 descriptor, + VAddr address, u32 id) : memory(&memory), id(id), address(address), process(std::move(process)) { IPC::MappedBufferDescInfo desc{descriptor}; size = desc.size; diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index c0702cdb2..ec010ec84 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -13,15 +13,14 @@ #include #include #include -#include #include #include #include "common/common_types.h" #include "common/serialization/boost_small_vector.hpp" #include "common/swap.h" #include "core/hle/ipc.h" +#include "core/hle/kernel/k_server_session.h" #include "core/hle/kernel/object.h" -#include "core/hle/kernel/server_session.h" namespace Service { class ServiceFrameworkBase; @@ -36,7 +35,7 @@ namespace Kernel { class HandleTable; class Process; class Thread; -class Event; +class KEvent; class HLERequestContext; class KernelSystem; @@ -61,14 +60,14 @@ public: * associated ServerSession alive for the duration of the connection. * @param server_session Owning pointer to the ServerSession associated with the connection. */ - virtual void ClientConnected(std::shared_ptr server_session); + virtual void ClientConnected(KServerSession* server_session); /** * Signals that a client has just disconnected from this HLE handler and releases the * associated ServerSession. * @param server_session ServerSession associated with the connection. */ - virtual void ClientDisconnected(std::shared_ptr server_session); + virtual void ClientDisconnected(KServerSession* server_session); /// Empty placeholder structure for services with no per-session data. The session data classes /// in each service must inherit from this. @@ -77,7 +76,7 @@ public: private: template - void serialize(Archive& ar, const unsigned int file_version) {} + void serialize(Archive& ar, const u32 file_version) {} friend class boost::serialization::access; }; @@ -87,7 +86,7 @@ protected: /// Returns the session data associated with the server session. template - T* GetSessionData(std::shared_ptr session) { + T* GetSessionData(KServerSession* session) { static_assert(std::is_base_of(), "T is not a subclass of SessionDataBase"); auto itr = std::find_if(connected_sessions.begin(), connected_sessions.end(), @@ -97,9 +96,9 @@ protected: } struct SessionInfo { - SessionInfo(std::shared_ptr session, std::unique_ptr data); + SessionInfo(KServerSession* session, std::unique_ptr data); - std::shared_ptr session; + KServerSession* session; std::unique_ptr data; private: @@ -127,8 +126,8 @@ private: class MappedBuffer { public: - MappedBuffer(Memory::MemorySystem& memory, std::shared_ptr process, u32 descriptor, - VAddr address, u32 id); + MappedBuffer(Memory::MemorySystem& memory, Process* process, u32 descriptor, VAddr address, + u32 id); // interface for service void Read(void* dest_buffer, std::size_t offset, std::size_t size); @@ -151,7 +150,7 @@ private: Memory::MemorySystem* memory; u32 id; VAddr address; - std::shared_ptr process; + Process* process; u32 size; IPC::MappedBufferPermissions perms; @@ -199,8 +198,7 @@ private: */ class HLERequestContext : public std::enable_shared_from_this { public: - HLERequestContext(KernelSystem& kernel, std::shared_ptr session, - std::shared_ptr thread); + explicit HLERequestContext(KernelSystem& kernel, KServerSession* session, Thread* thread); ~HLERequestContext(); /// Returns a pointer to the IPC command buffer for this request. @@ -217,21 +215,21 @@ public: * Returns the session through which this request was made. This can be used as a map key to * access per-client data on services. */ - std::shared_ptr Session() const { + KServerSession* Session() const { return session; } /** * Returns the client thread that made the service request. */ - std::shared_ptr ClientThread() const { + Thread* ClientThread() const { return thread; } class WakeupCallback { public: virtual ~WakeupCallback() = default; - virtual void WakeUp(std::shared_ptr thread, HLERequestContext& context, + virtual void WakeUp(Thread* thread, HLERequestContext& context, ThreadWakeupReason reason) = 0; private: @@ -251,9 +249,8 @@ public: * was called. * @returns Event that when signaled will resume the thread and call the callback function. */ - std::shared_ptr SleepClientThread(const std::string& reason, - std::chrono::nanoseconds timeout, - std::shared_ptr callback); + KEvent* SleepClientThread(const std::string& reason, std::chrono::nanoseconds timeout, + std::shared_ptr callback); private: template @@ -264,7 +261,7 @@ private: future = std::move(fut); } - void WakeUp(std::shared_ptr thread, Kernel::HLERequestContext& ctx, + void WakeUp(Kernel::Thread* thread, Kernel::HLERequestContext& ctx, Kernel::ThreadWakeupReason reason) { functor(ctx); } @@ -329,13 +326,13 @@ public: * Resolves a object id from the request command buffer into a pointer to an object. See the * "HLE handle protocol" section in the class documentation for more details. */ - std::shared_ptr GetIncomingHandle(u32 id_from_cmdbuf) const; + KAutoObject* GetIncomingHandle(u32 id_from_cmdbuf) const; /** * Adds an outgoing object to the response, returning the id which should be used to reference * it. See the "HLE handle protocol" section in the class documentation for more details. */ - u32 AddOutgoingHandle(std::shared_ptr object); + u32 AddOutgoingHandle(KAutoObject* object); /** * Discards all Objects from the context, invalidating all ids. This may be called after reading @@ -363,8 +360,8 @@ public: MappedBuffer& GetMappedBuffer(u32 id_from_cmdbuf); /// Populates this context with data from the requesting process/thread. - ResultCode PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf, - std::shared_ptr src_process); + ResultCode PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf, Process* src_process); + /// Writes data from this context back to the requesting process/thread. ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process) const; @@ -377,10 +374,10 @@ public: private: KernelSystem& kernel; std::array cmd_buf; - std::shared_ptr session; - std::shared_ptr thread; + KServerSession* session; + Thread* thread; // TODO(yuriks): Check common usage of this and optimize size accordingly - boost::container::small_vector, 8> request_handles; + boost::container::small_vector request_handles; // The static buffers will be created when the IPC request is translated. std::array, IPC::MAX_STATIC_BUFFERS> static_buffers; // The mapped buffers will be created when the IPC request is translated diff --git a/src/core/hle/kernel/ipc.cpp b/src/core/hle/kernel/ipc.cpp index dade1f520..5591da130 100644 --- a/src/core/hle/kernel/ipc.cpp +++ b/src/core/hle/kernel/ipc.cpp @@ -7,9 +7,9 @@ #include "common/memory_ref.h" #include "core/core.h" #include "core/hle/ipc.h" -#include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/ipc.h" #include "core/hle/kernel/ipc_debugger/recorder.h" +#include "core/hle/kernel/k_handle_table.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/memory.h" #include "core/hle/kernel/process.h" @@ -19,13 +19,12 @@ namespace Kernel { ResultCode TranslateCommandBuffer(Kernel::KernelSystem& kernel, Memory::MemorySystem& memory, - std::shared_ptr src_thread, - std::shared_ptr dst_thread, VAddr src_address, + Thread* src_thread, Thread* dst_thread, VAddr src_address, VAddr dst_address, std::vector& mapped_buffer_context, bool reply) { - auto src_process = src_thread->owner_process.lock(); - auto dst_process = dst_thread->owner_process.lock(); + auto src_process = src_thread->owner_process; + auto dst_process = dst_thread->owner_process; ASSERT(src_process && dst_process); IPC::Header header; @@ -66,30 +65,34 @@ ResultCode TranslateCommandBuffer(Kernel::KernelSystem& kernel, Memory::MemorySy for (u32 j = 0; j < num_handles; ++j) { Handle handle = cmd_buf[i]; - std::shared_ptr object = nullptr; // Perform pseudo-handle detection here because by the time this function is called, // the current thread and process are no longer the ones which created this IPC // request, but the ones that are handling it. - if (handle == CurrentThread) { - object = src_thread; - } else if (handle == CurrentProcess) { - object = src_process; - } else if (handle != 0) { - object = src_process->handle_table.GetGeneric(handle); - if (descriptor == IPC::DescriptorType::MoveHandle) { - src_process->handle_table.Close(handle); + KScopedAutoObject object = [&]() -> KScopedAutoObject { + if (handle == CurrentThread) { + return src_thread; + } else if (handle == CurrentProcess) { + return src_process; + } else if (handle != 0) { + auto obj = src_process->handle_table.GetObject(handle); + if (descriptor == IPC::DescriptorType::MoveHandle) { + src_process->handle_table.Remove(handle); + } + return obj; } - } + return nullptr; + }(); - if (object == nullptr) { + if (object.IsNull()) { // Note: The real kernel sets invalid translated handles to 0 in the target // command buffer. cmd_buf[i++] = 0; continue; } - auto result = dst_process->handle_table.Create(std::move(object)); - cmd_buf[i++] = result.ValueOr(0); + Handle dst_handle = 0; + dst_process->handle_table.Add(&dst_handle, object.GetPointerUnsafe()); + cmd_buf[i++] = dst_handle; } break; } diff --git a/src/core/hle/kernel/ipc.h b/src/core/hle/kernel/ipc.h index c1fd5b6fb..ff100ae2b 100644 --- a/src/core/hle/kernel/ipc.h +++ b/src/core/hle/kernel/ipc.h @@ -4,7 +4,6 @@ #pragma once -#include #include #include #include "common/common_types.h" @@ -41,8 +40,7 @@ private: /// Performs IPC command buffer translation from one process to another. ResultCode TranslateCommandBuffer(KernelSystem& system, Memory::MemorySystem& memory, - std::shared_ptr src_thread, - std::shared_ptr dst_thread, VAddr src_address, + Thread* src_thread, Thread* dst_thread, VAddr src_address, VAddr dst_address, std::vector& mapped_buffer_context, bool reply); diff --git a/src/core/hle/kernel/ipc_debugger/recorder.cpp b/src/core/hle/kernel/ipc_debugger/recorder.cpp index 5c2bfd2ee..3c3dbc745 100644 --- a/src/core/hle/kernel/ipc_debugger/recorder.cpp +++ b/src/core/hle/kernel/ipc_debugger/recorder.cpp @@ -4,20 +4,23 @@ #include "common/assert.h" #include "common/logging/log.h" -#include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/client_session.h" +#include "common/scope_exit.h" #include "core/hle/kernel/ipc_debugger/recorder.h" +#include "core/hle/kernel/k_client_port.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_port.h" +#include "core/hle/kernel/k_server_port.h" +#include "core/hle/kernel/k_server_session.h" +#include "core/hle/kernel/k_session.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/server_port.h" -#include "core/hle/kernel/server_session.h" -#include "core/hle/kernel/session.h" #include "core/hle/kernel/thread.h" #include "core/hle/service/service.h" namespace IPCDebugger { namespace { -ObjectInfo GetObjectInfo(const Kernel::Object* object) { + +ObjectInfo GetObjectInfo(const Kernel::KAutoObject* object) { if (object == nullptr) { return {}; } @@ -37,29 +40,33 @@ ObjectInfo GetObjectInfo(const Kernel::Process* process) { } return {process->GetTypeName(), process->GetName(), static_cast(process->process_id)}; } -} // namespace + +} // Anonymous namespace Recorder::Recorder() = default; + Recorder::~Recorder() = default; bool Recorder::IsEnabled() const { return enabled.load(std::memory_order_relaxed); } -void Recorder::RegisterRequest(const std::shared_ptr& client_session, - const std::shared_ptr& client_thread) { +void Recorder::RegisterRequest(const Kernel::KClientSession* client_session, + const Kernel::Thread* client_thread) { const u32 thread_id = client_thread->GetThreadId(); - if (auto owner_process = client_thread->owner_process.lock()) { - RequestRecord record = {/* id */ ++record_count, - /* status */ RequestStatus::Sent, - /* client_process */ GetObjectInfo(owner_process.get()), - /* client_thread */ GetObjectInfo(client_thread.get()), - /* client_session */ GetObjectInfo(client_session.get()), - /* client_port */ GetObjectInfo(client_session->parent->port.get()), - /* server_process */ {}, - /* server_thread */ {}, - /* server_session */ GetObjectInfo(client_session->parent->server)}; + if (auto owner_process = client_thread->owner_process) { + RequestRecord record = { + .id = ++record_count, + .status = RequestStatus::Sent, + .client_process = GetObjectInfo(owner_process), + .client_thread = GetObjectInfo(client_thread), + .client_session = GetObjectInfo(client_session), + .client_port = GetObjectInfo(client_session->GetParent()->GetParent()), + .server_process = {}, + .server_thread = {}, + .server_session = GetObjectInfo(&client_session->GetParent()->GetServerSession()), + }; record_map.insert_or_assign(thread_id, std::make_unique(record)); client_session_map.insert_or_assign(thread_id, client_session); @@ -67,10 +74,10 @@ void Recorder::RegisterRequest(const std::shared_ptr& cli } } -void Recorder::SetRequestInfo(const std::shared_ptr& client_thread, +void Recorder::SetRequestInfo(const Kernel::Thread* client_thread, std::vector untranslated_cmdbuf, std::vector translated_cmdbuf, - const std::shared_ptr& server_thread) { + const Kernel::Thread* server_thread) { const u32 thread_id = client_thread->GetThreadId(); if (!record_map.count(thread_id)) { // This is possible when the recorder is enabled after application started @@ -84,30 +91,36 @@ void Recorder::SetRequestInfo(const std::shared_ptr& client_thre record.translated_request_cmdbuf = std::move(translated_cmdbuf); if (server_thread) { - if (auto owner_process = server_thread->owner_process.lock()) { - record.server_process = GetObjectInfo(owner_process.get()); + if (auto owner_process = server_thread->owner_process) { + record.server_process = GetObjectInfo(owner_process); } - record.server_thread = GetObjectInfo(server_thread.get()); + record.server_thread = GetObjectInfo(server_thread); } else { record.is_hle = true; } // Function name ASSERT_MSG(client_session_map.count(thread_id), "Client session is missing"); - const auto& client_session = client_session_map[thread_id]; - if (client_session->parent->port && - client_session->parent->port->GetServerPort()->hle_handler) { + const auto client_session = client_session_map[thread_id]; - record.function_name = std::dynamic_pointer_cast( - client_session->parent->port->GetServerPort()->hle_handler) + SCOPE_EXIT({ + client_session_map.erase(thread_id); + InvokeCallbacks(record); + }); + + auto port = client_session->GetParent()->GetParent(); + if (!port) { + return; + } + + auto hle_handler = port->GetParent()->GetServerPort().GetHleHandler(); + if (hle_handler) { + record.function_name = std::dynamic_pointer_cast(hle_handler) ->GetFunctionName({record.untranslated_request_cmdbuf[0]}); } - client_session_map.erase(thread_id); - - InvokeCallbacks(record); } -void Recorder::SetReplyInfo(const std::shared_ptr& client_thread, +void Recorder::SetReplyInfo(const Kernel::Thread* client_thread, std::vector untranslated_cmdbuf, std::vector translated_cmdbuf) { const u32 thread_id = client_thread->GetThreadId(); @@ -129,7 +142,7 @@ void Recorder::SetReplyInfo(const std::shared_ptr& client_thread record_map.erase(thread_id); } -void Recorder::SetHLEUnimplemented(const std::shared_ptr& client_thread) { +void Recorder::SetHLEUnimplemented(const Kernel::Thread* client_thread) { const u32 thread_id = client_thread->GetThreadId(); if (!record_map.count(thread_id)) { // This is possible when the recorder is enabled after application started diff --git a/src/core/hle/kernel/ipc_debugger/recorder.h b/src/core/hle/kernel/ipc_debugger/recorder.h index ebd8bf5d1..9fb53f558 100644 --- a/src/core/hle/kernel/ipc_debugger/recorder.h +++ b/src/core/hle/kernel/ipc_debugger/recorder.h @@ -15,8 +15,9 @@ #include "common/common_types.h" namespace Kernel { -class ClientSession; +class KClientSession; class Thread; +enum class ClassTokenType : u32; } // namespace Kernel namespace IPCDebugger { @@ -27,7 +28,7 @@ namespace IPCDebugger { struct ObjectInfo { std::string type; std::string name; - int id = -1; + Kernel::ClassTokenType id; }; /** @@ -80,28 +81,28 @@ public: /** * Registers a request into the recorder. The request is then assoicated with the client thread. */ - void RegisterRequest(const std::shared_ptr& client_session, - const std::shared_ptr& client_thread); + void RegisterRequest(const Kernel::KClientSession* client_session, + const Kernel::Thread* client_thread); /** * Sets the request information of the request record associated with the client thread. * When the server thread is empty, the request will be considered HLE. */ - void SetRequestInfo(const std::shared_ptr& client_thread, - std::vector untranslated_cmdbuf, std::vector translated_cmdbuf, - const std::shared_ptr& server_thread = {}); + void SetRequestInfo(const Kernel::Thread* client_thread, std::vector untranslated_cmdbuf, + std::vector translated_cmdbuf, + const Kernel::Thread* server_thread = nullptr); /** * Sets the reply information of the request record assoicated with the client thread. * The request is then unlinked from the client thread. */ - void SetReplyInfo(const std::shared_ptr& client_thread, - std::vector untranslated_cmdbuf, std::vector translated_cmdbuf); + void SetReplyInfo(const Kernel::Thread* client_thread, std::vector untranslated_cmdbuf, + std::vector translated_cmdbuf); /** * Set the status of a record to HLEUnimplemented. */ - void SetHLEUnimplemented(const std::shared_ptr& client_thread); + void SetHLEUnimplemented(const Kernel::Thread* client_thread); /** * Set the status of the debugger (enabled/disabled). @@ -118,7 +119,7 @@ private: int record_count{}; // Temporary client session map for function name handling - std::unordered_map> client_session_map; + std::unordered_map client_session_map; std::atomic_bool enabled{false}; diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/k_address_arbiter.cpp similarity index 51% rename from src/core/hle/kernel/address_arbiter.cpp rename to src/core/hle/kernel/k_address_arbiter.cpp index 5254fa148..89d0504aa 100644 --- a/src/core/hle/kernel/address_arbiter.cpp +++ b/src/core/hle/kernel/k_address_arbiter.cpp @@ -1,95 +1,31 @@ -// Copyright 2014 Citra Emulator Project +// Copyright 2023 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include +#include +#include +#include #include "common/archives.h" -#include "common/common_types.h" #include "common/logging/log.h" -#include "core/hle/kernel/address_arbiter.h" #include "core/hle/kernel/errors.h" +#include "core/hle/kernel/k_address_arbiter.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/thread.h" #include "core/memory.h" namespace Kernel { -void AddressArbiter::WaitThread(std::shared_ptr thread, VAddr wait_address) { - thread->wait_address = wait_address; - thread->status = ThreadStatus::WaitArb; - waiting_threads.emplace_back(std::move(thread)); -} - -u64 AddressArbiter::ResumeAllThreads(VAddr address) { - // Determine which threads are waiting on this address, those should be woken up. - auto itr = std::stable_partition(waiting_threads.begin(), waiting_threads.end(), - [address](const auto& thread) { - ASSERT_MSG(thread->status == ThreadStatus::WaitArb, - "Inconsistent AddressArbiter state"); - return thread->wait_address != address; - }); - - // Wake up all the found threads - const u64 num_threads = std::distance(itr, waiting_threads.end()); - std::for_each(itr, waiting_threads.end(), [](auto& thread) { thread->ResumeFromWait(); }); - - // Remove the woken up threads from the wait list. - waiting_threads.erase(itr, waiting_threads.end()); - return num_threads; -} - -bool AddressArbiter::ResumeHighestPriorityThread(VAddr address) { - // Determine which threads are waiting on this address, those should be considered for wakeup. - auto matches_start = std::stable_partition( - waiting_threads.begin(), waiting_threads.end(), [address](const auto& thread) { - ASSERT_MSG(thread->status == ThreadStatus::WaitArb, - "Inconsistent AddressArbiter state"); - return thread->wait_address != address; - }); - - // Iterate through threads, find highest priority thread that is waiting to be arbitrated. - // Note: The real kernel will pick the first thread in the list if more than one have the - // same highest priority value. Lower priority values mean higher priority. - auto itr = std::min_element(matches_start, waiting_threads.end(), - [](const auto& lhs, const auto& rhs) { - return lhs->current_priority < rhs->current_priority; - }); - - if (itr == waiting_threads.end()) { - return false; - } - - auto thread = *itr; - thread->ResumeFromWait(); - waiting_threads.erase(itr); - - return true; -} - -AddressArbiter::AddressArbiter(KernelSystem& kernel) - : Object(kernel), kernel(kernel), timeout_callback(std::make_shared(*this)) {} - -AddressArbiter::~AddressArbiter() { - if (resource_limit) { - resource_limit->Release(ResourceLimitType::AddressArbiter, 1); - } -} - -std::shared_ptr KernelSystem::CreateAddressArbiter(std::string name) { - auto address_arbiter = std::make_shared(*this); - address_arbiter->name = std::move(name); - return address_arbiter; -} - -class AddressArbiter::Callback : public WakeupCallback { +class KAddressArbiter::Callback : public WakeupCallback { public: - explicit Callback(AddressArbiter& _parent) : parent(_parent) {} - AddressArbiter& parent; + explicit Callback(KAddressArbiter& _parent) : parent(_parent) {} + KAddressArbiter& parent; - void WakeUp(ThreadWakeupReason reason, std::shared_ptr thread, - std::shared_ptr object) override { - parent.WakeUp(reason, std::move(thread), std::move(object)); + void WakeUp(ThreadWakeupReason reason, Thread* thread, + KSynchronizationObject* object) override { + parent.WakeUp(reason, thread, object); } private: @@ -100,16 +36,87 @@ private: friend class boost::serialization::access; }; -void AddressArbiter::WakeUp(ThreadWakeupReason reason, std::shared_ptr thread, - std::shared_ptr object) { +KAddressArbiter::KAddressArbiter(KernelSystem& kernel) + : KAutoObjectWithSlabHeapAndContainer{kernel}, + m_timeout_callback(std::make_shared(*this)) {} + +KAddressArbiter::~KAddressArbiter() = default; + +void KAddressArbiter::Initialize(Process* owner) { + m_owner = owner; + m_owner->Open(); +} + +void KAddressArbiter::PostDestroy(uintptr_t arg) { + Process* owner = reinterpret_cast(arg); + if (owner != nullptr) { + owner->ReleaseResource(ResourceLimitType::AddressArbiter, 1); + owner->Close(); + } +} + +void KAddressArbiter::WaitThread(Thread* thread, VAddr wait_address) { + thread->m_wait_address = wait_address; + thread->m_status = ThreadStatus::WaitArb; + m_waiting_threads.emplace_back(thread); +} + +u64 KAddressArbiter::ResumeAllThreads(VAddr address) { + // Determine which threads are waiting on this address, those should be woken up. + auto itr = std::stable_partition(m_waiting_threads.begin(), m_waiting_threads.end(), + [address](const auto& thread) { + ASSERT_MSG(thread->status == ThreadStatus::WaitArb, + "Inconsistent AddressArbiter state"); + return thread->wait_address != address; + }); + + // Wake up all the found threads + const u64 num_threads = std::distance(itr, m_waiting_threads.end()); + std::for_each(itr, m_waiting_threads.end(), [](auto& thread) { thread->ResumeFromWait(); }); + + // Remove the woken up threads from the wait list. + m_waiting_threads.erase(itr, m_waiting_threads.end()); + return num_threads; +} + +bool KAddressArbiter::ResumeHighestPriorityThread(VAddr address) { + // Determine which threads are waiting on this address, those should be considered for wakeup. + auto matches_start = std::stable_partition( + m_waiting_threads.begin(), m_waiting_threads.end(), [address](const auto& thread) { + ASSERT_MSG(thread->status == ThreadStatus::WaitArb, + "Inconsistent AddressArbiter state"); + return thread->wait_address != address; + }); + + // Iterate through threads, find highest priority thread that is waiting to be arbitrated. + // Note: The real kernel will pick the first thread in the list if more than one have the + // same highest priority value. Lower priority values mean higher priority. + auto itr = std::min_element(matches_start, m_waiting_threads.end(), + [](const auto lhs, const auto rhs) { + return lhs->GetCurrentPriority() < rhs->GetCurrentPriority(); + }); + + if (itr == m_waiting_threads.end()) { + return false; + } + + auto thread = *itr; + thread->ResumeFromWait(); + m_waiting_threads.erase(itr); + + return true; +} + +void KAddressArbiter::WakeUp(ThreadWakeupReason reason, Thread* thread, + KSynchronizationObject* object) { ASSERT(reason == ThreadWakeupReason::Timeout); // Remove the newly-awakened thread from the Arbiter's waiting list. - waiting_threads.erase(std::remove(waiting_threads.begin(), waiting_threads.end(), thread), - waiting_threads.end()); + m_waiting_threads.erase(std::remove(m_waiting_threads.begin(), m_waiting_threads.end(), thread), + m_waiting_threads.end()); }; -ResultCode AddressArbiter::ArbitrateAddress(std::shared_ptr thread, ArbitrationType type, - VAddr address, s32 value, u64 nanoseconds) { +ResultCode KAddressArbiter::ArbitrateAddress(Thread* thread, ArbitrationType type, VAddr address, + s32 value, u64 nanoseconds) { switch (type) { // Signal thread(s) waiting for arbitrate address... @@ -130,41 +137,42 @@ ResultCode AddressArbiter::ArbitrateAddress(std::shared_ptr thread, Arbi // The tick count is taken directly from official HOS kernel. The priority value is one less // than official kernel as the affected FMV threads dont meet the priority threshold of 50. // TODO: Revisit this when scheduler is rewritten and adjust if there isn't a problem there. - if (num_threads == 0 && thread->current_priority >= 49) { - kernel.current_cpu->GetTimer().AddTicks(1614u); + auto* core = m_kernel.current_cpu; + if (num_threads == 0 && core->GetID() == 0 && thread->GetCurrentPriority() >= 49) { + core->GetTimer().AddTicks(1614u); } break; } // Wait current thread (acquire the arbiter)... case ArbitrationType::WaitIfLessThan: - if ((s32)kernel.memory.Read32(address) < value) { - WaitThread(std::move(thread), address); + if ((s32)m_kernel.memory.Read32(address) < value) { + WaitThread(thread, address); } break; case ArbitrationType::WaitIfLessThanWithTimeout: - if ((s32)kernel.memory.Read32(address) < value) { - thread->wakeup_callback = timeout_callback; + if ((s32)m_kernel.memory.Read32(address) < value) { + thread->SetWakeupCallback(m_timeout_callback); thread->WakeAfterDelay(nanoseconds); - WaitThread(std::move(thread), address); + WaitThread(thread, address); } break; case ArbitrationType::DecrementAndWaitIfLessThan: { - s32 memory_value = kernel.memory.Read32(address); + s32 memory_value = m_kernel.memory.Read32(address); if (memory_value < value) { // Only change the memory value if the thread should wait - kernel.memory.Write32(address, (s32)memory_value - 1); - WaitThread(std::move(thread), address); + m_kernel.memory.Write32(address, (s32)memory_value - 1); + WaitThread(thread, address); } break; } case ArbitrationType::DecrementAndWaitIfLessThanWithTimeout: { - s32 memory_value = kernel.memory.Read32(address); + s32 memory_value = m_kernel.memory.Read32(address); if (memory_value < value) { // Only change the memory value if the thread should wait - kernel.memory.Write32(address, (s32)memory_value - 1); - thread->wakeup_callback = timeout_callback; + m_kernel.memory.Write32(address, (s32)memory_value - 1); + thread->SetWakeupCallback(m_timeout_callback); thread->WakeAfterDelay(nanoseconds); - WaitThread(std::move(thread), address); + WaitThread(thread, address); } break; } @@ -178,30 +186,23 @@ ResultCode AddressArbiter::ArbitrateAddress(std::shared_ptr thread, Arbi // the thread to sleep if (type == ArbitrationType::WaitIfLessThanWithTimeout || type == ArbitrationType::DecrementAndWaitIfLessThanWithTimeout) { - return RESULT_TIMEOUT; } - return RESULT_SUCCESS; + + R_SUCCEED(); } +template +void KAddressArbiter::serialize(Archive& ar, const unsigned int file_version) { + ar& boost::serialization::base_object(*this); + ar& m_name; + ar& m_waiting_threads; + ar& m_timeout_callback; +} + +SERIALIZE_IMPL(KAddressArbiter) + } // namespace Kernel -namespace boost::serialization { - -template -void save_construct_data(Archive& ar, const Kernel::AddressArbiter::Callback* t, - const unsigned int) { - ar << Kernel::SharedFrom(&t->parent); -} - -template -void load_construct_data(Archive& ar, Kernel::AddressArbiter::Callback* t, const unsigned int) { - std::shared_ptr parent; - ar >> parent; - ::new (t) Kernel::AddressArbiter::Callback(*parent); -} - -} // namespace boost::serialization - SERIALIZE_EXPORT_IMPL(Kernel::AddressArbiter) -SERIALIZE_EXPORT_IMPL(Kernel::AddressArbiter::Callback) +SERIALIZE_EXPORT_IMPL(Kernel::KAddressArbiter::Callback) diff --git a/src/core/hle/kernel/k_address_arbiter.h b/src/core/hle/kernel/k_address_arbiter.h new file mode 100644 index 000000000..c6f57a098 --- /dev/null +++ b/src/core/hle/kernel/k_address_arbiter.h @@ -0,0 +1,79 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include "core/hle/kernel/slab_helpers.h" +#include "core/hle/kernel/thread.h" +#include "core/hle/result.h" + +namespace Kernel { + +class Thread; + +enum class ArbitrationType : u32 { + Signal, + WaitIfLessThan, + DecrementAndWaitIfLessThan, + WaitIfLessThanWithTimeout, + DecrementAndWaitIfLessThanWithTimeout, +}; + +/** + * Address arbiters are an underlying kernel synchronization object that can be created/used via + * supervisor calls (SVCs). They function as sort of a global lock. Typically, games/other CTR + * applications use them as an underlying mechanism to implement thread-safe barriers, events, and + * semaphores. + **/ +class KAddressArbiter final : public KAutoObjectWithSlabHeapAndContainer, + public WakeupCallback { + KERNEL_AUTOOBJECT_TRAITS(KAddressArbiter, KAutoObject); + +public: + explicit KAddressArbiter(KernelSystem& kernel); + ~KAddressArbiter() override; + + void Initialize(Process* owner); + + uintptr_t GetPostDestroyArgument() const override { + return reinterpret_cast(m_owner); + } + + static void PostDestroy(uintptr_t arg); + + Process* GetOwner() const override { + return m_owner; + } + + ResultCode ArbitrateAddress(Thread* thread, ArbitrationType type, VAddr address, s32 value, + u64 nanoseconds); + +private: + void WaitThread(Thread* thread, VAddr wait_address); + + u64 ResumeAllThreads(VAddr address); + + bool ResumeHighestPriorityThread(VAddr address); + + void WakeUp(ThreadWakeupReason reason, Thread* thread, KSynchronizationObject* object) override; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const u32 file_version); + +public: + Process* m_owner{}; + std::string m_name{}; + std::vector m_waiting_threads; + class Callback; + std::shared_ptr m_timeout_callback; +}; + +} // namespace Kernel + +BOOST_CLASS_EXPORT_KEY(Kernel::KAddressArbiter) +BOOST_CLASS_EXPORT_KEY(Kernel::KAddressArbiter::Callback) diff --git a/src/core/hle/kernel/k_auto_object.h b/src/core/hle/kernel/k_auto_object.h index eae7159b8..d691828bc 100644 --- a/src/core/hle/kernel/k_auto_object.h +++ b/src/core/hle/kernel/k_auto_object.h @@ -15,26 +15,28 @@ namespace Kernel { class KernelSystem; class Process; +using Handle = u32; + enum class ClassTokenType : u32 { KAutoObject = 0, - WaitObject = 1, + KSynchronizationObject = 1, KSemaphore = 27, KEvent = 31, KTimer = 53, KMutex = 57, - KDebug = 77, + Debug = 77, KServerPort = 85, - KDmaObject = 89, + DmaObject = 89, KClientPort = 101, - KCodeSet = 104, + CodeSet = 104, KSession = 112, - KThread = 141, + Thread = 141, KServerSession = 149, KAddressArbiter = 152, KClientSession = 165, KPort = 168, KSharedMemory = 176, - KProcess = 197, + Process = 197, KResourceLimit = 200, }; DECLARE_ENUM_FLAG_OPERATORS(ClassTokenType) @@ -284,3 +286,11 @@ private: }; } // namespace Kernel + +#define CONSTRUCT_KERNEL_OBJECT(T) \ + namespace boost::serialization { \ + template \ + void load_construct_data(Archive& ar, T* t, const unsigned int file_version) { \ + ::new (t) T(Core::Global()); \ + } \ + } diff --git a/src/core/hle/kernel/k_auto_object_container.cpp b/src/core/hle/kernel/k_auto_object_container.cpp new file mode 100644 index 000000000..e33de97d1 --- /dev/null +++ b/src/core/hle/kernel/k_auto_object_container.cpp @@ -0,0 +1,31 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "core/hle/kernel/k_auto_object_container.h" + +namespace Kernel { + +void KAutoObjectWithListContainer::Register(KAutoObject* obj) { + // KScopedLightMutex lk{m_mutex}; + m_object_list.push_back(*obj); +} + +void KAutoObjectWithListContainer::Unregister(KAutoObject* obj) { + // KScopedLightMutex lk{m_mutex}; + for (auto it = m_object_list.begin(); it != m_object_list.end(); it++) { + if (std::addressof(*it) == obj) { + m_object_list.erase(it); + return; + } + } +} + +size_t KAutoObjectWithListContainer::GetOwnedCount(Process* owner) { + // KScopedLightMutex lk{m_mutex}; + return std::count_if(m_object_list.begin(), m_object_list.end(), + [&](const auto& obj) { return obj.GetOwner() == owner; }); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_auto_object_container.h b/src/core/hle/kernel/k_auto_object_container.h new file mode 100644 index 000000000..c9b13bbf7 --- /dev/null +++ b/src/core/hle/kernel/k_auto_object_container.h @@ -0,0 +1,37 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_funcs.h" +#include "core/hle/kernel/k_auto_object.h" +#include "core/hle/kernel/k_linked_list.h" + +namespace Kernel { + +class KernelSystem; +class Process; + +class KAutoObjectWithListContainer { +public: + CITRA_NON_COPYABLE(KAutoObjectWithListContainer); + CITRA_NON_MOVEABLE(KAutoObjectWithListContainer); + + using ListType = KLinkedList; + + KAutoObjectWithListContainer(KernelSystem& kernel) : m_object_list(kernel) {} + + void Initialize() {} + void Finalize() {} + + void Register(KAutoObject* obj); + void Unregister(KAutoObject* obj); + size_t GetOwnedCount(Process* owner); + +private: + // KLightMutex m_mutex; + ListType m_object_list; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_client_port.cpp b/src/core/hle/kernel/k_client_port.cpp new file mode 100644 index 000000000..6849c57c3 --- /dev/null +++ b/src/core/hle/kernel/k_client_port.cpp @@ -0,0 +1,69 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "common/archives.h" +#include "common/assert.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/k_client_port.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_server_port.h" +#include "core/hle/kernel/k_server_session.h" +#include "core/hle/kernel/k_session.h" + +SERIALIZE_EXPORT_IMPL(Kernel::KClientPort) + +namespace Kernel { + +KClientPort::KClientPort(KernelSystem& kernel) : KAutoObject(kernel) {} + +KClientPort::~KClientPort() = default; + +void KClientPort::Initialize(KPort* parent, s32 max_sessions) { + // Set member variables. + m_parent = parent; + m_max_sessions = max_sessions; +} + +ResultCode KClientPort::CreateSession(KClientSession** out) { + R_UNLESS(m_active_sessions < m_max_sessions, ERR_MAX_CONNECTIONS_REACHED); + m_active_sessions++; + + // Allocate a new session. + KSession* session = KSession::Create(m_kernel); + + // Initialize the session. + session->Initialize(this); + + // Register the session. + KSession::Register(m_kernel, session); + + // Wake the threads waiting on the ServerPort + m_server_port->WakeupAllWaitingThreads(); + + // We succeeded, so set the output. + *out = std::addressof(session->GetClientSession()); + R_SUCCEED(); +} + +void KClientPort::ConnectionClosed() { + ASSERT(m_active_sessions > 0); + --m_active_sessions; +} + +template +void KClientPort::serialize(Archive& ar, const u32 file_version) { + ar& boost::serialization::base_object(*this); + ar& m_server_port; + ar& m_max_sessions; + ar& m_active_sessions; + ar& m_name; +} + +SERIALIZE_IMPL(KClientPort) + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_client_port.h b/src/core/hle/kernel/k_client_port.h new file mode 100644 index 000000000..a263dd26d --- /dev/null +++ b/src/core/hle/kernel/k_client_port.h @@ -0,0 +1,52 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "core/hle/kernel/k_auto_object.h" +#include "core/hle/kernel/k_server_port.h" +#include "core/hle/result.h" + +namespace Kernel { + +class KClientSession; + +class KClientPort final : public KAutoObject { + KERNEL_AUTOOBJECT_TRAITS(KClientPort, KAutoObject); + +public: + explicit KClientPort(KernelSystem& kernel); + ~KClientPort() override; + + void Initialize(KPort* parent, s32 max_sessions); + + const KPort* GetParent() const { + return m_parent; + } + KPort* GetParent() { + return m_parent; + } + + ResultCode CreateSession(KClientSession** out); + void ConnectionClosed(); + +private: + KPort* m_parent{}; + u32 m_max_sessions{}; + u32 m_active_sessions{}; + std::string m_name; + + friend class KernelSystem; + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const u32 file_version); +}; + +} // namespace Kernel + +BOOST_CLASS_EXPORT_KEY(Kernel::KClientPort) +CONSTRUCT_KERNEL_OBJECT(Kernel::KClientPort) diff --git a/src/core/hle/kernel/k_client_session.cpp b/src/core/hle/kernel/k_client_session.cpp new file mode 100644 index 000000000..65af2a085 --- /dev/null +++ b/src/core/hle/kernel/k_client_session.cpp @@ -0,0 +1,40 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "common/archives.h" +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_server_session.h" +#include "core/hle/kernel/k_session.h" +#include "core/hle/kernel/thread.h" + +SERIALIZE_EXPORT_IMPL(Kernel::ClientSession) + +namespace Kernel { + +KClientSession::KClientSession(KernelSystem& kernel) : KAutoObject(kernel) {} + +KClientSession::~KClientSession() = default; + +void KClientSession::Destroy() { + m_parent->OnClientClosed(); + m_parent->Close(); +} + +void KClientSession::OnServerClosed() {} + +ResultCode KClientSession::SendSyncRequest(Thread* thread) { + // Signal the server session that new data is available + return m_parent->GetServerSession().HandleSyncRequest(thread); +} + +template +void KClientSession::serialize(Archive& ar, const u32 file_version) { + ar& boost::serialization::base_object(*this); + ar& m_parent; +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_client_session.h b/src/core/hle/kernel/k_client_session.h new file mode 100644 index 000000000..bf7572910 --- /dev/null +++ b/src/core/hle/kernel/k_client_session.h @@ -0,0 +1,49 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "core/hle/kernel/k_auto_object.h" +#include "core/hle/result.h" + +namespace Kernel { + +class KSession; +class Thread; + +class KClientSession final : public KAutoObject { + KERNEL_AUTOOBJECT_TRAITS(KClientSession, KAutoObject); + +public: + explicit KClientSession(KernelSystem& kernel); + ~KClientSession() override; + + void Initialize(KSession* parent) { + // Set member variables. + m_parent = parent; + } + + void Destroy() override; + + KSession* GetParent() const { + return m_parent; + } + + ResultCode SendSyncRequest(Thread* thread); + + void OnServerClosed(); + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const u32 file_version); + +private: + KSession* m_parent{}; +}; + +} // namespace Kernel + +BOOST_CLASS_EXPORT_KEY(Kernel::KClientSession) diff --git a/src/core/hle/kernel/k_code_set.h b/src/core/hle/kernel/k_code_set.h new file mode 100644 index 000000000..2c082b499 --- /dev/null +++ b/src/core/hle/kernel/k_code_set.h @@ -0,0 +1,76 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +namespace Kernel { + +class CodeSet { +public: + CodeSet() = default; + ~CodeSet() = default; + + struct Segment { + std::size_t offset = 0; + VAddr addr = 0; + u32 size = 0; + + private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const u32 file_version) { + ar& offset; + ar& addr; + ar& size; + } + }; + + Segment& CodeSegment() { + return segments[0]; + } + + const Segment& CodeSegment() const { + return segments[0]; + } + + Segment& RODataSegment() { + return segments[1]; + } + + const Segment& RODataSegment() const { + return segments[1]; + } + + Segment& DataSegment() { + return segments[2]; + } + + const Segment& DataSegment() const { + return segments[2]; + } + + std::vector memory; + + std::array segments; + VAddr entrypoint; + + u64 program_id; + std::string name; + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const u32 file_version) { + ar& memory; + ar& segments; + ar& entrypoint; + ar& program_id; + ar& name; + } +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_event.cpp b/src/core/hle/kernel/k_event.cpp new file mode 100644 index 000000000..7a41895ab --- /dev/null +++ b/src/core/hle/kernel/k_event.cpp @@ -0,0 +1,78 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/archives.h" +#include "common/assert.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/thread.h" + +SERIALIZE_EXPORT_IMPL(Kernel::KEvent) + +namespace Kernel { + +KEvent::KEvent(KernelSystem& kernel) : KAutoObjectWithSlabHeapAndContainer(kernel) {} + +KEvent::~KEvent() = default; + +void KEvent::Initialize(Process* owner, ResetType reset_type) { + // Open a reference to the owner process. + if (owner) { + owner->Open(); + m_owner = owner; + } + + // Set member variables. + m_reset_type = reset_type; +} + +void KEvent::PostDestroy(uintptr_t arg) { + Process* owner = reinterpret_cast(arg); + if (owner != nullptr) { + owner->ReleaseResource(ResourceLimitType::Event, 1); + owner->Close(); + } +} + +bool KEvent::ShouldWait(const Thread* thread) const { + return !m_signaled; +} + +void KEvent::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); + if (m_reset_type == ResetType::OneShot) { + m_signaled = false; + } +} + +void KEvent::Signal() { + m_signaled = true; + this->WakeupAllWaitingThreads(); +} + +void KEvent::Clear() { + m_signaled = false; +} + +void KEvent::WakeupAllWaitingThreads() { + KSynchronizationObject::WakeupAllWaitingThreads(); + if (m_reset_type == ResetType::Pulse) { + m_signaled = false; + } +} + +template +void KEvent::serialize(Archive& ar, const u32 file_version) { + ar& boost::serialization::base_object(*this); + ar& m_owner; + ar& m_reset_type; + ar& m_signaled; +} + +SERIALIZE_IMPL(KEvent) + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_event.h b/src/core/hle/kernel/k_event.h new file mode 100644 index 000000000..ef5e750d1 --- /dev/null +++ b/src/core/hle/kernel/k_event.h @@ -0,0 +1,67 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "core/global.h" +#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/kernel/slab_helpers.h" + +namespace Kernel { + +class KEvent final : public KAutoObjectWithSlabHeapAndContainer { + KERNEL_AUTOOBJECT_TRAITS(KEvent, KSynchronizationObject); + +public: + explicit KEvent(KernelSystem& kernel); + ~KEvent() override; + + std::string GetName() const { + return m_name; + } + void SetName(const std::string& name) { + m_name = name; + } + + void Initialize(Process* owner, ResetType reset_type); + + uintptr_t GetPostDestroyArgument() const override { + return reinterpret_cast(m_owner); + } + + static void PostDestroy(uintptr_t arg); + + Process* GetOwner() const override { + return m_owner; + } + + ResetType GetResetType() const { + return m_reset_type; + } + + bool ShouldWait(const Thread* thread) const override; + void Acquire(Thread* thread) override; + + void WakeupAllWaitingThreads() override; + + void Signal(); + void Clear(); + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const u32 file_version); + +private: + Process* m_owner{}; + ResetType m_reset_type{}; + bool m_signaled{}; + std::string m_name; +}; + +} // namespace Kernel + +BOOST_CLASS_EXPORT_KEY(Kernel::KEvent) +CONSTRUCT_KERNEL_OBJECT(Kernel::KEvent) diff --git a/src/core/hle/kernel/k_handle_table.cpp b/src/core/hle/kernel/k_handle_table.cpp new file mode 100644 index 000000000..55fc6139c --- /dev/null +++ b/src/core/hle/kernel/k_handle_table.cpp @@ -0,0 +1,107 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "common/archives.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/k_handle_table.h" +#include "core/hle/kernel/process.h" + +namespace Kernel { + +ResultCode KHandleTable::Finalize() { + // Close and free all entries. + for (size_t i = 0; i < m_table_size; i++) { + if (KAutoObject* obj = m_objects[i]; obj != nullptr) { + obj->Close(); + } + } + + R_SUCCEED(); +} + +bool KHandleTable::Remove(Handle handle) { + // Don't allow removal of a pseudo-handle. + if (handle == KernelHandle::CurrentProcess || handle == KernelHandle::CurrentThread) + [[unlikely]] { + return false; + } + + // Handles must not have reserved bits set. + const auto handle_pack = HandlePack(handle); + if (handle_pack.reserved != 0) [[unlikely]] { + return false; + } + + // Find the object and free the entry. + KAutoObject* obj = nullptr; + { + // KScopedLightMutex lk{m_mutex}; + if (this->IsValidHandle(handle)) [[likely]] { + const auto index = handle_pack.index; + + obj = m_objects[index]; + this->FreeEntry(index); + } else { + return false; + } + } + + // Close the object. + obj->Close(); + return true; +} + +ResultCode KHandleTable::Add(Handle* out_handle, KAutoObject* obj) { + // KScopedLightMutex lk{m_mutex}; + + // Never exceed our capacity. + R_UNLESS(m_count < m_table_size, ERR_OUT_OF_HANDLES); + + // Allocate entry, set output handle. + const auto linear_id = this->AllocateLinearId(); + const auto index = this->AllocateEntry(); + + m_entry_infos[index].linear_id = linear_id; + m_objects[index] = obj; + + obj->Open(); + + *out_handle = EncodeHandle(static_cast(index), linear_id); + + R_SUCCEED(); +} + +KScopedAutoObject KHandleTable::GetObjectForIpc(Handle handle, + Thread* cur_thread) const { + // Handle pseudo-handles. + ASSERT(cur_thread != nullptr); + if (handle == KernelHandle::CurrentProcess) { + auto* cur_process = cur_thread->GetOwner(); + ASSERT(cur_process != nullptr); + return cur_process; + } + if (handle == KernelHandle::CurrentThread) { + return cur_thread; + } + + return this->GetObjectForIpcWithoutPseudoHandle(handle); +} + +template +void KHandleTable::serialize(Archive& ar, const u32 file_version) { + ar& m_entry_infos; + ar& m_objects; + ar& m_free_head_index; + ar& m_table_size; + ar& m_next_id; + ar& m_max_count; + ar& m_next_linear_id; + ar& m_count; +} + +SERIALIZE_IMPL(KHandleTable) + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_handle_table.cpp.autosave b/src/core/hle/kernel/k_handle_table.cpp.autosave new file mode 100644 index 000000000..f55163ce3 --- /dev/null +++ b/src/core/hle/kernel/k_handle_table.cpp.autosave @@ -0,0 +1,106 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "common/archives.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/k_handle_table.h" + +namespace Kernel { + +ResultCode KHandleTable::Finalize() { + // Close and free all entries. + for (size_t i = 0; i < m_table_size; i++) { + if (KAutoObject* obj = m_objects[i]; obj != nullptr) { + obj->Close(); + } + } + + R_SUCCEED(); +} + +bool KHandleTable::Remove(Handle handle) { + // Don't allow removal of a pseudo-handle. + if (handle == KernelHandle::CurrentProcess || handle == KernelHandle::CurrentThread) + [[unlikely]] { + return false; + } + + // Handles must not have reserved bits set. + const auto handle_pack = HandlePack(handle); + if (handle_pack.reserved != 0) [[unlikely]] { + return false; + } + + // Find the object and free the entry. + KAutoObject* obj = nullptr; + { + // KScopedLightMutex lk{m_mutex}; + if (this->IsValidHandle(handle)) [[likely]] { + const auto index = handle_pack.index; + + obj = m_objects[index]; + this->FreeEntry(index); + } else { + return false; + } + } + + // Close the object. + obj->Close(); + return true; +} + +ResultCode KHandleTable::Add(Handle* out_handle, KAutoObject* obj) { + // KScopedLightMutex lk{m_mutex}; + + // Never exceed our capacity. + R_UNLESS(m_count < m_table_size, ERR_OUT_OF_HANDLES); + + // Allocate entry, set output handle. + const auto linear_id = this->AllocateLinearId(); + const auto index = this->AllocateEntry(); + + m_entry_infos[index].linear_id = linear_id; + m_objects[index] = obj; + + obj->Open(); + + *out_handle = EncodeHandle(static_cast(index), linear_id); + + R_SUCCEED(); +} + +KScopedAutoObject KHandleTable::GetObjectForIpc(Handle handle, + Thread* cur_thread) const { + // Handle pseudo-handles. + ASSERT(cur_thread != nullptr); + if (handle == KernelHandle::CurrentProcess) { + auto* cur_process = cur_thread->GetOwner(); + ASSERT(cur_process != nullptr); + return cur_process; + } + if (handle == KernelHandle::CurrentThread) { + return cur_thread; + } + + return this->GetObjectForIpcWithoutPseudoHandle(handle); +} + +template +void KHandleTable::serialize(Archive& ar, const u32 file_version) { + ar& m_entry_infos; + ar& m_objects; + ar& m_free_head_index; + ar& m_table_size; + ar& m_next_id; + ar& m_max_count; + ar& m_next_linear_id; + ar& m_count; +} + +SERIALIZE_IMPL(KHandleTable) + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_handle_table.h b/src/core/hle/kernel/k_handle_table.h new file mode 100644 index 000000000..68ca49920 --- /dev/null +++ b/src/core/hle/kernel/k_handle_table.h @@ -0,0 +1,279 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/common_types.h" +#include "core/hle/kernel/k_auto_object.h" +#include "core/hle/kernel/thread.h" +#include "core/hle/result.h" + +namespace Kernel { + +enum KernelHandle : Handle { + CurrentThread = 0xFFFF8000, + CurrentProcess = 0xFFFF8001, +}; + +class KHandleTable { + CITRA_NON_COPYABLE(KHandleTable); + CITRA_NON_MOVEABLE(KHandleTable); + +public: + static constexpr size_t MaxTableSize = 1024; + +public: + explicit KHandleTable(KernelSystem& kernel) : m_kernel(kernel) {} + + ResultCode Initialize(s32 size) { + // KScopedLightMutex lk{m_mutex}; + + // Initialize all fields. + m_max_count = 0; + m_table_size = static_cast((size <= 0) ? MaxTableSize : size); + m_next_linear_id = MinLinearId; + m_count = 0; + m_free_head_index = -1; + + // Create the arrays + m_objects.resize(m_table_size); + m_entry_infos.resize(m_table_size); + + // 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 = static_cast(i - 1); + m_free_head_index = i; + } + + R_SUCCEED(); + } + + size_t GetTableSize() const { + return m_table_size; + } + size_t GetCount() const { + return m_count; + } + size_t GetMaxCount() const { + return m_max_count; + } + + ResultCode Finalize(); + bool Remove(Handle handle); + ResultCode Add(Handle* out_handle, KAutoObject* obj); + + template + KScopedAutoObject GetObjectWithoutPseudoHandle(Handle handle) const { + // KScopedLightMutex lk{m_mutex}; + + if constexpr (std::is_same_v) { + return this->GetObjectImpl(handle); + } else { + if (auto* obj = this->GetObjectImpl(handle); obj != nullptr) [[likely]] { + return obj->DynamicCast(); + } else { + return nullptr; + } + } + } + + template + KScopedAutoObject GetObject(Handle handle) const { + // Handle pseudo-handles. + if constexpr (std::derived_from) { + if (handle == KernelHandle::CurrentProcess) { + auto* const cur_process = m_kernel.GetCurrentProcess(); + ASSERT(cur_process != nullptr); + return cur_process; + } + } else if constexpr (std::derived_from) { + if (handle == KernelHandle::CurrentThread) { + auto* const cur_thread = m_kernel.GetCurrentThreadManager().GetCurrentThread(); + ASSERT(cur_thread != nullptr); + return cur_thread; + } + } + + return this->template GetObjectWithoutPseudoHandle(handle); + } + + KScopedAutoObject GetObjectForIpcWithoutPseudoHandle(Handle handle) const { + return this->GetObjectImpl(handle); + } + + KScopedAutoObject GetObjectForIpc(Handle handle, Thread* cur_thread) const; + + template + bool GetMultipleObjects(T** out, const Handle* handles, size_t num_handles) const { + // Try to convert and open all the handles. + size_t num_opened; + { + // KScopedLightMutex lk{m_mutex}; + 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 (cur_object == nullptr) [[unlikely]] { + break; + } + + // Cast the current object to the desired type. + T* cur_t = cur_object->DynamicCast(); + if (cur_t == nullptr) [[unlikely]] { + break; + } + + // Open a reference to the current object. + cur_t->Open(); + out[num_opened] = cur_t; + } + } + + // If we converted every object, succeed. + if (num_opened == num_handles) [[likely]] { + 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: + s32 AllocateEntry() { + 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; + } + + void FreeEntry(s32 index) { + ASSERT(m_count > 0); + + m_objects[index] = nullptr; + m_entry_infos[index].next_free_index = static_cast(m_free_head_index); + + m_free_head_index = index; + + --m_count; + } + + u16 AllocateLinearId() { + const u16 id = m_next_linear_id++; + if (m_next_linear_id > MaxLinearId) { + m_next_linear_id = MinLinearId; + } + return id; + } + + bool IsValidHandle(Handle handle) const { + // Unpack the handle. + const auto handle_pack = HandlePack(handle); + const auto raw_value = handle_pack.raw; + const auto index = handle_pack.index; + const auto linear_id = handle_pack.linear_id; + const auto reserved = handle_pack.reserved; + ASSERT(reserved == 0); + + // Validate our indexing information. + if (raw_value == 0) [[unlikely]] { + return false; + } + if (linear_id == 0) [[unlikely]] { + return false; + } + if (index >= m_table_size) [[unlikely]] { + return false; + } + + // Check that there's an object, and our serial id is correct. + if (m_objects[index] == nullptr) [[unlikely]] { + return false; + } + if (m_entry_infos[index].GetLinearId() != linear_id) [[unlikely]] { + return false; + } + + return true; + } + + KAutoObject* GetObjectImpl(Handle handle) const { + // Handles must not have reserved bits set. + const auto handle_pack = HandlePack(handle); + if (handle_pack.reserved != 0) [[unlikely]] { + return nullptr; + } + + if (this->IsValidHandle(handle)) [[likely]] { + return m_objects[handle_pack.index]; + } else { + return nullptr; + } + } + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const u32 file_version); + +private: + union HandlePack { + constexpr HandlePack() = default; + constexpr HandlePack(Handle handle) : raw{static_cast(handle)} {} + + u32 raw{}; + BitField<0, 15, u32> index; + BitField<15, 15, u32> linear_id; + BitField<30, 2, u32> reserved; + }; + + static constexpr Handle EncodeHandle(u16 index, u16 linear_id) { + HandlePack handle{}; + handle.index.Assign(index); + handle.linear_id.Assign(linear_id); + handle.reserved.Assign(0); + return handle.raw; + } + +private: + static constexpr u16 MinLinearId = 1; + static constexpr u16 MaxLinearId = 0x7FFF; + + union EntryInfo { + u16 linear_id; + s16 next_free_index; + + constexpr u16 GetLinearId() const { + return linear_id; + } + constexpr s32 GetNextFreeIndex() const { + return next_free_index; + } + }; + +private: + KernelSystem& m_kernel; + std::vector m_entry_infos{}; + std::vector m_objects{}; + s32 m_free_head_index{}; + u16 m_table_size{}; + u16 m_next_id{}; + u16 m_max_count{}; + u16 m_next_linear_id{}; + u16 m_count{}; + // KLightMutex mutex; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_linked_list.h b/src/core/hle/kernel/k_linked_list.h new file mode 100644 index 000000000..c2de2facf --- /dev/null +++ b/src/core/hle/kernel/k_linked_list.h @@ -0,0 +1,237 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/intrusive_list.h" +#include "core/hle/kernel/slab_helpers.h" + +namespace Kernel { + +class KernelSystem; + +class KLinkedListNode : public Common::IntrusiveListBaseNode, + public KSlabAllocated { + +public: + explicit KLinkedListNode(KernelSystem&) {} + KLinkedListNode() = default; + + void Initialize(void* it) { + m_item = it; + } + + void* GetItem() const { + return m_item; + } + +private: + void* m_item = nullptr; +}; + +template +class KLinkedList : private Common::IntrusiveListBaseTraits::ListType { +private: + using BaseList = Common::IntrusiveListBaseTraits::ListType; + +public: + template + class Iterator; + + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + using pointer = value_type*; + using const_pointer = const value_type*; + using reference = value_type&; + using const_reference = const value_type&; + using iterator = Iterator; + using const_iterator = Iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + template + class Iterator { + private: + using BaseIterator = BaseList::iterator; + friend class KLinkedList; + + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename KLinkedList::value_type; + using difference_type = typename KLinkedList::difference_type; + using pointer = std::conditional_t; + using reference = + std::conditional_t; + + public: + explicit Iterator(BaseIterator it) : m_base_it(it) {} + + pointer GetItem() const { + return static_cast(m_base_it->GetItem()); + } + + bool operator==(const Iterator& rhs) const { + return m_base_it == rhs.m_base_it; + } + + bool operator!=(const Iterator& rhs) const { + return !(*this == rhs); + } + + pointer operator->() const { + return this->GetItem(); + } + + reference operator*() const { + return *this->GetItem(); + } + + Iterator& operator++() { + ++m_base_it; + return *this; + } + + Iterator& operator--() { + --m_base_it; + return *this; + } + + Iterator operator++(int) { + const Iterator it{*this}; + ++(*this); + return it; + } + + Iterator operator--(int) { + const Iterator it{*this}; + --(*this); + return it; + } + + operator Iterator() const { + return Iterator(m_base_it); + } + + private: + BaseIterator m_base_it; + }; + +public: + constexpr KLinkedList(KernelSystem& kernel_) : BaseList(), kernel{kernel_} {} + + ~KLinkedList() { + // Erase all elements. + for (auto it = begin(); it != end(); it = erase(it)) { + } + + // Ensure we succeeded. + ASSERT(this->empty()); + } + + // Iterator accessors. + iterator begin() { + return iterator(BaseList::begin()); + } + + const_iterator begin() const { + return const_iterator(BaseList::begin()); + } + + iterator end() { + return iterator(BaseList::end()); + } + + const_iterator end() const { + return const_iterator(BaseList::end()); + } + + const_iterator cbegin() const { + return this->begin(); + } + + const_iterator cend() const { + return this->end(); + } + + reverse_iterator rbegin() { + return reverse_iterator(this->end()); + } + + const_reverse_iterator rbegin() const { + return const_reverse_iterator(this->end()); + } + + reverse_iterator rend() { + return reverse_iterator(this->begin()); + } + + const_reverse_iterator rend() const { + return const_reverse_iterator(this->begin()); + } + + const_reverse_iterator crbegin() const { + return this->rbegin(); + } + + const_reverse_iterator crend() const { + return this->rend(); + } + + // Content management. + using BaseList::empty; + using BaseList::size; + + reference back() { + return *(--this->end()); + } + + const_reference back() const { + return *(--this->end()); + } + + reference front() { + return *this->begin(); + } + + const_reference front() const { + return *this->begin(); + } + + iterator insert(const_iterator pos, reference ref) { + KLinkedListNode* new_node = KLinkedListNode::Allocate(kernel); + ASSERT(new_node != nullptr); + new_node->Initialize(std::addressof(ref)); + return iterator(BaseList::insert(pos.m_base_it, *new_node)); + } + + void push_back(reference ref) { + this->insert(this->end(), ref); + } + + void push_front(reference ref) { + this->insert(this->begin(), ref); + } + + void pop_back() { + this->erase(--this->end()); + } + + void pop_front() { + this->erase(this->begin()); + } + + iterator erase(const iterator pos) { + KLinkedListNode* freed_node = std::addressof(*pos.m_base_it); + iterator ret = iterator(BaseList::erase(pos.m_base_it)); + KLinkedListNode::Free(kernel, freed_node); + + return ret; + } + +private: + KernelSystem& kernel; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_mutex.cpp b/src/core/hle/kernel/k_mutex.cpp new file mode 100644 index 000000000..e2486c695 --- /dev/null +++ b/src/core/hle/kernel/k_mutex.cpp @@ -0,0 +1,151 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/archives.h" +#include "common/assert.h" +#include "core/core.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/k_mutex.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/thread.h" + +SERIALIZE_EXPORT_IMPL(Kernel::KMutex) + +namespace Kernel { + +void ReleaseThreadMutexes(Thread* thread) { + for (KMutex* mtx : thread->m_held_mutexes) { + mtx->m_lock_count = 0; + mtx->m_holding_thread = nullptr; + mtx->WakeupAllWaitingThreads(); + } + thread->m_held_mutexes.clear(); +} + +KMutex::KMutex(KernelSystem& kernel) : KAutoObjectWithSlabHeapAndContainer(kernel) {} + +KMutex::~KMutex() = default; + +void KMutex::Initialize(Process* owner, bool initial_locked) { + // Open a reference to the owner process. + if (owner) { + owner->Open(); + m_owner = owner; + } + + // Set default priority + m_priority = ThreadPrioLowest; + + // Acquire mutex with current thread if initialized as locked + if (initial_locked) { + Thread* thread = m_kernel.GetCurrentThreadManager().GetCurrentThread(); + this->Acquire(thread); + } +} + +void KMutex::PostDestroy(uintptr_t arg) { + Process* owner = reinterpret_cast(arg); + if (owner != nullptr) { + owner->ReleaseResource(ResourceLimitType::Mutex, 1); + owner->Close(); + } +} + +bool KMutex::ShouldWait(const Thread* thread) const { + return m_lock_count > 0 && thread != m_holding_thread; +} + +void KMutex::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); + + // Actually "acquire" the mutex only if we don't already have it + if (m_lock_count == 0) { + m_priority = thread->m_current_priority; + thread->m_held_mutexes.insert(this); + m_holding_thread = thread; + thread->UpdatePriority(); + m_kernel.PrepareReschedule(); + } + + m_lock_count++; +} + +ResultCode KMutex::Release(Thread* thread) { + // We can only release the mutex if it's held by the calling thread. + if (thread != m_holding_thread) { + if (m_holding_thread) { + LOG_ERROR( + Kernel, + "Tried to release a mutex (owned by thread id {}) from a different thread id {}", + m_holding_thread->m_thread_id, thread->m_thread_id); + } + return ResultCode(ErrCodes::WrongLockingThread, ErrorModule::Kernel, + ErrorSummary::InvalidArgument, ErrorLevel::Permanent); + } + + // Note: It should not be possible for the situation where the mutex has a holding thread with a + // zero lock count to occur. The real kernel still checks for this, so we do too. + if (m_lock_count <= 0) { + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::Kernel, + ErrorSummary::InvalidState, ErrorLevel::Permanent); + } + + m_lock_count--; + + // Yield to the next thread only if we've fully released the mutex + if (m_lock_count == 0) { + m_holding_thread->m_held_mutexes.erase(this); + m_holding_thread->UpdatePriority(); + m_holding_thread = nullptr; + WakeupAllWaitingThreads(); + m_kernel.PrepareReschedule(); + } + + R_SUCCEED(); +} + +void KMutex::AddWaitingThread(Thread* thread) { + KSynchronizationObject::AddWaitingThread(thread); + thread->m_pending_mutexes.insert(this); + this->UpdatePriority(); +} + +void KMutex::RemoveWaitingThread(Thread* thread) { + KSynchronizationObject::RemoveWaitingThread(thread); + thread->m_pending_mutexes.erase(this); + this->UpdatePriority(); +} + +void KMutex::UpdatePriority() { + if (!m_holding_thread) { + return; + } + + u32 best_priority = ThreadPrioLowest; + for (const Thread* waiter : GetWaitingThreads()) { + if (waiter->m_current_priority < best_priority) { + best_priority = waiter->m_current_priority; + } + } + + if (best_priority != m_priority) { + m_priority = best_priority; + m_holding_thread->UpdatePriority(); + } +} + +template +void KMutex::serialize(Archive& ar, const u32 file_version) { + ar& boost::serialization::base_object(*this); + ar& m_lock_count; + ar& m_priority; + ar& m_holding_thread; +} + +SERIALIZE_IMPL(KMutex) + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_mutex.h b/src/core/hle/kernel/k_mutex.h new file mode 100644 index 000000000..90affdaa0 --- /dev/null +++ b/src/core/hle/kernel/k_mutex.h @@ -0,0 +1,81 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "core/global.h" +#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/slab_helpers.h" +#include "core/hle/result.h" + +namespace Kernel { + +class Thread; + +class KMutex final : public KAutoObjectWithSlabHeapAndContainer { + KERNEL_AUTOOBJECT_TRAITS(KMutex, KSynchronizationObject); + +public: + explicit KMutex(KernelSystem& kernel); + ~KMutex() override; + + void Initialize(Process* owner, bool initial_locked); + + uintptr_t GetPostDestroyArgument() const override { + return reinterpret_cast(m_owner); + } + + static void PostDestroy(uintptr_t arg); + + Process* GetOwner() const override { + return m_owner; + } + + u32 GetPriority() const { + return m_priority; + } + + bool ShouldWait(const Thread* thread) const override; + void Acquire(Thread* thread) override; + + void AddWaitingThread(Thread* thread) override; + void RemoveWaitingThread(Thread* thread) override; + + /** + * Elevate the mutex priority to the best priority + * among the priorities of all its waiting threads. + */ + void UpdatePriority(); + + /** + * Attempts to release the mutex from the specified thread. + * @param thread Thread that wants to release the mutex. + * @returns The result code of the operation. + */ + ResultCode Release(Thread* thread); + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const u32 file_version); + +public: + Process* m_owner{}; + int m_lock_count{}; + u32 m_priority{}; + Thread* m_holding_thread{}; +}; + +/** + * Releases all the mutexes held by the specified thread + * @param thread Thread that is holding the mutexes + */ +void ReleaseThreadMutexes(Thread* thread); + +} // namespace Kernel + +BOOST_CLASS_EXPORT_KEY(Kernel::KMutex) +CONSTRUCT_KERNEL_OBJECT(Kernel::KMutex) diff --git a/src/core/hle/kernel/k_object_name.cpp b/src/core/hle/kernel/k_object_name.cpp new file mode 100644 index 000000000..ff715b162 --- /dev/null +++ b/src/core/hle/kernel/k_object_name.cpp @@ -0,0 +1,103 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/kernel/k_object_name.h" + +namespace Kernel { + +KObjectNameGlobalData::KObjectNameGlobalData(KernelSystem& kernel) {} + +KObjectNameGlobalData::~KObjectNameGlobalData() = default; + +void KObjectName::Initialize(KAutoObject* obj, const char* name) { + // Set member variables. + m_object = obj; + std::strncpy(m_name.data(), name, sizeof(m_name) - 1); + m_name[sizeof(m_name) - 1] = '\x00'; + + // Open a reference to the object we hold. + m_object->Open(); +} + +bool KObjectName::MatchesName(const char* name) const { + return std::strncmp(m_name.data(), name, sizeof(m_name)) == 0; +} + +ResultCode KObjectName::NewFromName(KernelSystem& kernel, KAutoObject* obj, const char* name) { + // Create a new object name. + KObjectName* new_name = KObjectName::Allocate(kernel); + R_UNLESS(new_name != nullptr, ResultCode{0xD86007F3}); + + // Initialize the new name. + new_name->Initialize(obj, name); + + // Check if there's an existing name. + { + // Get the global data. + KObjectNameGlobalData& gd{kernel.ObjectNameGlobalData()}; + + // Ensure we have exclusive access to the global list. + // KScopedLightMutex lk{gd.GetObjectListLock()}; + + // If the object doesn't exist, put it into the list. + KScopedAutoObject existing_object = FindImpl(kernel, name); + if (existing_object.IsNull()) { + gd.GetObjectList().push_back(*new_name); + R_SUCCEED(); + } + } + + // The object already exists, the kernel does not check for this. + UNREACHABLE(); +} + +ResultCode KObjectName::Delete(KernelSystem& kernel, KAutoObject* obj, const char* compare_name) { + // Get the global data. + KObjectNameGlobalData& gd{kernel.ObjectNameGlobalData()}; + + // Ensure we have exclusive access to the global list. + // KScopedLightMutex lk{gd.GetObjectListLock()}; + + // Find a matching entry in the list, and delete it. + for (auto& name : gd.GetObjectList()) { + if (name.MatchesName(compare_name) && obj == name.GetObject()) { + // We found a match, clean up its resources. + obj->Close(); + gd.GetObjectList().erase(gd.GetObjectList().iterator_to(name)); + KObjectName::Free(kernel, std::addressof(name)); + R_SUCCEED(); + } + } + + // We didn't find the object in the list. + R_THROW(ERR_NOT_FOUND); +} + +KScopedAutoObject KObjectName::Find(KernelSystem& kernel, const char* name) { + // Get the global data. + // KObjectNameGlobalData& gd{kernel.ObjectNameGlobalData()}; + + // Ensure we have exclusive access to the global list. + // KScopedLightMutex lk{gd.GetObjectListLock()}; + + return FindImpl(kernel, name); +} + +KScopedAutoObject KObjectName::FindImpl(KernelSystem& kernel, + const char* compare_name) { + // Get the global data. + KObjectNameGlobalData& gd{kernel.ObjectNameGlobalData()}; + + // Try to find a matching object in the global list. + for (const auto& name : gd.GetObjectList()) { + if (name.MatchesName(compare_name)) { + return name.GetObject(); + } + } + + // There's no matching entry in the list. + return nullptr; +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_object_name.h b/src/core/hle/kernel/k_object_name.h new file mode 100644 index 000000000..b77143fd0 --- /dev/null +++ b/src/core/hle/kernel/k_object_name.h @@ -0,0 +1,81 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/intrusive_list.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/slab_helpers.h" + +namespace Kernel { + +class KObjectNameGlobalData; + +class KObjectName : public KSlabAllocated, + public Common::IntrusiveListBaseNode { +public: + explicit KObjectName(KernelSystem&) {} + virtual ~KObjectName() = default; + + static constexpr size_t NameLengthMax = 12; + using List = Common::IntrusiveListBaseTraits::ListType; + + static ResultCode NewFromName(KernelSystem& kernel, KAutoObject* obj, const char* name); + static ResultCode Delete(KernelSystem& kernel, KAutoObject* obj, const char* name); + + static KScopedAutoObject Find(KernelSystem& kernel, const char* name); + + template + static ResultCode Delete(KernelSystem& kernel, const char* name) { + // Find the object. + KScopedAutoObject obj = Find(kernel, name); + R_UNLESS(obj.IsNotNull(), ERR_NOT_FOUND); + + // Cast the object to the desired type. + Derived* derived = obj->DynamicCast(); + R_UNLESS(derived != nullptr, ERR_NOT_FOUND); + + // Check that the object is closed. + R_UNLESS(derived->IsServerClosed(), ERR_INVALID_ADDRESS_STATE); + + R_RETURN(Delete(kernel, obj.GetPointerUnsafe(), name)); + } + + template + requires(std::derived_from) + static KScopedAutoObject Find(KernelSystem& kernel, const char* name) { + return Find(kernel, name); + } + +private: + static KScopedAutoObject FindImpl(KernelSystem& kernel, const char* name); + + void Initialize(KAutoObject* obj, const char* name); + + bool MatchesName(const char* name) const; + KAutoObject* GetObject() const { + return m_object; + } + +private: + std::array m_name{}; + KAutoObject* m_object{}; +}; + +class KObjectNameGlobalData { +public: + explicit KObjectNameGlobalData(KernelSystem& kernel); + ~KObjectNameGlobalData(); + + KObjectName::List& GetObjectList() { + return m_object_list; + } + +private: + // KMutex m_mutex; + KObjectName::List m_object_list; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_port.cpp b/src/core/hle/kernel/k_port.cpp new file mode 100644 index 000000000..4044c2123 --- /dev/null +++ b/src/core/hle/kernel/k_port.cpp @@ -0,0 +1,25 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/kernel/k_port.h" + +namespace Kernel { + +KPort::KPort(KernelSystem& kernel) + : KAutoObjectWithSlabHeapAndContainer{kernel}, m_server{kernel}, m_client{kernel} {} + +KPort::~KPort() = default; + +void KPort::Initialize(s32 max_sessions) { + // Open a new reference count to the initialized port. + this->Open(); + + // Create and initialize our server/client pair. + KAutoObject::Create(std::addressof(m_server)); + KAutoObject::Create(std::addressof(m_client)); + m_server.Initialize(this); + m_client.Initialize(this, max_sessions); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_port.h b/src/core/hle/kernel/k_port.h new file mode 100644 index 000000000..486e2a778 --- /dev/null +++ b/src/core/hle/kernel/k_port.h @@ -0,0 +1,52 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "core/hle/kernel/k_client_port.h" +#include "core/hle/kernel/k_server_port.h" +#include "core/hle/kernel/slab_helpers.h" +#include "core/hle/result.h" + +namespace Kernel { + +class KServerSession; + +class KPort final : public KAutoObjectWithSlabHeapAndContainer { + KERNEL_AUTOOBJECT_TRAITS(KPort, KAutoObject); + +public: + explicit KPort(KernelSystem& kernel); + ~KPort() override; + + static void PostDestroy(uintptr_t arg) {} + + void Initialize(s32 max_sessions); + void OnClientClosed(); + void OnServerClosed(); + + bool IsServerClosed() const; + + ResultCode EnqueueSession(KServerSession* session); + + KClientPort& GetClientPort() { + return m_client; + } + KServerPort& GetServerPort() { + return m_server; + } + const KClientPort& GetClientPort() const { + return m_client; + } + const KServerPort& GetServerPort() const { + return m_server; + } + +private: + KServerPort m_server; + KClientPort m_client; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp similarity index 82% rename from src/core/hle/kernel/resource_limit.cpp rename to src/core/hle/kernel/k_resource_limit.cpp index 8b57f22c4..8d1cccf9c 100644 --- a/src/core/hle/kernel/resource_limit.cpp +++ b/src/core/hle/kernel/k_resource_limit.cpp @@ -1,42 +1,37 @@ -// Copyright 2015 Citra Emulator Project +// Copyright 2023 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include "common/archives.h" #include "common/assert.h" #include "common/settings.h" -#include "core/hle/kernel/resource_limit.h" +#include "core/hle/kernel/k_resource_limit.h" -SERIALIZE_EXPORT_IMPL(Kernel::ResourceLimit) +SERIALIZE_EXPORT_IMPL(Kernel::KResourceLimit) namespace Kernel { -ResourceLimit::ResourceLimit(KernelSystem& kernel) : Object(kernel) {} +KResourceLimit::KResourceLimit(KernelSystem& kernel) + : KAutoObjectWithSlabHeapAndContainer(kernel) {} -ResourceLimit::~ResourceLimit() = default; +KResourceLimit::~KResourceLimit() = default; -std::shared_ptr ResourceLimit::Create(KernelSystem& kernel, std::string name) { - auto resource_limit = std::make_shared(kernel); - resource_limit->m_name = std::move(name); - return resource_limit; -} - -s32 ResourceLimit::GetCurrentValue(ResourceLimitType type) const { +s32 KResourceLimit::GetCurrentValue(ResourceLimitType type) const { const auto index = static_cast(type); return m_current_values[index]; } -s32 ResourceLimit::GetLimitValue(ResourceLimitType type) const { +s32 KResourceLimit::GetLimitValue(ResourceLimitType type) const { const auto index = static_cast(type); return m_limit_values[index]; } -void ResourceLimit::SetLimitValue(ResourceLimitType type, s32 value) { +void KResourceLimit::SetLimitValue(ResourceLimitType type, s32 value) { const auto index = static_cast(type); m_limit_values[index] = value; } -bool ResourceLimit::Reserve(ResourceLimitType type, s32 amount) { +bool KResourceLimit::Reserve(ResourceLimitType type, s32 amount) { const auto index = static_cast(type); const s32 limit = m_limit_values[index]; const s32 new_value = m_current_values[index] + amount; @@ -49,7 +44,7 @@ bool ResourceLimit::Reserve(ResourceLimitType type, s32 amount) { return true; } -bool ResourceLimit::Release(ResourceLimitType type, s32 amount) { +bool KResourceLimit::Release(ResourceLimitType type, s32 amount) { const auto index = static_cast(type); const s32 value = m_current_values[index]; if (amount > value) { @@ -67,8 +62,15 @@ ResourceLimitList::ResourceLimitList(KernelSystem& kernel) { const bool is_new_3ds = Settings::values.is_new_3ds.GetValue(); const auto& appmemalloc = kernel.GetMemoryRegion(MemoryRegion::APPLICATION); + const auto CreateLimit = [&](std::string name) { + KResourceLimit* limit = KResourceLimit::Create(kernel); + limit->Initialize(name); + KResourceLimit::Register(kernel, limit); + return limit; + }; + // Create the Application resource limit - auto resource_limit = ResourceLimit::Create(kernel, "Applications"); + auto resource_limit = CreateLimit("Applications"); resource_limit->SetLimitValue(ResourceLimitType::Priority, 0x18); resource_limit->SetLimitValue(ResourceLimitType::Commit, appmemalloc->size); resource_limit->SetLimitValue(ResourceLimitType::Thread, 0x20); @@ -82,7 +84,7 @@ ResourceLimitList::ResourceLimitList(KernelSystem& kernel) { resource_limits[static_cast(ResourceLimitCategory::Application)] = resource_limit; // Create the SysApplet resource limit - resource_limit = ResourceLimit::Create(kernel, "System Applets"); + resource_limit = CreateLimit("System Applets"); resource_limit->SetLimitValue(ResourceLimitType::Priority, 0x4); resource_limit->SetLimitValue(ResourceLimitType::Commit, is_new_3ds ? 0x5E06000 : 0x2606000); resource_limit->SetLimitValue(ResourceLimitType::Thread, is_new_3ds ? 0x1D : 0xE); @@ -96,7 +98,7 @@ ResourceLimitList::ResourceLimitList(KernelSystem& kernel) { resource_limits[static_cast(ResourceLimitCategory::SysApplet)] = resource_limit; // Create the LibApplet resource limit - resource_limit = ResourceLimit::Create(kernel, "Library Applets"); + resource_limit = CreateLimit("Library Applets"); resource_limit->SetLimitValue(ResourceLimitType::Priority, 0x4); resource_limit->SetLimitValue(ResourceLimitType::Commit, 0x602000); resource_limit->SetLimitValue(ResourceLimitType::Thread, 0xE); @@ -110,7 +112,7 @@ ResourceLimitList::ResourceLimitList(KernelSystem& kernel) { resource_limits[static_cast(ResourceLimitCategory::LibApplet)] = resource_limit; // Create the Other resource limit - resource_limit = ResourceLimit::Create(kernel, "Others"); + resource_limit = CreateLimit("Others"); resource_limit->SetLimitValue(ResourceLimitType::Priority, 0x4); resource_limit->SetLimitValue(ResourceLimitType::Commit, is_new_3ds ? 0x2182000 : 0x1682000); resource_limit->SetLimitValue(ResourceLimitType::Thread, is_new_3ds ? 0xE1 : 0xCA); @@ -126,7 +128,7 @@ ResourceLimitList::ResourceLimitList(KernelSystem& kernel) { ResourceLimitList::~ResourceLimitList() = default; -std::shared_ptr ResourceLimitList::GetForCategory(ResourceLimitCategory category) { +KResourceLimit* ResourceLimitList::GetForCategory(ResourceLimitCategory category) { switch (category) { case ResourceLimitCategory::Application: case ResourceLimitCategory::SysApplet: diff --git a/src/core/hle/kernel/resource_limit.h b/src/core/hle/kernel/k_resource_limit.h similarity index 61% rename from src/core/hle/kernel/resource_limit.h rename to src/core/hle/kernel/k_resource_limit.h index cfc9dae0a..25e32b32b 100644 --- a/src/core/hle/kernel/resource_limit.h +++ b/src/core/hle/kernel/k_resource_limit.h @@ -1,17 +1,15 @@ -// Copyright 2015 Citra Emulator Project +// Copyright 2023 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once #include -#include #include #include -#include -#include #include "common/common_types.h" -#include "core/hle/kernel/object.h" +#include "core/global.h" +#include "core/hle/kernel/slab_helpers.h" namespace Kernel { @@ -36,28 +34,14 @@ enum class ResourceLimitType : u32 { Max = 10, }; -class ResourceLimit final : public Object { +class KResourceLimit final : public KAutoObjectWithSlabHeapAndContainer { + KERNEL_AUTOOBJECT_TRAITS(KResourceLimit, KAutoObject); + public: - explicit ResourceLimit(KernelSystem& kernel); - ~ResourceLimit() override; + explicit KResourceLimit(KernelSystem& kernel); + ~KResourceLimit() override; - /** - * Creates a resource limit object. - */ - static std::shared_ptr Create(KernelSystem& kernel, - std::string name = "Unknown"); - - std::string GetTypeName() const override { - return "ResourceLimit"; - } - std::string GetName() const override { - return m_name; - } - - static constexpr HandleType HANDLE_TYPE = HandleType::ResourceLimit; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } + void Initialize(std::string name); s32 GetCurrentValue(ResourceLimitType type) const; s32 GetLimitValue(ResourceLimitType type) const; @@ -71,16 +55,16 @@ private: using ResourceArray = std::array(ResourceLimitType::Max)>; ResourceArray m_limit_values{}; ResourceArray m_current_values{}; - std::string m_name; + std::string m_name{}; private: friend class boost::serialization::access; template void serialize(Archive& ar, const unsigned int file_version) { - ar& boost::serialization::base_object(*this); - ar& m_name; + ar& boost::serialization::base_object(*this); ar& m_limit_values; ar& m_current_values; + ar& m_name; } }; @@ -94,10 +78,10 @@ public: * @param category The resource limit category * @returns The resource limit associated with the category */ - std::shared_ptr GetForCategory(ResourceLimitCategory category); + KResourceLimit* GetForCategory(ResourceLimitCategory category); private: - std::array, 4> resource_limits; + std::array resource_limits; friend class boost::serialization::access; template @@ -108,5 +92,5 @@ private: } // namespace Kernel -BOOST_CLASS_EXPORT_KEY(Kernel::ResourceLimit) -CONSTRUCT_KERNEL_OBJECT(Kernel::ResourceLimit) +BOOST_CLASS_EXPORT_KEY(Kernel::KResourceLimit) +CONSTRUCT_KERNEL_OBJECT(Kernel::KResourceLimit) diff --git a/src/core/hle/kernel/k_scoped_resource_reservation.h b/src/core/hle/kernel/k_scoped_resource_reservation.h new file mode 100644 index 000000000..da1acd174 --- /dev/null +++ b/src/core/hle/kernel/k_scoped_resource_reservation.h @@ -0,0 +1,50 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/process.h" + +namespace Kernel { + +class KScopedResourceReservation { +public: + explicit KScopedResourceReservation(KResourceLimit* l, ResourceLimitType type, s32 amount = 1) + : m_limit(l), m_amount(amount), m_type(type) { + if (m_limit) { + m_succeeded = m_limit->Reserve(m_type, m_amount); + } else { + m_succeeded = true; + } + } + + explicit KScopedResourceReservation(const Process* p, ResourceLimitType type, s32 amount = 1) + : KScopedResourceReservation(p->resource_limit, type, amount) {} + + ~KScopedResourceReservation() noexcept { + if (m_limit && m_succeeded) { + // Resource was not committed, release the reservation. + m_limit->Release(m_type, m_amount); + } + } + + /// Commit the resource reservation, destruction of this object does not release the resource + void Commit() { + m_limit = nullptr; + } + + bool Succeeded() const { + return m_succeeded; + } + +private: + KResourceLimit* m_limit{}; + s32 m_amount{}; + ResourceLimitType m_type{}; + bool m_succeeded{}; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_semaphore.cpp b/src/core/hle/kernel/k_semaphore.cpp new file mode 100644 index 000000000..d9dd7968f --- /dev/null +++ b/src/core/hle/kernel/k_semaphore.cpp @@ -0,0 +1,77 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "common/archives.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/k_semaphore.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" + +SERIALIZE_EXPORT_IMPL(Kernel::KSemaphore) + +namespace Kernel { + +KSemaphore::KSemaphore(KernelSystem& kernel) : KAutoObjectWithSlabHeapAndContainer(kernel) {} + +KSemaphore::~KSemaphore() = default; + +void KSemaphore::Initialize(Process* owner, s32 initial_count, s32 max_count, std::string name) { + // Open a reference to the owner process. + if (owner) { + owner->Open(); + m_owner = owner; + } + + // Set member variables + m_available_count = initial_count; + m_max_count = max_count; + m_name = name; +} + +void KSemaphore::PostDestroy(uintptr_t arg) { + Process* owner = reinterpret_cast(arg); + if (owner != nullptr) { + owner->ReleaseResource(ResourceLimitType::Semaphore, 1); + owner->Close(); + } +} + +bool KSemaphore::ShouldWait(const Thread* thread) const { + return m_available_count <= 0; +} + +void KSemaphore::Acquire(Thread* thread) { + if (m_available_count <= 0) { + return; + } + --m_available_count; +} + +ResultCode KSemaphore::Release(s32* out_count, s32 release_count) { + R_UNLESS(release_count + m_available_count <= m_max_count, ERR_OUT_OF_RANGE_KERNEL); + + // Update available count. + const s32 previous_count = m_available_count; + m_available_count += release_count; + + // Wakeup waiting threads and return. + this->WakeupAllWaitingThreads(); + *out_count = previous_count; + R_SUCCEED(); +} + +template +void KSemaphore::serialize(Archive& ar, const u32 file_version) { + ar& boost::serialization::base_object(*this); + ar& m_max_count; + ar& m_available_count; +} + +SERIALIZE_IMPL(KSemaphore) + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_semaphore.h b/src/core/hle/kernel/k_semaphore.h new file mode 100644 index 000000000..0493366f4 --- /dev/null +++ b/src/core/hle/kernel/k_semaphore.h @@ -0,0 +1,59 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/common_types.h" +#include "core/global.h" +#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/kernel/slab_helpers.h" +#include "core/hle/result.h" + +namespace Kernel { + +class ResourceLimit; + +class KSemaphore final + : public KAutoObjectWithSlabHeapAndContainer { + KERNEL_AUTOOBJECT_TRAITS(KSemaphore, KSynchronizationObject); + +public: + explicit KSemaphore(KernelSystem& kernel); + ~KSemaphore() override; + + void Initialize(Process* owner, s32 initial_count, s32 max_count, std::string name); + + uintptr_t GetPostDestroyArgument() const override { + return reinterpret_cast(m_owner); + } + + static void PostDestroy(uintptr_t arg); + + Process* GetOwner() const override { + return m_owner; + } + + bool ShouldWait(const Thread* thread) const override; + void Acquire(Thread* thread) override; + + ResultCode Release(s32* out_count, s32 release_count); + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const u32 file_version); + +private: + Process* m_owner{}; + s32 m_max_count{}; + s32 m_available_count{}; + std::string m_name; +}; + +} // namespace Kernel + +BOOST_CLASS_EXPORT_KEY(Kernel::KSemaphore) +CONSTRUCT_KERNEL_OBJECT(Kernel::KSemaphore) diff --git a/src/core/hle/kernel/k_server_port.cpp b/src/core/hle/kernel/k_server_port.cpp new file mode 100644 index 000000000..ab4c313bc --- /dev/null +++ b/src/core/hle/kernel/k_server_port.cpp @@ -0,0 +1,63 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include "common/archives.h" +#include "common/assert.h" +#include "core/hle/kernel/k_port.h" +#include "core/hle/kernel/k_server_port.h" +#include "core/hle/kernel/k_server_session.h" +#include "core/hle/kernel/thread.h" + +SERIALIZE_EXPORT_IMPL(Kernel::KServerPort) + +namespace Kernel { + +KServerPort::KServerPort(KernelSystem& kernel) : KSynchronizationObject(kernel) {} + +KServerPort::~KServerPort() = default; + +void KServerPort::Initialize(KPort* parent, std::string name) { + m_parent = parent; + m_name = name; +} + +void KServerPort::Destroy() { + // Close our reference to our parent. + m_parent->Close(); +} + +KServerSession* KServerPort::AcceptSession() { + // Return the first session in the list. + if (m_pending_sessions.empty()) { + return nullptr; + } + + KServerSession* session = m_pending_sessions.back(); + m_pending_sessions.pop_back(); + return session; +} + +bool KServerPort::ShouldWait(const Thread* thread) const { + // If there are no pending sessions, we wait until a new one is added. + return m_pending_sessions.size() == 0; +} + +void KServerPort::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); +} + +template +void KServerPort::serialize(Archive& ar, const u32 file_version) { + ar& boost::serialization::base_object(*this); + ar& m_name; + ar& m_pending_sessions; + ar& m_hle_handler; +} +SERIALIZE_IMPL(KServerPort) + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_server_port.h b/src/core/hle/kernel/k_server_port.h new file mode 100644 index 000000000..5d0422a91 --- /dev/null +++ b/src/core/hle/kernel/k_server_port.h @@ -0,0 +1,61 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include "core/hle/kernel/k_server_session.h" +#include "core/hle/kernel/k_synchronization_object.h" + +namespace Kernel { + +class KClientPort; +class KServerSession; +class KPort; +class SessionRequestHandler; + +class KServerPort final : public KSynchronizationObject { + KERNEL_AUTOOBJECT_TRAITS(KServerPort, KSynchronizationObject); + +public: + explicit KServerPort(KernelSystem& kernel); + ~KServerPort() override; + + void Initialize(KPort* parent, std::string name); + + void Destroy() override; + + void EnqueueSession(KServerSession* session); + + KServerSession* AcceptSession(); + + bool ShouldWait(const Thread* thread) const override; + void Acquire(Thread* thread) override; + + void SetHleHandler(std::shared_ptr hle_handler_) { + m_hle_handler = std::move(hle_handler_); + } + + std::shared_ptr GetHleHandler() { + return m_hle_handler; + } + +private: + KPort* m_parent{}; + std::string m_name; + std::vector m_pending_sessions; + std::shared_ptr m_hle_handler; + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int file_version); +}; + +} // namespace Kernel + +BOOST_CLASS_EXPORT_KEY(Kernel::KServerPort) +CONSTRUCT_KERNEL_OBJECT(Kernel::KServerPort) diff --git a/src/core/hle/kernel/k_server_session.cpp b/src/core/hle/kernel/k_server_session.cpp new file mode 100644 index 000000000..fe537343e --- /dev/null +++ b/src/core/hle/kernel/k_server_session.cpp @@ -0,0 +1,145 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "common/archives.h" +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_server_session.h" +#include "core/hle/kernel/k_session.h" +#include "core/hle/kernel/thread.h" + +SERIALIZE_EXPORT_IMPL(Kernel::KServerSession) + +namespace Kernel { + +KServerSession::KServerSession(KernelSystem& kernel) : KSynchronizationObject(kernel) {} + +KServerSession::~KServerSession() = default; + +void KServerSession::Destroy() { + m_parent->OnServerClosed(); + m_parent->Close(); +} + +bool KServerSession::ShouldWait(const Thread* thread) const { + // Closed sessions should never wait, an error will be returned from svcReplyAndReceive. + const auto state = m_parent->GetState(); + if (state != KSessionState::Normal) { + return false; + } + // Wait if we have no pending requests, or if we're currently handling a request. + return pending_requesting_threads.empty() || currently_handling != nullptr; +} + +void KServerSession::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); + + // If the client endpoint was closed, don't do anything. This KServerSession is now useless and + // will linger until its last handle is closed by the running application. + const auto state = m_parent->GetState(); + if (state != KSessionState::Normal) { + return; + } + + // We are now handling a request, pop it from the stack. + ASSERT(!pending_requesting_threads.empty()); + currently_handling = pending_requesting_threads.back(); + pending_requesting_threads.pop_back(); +} + +void KServerSession::OnClientClosed() { + // Notify HLE handler that client session has been disconnected. + if (hle_handler) { + hle_handler->ClientDisconnected(this); + } + + // Clean up the list of client threads with pending requests, they are unneeded now that the + // client endpoint is closed. + pending_requesting_threads.clear(); + currently_handling = nullptr; + + // Notify any threads waiting on the KServerSession that the endpoint has been closed. Note + // that this call has to happen after `Session::client` has been set to nullptr to let the + // ServerSession know that the client endpoint has been closed. + this->WakeupAllWaitingThreads(); +} + +ResultCode KServerSession::HandleSyncRequest(Thread* thread) { + // The KServerSession received a sync request, this means that there's new data available + // from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or + // similar. + + // If this KServerSession has an associated HLE handler, forward the request to it. + if (hle_handler != nullptr) { + std::array cmd_buf; + auto current_process = thread->GetOwner(); + ASSERT(current_process); + m_kernel.memory.ReadBlock(*current_process, thread->GetCommandBufferAddress(), + cmd_buf.data(), cmd_buf.size() * sizeof(u32)); + + auto context = + std::make_shared(m_kernel, SharedFrom(this), thread); + context->PopulateFromIncomingCommandBuffer(cmd_buf.data(), current_process); + + hle_handler->HandleSyncRequest(*context); + + ASSERT(thread->m_status == Kernel::ThreadStatus::Running || + thread->m_status == Kernel::ThreadStatus::WaitHleEvent); + // Only write the response immediately if the thread is still running. If the HLE handler + // put the thread to sleep then the writing of the command buffer will be deferred to the + // wakeup callback. + if (thread->m_status == Kernel::ThreadStatus::Running) { + context->WriteToOutgoingCommandBuffer(cmd_buf.data(), *current_process); + m_kernel.memory.WriteBlock(*current_process, thread->GetCommandBufferAddress(), + cmd_buf.data(), cmd_buf.size() * sizeof(u32)); + } + } + + if (thread->m_status == ThreadStatus::Running) { + // Put the thread to sleep until the server replies, it will be awoken in + // svcReplyAndReceive for LLE servers. + thread->m_status = ThreadStatus::WaitIPC; + + if (hle_handler != nullptr) { + // For HLE services, we put the request threads to sleep for a short duration to + // simulate IPC overhead, but only if the HLE handler didn't put the thread to sleep for + // other reasons like an async callback. The IPC overhead is needed to prevent + // starvation when a thread only does sync requests to HLE services while a + // lower-priority thread is waiting to run. + + // This delay was approximated in a homebrew application by measuring the average time + // it takes for svcSendSyncRequest to return when performing the SetLcdForceBlack IPC + // request to the GSP:GPU service in a n3DS with firmware 11.6. The measured values have + // a high variance and vary between models. + static constexpr u64 IPCDelayNanoseconds = 39000; + thread->WakeAfterDelay(IPCDelayNanoseconds); + } else { + // Add the thread to the list of threads that have issued a sync request with this + // server. + pending_requesting_threads.push_back(std::move(thread)); + } + } + + // If this KServerSession does not have an HLE implementation, + // just wake up the threads waiting on it. + this->WakeupAllWaitingThreads(); + R_SUCCEED(); +} + +template +void KServerSession::serialize(Archive& ar, const u32 file_version) { + ar& boost::serialization::base_object(*this); + ar& m_name; + ar& m_parent; + ar& hle_handler; + ar& pending_requesting_threads; + ar& currently_handling; + ar& mapped_buffer_context; +} +SERIALIZE_IMPL(KServerSession) + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_server_session.h b/src/core/hle/kernel/k_server_session.h new file mode 100644 index 000000000..2f8b35256 --- /dev/null +++ b/src/core/hle/kernel/k_server_session.h @@ -0,0 +1,80 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include "common/common_types.h" +#include "core/hle/kernel/ipc.h" +#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/result.h" + +namespace Kernel { + +class ClientSession; +class ClientPort; +class KSession; +class SessionRequestHandler; +class Thread; + +class KServerSession final : public KSynchronizationObject { + KERNEL_AUTOOBJECT_TRAITS(KServerSession, KSynchronizationObject); + +public: + ~KServerSession() override; + explicit KServerSession(KernelSystem& kernel); + + void Destroy() override; + + void Initialize(KSession* parent) { + m_parent = parent; + } + + KSession* GetParent() const { + return m_parent; + } + + Thread* GetCurrent() { + return currently_handling; + } + + std::vector& GetMappedBufferContext() { + return mapped_buffer_context; + } + + void SetHleHandler(std::shared_ptr&& hle_handler_) { + hle_handler = std::move(hle_handler_); + } + + std::shared_ptr& GetHleHandler() { + return hle_handler; + } + + void OnClientClosed(); + ResultCode HandleSyncRequest(Thread* thread); + + bool ShouldWait(const Thread* thread) const override; + + void Acquire(Thread* thread) override; + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const u32 file_version); + +public: + std::string m_name; + KSession* m_parent{}; + std::shared_ptr hle_handler; + std::vector pending_requesting_threads; + Thread* currently_handling; + std::vector mapped_buffer_context; +}; + +} // namespace Kernel + +BOOST_CLASS_EXPORT_KEY(Kernel::KServerSession) +CONSTRUCT_KERNEL_OBJECT(Kernel::KServerSession) diff --git a/src/core/hle/kernel/k_session.cpp b/src/core/hle/kernel/k_session.cpp new file mode 100644 index 000000000..55eea8f5c --- /dev/null +++ b/src/core/hle/kernel/k_session.cpp @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/k_client_port.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_server_session.h" +#include "core/hle/kernel/k_session.h" + +namespace Kernel { + +KSession::KSession(KernelSystem& kernel) + : KAutoObjectWithSlabHeapAndContainer{kernel}, m_server{kernel}, m_client{kernel} {} + +KSession::~KSession() = default; + +void KSession::Initialize(KClientPort* client_port) { + // Increment reference count. + // Because reference count is one on creation, this will result + // in a reference count of two. Thus, when both server and client are closed + // this object will be destroyed. + this->Open(); + + // Create our sub sessions. + KAutoObject::Create(std::addressof(m_server)); + KAutoObject::Create(std::addressof(m_client)); + + // Initialize our sub sessions. + m_state = KSessionState::Normal; + m_server.Initialize(this); + m_client.Initialize(this); + + // Set our port. + m_port = client_port; + if (m_port != nullptr) { + m_port->Open(); + } + + if (m_server_port->hle_handler) + m_server_port->hle_handler->ClientConnected(server); + else + m_server_port->pending_sessions.push_back(server); + + // Mark initialized. + m_initialized = true; +} + +void KSession::Finalize() { + if (m_port != nullptr) { + m_port->ConnectionClosed(); + m_port->Close(); + } +} + +void KSession::OnServerClosed() { + if (m_state == KSessionState::Normal) { + m_state = KSessionState::ServerClosed; + m_client.OnServerClosed(); + } +} + +void KSession::OnClientClosed() { + if (m_state == KSessionState::Normal) { + m_state = KSessionState::ClientClosed; + m_server.OnClientClosed(); + } +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_session.h b/src/core/hle/kernel/k_session.h new file mode 100644 index 000000000..eba34f683 --- /dev/null +++ b/src/core/hle/kernel/k_session.h @@ -0,0 +1,76 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_server_session.h" +#include "core/hle/kernel/slab_helpers.h" + +namespace Kernel { + +class KClientPort; + +enum class KSessionState : u8 { + Invalid = 0, + Normal = 1, + ClientClosed = 2, + ServerClosed = 3, +}; + +class KSession final : public KAutoObjectWithSlabHeapAndContainer { + KERNEL_AUTOOBJECT_TRAITS(KSession, KAutoObject); + +public: + explicit KSession(KernelSystem& kernel); + ~KSession() override; + + void Initialize(KClientPort* port); + void Finalize() override; + + bool IsInitialized() const override { + return m_initialized; + } + + static void PostDestroy(uintptr_t arg) {} + + void OnServerClosed(); + + void OnClientClosed(); + + KSessionState GetState() const { + return m_state; + } + + KClientSession& GetClientSession() { + return m_client; + } + + KServerSession& GetServerSession() { + return m_server; + } + + const KClientSession& GetClientSession() const { + return m_client; + } + + const KServerSession& GetServerSession() const { + return m_server; + } + + KClientPort* GetParent() { + return m_port; + } + +private: + KServerSession m_server; + KClientSession m_client; + KClientPort* m_port{}; + KSessionState m_state{}; + bool m_initialized{}; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_shared_memory.cpp b/src/core/hle/kernel/k_shared_memory.cpp new file mode 100644 index 000000000..fcb4bee17 --- /dev/null +++ b/src/core/hle/kernel/k_shared_memory.cpp @@ -0,0 +1,238 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/archives.h" +#include "common/logging/log.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/memory.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/k_shared_memory.h" +#include "core/memory.h" + +SERIALIZE_EXPORT_IMPL(Kernel::KSharedMemory) + +namespace Kernel { + +KSharedMemory::KSharedMemory(KernelSystem& kernel) : KAutoObjectWithSlabHeapAndContainer(kernel) {} + +KSharedMemory::~KSharedMemory() = default; + +ResultCode KSharedMemory::Initialize(Process* owner, u32 size, MemoryPermission permissions, + MemoryPermission other_permissions, VAddr address, + MemoryRegion region) { + // Open a reference to our owner process. + if (owner) { + owner->Open(); + m_owner = owner; + } + + // Set member variables. + m_base_address = address; + m_size = size; + m_permissions = permissions; + m_other_permissions = other_permissions; + + // Allocate the shared memory block. + if (address == 0) { + // We need to allocate a block from the Linear Heap ourselves. + // We'll manually allocate some memory from the linear heap in the specified region. + auto memory_region = m_kernel.GetMemoryRegion(region); + auto offset = memory_region->LinearAllocate(size); + ASSERT_MSG(offset, "Not enough space in region to allocate shared memory!"); + + // Store the backing blocks of allocated memory. + auto& memory = m_kernel.memory; + std::fill(memory.GetFCRAMPointer(*offset), memory.GetFCRAMPointer(*offset + size), 0); + m_backing_blocks = {{memory.GetFCRAMRef(*offset), size}}; + m_holding_memory += MemoryRegionInfo::Interval(*offset, *offset + size); + m_linear_heap_phys_offset = *offset; + + // Increase the amount of used linear heap memory for the owner process. + if (m_owner) { + m_owner->memory_used += size; + } + } else { + // The memory is already available and mapped in the owner process. + ASSERT(m_owner); + auto& vm_manager = m_owner->vm_manager; + R_TRY(vm_manager.ChangeMemoryState(address, size, MemoryState::Private, + VMAPermission::ReadWrite, MemoryState::Locked, + KSharedMemory::ConvertPermissions(permissions))); + + // Should succeed after verifying memory state above. + auto backing_blocks = vm_manager.GetBackingBlocksForRange(address, size); + ASSERT(backing_blocks.Succeeded()); + m_backing_blocks = std::move(backing_blocks).Unwrap(); + } + + R_SUCCEED(); +} + +void KSharedMemory::InitializeForApplet(u32 offset, u32 size, MemoryPermission permissions, + MemoryPermission other_permissions) { + // Allocate memory in heap + auto memory_region = m_kernel.GetMemoryRegion(MemoryRegion::SYSTEM); + auto backing_blocks = memory_region->HeapAllocate(size); + ASSERT_MSG(!backing_blocks.empty(), "Not enough space in region to allocate shared memory!"); + + // Set member variables + m_holding_memory = backing_blocks; + m_base_address = Memory::HEAP_VADDR + offset; + m_size = size; + m_permissions = permissions; + m_other_permissions = other_permissions; + + // Initialize backing blocks + auto& memory = m_kernel.memory; + for (const auto& interval : backing_blocks) { + const VAddr addr = interval.lower(); + const VAddr end = interval.upper(); + m_backing_blocks.emplace_back(memory.GetFCRAMRef(addr), end - addr); + std::fill(memory.GetFCRAMPointer(addr), memory.GetFCRAMPointer(end), 0); + } +} + +void KSharedMemory::Finalize() { + auto memory_region = m_kernel.GetMemoryRegion(MemoryRegion::SYSTEM); + for (const auto& interval : m_holding_memory) { + memory_region->Free(interval.lower(), interval.upper() - interval.lower()); + } + + if (m_owner) { + if (m_base_address != 0) { + m_owner->vm_manager.ChangeMemoryState(m_base_address, m_size, MemoryState::Locked, + VMAPermission::None, MemoryState::Private, + VMAPermission::ReadWrite); + } else { + m_owner->memory_used -= m_size; + } + } +} + +void KSharedMemory::PostDestroy(uintptr_t arg) { + Process* owner = reinterpret_cast(arg); + if (owner != nullptr) { + owner->ReleaseResource(ResourceLimitType::SharedMemory, 1); + owner->Close(); + } +} + +ResultCode KSharedMemory::Map(Process& target_process, VAddr address, MemoryPermission permissions, + MemoryPermission other_permissions) { + + const MemoryPermission own_other_permissions = + &target_process == m_owner ? m_permissions : m_other_permissions; + + // Automatically allocated memory blocks can only be mapped with other_permissions = DontCare + R_UNLESS(m_base_address != 0 || other_permissions == MemoryPermission::DontCare, + ERR_INVALID_COMBINATION); + + // Heap-backed memory blocks can not be mapped with other_permissions = DontCare + R_UNLESS(m_base_address == 0 || other_permissions != MemoryPermission::DontCare, + ERR_INVALID_COMBINATION); + + // Error out if the requested permissions don't match what the creator process allows. + if (static_cast(permissions) & ~static_cast(own_other_permissions)) { + LOG_ERROR(Kernel, "cannot map address={:#08X}, permissions don't match", address); + R_THROW(ERR_INVALID_COMBINATION); + } + + // Error out if the provided permissions are not compatible with what the creator process needs. + if (other_permissions != MemoryPermission::DontCare && + static_cast(m_permissions) & ~static_cast(other_permissions)) { + LOG_ERROR(Kernel, "cannot map address={:#08X}, permissions don't match", address); + R_THROW(ERR_WRONG_PERMISSION); + } + + // TODO(Subv): Check for the Shared Device Mem flag in the creator process. + /*if (was_created_with_shared_device_mem && address != 0) { + return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); + }*/ + + // TODO(Subv): The same process that created a SharedMemory object + // can not map it in its own address space unless it was created with addr=0, result 0xD900182C. + + if (address != 0) { + if (address < Memory::HEAP_VADDR || address + m_size >= Memory::SHARED_MEMORY_VADDR_END) { + LOG_ERROR(Kernel, "cannot map address={:#08X}, invalid address", address); + R_THROW(ERR_INVALID_ADDRESS); + } + } + + VAddr target_address = address; + if (m_base_address == 0 && target_address == 0) { + // Calculate the address at which to map the memory block. + // Note: even on new firmware versions, the target address is still in the old linear heap + // region. This exception is made to keep the shared font compatibility. See + // APT:GetSharedFont for detail. + target_address = m_linear_heap_phys_offset + Memory::LINEAR_HEAP_VADDR; + } + { + auto vma = target_process.vm_manager.FindVMA(target_address); + if (vma->second.type != VMAType::Free || + vma->second.base + vma->second.size < target_address + m_size) { + LOG_ERROR(Kernel, "cannot map address={:#08X}, mapping to already allocated memory", + address); + R_THROW(ERR_INVALID_ADDRESS_STATE); + } + } + + // Map the memory block into the target process + VAddr interval_target = target_address; + for (const auto& interval : m_backing_blocks) { + auto vma = target_process.vm_manager.MapBackingMemory(interval_target, interval.first, + interval.second, MemoryState::Shared); + ASSERT(vma.Succeeded()); + target_process.vm_manager.Reprotect(vma.Unwrap(), ConvertPermissions(permissions)); + interval_target += interval.second; + } + + R_SUCCEED(); +} + +ResultCode KSharedMemory::Unmap(Process& target_process, VAddr address) { + // TODO(Subv): Verify what happens if the application tries to unmap an address that is not + // mapped to a SharedMemory. + return target_process.vm_manager.UnmapRange(address, m_size); +} + +VMAPermission KSharedMemory::ConvertPermissions(MemoryPermission permission) { + u32 masked_permissions = + static_cast(permission) & static_cast(MemoryPermission::ReadWriteExecute); + return static_cast(masked_permissions); +}; + +u8* KSharedMemory::GetPointer(u32 offset) { + if (m_backing_blocks.size() != 1) { + LOG_WARNING(Kernel, "Unsafe GetPointer on discontinuous SharedMemory"); + } + return m_backing_blocks[0].first + offset; +} + +const u8* KSharedMemory::GetPointer(u32 offset) const { + if (m_backing_blocks.size() != 1) { + LOG_WARNING(Kernel, "Unsafe GetPointer on discontinuous SharedMemory"); + } + return m_backing_blocks[0].first + offset; +} + +template +void KSharedMemory::serialize(Archive& ar, const u32 file_version) { + ar& boost::serialization::base_object(*this); + ar& m_linear_heap_phys_offset; + ar& m_backing_blocks; + ar& m_size; + ar& m_permissions; + ar& m_other_permissions; + ar& m_owner; + ar& m_base_address; + ar& m_holding_memory; +} + +SERIALIZE_IMPL(KSharedMemory) + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_shared_memory.h b/src/core/hle/kernel/k_shared_memory.h new file mode 100644 index 000000000..c59ba84d4 --- /dev/null +++ b/src/core/hle/kernel/k_shared_memory.h @@ -0,0 +1,107 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/common_types.h" +#include "core/global.h" +#include "core/hle/kernel/slab_helpers.h" +#include "core/hle/result.h" + +namespace Kernel { + +enum class VMAPermission : u8; + +class KSharedMemory final : public KAutoObjectWithSlabHeapAndContainer { + KERNEL_AUTOOBJECT_TRAITS(KSharedMemory, KAutoObject); + +public: + explicit KSharedMemory(KernelSystem& kernel); + ~KSharedMemory() override; + + ResultCode Initialize(Process* owner, u32 size, MemoryPermission permissions, + MemoryPermission other_permissions, VAddr address, MemoryRegion region); + + void InitializeForApplet(u32 offset, u32 size, MemoryPermission permissions, + MemoryPermission other_permissions); + + void Finalize() override; + + uintptr_t GetPostDestroyArgument() const override { + return reinterpret_cast(m_owner); + } + + static void PostDestroy(uintptr_t arg); + + Process* GetOwner() const override { + return m_owner; + } + + u64 GetSize() const { + return m_size; + } + + u64 GetLinearHeapPhysicalOffset() const { + return m_linear_heap_phys_offset; + } + + /** + * Converts the specified MemoryPermission into the equivalent VMAPermission. + * @param permission The MemoryPermission to convert. + */ + static VMAPermission ConvertPermissions(MemoryPermission permission); + + /** + * Maps a shared memory block to an address in the target process' address space + * @param target_process Process on which to map the memory block. + * @param address Address in system memory to map shared memory block to + * @param permissions Memory block map permissions (specified by SVC field) + * @param other_permissions Memory block map other permissions (specified by SVC field) + */ + ResultCode Map(Process& target_process, VAddr address, MemoryPermission permissions, + MemoryPermission other_permissions); + + /** + * Unmaps a shared memory block from the specified address in system memory + * @param target_process Process from which to unmap the memory block. + * @param address Address in system memory where the shared memory block is mapped + * @return Result code of the unmap operation + */ + ResultCode Unmap(Process& target_process, VAddr address); + + /** + * Gets a pointer to the shared memory block + * @param offset Offset from the start of the shared memory block to get pointer + * @return A pointer to the shared memory block from the specified offset + */ + u8* GetPointer(u32 offset = 0); + + /** + * Gets a constant pointer to the shared memory block + * @param offset Offset from the start of the shared memory block to get pointer + * @return A constant pointer to the shared memory block from the specified offset + */ + const u8* GetPointer(u32 offset = 0) const; + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const u32 file_version); + +private: + Process* m_owner{}; + PAddr m_linear_heap_phys_offset{}; + VAddr m_base_address{}; + u32 m_size{}; + MemoryPermission m_permissions{}; + MemoryPermission m_other_permissions{}; + std::vector> m_backing_blocks; + MemoryRegionInfo::IntervalSet m_holding_memory; +}; + +} // namespace Kernel + +BOOST_CLASS_EXPORT_KEY(Kernel::KSharedMemory) +CONSTRUCT_KERNEL_OBJECT(Kernel::KSharedMemory) diff --git a/src/core/hle/kernel/k_synchronization_object.cpp b/src/core/hle/kernel/k_synchronization_object.cpp new file mode 100644 index 000000000..0efff201d --- /dev/null +++ b/src/core/hle/kernel/k_synchronization_object.cpp @@ -0,0 +1,116 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include "common/archives.h" +#include "common/assert.h" +#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/thread.h" + +namespace Kernel { + +KSynchronizationObject::KSynchronizationObject(KernelSystem& kernel) : KAutoObject(kernel) {} + +KSynchronizationObject::~KSynchronizationObject() = default; + +void KSynchronizationObject::AddWaitingThread(Thread* thread) { + auto it = std::ranges::find(waiting_threads, thread); + if (it == waiting_threads.end()) { + waiting_threads.push_back(thread); + } +} + +void KSynchronizationObject::RemoveWaitingThread(Thread* thread) { + // If a thread passed multiple handles to the same object, + // the kernel might attempt to remove the thread from the object's + // waiting threads list multiple times. + auto it = std::ranges::find(waiting_threads, thread); + if (it != waiting_threads.end()) { + waiting_threads.erase(it); + } +} + +Thread* KSynchronizationObject::GetHighestPriorityReadyThread() const { + Thread* candidate = nullptr; + u32 candidate_priority = ThreadPrioLowest + 1; + + for (auto* thread : waiting_threads) { + // The list of waiting threads must not contain threads that are not waiting to be awakened. + ASSERT_MSG(thread->GetStatus() == ThreadStatus::WaitSynchAny || + thread->GetStatus() == ThreadStatus::WaitSynchAll || + thread->GetStatus() == ThreadStatus::WaitHleEvent, + "Inconsistent thread statuses in waiting_threads"); + + if (thread->GetCurrentPriority() >= candidate_priority || ShouldWait(thread)) { + continue; + } + + // A thread is ready to run if it's either in ThreadStatus::WaitSynchAny or + // in ThreadStatus::WaitSynchAll and the rest of the objects it is waiting on are ready. + bool ready_to_run = true; + if (thread->GetStatus() == ThreadStatus::WaitSynchAll) { + ready_to_run = std::ranges::none_of(thread->m_wait_objects, [thread](const auto* object) { + return object->ShouldWait(thread); + }); + } + + if (ready_to_run) { + candidate = thread; + candidate_priority = thread->GetCurrentPriority(); + } + } + + return candidate; +} + +void KSynchronizationObject::WakeupAllWaitingThreads() { + while (auto thread = GetHighestPriorityReadyThread()) { + if (!thread->IsSleepingOnWaitAll()) { + Acquire(thread); + } else { + for (auto& object : thread->m_wait_objects) { + object->Acquire(thread); + } + } + + // Invoke the wakeup callback before clearing the wait objects + if (thread->m_wakeup_callback) { + thread->m_wakeup_callback->WakeUp(ThreadWakeupReason::Signal, thread, this); + } + + for (auto& object : thread->m_wait_objects) { + object->RemoveWaitingThread(thread); + } + thread->m_wait_objects.clear(); + thread->ResumeFromWait(); + } + + if (hle_notifier) { + hle_notifier(); + } +} + +std::vector& KSynchronizationObject::GetWaitingThreads() const { + return waiting_threads; +} + +void KSynchronizationObject::SetHLENotifier(std::function callback) { + hle_notifier = std::move(callback); +} + +template +void KSynchronizationObject::serialize(Archive& ar, const unsigned int file_version) { + ar& boost::serialization::base_object(*this); + ar& waiting_threads; + // NB: hle_notifier *not* serialized since it's a callback! + // Fortunately it's only used in one place (DSP) so we can reconstruct it there +} +SERIALIZE_IMPL(KSynchronizationObject) + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_synchronization_object.cpp.autosave b/src/core/hle/kernel/k_synchronization_object.cpp.autosave new file mode 100644 index 000000000..4a5999cd8 --- /dev/null +++ b/src/core/hle/kernel/k_synchronization_object.cpp.autosave @@ -0,0 +1,116 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include "common/archives.h" +#include "common/assert.h" +#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/thread.h" + +namespace Kernel { + +KSynchronizationObject::KSynchronizationObject(KernelSystem& kernel) : KAutoObject(kernel) {} + +KSynchronizationObject::~KSynchronizationObject() = default; + +void KSynchronizationObject::AddWaitingThread(Thread* thread) { + auto it = std::ranges::find(waiting_threads, thread); + if (it == waiting_threads.end()) { + waiting_threads.push_back(thread); + } +} + +void KSynchronizationObject::RemoveWaitingThread(Thread* thread) { + // If a thread passed multiple handles to the same object, + // the kernel might attempt to remove the thread from the object's + // waiting threads list multiple times. + auto it = std::ranges::find(waiting_threads, thread); + if (it != waiting_threads.end()) { + waiting_threads.erase(it); + } +} + +Thread* KSynchronizationObject::GetHighestPriorityReadyThread() const { + Thread* candidate = nullptr; + u32 candidate_priority = ThreadPrioLowest + 1; + + for (auto* thread : waiting_threads) { + // The list of waiting threads must not contain threads that are not waiting to be awakened. + ASSERT_MSG(thread->GetStatus() == ThreadStatus::WaitSynchAny || + thread->GetStatus() == ThreadStatus::WaitSynchAll || + thread->GetStatus() == ThreadStatus::WaitHleEvent, + "Inconsistent thread statuses in waiting_threads"); + + if (thread->GetCurrentPriority() >= candidate_priority || ShouldWait(thread)) { + continue; + } + + // A thread is ready to run if it's either in ThreadStatus::WaitSynchAny or + // in ThreadStatus::WaitSynchAll and the rest of the objects it is waiting on are ready. + bool ready_to_run = true; + if (thread->GetStatus() == ThreadStatus::WaitSynchAll) { + ready_to_run = std::ranges::none_of(thread->m_wait_objects, [thread](const auto* object) { + return object->ShouldWait(thread); + }); + } + + if (ready_to_run) { + candidate = thread; + candidate_priority = thread->GetCurrentPriority(); + } + } + + return candidate; +} + +void KSynchronizationObject::WakeupAllWaitingThreads() { + while (auto thread = GetHighestPriorityReadyThread()) { + if (!thread->IsSleepingOnWaitAll()) { + Acquire(thread); + } else { + for (auto& object : thread->m_wait_objects) { + object->Acquire(thread); + } + } + + // Invoke the wakeup callback before clearing the wait objects + if (thread->m_wakeup_callback) { + thread->m_wakeup_callback->WakeUp(ThreadWakeupReason::Signal, thread, this); + } + + for (auto& object : thread->m_wait_objects) { + object->RemoveWaitingThread(thread); + } + thread->m_wait_objects.clear(); + thread->ResumeFromWait(); + } + + if (hle_notifier) { + hle_notifier(); + } +} + +const std::vector& KSynchronizationObject::GetWaitingThreads() const { + return waiting_threads; +} + +void KSynchronizationObject::SetHLENotifier(std::function callback) { + hle_notifier = std::move(callback); +} + +template +void KSynchronizationObject::serialize(Archive& ar, const unsigned int file_version) { + ar& boost::serialization::base_object(*this); + ar& waiting_threads; + // NB: hle_notifier *not* serialized since it's a callback! + // Fortunately it's only used in one place (DSP) so we can reconstruct it there +} +SERIALIZE_IMPL(KSynchronizationObject) + +} // namespace Kernel diff --git a/src/core/hle/kernel/wait_object.h b/src/core/hle/kernel/k_synchronization_object.h similarity index 65% rename from src/core/hle/kernel/wait_object.h rename to src/core/hle/kernel/k_synchronization_object.h index 7f73eab9f..faa88e77b 100644 --- a/src/core/hle/kernel/wait_object.h +++ b/src/core/hle/kernel/k_synchronization_object.h @@ -1,26 +1,25 @@ -// Copyright 2014 Citra Emulator Project +// Copyright 2023 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once #include -#include +#include #include -#include -#include -#include -#include "common/common_types.h" -#include "core/hle/kernel/object.h" +#include +#include "core/hle/kernel/k_auto_object.h" namespace Kernel { class Thread; -/// Class that represents a Kernel object that a thread can be waiting on -class WaitObject : public Object { +class KSynchronizationObject : public KAutoObject { + KERNEL_AUTOOBJECT_TRAITS(KSynchronizationObject, KAutoObject); + public: - using Object::Object; + explicit KSynchronizationObject(KernelSystem& kernel); + ~KSynchronizationObject(); /** * Check if the specified thread should wait until the object is available @@ -36,7 +35,7 @@ public: * Add a thread to wait on this object * @param thread Pointer to thread to add */ - virtual void AddWaitingThread(std::shared_ptr thread); + virtual void AddWaitingThread(Thread* thread); /** * Removes a thread from waiting on this object (e.g. if it was resumed already) @@ -51,17 +50,17 @@ public: virtual void WakeupAllWaitingThreads(); /// Obtains the highest priority thread that is ready to run from this object's waiting list. - std::shared_ptr GetHighestPriorityReadyThread() const; + Thread* GetHighestPriorityReadyThread() const; /// Get a const reference to the waiting threads list for debug use - const std::vector>& GetWaitingThreads() const; + std::vector& GetWaitingThreads() const; /// Sets a callback which is called when the object becomes available void SetHLENotifier(std::function callback); private: /// Threads waiting for this object to become available - std::vector> waiting_threads; + std::vector waiting_threads; /// Function to call when this object becomes available std::function hle_notifier; @@ -72,13 +71,4 @@ private: void serialize(Archive& ar, const unsigned int file_version); }; -// Specialization of DynamicObjectCast for WaitObjects -template <> -inline std::shared_ptr DynamicObjectCast(std::shared_ptr object) { - if (object != nullptr && object->IsWaitable()) { - return std::static_pointer_cast(object); - } - return nullptr; -} - } // namespace Kernel diff --git a/src/core/hle/kernel/k_synchronization_object.h.autosave b/src/core/hle/kernel/k_synchronization_object.h.autosave new file mode 100644 index 000000000..fa299d6d3 --- /dev/null +++ b/src/core/hle/kernel/k_synchronization_object.h.autosave @@ -0,0 +1,74 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include "core/hle/kernel/k_auto_object.h" + +namespace Kernel { + +class Thread; + +class KSynchronizationObject : public KAutoObject { + KERNEL_AUTOOBJECT_TRAITS(KSynchronizationObject, KAutoObject); + +public: + explicit KSynchronizationObject(KernelSystem& kernel); + ~KSynchronizationObject(); + + /** + * Check if the specified thread should wait until the object is available + * @param thread The thread about which we're deciding. + * @return True if the current thread should wait due to this object being unavailable + */ + virtual bool ShouldWait(const Thread* thread) const = 0; + + /// Acquire/lock the object for the specified thread if it is available + virtual void Acquire(Thread* thread) = 0; + + /** + * Add a thread to wait on this object + * @param thread Pointer to thread to add + */ + virtual void AddWaitingThread(Thread* thread); + + /** + * Removes a thread from waiting on this object (e.g. if it was resumed already) + * @param thread Pointer to thread to remove + */ + virtual void RemoveWaitingThread(Thread* thread); + + /** + * Wake up all threads waiting on this object that can be awoken, in priority order, + * and set the synchronization result and output of the thread. + */ + virtual void WakeupAllWaitingThreads(); + + /// Obtains the highest priority thread that is ready to run from this object's waiting list. + Thread* GetHighestPriorityReadyThread() const; + + /// Get a const reference to the waiting threads list for debug use + const std::vector& GetWaitingThreads() const; + + /// Sets a callback which is called when the object becomes available + void SetHLENotifier(std::function callback); + +private: + /// Threads waiting for this object to become available + std::vector waiting_threads; + + /// Function to call when this object becomes available + std::function hle_notifier; + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int file_version); +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_timer.cpp b/src/core/hle/kernel/k_timer.cpp new file mode 100644 index 000000000..8a58641c7 --- /dev/null +++ b/src/core/hle/kernel/k_timer.cpp @@ -0,0 +1,119 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/archives.h" +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/hle/kernel/k_timer.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/k_resource_limit.h" + +SERIALIZE_EXPORT_IMPL(Kernel::KTimer) + +namespace Kernel { + +KTimer::KTimer(KernelSystem& kernel) + : KAutoObjectWithSlabHeapAndContainer(kernel), m_timer_manager(kernel.GetTimerManager()) {} + +KTimer::~KTimer() = default; + +void KTimer::Initialize(Process* owner, ResetType reset_type) { + // Open a reference to the owner process. + owner->Open(); + + // Set member variables. + m_owner = owner; + m_reset_type = reset_type; + + // Register to TimerManager + m_callback_id = m_timer_manager.GetNextCallbackId(); + m_timer_manager.Register(m_callback_id, this); +} + +void KTimer::Finalize() { + this->Cancel(); + m_timer_manager.Unregister(m_callback_id); +} + +void KTimer::PostDestroy(uintptr_t arg) { + // Release the session count resource the owner process holds. + Process* owner = reinterpret_cast(arg); + owner->ReleaseResource(ResourceLimitType::Timer, 1); + owner->Close(); +} + +bool KTimer::ShouldWait(const Thread* thread) const { + return !m_signaled; +} + +void KTimer::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); + if (m_reset_type == ResetType::OneShot) { + m_signaled = false; + } +} + +void KTimer::Set(s64 initial, s64 interval) { + // Ensure we get rid of any previous scheduled event + this->Cancel(); + + // Set member variables + m_initial_delay = initial; + m_interval_delay = interval; + + if (initial == 0) { + // Immediately invoke the callback + this->Signal(0); + } else { + auto& timing = m_kernel.timing; + timing.ScheduleEvent(nsToCycles(initial), m_timer_manager.GetEventType(), m_callback_id); + } +} + +void KTimer::Cancel() { + auto& timing = m_kernel.timing; + timing.UnscheduleEvent(m_timer_manager.GetEventType(), m_callback_id); +} + +void KTimer::Clear() { + m_signaled = false; +} + +void KTimer::WakeupAllWaitingThreads() { + KSynchronizationObject::WakeupAllWaitingThreads(); + if (m_reset_type == ResetType::Pulse) { + m_signaled = false; + } +} + +void KTimer::Signal(s64 cycles_late) { + LOG_TRACE(Kernel, "Timer {} fired", GetObjectId()); + m_signaled = true; + + // Resume all waiting threads + this->WakeupAllWaitingThreads(); + + // Reschedule the timer with the interval delay + if (m_interval_delay != 0) { + auto& timing = m_kernel.timing; + const s64 cycles_into_future = nsToCycles(m_interval_delay) - cycles_late; + timing.ScheduleEvent(cycles_into_future, m_timer_manager.GetEventType(), m_callback_id); + } +} + +void TimerManager::TimerCallback(u64 callback_id, s64 cycles_late) { + KTimer* timer = m_timer_callback_table.at(callback_id); + ASSERT_MSG(timer, "Callback fired for invalid timer {:016x}", callback_id); + timer->Signal(cycles_late); +} + +TimerManager::TimerManager(Core::Timing& timing) : m_timing(timing) { + m_timer_callback_event_type = + timing.RegisterEvent("TimerCallback", [this](u64 thread_id, s64 cycle_late) { + this->TimerCallback(thread_id, cycle_late); + }); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_timer.h b/src/core/hle/kernel/k_timer.h new file mode 100644 index 000000000..6c422f8a4 --- /dev/null +++ b/src/core/hle/kernel/k_timer.h @@ -0,0 +1,132 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/common_types.h" +#include "core/core_timing.h" +#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/kernel/slab_helpers.h" + +namespace Core { +class Timing; +} + +namespace Kernel { + +class KTimer; + +class TimerManager { +public: + explicit TimerManager(Core::Timing& timing); + ~TimerManager(); + + u64 GetNextCallbackId() { + return +m_next_timer_callback_id; + } + + Core::TimingEventType* GetEventType() { + return m_timer_callback_event_type; + } + + void Register(u64 callback_id, KTimer* timer) { + m_timer_callback_table[callback_id] = timer; + } + + void Unregister(u64 callback_id) { + m_timer_callback_table.erase(callback_id); + } + +private: + void TimerCallback(u64 callback_id, s64 cycles_late); + +private: + Core::Timing& m_timing; + Core::TimingEventType* m_timer_callback_event_type{}; + u64 m_next_timer_callback_id{}; + std::unordered_map m_timer_callback_table; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const u32 file_version) { + ar& m_next_timer_callback_id; + ar& m_timer_callback_table; + } +}; + +class ResourceLimit; + +class KTimer final : public KAutoObjectWithSlabHeapAndContainer { + KERNEL_AUTOOBJECT_TRAITS(KTimer, KSynchronizationObject); + +public: + explicit KTimer(KernelSystem& kernel); + ~KTimer() override; + + void Initialize(Process* owner, ResetType reset_type); + + void Finalize() override; + + uintptr_t GetPostDestroyArgument() const override { + return reinterpret_cast(m_owner); + } + + static void PostDestroy(uintptr_t arg); + + Process* GetOwner() const override { + return m_owner; + } + + ResetType GetResetType() const { + return m_reset_type; + } + + u64 GetInitialDelay() const { + return m_initial_delay; + } + + u64 GetIntervalDelay() const { + return m_interval_delay; + } + + void Set(s64 initial, s64 interval); + void Signal(s64 cycles_late); + + void Cancel(); + void Clear(); + + void WakeupAllWaitingThreads() override; + bool ShouldWait(const Thread* thread) const override; + void Acquire(Thread* thread) override; + +private: + TimerManager& m_timer_manager; + Process* m_owner{}; + ResetType m_reset_type{}; + u64 m_initial_delay{}; + u64 m_interval_delay{}; + bool m_signaled{}; + u64 m_callback_id{}; + + friend class KernelSystem; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const u32 file_version) { + ar& boost::serialization::base_object(*this); + ar& m_owner; + ar& m_reset_type; + ar& m_initial_delay; + ar& m_interval_delay; + ar& m_signaled; + ar& m_callback_id; + } +}; + +} // namespace Kernel + +BOOST_CLASS_EXPORT_KEY(Kernel::KTimer) +CONSTRUCT_KERNEL_OBJECT(Kernel::KTimer) diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 737c73520..fde3da7ec 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -6,16 +6,24 @@ #include #include #include "common/archives.h" -#include "core/hle/kernel/client_port.h" #include "core/hle/kernel/config_mem.h" #include "core/hle/kernel/ipc_debugger/recorder.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/memory.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/resource_limit.h" +#include "core/hle/kernel/k_resource_limit.h" #include "core/hle/kernel/shared_page.h" #include "core/hle/kernel/thread.h" -#include "core/hle/kernel/timer.h" +#include "core/hle/kernel/k_timer.h" +#include "core/hle/kernel/k_object_name.h" +#include "core/hle/kernel/k_slab_heap.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_shared_memory.h" +#include "core/hle/kernel/k_mutex.h" +#include "core/hle/kernel/k_semaphore.h" +#include "core/hle/kernel/k_address_arbiter.h" +#include "core/hle/kernel/k_port.h" +#include "core/hle/kernel/k_session.h" namespace Kernel { @@ -60,16 +68,16 @@ u32 KernelSystem::GenerateObjectID() { return next_object_id++; } -std::shared_ptr KernelSystem::GetCurrentProcess() const { +Process* KernelSystem::GetCurrentProcess() const { return current_process; } -void KernelSystem::SetCurrentProcess(std::shared_ptr process) { +void KernelSystem::SetCurrentProcess(Process* process) { current_process = process; SetCurrentMemoryPageTable(process->vm_manager.page_table); } -void KernelSystem::SetCurrentProcessForCPU(std::shared_ptr process, u32 core_id) { +void KernelSystem::SetCurrentProcessForCPU(Process* process, u32 core_id) { if (current_cpu->GetID() == core_id) { current_process = process; SetCurrentMemoryPageTable(process->vm_manager.page_table); @@ -148,14 +156,14 @@ const IPCDebugger::Recorder& KernelSystem::GetIPCRecorder() const { return *ipc_recorder; } -void KernelSystem::AddNamedPort(std::string name, std::shared_ptr port) { - named_ports.emplace(std::move(name), std::move(port)); -} - u32 KernelSystem::NewThreadId() { return next_thread_id++; } +u32 KernelSystem::NewProcessId() { + return ++next_process_id; +} + void KernelSystem::ResetThreadIDs() { next_thread_id = 0; } @@ -163,7 +171,6 @@ void KernelSystem::ResetThreadIDs() { template void KernelSystem::serialize(Archive& ar, const unsigned int file_version) { ar& memory_regions; - ar& named_ports; // current_cpu set externally // NB: subsystem references and prepare_reschedule_callback are constant ar&* resource_limits.get(); @@ -202,10 +209,62 @@ void KernelSystem::UnregisterKernelObject(KAutoObject* object) { registered_objects.erase(object); } -struct KernelSystem::SlabHeapContainer {}; +struct KernelSystem::SlabHeapContainer { + KSlabHeap event; + KSlabHeap port; + KSlabHeap process; + KSlabHeap resource_limit; + KSlabHeap session; + KSlabHeap shared_memory; + KSlabHeap thread; + KSlabHeap object_name; + KSlabHeap address_arbiter; + KSlabHeap semaphore; + KSlabHeap mutex; +}; template -KSlabHeap& KernelSystem::SlabHeap() {} +KSlabHeap& KernelSystem::SlabHeap() { + if constexpr (std::is_same_v) { + return slab_heap_container->event; + } else if constexpr (std::is_same_v) { + return slab_heap_container->port; + } else if constexpr (std::is_same_v) { + return slab_heap_container->process; + } else if constexpr (std::is_same_v) { + return slab_heap_container->resource_limit; + } else if constexpr (std::is_same_v) { + return slab_heap_container->session; + } else if constexpr (std::is_same_v) { + return slab_heap_container->shared_memory; + } else if constexpr (std::is_same_v) { + return slab_heap_container->thread; + } else if constexpr (std::is_same_v) { + return slab_heap_container->address_arbiter; + } else if constexpr (std::is_same_v) { + return slab_heap_container->semaphore; + } else if constexpr (std::is_same_v) { + return slab_heap_container->mutex; + } else if constexpr (std::is_same_v) { + return slab_heap_container->object_name; + } +} + +KObjectNameGlobalData& KernelSystem::ObjectNameGlobalData() { + return *object_name_global_data; +} + +template KSlabHeap& KernelSystem::SlabHeap(); +template KSlabHeap& KernelSystem::SlabHeap(); +template KSlabHeap& KernelSystem::SlabHeap(); +template KSlabHeap& KernelSystem::SlabHeap(); +template KSlabHeap& KernelSystem::SlabHeap(); +template KSlabHeap& KernelSystem::SlabHeap(); +template KSlabHeap& KernelSystem::SlabHeap(); +template KSlabHeap& KernelSystem::SlabHeap(); +template KSlabHeap& KernelSystem::SlabHeap(); +template KSlabHeap& KernelSystem::SlabHeap(); +template KSlabHeap& KernelSystem::SlabHeap(); SERIALIZE_IMPL(KernelSystem) diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 928bbecb0..fcea239a9 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -9,14 +9,11 @@ #include #include #include -#include -#include -#include #include #include +#include "common/bit_field.h" #include "common/common_types.h" #include "core/hle/kernel/memory.h" -#include "core/hle/result.h" #include "core/memory.h" namespace ConfigMem { @@ -42,18 +39,9 @@ class Recorder; namespace Kernel { -class AddressArbiter; -class Event; -class Mutex; class CodeSet; class Process; class Thread; -class Semaphore; -class Timer; -class ClientPort; -class ServerPort; -class ClientSession; -class ServerSession; class ResourceLimitList; class SharedMemory; class ThreadManager; @@ -61,8 +49,10 @@ class TimerManager; class VMManager; class KAutoObject; struct AddressMapping; +class KObjectName; +class KObjectNameGlobalData; -enum class ResetType { +enum class ResetType : u32 { OneShot, Sticky, Pulse, @@ -136,6 +126,7 @@ private: template class KSlabHeap; +class KAutoObjectWithListContainer; class KernelSystem { public: @@ -145,134 +136,27 @@ public: u64 override_init_time = 0); ~KernelSystem(); - using PortPair = std::pair, std::shared_ptr>; - using SessionPair = std::pair, std::shared_ptr>; - - /** - * Creates an address arbiter. - * - * @param name Optional name used for debugging. - * @returns The created AddressArbiter. - */ - std::shared_ptr CreateAddressArbiter(std::string name = "Unknown"); - - /** - * Creates an event - * @param reset_type ResetType describing how to create event - * @param name Optional name of event - */ - std::shared_ptr CreateEvent(ResetType reset_type, std::string name = "Unknown"); - - /** - * Creates a mutex. - * @param initial_locked Specifies if the mutex should be locked initially - * @param name Optional name of mutex - * @return Pointer to new Mutex object - */ - std::shared_ptr CreateMutex(bool initial_locked, std::string name = "Unknown"); - - std::shared_ptr CreateCodeSet(std::string name, u64 program_id); - - std::shared_ptr CreateProcess(std::shared_ptr code_set); - /** * Terminates a process, killing its threads and removing it from the process list. * @param process Process to terminate. */ - void TerminateProcess(std::shared_ptr process); - - /** - * Creates and returns a new thread. The new thread is immediately scheduled - * @param name The friendly name desired for the thread - * @param entry_point The address at which the thread should start execution - * @param priority The thread's priority - * @param arg User data to pass to the thread - * @param processor_id The ID(s) of the processors on which the thread is desired to be run - * @param stack_top The address of the thread's stack top - * @param owner_process The parent process for the thread - * @return A shared pointer to the newly created thread - */ - ResultVal> CreateThread(std::string name, VAddr entry_point, - u32 priority, u32 arg, s32 processor_id, - VAddr stack_top, - std::shared_ptr owner_process); - - /** - * Creates a semaphore. - * @param initial_count Number of slots reserved for other threads - * @param max_count Maximum number of slots the semaphore can have - * @param name Optional name of semaphore - * @return The created semaphore - */ - ResultVal> CreateSemaphore(s32 initial_count, s32 max_count, - std::string name = "Unknown"); - - /** - * Creates a timer - * @param reset_type ResetType describing how to create the timer - * @param name Optional name of timer - * @return The created Timer - */ - std::shared_ptr CreateTimer(ResetType reset_type, std::string name = "Unknown"); - - /** - * Creates a pair of ServerPort and an associated ClientPort. - * - * @param max_sessions Maximum number of sessions to the port - * @param name Optional name of the ports - * @return The created port tuple - */ - PortPair CreatePortPair(u32 max_sessions, std::string name = "UnknownPort"); - - /** - * Creates a pair of ServerSession and an associated ClientSession. - * @param name Optional name of the ports. - * @param client_port Optional The ClientPort that spawned this session. - * @return The created session tuple - */ - SessionPair CreateSessionPair(const std::string& name = "Unknown", - std::shared_ptr client_port = nullptr); + void TerminateProcess(Process* process); ResourceLimitList& ResourceLimit(); const ResourceLimitList& ResourceLimit() const; - /** - * Creates a shared memory object. - * @param owner_process Process that created this shared memory object. - * @param size Size of the memory block. Must be page-aligned. - * @param permissions Permission restrictions applied to the process which created the block. - * @param other_permissions Permission restrictions applied to other processes mapping the - * block. - * @param address The address from which to map the Shared Memory. - * @param region If the address is 0, the shared memory will be allocated in this region of the - * linear heap. - * @param name Optional object name, used for debugging purposes. - */ - ResultVal> CreateSharedMemory( - std::shared_ptr owner_process, u32 size, MemoryPermission permissions, - MemoryPermission other_permissions, VAddr address = 0, - MemoryRegion region = MemoryRegion::BASE, std::string name = "Unknown"); - - /** - * Creates a shared memory object from a block of memory managed by an HLE applet. - * @param offset The offset into the heap block that the SharedMemory will map. - * @param size Size of the memory block. Must be page-aligned. - * @param permissions Permission restrictions applied to the process which created the block. - * @param other_permissions Permission restrictions applied to other processes mapping the - * block. - * @param name Optional object name, used for debugging purposes. - */ - std::shared_ptr CreateSharedMemoryForApplet(u32 offset, u32 size, - MemoryPermission permissions, - MemoryPermission other_permissions, - std::string name = "Unknown Applet"); - u32 GenerateObjectID(); /// Gets the slab heap for the specified kernel object type. template KSlabHeap& SlabHeap(); + template + KAutoObjectWithListContainer& ObjectListContainer(); + + /// Gets global data for KObjectName. + KObjectNameGlobalData& ObjectNameGlobalData(); + /// Registers all kernel objects with the global emulation state, this is purely for tracking /// leaks after emulation has been shutdown. void RegisterKernelObject(KAutoObject* object); @@ -282,15 +166,15 @@ public: void UnregisterKernelObject(KAutoObject* object); /// Retrieves a process from the current list of processes. - std::shared_ptr GetProcessById(u32 process_id) const; + Process* GetProcessById(u32 process_id) const; - std::span> GetProcessList() const { + const std::vector& GetProcessList() const { return process_list; } - std::shared_ptr GetCurrentProcess() const; - void SetCurrentProcess(std::shared_ptr process); - void SetCurrentProcessForCPU(std::shared_ptr process, u32 core_id); + Process* GetCurrentProcess() const; + void SetCurrentProcess(Process* process); + void SetCurrentProcessForCPU(Process* process, u32 core_id); void SetCurrentMemoryPageTable(std::shared_ptr page_table); @@ -323,14 +207,12 @@ public: std::array, 3> memory_regions{}; - /// Adds a port to the named port table - void AddNamedPort(std::string name, std::shared_ptr port); - void PrepareReschedule() { prepare_reschedule_callback(); } u32 NewThreadId(); + u32 NewProcessId(); void ResetThreadIDs(); @@ -346,15 +228,15 @@ public: return hle_lock; } - /// Map of named ports managed by the kernel, which can be retrieved using the ConnectToPort - std::unordered_map> named_ports; - Core::ARM_Interface* current_cpu = nullptr; Memory::MemorySystem& memory; Core::Timing& timing; + // Lists all processes that exist in the current session. + std::vector process_list; + private: void MemoryInit(MemoryMode memory_mode, New3dsMemoryMode n3ds_mode, u64 override_init_time); @@ -376,11 +258,8 @@ private: // reserved for low-level services u32 next_process_id = 10; - // Lists all processes that exist in the current session. - std::vector> process_list; - - std::shared_ptr current_process; - std::vector> stored_processes; + Process* current_process; + std::vector stored_processes; std::vector> thread_managers; @@ -398,6 +277,8 @@ private: struct SlabHeapContainer; std::unique_ptr slab_heap_container; + std::unique_ptr object_name_global_data; + std::unordered_set registered_objects; /* diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp deleted file mode 100644 index 5ce8abe49..000000000 --- a/src/core/hle/kernel/mutex.cpp +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/archives.h" -#include "common/assert.h" -#include "core/core.h" -#include "core/hle/kernel/errors.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/mutex.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/resource_limit.h" -#include "core/hle/kernel/thread.h" - -SERIALIZE_EXPORT_IMPL(Kernel::Mutex) - -namespace Kernel { - -void ReleaseThreadMutexes(Thread* thread) { - for (auto& mtx : thread->held_mutexes) { - mtx->lock_count = 0; - mtx->holding_thread = nullptr; - mtx->WakeupAllWaitingThreads(); - } - thread->held_mutexes.clear(); -} - -Mutex::Mutex(KernelSystem& kernel) : WaitObject(kernel), kernel(kernel) {} - -Mutex::~Mutex() { - if (resource_limit) { - resource_limit->Release(ResourceLimitType::Mutex, 1); - } -} - -std::shared_ptr KernelSystem::CreateMutex(bool initial_locked, std::string name) { - auto mutex = std::make_shared(*this); - mutex->lock_count = 0; - mutex->name = std::move(name); - mutex->holding_thread = nullptr; - - // Acquire mutex with current thread if initialized as locked - if (initial_locked) { - mutex->Acquire(GetCurrentThreadManager().GetCurrentThread()); - } - - return mutex; -} - -bool Mutex::ShouldWait(const Thread* thread) const { - return lock_count > 0 && thread != holding_thread.get(); -} - -void Mutex::Acquire(Thread* thread) { - ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); - - // Actually "acquire" the mutex only if we don't already have it - if (lock_count == 0) { - priority = thread->current_priority; - thread->held_mutexes.insert(SharedFrom(this)); - holding_thread = SharedFrom(thread); - thread->UpdatePriority(); - kernel.PrepareReschedule(); - } - - lock_count++; -} - -ResultCode Mutex::Release(Thread* thread) { - // We can only release the mutex if it's held by the calling thread. - if (thread != holding_thread.get()) { - if (holding_thread) { - LOG_ERROR( - Kernel, - "Tried to release a mutex (owned by thread id {}) from a different thread id {}", - holding_thread->thread_id, thread->thread_id); - } - return ResultCode(ErrCodes::WrongLockingThread, ErrorModule::Kernel, - ErrorSummary::InvalidArgument, ErrorLevel::Permanent); - } - - // Note: It should not be possible for the situation where the mutex has a holding thread with a - // zero lock count to occur. The real kernel still checks for this, so we do too. - if (lock_count <= 0) - return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::Kernel, - ErrorSummary::InvalidState, ErrorLevel::Permanent); - - lock_count--; - - // Yield to the next thread only if we've fully released the mutex - if (lock_count == 0) { - holding_thread->held_mutexes.erase(SharedFrom(this)); - holding_thread->UpdatePriority(); - holding_thread = nullptr; - WakeupAllWaitingThreads(); - kernel.PrepareReschedule(); - } - - return RESULT_SUCCESS; -} - -void Mutex::AddWaitingThread(std::shared_ptr thread) { - WaitObject::AddWaitingThread(thread); - thread->pending_mutexes.insert(SharedFrom(this)); - UpdatePriority(); -} - -void Mutex::RemoveWaitingThread(Thread* thread) { - WaitObject::RemoveWaitingThread(thread); - thread->pending_mutexes.erase(SharedFrom(this)); - UpdatePriority(); -} - -void Mutex::UpdatePriority() { - if (!holding_thread) - return; - - u32 best_priority = ThreadPrioLowest; - for (auto& waiter : GetWaitingThreads()) { - if (waiter->current_priority < best_priority) - best_priority = waiter->current_priority; - } - - if (best_priority != priority) { - priority = best_priority; - holding_thread->UpdatePriority(); - } -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h deleted file mode 100644 index 5094b3c4f..000000000 --- a/src/core/hle/kernel/mutex.h +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include "common/common_types.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/resource_limit.h" -#include "core/hle/kernel/wait_object.h" -#include "core/hle/result.h" - -namespace Kernel { - -class Thread; - -class Mutex final : public WaitObject { -public: - explicit Mutex(KernelSystem& kernel); - ~Mutex() override; - - std::string GetTypeName() const override { - return "Mutex"; - } - std::string GetName() const override { - return name; - } - - static constexpr HandleType HANDLE_TYPE = HandleType::Mutex; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - std::shared_ptr resource_limit; - int lock_count; ///< Number of times the mutex has been acquired - u32 priority; ///< The priority of the mutex, used for priority inheritance. - std::string name; ///< Name of mutex (optional) - std::shared_ptr holding_thread; ///< Thread that has acquired the mutex - - /** - * Elevate the mutex priority to the best priority - * among the priorities of all its waiting threads. - */ - void UpdatePriority(); - - bool ShouldWait(const Thread* thread) const override; - void Acquire(Thread* thread) override; - - void AddWaitingThread(std::shared_ptr thread) override; - void RemoveWaitingThread(Thread* thread) override; - - /** - * Attempts to release the mutex from the specified thread. - * @param thread Thread that wants to release the mutex. - * @returns The result code of the operation. - */ - ResultCode Release(Thread* thread); - -private: - KernelSystem& kernel; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int file_version) { - ar& boost::serialization::base_object(*this); - ar& lock_count; - ar& priority; - ar& name; - ar& holding_thread; - ar& resource_limit; - } -}; - -/** - * Releases all the mutexes held by the specified thread - * @param thread Thread that is holding the mutexes - */ -void ReleaseThreadMutexes(Thread* thread); - -} // namespace Kernel - -BOOST_CLASS_EXPORT_KEY(Kernel::Mutex) -CONSTRUCT_KERNEL_OBJECT(Kernel::Mutex) diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index f9032f299..6a8e2432e 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -9,14 +9,14 @@ #include #include "common/archives.h" #include "common/assert.h" -#include "common/common_funcs.h" #include "common/logging/log.h" -#include "common/serialization/boost_vector.hpp" +#include "common/scope_exit.h" #include "core/core.h" #include "core/hle/kernel/errors.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" #include "core/hle/kernel/memory.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/resource_limit.h" +#include "core/hle/kernel/k_resource_limit.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/vm_manager.h" #include "core/hle/service/plgldr/plgldr.h" @@ -53,33 +53,7 @@ void Process::serialize(Archive& ar, const unsigned int file_version) { SERIALIZE_IMPL(Process) -std::shared_ptr KernelSystem::CreateCodeSet(std::string name, u64 program_id) { - auto codeset{std::make_shared(*this)}; - - codeset->name = std::move(name); - codeset->program_id = program_id; - - return codeset; -} - -CodeSet::CodeSet(KernelSystem& kernel) : Object(kernel) {} -CodeSet::~CodeSet() {} - -std::shared_ptr KernelSystem::CreateProcess(std::shared_ptr code_set) { - auto process{std::make_shared(*this)}; - - process->codeset = std::move(code_set); - process->flags.raw = 0; - process->flags.memory_region.Assign(MemoryRegion::APPLICATION); - process->status = ProcessStatus::Created; - process->process_id = ++next_process_id; - process->creation_time_ticks = timing.GetTicks(); - - process_list.push_back(process); - return process; -} - -void KernelSystem::TerminateProcess(std::shared_ptr process) { +void KernelSystem::TerminateProcess(Process* process) { LOG_INFO(Kernel_SVC, "Process {} exiting", process->process_id); ASSERT_MSG(process->status == ProcessStatus::Running, "Process has already exited"); @@ -164,6 +138,8 @@ void Process::ParseKernelCaps(const u32* kernel_caps, std::size_t len) { LOG_ERROR(Loader, "Unhandled kernel caps descriptor: 0x{:08X}", descriptor); } } + + handle_table.Initialize(handle_table_size); } void Process::Set3dsxKernelCaps() { @@ -185,22 +161,17 @@ void Process::Set3dsxKernelCaps() { void Process::Run(s32 main_thread_priority, u32 stack_size) { memory_region = kernel.GetMemoryRegion(flags.memory_region); - // Ensure we can reserve a thread. Real kernel returns 0xC860180C if this fails. - if (!resource_limit->Reserve(ResourceLimitType::Thread, 1)) { - return; - } - auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions, MemoryState memory_state) { HeapAllocate(segment.addr, segment.size, permissions, memory_state, true); - kernel.memory.WriteBlock(*this, segment.addr, codeset->memory.data() + segment.offset, + kernel.memory.WriteBlock(*this, segment.addr, codeset.memory.data() + segment.offset, segment.size); }; // Map CodeSet segments - MapSegment(codeset->CodeSegment(), VMAPermission::ReadExecute, MemoryState::Code); - MapSegment(codeset->RODataSegment(), VMAPermission::Read, MemoryState::Code); - MapSegment(codeset->DataSegment(), VMAPermission::ReadWrite, MemoryState::Private); + MapSegment(codeset.CodeSegment(), VMAPermission::ReadExecute, MemoryState::Code); + MapSegment(codeset.RODataSegment(), VMAPermission::Read, MemoryState::Code); + MapSegment(codeset.DataSegment(), VMAPermission::ReadWrite, MemoryState::Private); // Allocate and map stack HeapAllocate(Memory::HEAP_VADDR_END - stack_size, stack_size, VMAPermission::ReadWrite, @@ -218,9 +189,24 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) { } status = ProcessStatus::Running; - vm_manager.LogLayout(Common::Log::Level::Debug); - Kernel::SetupMainThread(kernel, codeset->entrypoint, main_thread_priority, SharedFrom(this)); + + // Place a tentative reservation of a thread for this process. + KScopedResourceReservation thread_reservation(this, ResourceLimitType::Thread); + ASSERT(thread_reservation.Succeeded()); + + // Create a new thread for the process. + Thread* main_thread = Thread::Create(m_kernel); + ASSERT(main_thread != nullptr); + SCOPE_EXIT({ main_thread->Close(); }); + + // Initialize the thread. + main_thread->Initialize("", codeset.entrypoint, main_thread_priority, 0, ideal_processor, + Memory::HEAP_VADDR_END, this); + + // Register the thread, and commit our reservation. + Thread::Register(m_kernel, main_thread); + thread_reservation.Commit(); } void Process::Exit() { @@ -395,7 +381,7 @@ ResultCode Process::LinearFree(VAddr target, u32 size) { return RESULT_SUCCESS; } -ResultVal Process::AllocateThreadLocalStorage() { +ResultCode Process::AllocateThreadLocalStorage(VAddr* out_tls_addr) { std::size_t tls_page; std::size_t tls_slot; bool needs_allocation = true; @@ -462,7 +448,8 @@ ResultVal Process::AllocateThreadLocalStorage() { static_cast(tls_slot) * Memory::TLS_ENTRY_SIZE; kernel.memory.ZeroBlock(*this, tls_address, Memory::TLS_ENTRY_SIZE); - return tls_address; + *out_tls_addr = tls_address; + R_SUCCEED(); } ResultCode Process::Map(VAddr target, VAddr source, u32 size, VMAPermission perms, @@ -562,6 +549,11 @@ ResultCode Process::Unmap(VAddr target, VAddr source, u32 size, VMAPermission pe return RESULT_SUCCESS; } +void Process::ReleaseResource(ResourceLimitType type, s32 amount) { + ASSERT(resource_limit); + resource_limit->Release(type, amount); +} + void Process::FreeAllMemory() { if (memory_region == nullptr || resource_limit == nullptr) { return; @@ -599,30 +591,35 @@ void Process::FreeAllMemory() { } Kernel::Process::Process(KernelSystem& kernel) - : Object(kernel), handle_table(kernel), vm_manager(kernel.memory, *this), kernel(kernel) { + : KAutoObjectWithSlabHeapAndContainer(kernel), handle_table(kernel), + vm_manager(kernel.memory, *this), kernel(kernel) { kernel.memory.RegisterPageTable(vm_manager.page_table); } -Kernel::Process::~Process() { - LOG_INFO(Kernel, "Cleaning up process {}", process_id); - // Release all objects this process owns first so that their potential destructor can do clean - // up with this process before further destruction. - // TODO(wwylele): explicitly destroy or invalidate objects this process owns (threads, shared - // memory etc.) even if they are still referenced by other processes. - handle_table.Clear(); +Kernel::Process::~Process() = default; +void Process::Initialize(CodeSet&& code_set) { + codeset = std::move(code_set); + flags.memory_region.Assign(MemoryRegion::APPLICATION); + status = ProcessStatus::Created; + process_id = m_kernel.NewProcessId(); + creation_time_ticks = m_kernel.timing.GetTicks(); + m_kernel.process_list.push_back(this); +} + +void Process::Finalize() { + handle_table.Finalize(); FreeAllMemory(); kernel.memory.UnregisterPageTable(vm_manager.page_table); } -std::shared_ptr KernelSystem::GetProcessById(u32 process_id) const { - auto itr = std::find_if( - process_list.begin(), process_list.end(), - [&](const std::shared_ptr& process) { return process->process_id == process_id; }); +Process* KernelSystem::GetProcessById(u32 process_id) const { + auto it = std::ranges::find_if( + process_list, [&](const auto process) { return process->process_id == process_id; }); - if (itr == process_list.end()) + if (it == process_list.end()) { return nullptr; - - return *itr; + } + return *it; } } // namespace Kernel diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index a7d493f19..ff10fb07b 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -4,11 +4,9 @@ #pragma once -#include #include #include #include -#include #include #include #include @@ -17,8 +15,9 @@ #include #include "common/bit_field.h" #include "common/common_types.h" -#include "core/hle/kernel/handle_table.h" -#include "core/hle/kernel/object.h" +#include "core/hle/kernel/k_code_set.h" +#include "core/hle/kernel/k_handle_table.h" +#include "core/hle/kernel/slab_helpers.h" #include "core/hle/kernel/vm_manager.h" namespace Kernel { @@ -59,121 +58,37 @@ union ProcessFlags { BitField<12, 1, u16> loaded_high; ///< Application loaded high (not at 0x00100000). }; -enum class ProcessStatus { Created, Running, Exited }; - -class ResourceLimit; -struct MemoryRegionInfo; - -class CodeSet final : public Object { -public: - explicit CodeSet(KernelSystem& kernel); - ~CodeSet() override; - - struct Segment { - std::size_t offset = 0; - VAddr addr = 0; - u32 size = 0; - - private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int file_version) { - ar& offset; - ar& addr; - ar& size; - } - }; - - std::string GetTypeName() const override { - return "CodeSet"; - } - std::string GetName() const override { - return name; - } - - static constexpr HandleType HANDLE_TYPE = HandleType::CodeSet; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - Segment& CodeSegment() { - return segments[0]; - } - - const Segment& CodeSegment() const { - return segments[0]; - } - - Segment& RODataSegment() { - return segments[1]; - } - - const Segment& RODataSegment() const { - return segments[1]; - } - - Segment& DataSegment() { - return segments[2]; - } - - const Segment& DataSegment() const { - return segments[2]; - } - - std::vector memory; - - std::array segments; - VAddr entrypoint; - - /// Name of the process - std::string name; - /// Title ID corresponding to the process - u64 program_id; - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int file_version) { - ar& boost::serialization::base_object(*this); - ar& memory; - ar& segments; - ar& entrypoint; - ar& name; - ar& program_id; - } +enum class ProcessStatus { + Created, + Running, + Exited, }; -class Process final : public Object { +class KResourceLimit; +enum class ResourceLimitType : u32; +struct MemoryRegionInfo; + +class Process final : public KAutoObjectWithSlabHeapAndContainer { + KERNEL_AUTOOBJECT_TRAITS(Process, KAutoObject); + public: explicit Process(Kernel::KernelSystem& kernel); ~Process() override; - std::string GetTypeName() const override { - return "Process"; - } - std::string GetName() const override { - return codeset->name; - } + KHandleTable handle_table; - static constexpr HandleType HANDLE_TYPE = HandleType::Process; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - HandleTable handle_table; - - std::shared_ptr codeset; + CodeSet codeset; /// Resource limit descriptor for this process - std::shared_ptr resource_limit; + KResourceLimit* resource_limit; /// The process may only call SVCs which have the corresponding bit set. std::bitset<0x80> svc_access_mask; /// Maximum size of the handle table for the process. - unsigned int handle_table_size = 0x200; + u32 handle_table_size = 0x200; /// Special memory ranges mapped into this processes address space. This is used to give /// processes access to specific I/O regions and device memory. boost::container::static_vector address_mappings; - ProcessFlags flags; + ProcessFlags flags{}; bool no_thread_restrictions = false; /// Kernel compatibility version for this process u16 kernel_version = 0; @@ -188,6 +103,12 @@ public: // Creation time in ticks of the process. u64 creation_time_ticks; + void Initialize(CodeSet&& code_set); + + static void PostDestroy(uintptr_t arg) {} + + void Finalize() override; + /** * Parses a list of kernel capability descriptors (as found in the ExHeader) and applies them * to this process. @@ -209,9 +130,6 @@ public: */ void Exit(); - /////////////////////////////////////////////////////////////////////////////////////////////// - // Memory Management - VMManager vm_manager; u32 memory_used = 0; @@ -239,13 +157,15 @@ public: ResultVal LinearAllocate(VAddr target, u32 size, VMAPermission perms); ResultCode LinearFree(VAddr target, u32 size); - ResultVal AllocateThreadLocalStorage(); + ResultCode AllocateThreadLocalStorage(VAddr* out_tls); ResultCode Map(VAddr target, VAddr source, u32 size, VMAPermission perms, bool privileged = false); ResultCode Unmap(VAddr target, VAddr source, u32 size, VMAPermission perms, bool privileged = false); + void ReleaseResource(ResourceLimitType type, s32 amount); + private: void FreeAllMemory(); @@ -258,7 +178,5 @@ private: } // namespace Kernel -BOOST_CLASS_EXPORT_KEY(Kernel::CodeSet) BOOST_CLASS_EXPORT_KEY(Kernel::Process) -CONSTRUCT_KERNEL_OBJECT(Kernel::CodeSet) CONSTRUCT_KERNEL_OBJECT(Kernel::Process) diff --git a/src/core/hle/kernel/semaphore.cpp b/src/core/hle/kernel/semaphore.cpp deleted file mode 100644 index 5e2412fe6..000000000 --- a/src/core/hle/kernel/semaphore.cpp +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/archives.h" -#include "core/hle/kernel/errors.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/process.h" -#include "core/hle/kernel/resource_limit.h" -#include "core/hle/kernel/semaphore.h" -#include "core/hle/kernel/thread.h" - -SERIALIZE_EXPORT_IMPL(Kernel::Semaphore) - -namespace Kernel { - -Semaphore::Semaphore(KernelSystem& kernel) : WaitObject(kernel) {} - -Semaphore::~Semaphore() { - if (resource_limit) { - resource_limit->Release(ResourceLimitType::Semaphore, 1); - } -} - -ResultVal> KernelSystem::CreateSemaphore(s32 initial_count, - s32 max_count, - std::string name) { - - if (initial_count > max_count) { - return ERR_INVALID_COMBINATION_KERNEL; - } - - // When the semaphore is created, some slots are reserved for other threads, - // and the rest is reserved for the caller thread - auto semaphore = std::make_shared(*this); - semaphore->max_count = max_count; - semaphore->available_count = initial_count; - semaphore->name = std::move(name); - return semaphore; -} - -bool Semaphore::ShouldWait(const Thread* thread) const { - return available_count <= 0; -} - -void Semaphore::Acquire(Thread* thread) { - if (available_count <= 0) - return; - --available_count; -} - -ResultVal Semaphore::Release(s32 release_count) { - if (max_count - available_count < release_count) - return ERR_OUT_OF_RANGE_KERNEL; - - s32 previous_count = available_count; - available_count += release_count; - - WakeupAllWaitingThreads(); - - return previous_count; -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/semaphore.h b/src/core/hle/kernel/semaphore.h deleted file mode 100644 index 2fa3b720b..000000000 --- a/src/core/hle/kernel/semaphore.h +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include "common/common_types.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/wait_object.h" -#include "core/hle/result.h" - -namespace Kernel { - -class ResourceLimit; - -class Semaphore final : public WaitObject { -public: - explicit Semaphore(KernelSystem& kernel); - ~Semaphore() override; - - std::string GetTypeName() const override { - return "Semaphore"; - } - std::string GetName() const override { - return name; - } - - static constexpr HandleType HANDLE_TYPE = HandleType::Semaphore; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - std::shared_ptr resource_limit; - s32 max_count; ///< Maximum number of simultaneous holders the semaphore can have - s32 available_count; ///< Number of free slots left in the semaphore - std::string name; ///< Name of semaphore (optional) - - bool ShouldWait(const Thread* thread) const override; - void Acquire(Thread* thread) override; - - /** - * Releases a certain number of slots from a semaphore. - * @param release_count The number of slots to release - * @return The number of free slots the semaphore had before this call - */ - ResultVal Release(s32 release_count); - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int file_version) { - ar& boost::serialization::base_object(*this); - ar& max_count; - ar& available_count; - ar& name; - ar& resource_limit; - } -}; - -} // namespace Kernel - -BOOST_CLASS_EXPORT_KEY(Kernel::Semaphore) -CONSTRUCT_KERNEL_OBJECT(Kernel::Semaphore) diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp deleted file mode 100644 index 721a029d8..000000000 --- a/src/core/hle/kernel/server_port.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include -#include "common/archives.h" -#include "common/assert.h" -#include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/errors.h" -#include "core/hle/kernel/hle_ipc.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/server_port.h" -#include "core/hle/kernel/server_session.h" -#include "core/hle/kernel/thread.h" - -SERIALIZE_EXPORT_IMPL(Kernel::ServerPort) - -namespace Kernel { - -ServerPort::ServerPort(KernelSystem& kernel) : WaitObject(kernel) {} -ServerPort::~ServerPort() {} - -ResultVal> ServerPort::Accept() { - if (pending_sessions.empty()) { - return ERR_NO_PENDING_SESSIONS; - } - - auto session = std::move(pending_sessions.back()); - pending_sessions.pop_back(); - return session; -} - -bool ServerPort::ShouldWait(const Thread* thread) const { - // If there are no pending sessions, we wait until a new one is added. - return pending_sessions.size() == 0; -} - -void ServerPort::Acquire(Thread* thread) { - ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); -} - -KernelSystem::PortPair KernelSystem::CreatePortPair(u32 max_sessions, std::string name) { - auto server_port{std::make_shared(*this)}; - auto client_port{std::make_shared(*this)}; - - server_port->name = name + "_Server"; - client_port->name = name + "_Client"; - client_port->server_port = server_port; - client_port->max_sessions = max_sessions; - client_port->active_sessions = 0; - - return std::make_pair(std::move(server_port), std::move(client_port)); -} - -template -void ServerPort::serialize(Archive& ar, const unsigned int file_version) { - ar& boost::serialization::base_object(*this); - ar& name; - ar& pending_sessions; - ar& hle_handler; -} -SERIALIZE_IMPL(ServerPort) - -} // namespace Kernel diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h deleted file mode 100644 index 00eb10100..000000000 --- a/src/core/hle/kernel/server_port.h +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include "common/common_types.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/server_session.h" -#include "core/hle/kernel/wait_object.h" -#include "core/hle/result.h" - -namespace Kernel { - -class ClientPort; -class ServerSession; -class SessionRequestHandler; - -class ServerPort final : public WaitObject { -public: - explicit ServerPort(KernelSystem& kernel); - ~ServerPort() override; - - std::string GetTypeName() const override { - return "ServerPort"; - } - std::string GetName() const override { - return name; - } - - static constexpr HandleType HANDLE_TYPE = HandleType::ServerPort; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - /** - * Accepts a pending incoming connection on this port. If there are no pending sessions, will - * return ERR_NO_PENDING_SESSIONS. - */ - ResultVal> Accept(); - - /** - * Sets the HLE handler template for the port. ServerSessions crated by connecting to this port - * will inherit a reference to this handler. - */ - void SetHleHandler(std::shared_ptr hle_handler_) { - hle_handler = std::move(hle_handler_); - } - - std::string name; ///< Name of port (optional) - - /// ServerSessions waiting to be accepted by the port - std::vector> pending_sessions; - - /// This session's HLE request handler template (optional) - /// ServerSessions created from this port inherit a reference to this handler. - std::shared_ptr hle_handler; - - bool ShouldWait(const Thread* thread) const override; - void Acquire(Thread* thread) override; - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int file_version); -}; - -} // namespace Kernel - -BOOST_CLASS_EXPORT_KEY(Kernel::ServerPort) -CONSTRUCT_KERNEL_OBJECT(Kernel::ServerPort) diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp deleted file mode 100644 index 2e0c647e4..000000000 --- a/src/core/hle/kernel/server_session.cpp +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include "common/archives.h" -#include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/client_session.h" -#include "core/hle/kernel/hle_ipc.h" -#include "core/hle/kernel/server_session.h" -#include "core/hle/kernel/session.h" -#include "core/hle/kernel/thread.h" - -SERIALIZE_EXPORT_IMPL(Kernel::ServerSession) - -namespace Kernel { - -template -void ServerSession::serialize(Archive& ar, const unsigned int file_version) { - ar& boost::serialization::base_object(*this); - ar& name; - ar& parent; - ar& hle_handler; - ar& pending_requesting_threads; - ar& currently_handling; - ar& mapped_buffer_context; -} -SERIALIZE_IMPL(ServerSession) - -ServerSession::ServerSession(KernelSystem& kernel) : WaitObject(kernel), kernel(kernel) {} -ServerSession::~ServerSession() { - // This destructor will be called automatically when the last ServerSession handle is closed by - // the emulated application. - - // Decrease the port's connection count. - if (parent->port) - parent->port->ConnectionClosed(); - - // TODO(Subv): Wake up all the ClientSession's waiting threads and set - // the SendSyncRequest result to 0xC920181A. - - parent->server = nullptr; -} - -ResultVal> ServerSession::Create(KernelSystem& kernel, - std::string name) { - auto server_session{std::make_shared(kernel)}; - - server_session->name = std::move(name); - server_session->parent = nullptr; - - return server_session; -} - -bool ServerSession::ShouldWait(const Thread* thread) const { - // Closed sessions should never wait, an error will be returned from svcReplyAndReceive. - if (parent->client == nullptr) - return false; - // Wait if we have no pending requests, or if we're currently handling a request. - return pending_requesting_threads.empty() || currently_handling != nullptr; -} - -void ServerSession::Acquire(Thread* thread) { - ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); - - // If the client endpoint was closed, don't do anything. This ServerSession is now useless and - // will linger until its last handle is closed by the running application. - if (parent->client == nullptr) - return; - - // We are now handling a request, pop it from the stack. - ASSERT(!pending_requesting_threads.empty()); - currently_handling = pending_requesting_threads.back(); - pending_requesting_threads.pop_back(); -} - -ResultCode ServerSession::HandleSyncRequest(std::shared_ptr thread) { - // The ServerSession received a sync request, this means that there's new data available - // from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or - // similar. - - // If this ServerSession has an associated HLE handler, forward the request to it. - if (hle_handler != nullptr) { - std::array cmd_buf; - auto current_process = thread->owner_process.lock(); - ASSERT(current_process); - kernel.memory.ReadBlock(*current_process, thread->GetCommandBufferAddress(), cmd_buf.data(), - cmd_buf.size() * sizeof(u32)); - - auto context = - std::make_shared(kernel, SharedFrom(this), thread); - context->PopulateFromIncomingCommandBuffer(cmd_buf.data(), current_process); - - hle_handler->HandleSyncRequest(*context); - - ASSERT(thread->status == Kernel::ThreadStatus::Running || - thread->status == Kernel::ThreadStatus::WaitHleEvent); - // Only write the response immediately if the thread is still running. If the HLE handler - // put the thread to sleep then the writing of the command buffer will be deferred to the - // wakeup callback. - if (thread->status == Kernel::ThreadStatus::Running) { - context->WriteToOutgoingCommandBuffer(cmd_buf.data(), *current_process); - kernel.memory.WriteBlock(*current_process, thread->GetCommandBufferAddress(), - cmd_buf.data(), cmd_buf.size() * sizeof(u32)); - } - } - - if (thread->status == ThreadStatus::Running) { - // Put the thread to sleep until the server replies, it will be awoken in - // svcReplyAndReceive for LLE servers. - thread->status = ThreadStatus::WaitIPC; - - if (hle_handler != nullptr) { - // For HLE services, we put the request threads to sleep for a short duration to - // simulate IPC overhead, but only if the HLE handler didn't put the thread to sleep for - // other reasons like an async callback. The IPC overhead is needed to prevent - // starvation when a thread only does sync requests to HLE services while a - // lower-priority thread is waiting to run. - - // This delay was approximated in a homebrew application by measuring the average time - // it takes for svcSendSyncRequest to return when performing the SetLcdForceBlack IPC - // request to the GSP:GPU service in a n3DS with firmware 11.6. The measured values have - // a high variance and vary between models. - static constexpr u64 IPCDelayNanoseconds = 39000; - thread->WakeAfterDelay(IPCDelayNanoseconds); - } else { - // Add the thread to the list of threads that have issued a sync request with this - // server. - pending_requesting_threads.push_back(std::move(thread)); - } - } - - // If this ServerSession does not have an HLE implementation, just wake up the threads waiting - // on it. - WakeupAllWaitingThreads(); - return RESULT_SUCCESS; -} - -KernelSystem::SessionPair KernelSystem::CreateSessionPair(const std::string& name, - std::shared_ptr port) { - auto server_session = ServerSession::Create(*this, name + "_Server").Unwrap(); - auto client_session{std::make_shared(*this)}; - client_session->name = name + "_Client"; - - std::shared_ptr parent(new Session); - parent->client = client_session.get(); - parent->server = server_session.get(); - parent->port = port; - - client_session->parent = parent; - server_session->parent = parent; - - return std::make_pair(std::move(server_session), std::move(client_session)); -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h deleted file mode 100644 index b91accce5..000000000 --- a/src/core/hle/kernel/server_session.h +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include "common/assert.h" -#include "common/common_types.h" -#include "core/hle/kernel/ipc.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/session.h" -#include "core/hle/kernel/wait_object.h" -#include "core/hle/result.h" -#include "core/memory.h" - -namespace Kernel { - -class ClientSession; -class ClientPort; -class ServerSession; -class Session; -class SessionRequestHandler; -class Thread; - -/** - * Kernel object representing the server endpoint of an IPC session. Sessions are the basic CTR-OS - * primitive for communication between different processes, and are used to implement service calls - * to the various system services. - * - * To make a service call, the client must write the command header and parameters to the buffer - * located at offset 0x80 of the TLS (Thread-Local Storage) area, then execute a SendSyncRequest - * SVC call with its ClientSession handle. The kernel will read the command header, using it to - * marshall the parameters to the process at the server endpoint of the session. - * After the server replies to the request, the response is marshalled back to the caller's - * TLS buffer and control is transferred back to it. - */ -class ServerSession final : public WaitObject { -public: - ~ServerSession() override; - explicit ServerSession(KernelSystem& kernel); - - std::string GetName() const override { - return name; - } - std::string GetTypeName() const override { - return "ServerSession"; - } - - static constexpr HandleType HANDLE_TYPE = HandleType::ServerSession; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - /** - * Sets the HLE handler for the session. This handler will be called to service IPC requests - * instead of the regular IPC machinery. (The regular IPC machinery is currently not - * implemented.) - */ - void SetHleHandler(std::shared_ptr hle_handler_) { - hle_handler = std::move(hle_handler_); - } - - /** - * Handle a sync request from the emulated application. - * @param thread Thread that initiated the request. - * @returns ResultCode from the operation. - */ - ResultCode HandleSyncRequest(std::shared_ptr thread); - - bool ShouldWait(const Thread* thread) const override; - - void Acquire(Thread* thread) override; - - std::string name; ///< The name of this session (optional) - std::shared_ptr parent; ///< The parent session, which links to the client endpoint. - std::shared_ptr - hle_handler; ///< This session's HLE request handler (optional) - - /// List of threads that are pending a response after a sync request. This list is processed in - /// a LIFO manner, thus, the last request will be dispatched first. - /// TODO(Subv): Verify if this is indeed processed in LIFO using a hardware test. - std::vector> pending_requesting_threads; - - /// Thread whose request is currently being handled. A request is considered "handled" when a - /// response is sent via svcReplyAndReceive. - /// TODO(Subv): Find a better name for this. - std::shared_ptr currently_handling; - - /// A temporary list holding mapped buffer info from IPC request, used for during IPC reply - std::vector mapped_buffer_context; - -private: - /** - * Creates a server session. The server session can have an optional HLE handler, - * which will be invoked to handle the IPC requests that this session receives. - * @param kernel The kernel instance to create the server session on - * @param name Optional name of the server session. - * @return The created server session - */ - static ResultVal> Create(KernelSystem& kernel, - std::string name = "Unknown"); - - friend class KernelSystem; - KernelSystem& kernel; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int file_version); -}; - -} // namespace Kernel - -BOOST_CLASS_EXPORT_KEY(Kernel::ServerSession) -CONSTRUCT_KERNEL_OBJECT(Kernel::ServerSession) diff --git a/src/core/hle/kernel/session.cpp b/src/core/hle/kernel/session.cpp deleted file mode 100644 index 8bb846c52..000000000 --- a/src/core/hle/kernel/session.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include "common/archives.h" -#include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/client_session.h" -#include "core/hle/kernel/server_session.h" -#include "core/hle/kernel/session.h" - -SERIALIZE_IMPL(Kernel::Session) - -namespace Kernel { - -template -void Session::serialize(Archive& ar, const unsigned int file_version) { - ar& client; - ar& server; - ar& port; -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/session.h b/src/core/hle/kernel/session.h deleted file mode 100644 index eca1a9252..000000000 --- a/src/core/hle/kernel/session.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include "core/hle/kernel/object.h" - -namespace Kernel { - -class ClientSession; -class ClientPort; -class ServerSession; - -/** - * Parent structure to link the client and server endpoints of a session with their associated - * client port. The client port need not exist, as is the case for portless sessions like the - * FS File and Directory sessions. When one of the endpoints of a session is destroyed, its - * corresponding field in this structure will be set to nullptr. - */ -class Session final { -public: - ClientSession* client = nullptr; ///< The client endpoint of the session. - ServerSession* server = nullptr; ///< The server endpoint of the session. - std::shared_ptr port; ///< The port that this session is associated with (optional). - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int file_version); -}; -} // namespace Kernel diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp deleted file mode 100644 index 46e216551..000000000 --- a/src/core/hle/kernel/shared_memory.cpp +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/archives.h" -#include "common/logging/log.h" -#include "core/hle/kernel/errors.h" -#include "core/hle/kernel/memory.h" -#include "core/hle/kernel/resource_limit.h" -#include "core/hle/kernel/shared_memory.h" -#include "core/memory.h" - -SERIALIZE_EXPORT_IMPL(Kernel::SharedMemory) - -namespace Kernel { - -SharedMemory::SharedMemory(KernelSystem& kernel) : Object(kernel), kernel(kernel) {} - -SharedMemory::~SharedMemory() { - for (const auto& interval : holding_memory) { - kernel.GetMemoryRegion(MemoryRegion::SYSTEM) - ->Free(interval.lower(), interval.upper() - interval.lower()); - } - - auto process = owner_process.lock(); - if (process) { - process->resource_limit->Release(ResourceLimitType::SharedMemory, 1); - if (base_address != 0) { - process->vm_manager.ChangeMemoryState(base_address, size, MemoryState::Locked, - VMAPermission::None, MemoryState::Private, - VMAPermission::ReadWrite); - } else { - process->memory_used -= size; - } - } -} - -ResultVal> KernelSystem::CreateSharedMemory( - std::shared_ptr owner_process, u32 size, MemoryPermission permissions, - MemoryPermission other_permissions, VAddr address, MemoryRegion region, std::string name) { - - auto shared_memory = std::make_shared(*this); - shared_memory->owner_process = owner_process; - shared_memory->name = std::move(name); - shared_memory->size = size; - shared_memory->permissions = permissions; - shared_memory->other_permissions = other_permissions; - - if (address == 0) { - // We need to allocate a block from the Linear Heap ourselves. - // We'll manually allocate some memory from the linear heap in the specified region. - auto memory_region = GetMemoryRegion(region); - auto offset = memory_region->LinearAllocate(size); - - ASSERT_MSG(offset, "Not enough space in region to allocate shared memory!"); - - std::fill(memory.GetFCRAMPointer(*offset), memory.GetFCRAMPointer(*offset + size), 0); - shared_memory->backing_blocks = {{memory.GetFCRAMRef(*offset), size}}; - shared_memory->holding_memory += MemoryRegionInfo::Interval(*offset, *offset + size); - shared_memory->linear_heap_phys_offset = *offset; - - // Increase the amount of used linear heap memory for the owner process. - if (owner_process != nullptr) { - owner_process->memory_used += size; - } - } else { - auto& vm_manager = owner_process->vm_manager; - // The memory is already available and mapped in the owner process. - - CASCADE_CODE(vm_manager.ChangeMemoryState(address, size, MemoryState::Private, - VMAPermission::ReadWrite, MemoryState::Locked, - SharedMemory::ConvertPermissions(permissions))); - - auto backing_blocks = vm_manager.GetBackingBlocksForRange(address, size); - ASSERT(backing_blocks.Succeeded()); // should success after verifying memory state above - shared_memory->backing_blocks = std::move(backing_blocks).Unwrap(); - } - - shared_memory->base_address = address; - return shared_memory; -} - -std::shared_ptr KernelSystem::CreateSharedMemoryForApplet( - u32 offset, u32 size, MemoryPermission permissions, MemoryPermission other_permissions, - std::string name) { - auto shared_memory{std::make_shared(*this)}; - - // Allocate memory in heap - auto memory_region = GetMemoryRegion(MemoryRegion::SYSTEM); - auto backing_blocks = memory_region->HeapAllocate(size); - ASSERT_MSG(!backing_blocks.empty(), "Not enough space in region to allocate shared memory!"); - shared_memory->holding_memory = backing_blocks; - shared_memory->owner_process = std::weak_ptr(); - shared_memory->name = std::move(name); - shared_memory->size = size; - shared_memory->permissions = permissions; - shared_memory->other_permissions = other_permissions; - for (const auto& interval : backing_blocks) { - shared_memory->backing_blocks.emplace_back(memory.GetFCRAMRef(interval.lower()), - interval.upper() - interval.lower()); - std::fill(memory.GetFCRAMPointer(interval.lower()), - memory.GetFCRAMPointer(interval.upper()), 0); - } - shared_memory->base_address = Memory::HEAP_VADDR + offset; - - return shared_memory; -} - -ResultCode SharedMemory::Map(Process& target_process, VAddr address, MemoryPermission permissions, - MemoryPermission other_permissions) { - - MemoryPermission own_other_permissions = - &target_process == owner_process.lock().get() ? this->permissions : this->other_permissions; - - // Automatically allocated memory blocks can only be mapped with other_permissions = DontCare - if (base_address == 0 && other_permissions != MemoryPermission::DontCare) { - return ERR_INVALID_COMBINATION; - } - - // Error out if the requested permissions don't match what the creator process allows. - if (static_cast(permissions) & ~static_cast(own_other_permissions)) { - LOG_ERROR(Kernel, "cannot map id={}, address=0x{:08X} name={}, permissions don't match", - GetObjectId(), address, name); - return ERR_INVALID_COMBINATION; - } - - // Heap-backed memory blocks can not be mapped with other_permissions = DontCare - if (base_address != 0 && other_permissions == MemoryPermission::DontCare) { - LOG_ERROR(Kernel, "cannot map id={}, address=0x{08X} name={}, permissions don't match", - GetObjectId(), address, name); - return ERR_INVALID_COMBINATION; - } - - // Error out if the provided permissions are not compatible with what the creator process needs. - if (other_permissions != MemoryPermission::DontCare && - static_cast(this->permissions) & ~static_cast(other_permissions)) { - LOG_ERROR(Kernel, "cannot map id={}, address=0x{:08X} name={}, permissions don't match", - GetObjectId(), address, name); - return ERR_WRONG_PERMISSION; - } - - // TODO(Subv): Check for the Shared Device Mem flag in the creator process. - /*if (was_created_with_shared_device_mem && address != 0) { - return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, - ErrorSummary::InvalidArgument, ErrorLevel::Usage); - }*/ - - // TODO(Subv): The same process that created a SharedMemory object - // can not map it in its own address space unless it was created with addr=0, result 0xD900182C. - - if (address != 0) { - if (address < Memory::HEAP_VADDR || address + size >= Memory::SHARED_MEMORY_VADDR_END) { - LOG_ERROR(Kernel, "cannot map id={}, address=0x{:08X} name={}, invalid address", - GetObjectId(), address, name); - return ERR_INVALID_ADDRESS; - } - } - - VAddr target_address = address; - - if (base_address == 0 && target_address == 0) { - // Calculate the address at which to map the memory block. - // Note: even on new firmware versions, the target address is still in the old linear heap - // region. This exception is made to keep the shared font compatibility. See - // APT:GetSharedFont for detail. - target_address = linear_heap_phys_offset + Memory::LINEAR_HEAP_VADDR; - } - { - auto vma = target_process.vm_manager.FindVMA(target_address); - if (vma->second.type != VMAType::Free || - vma->second.base + vma->second.size < target_address + size) { - LOG_ERROR( - Kernel, - "cannot map id={}, address=0x{:08X} name={}, mapping to already allocated memory", - GetObjectId(), address, name); - return ERR_INVALID_ADDRESS_STATE; - } - } - - // Map the memory block into the target process - VAddr interval_target = target_address; - for (const auto& interval : backing_blocks) { - auto vma = target_process.vm_manager.MapBackingMemory(interval_target, interval.first, - interval.second, MemoryState::Shared); - ASSERT(vma.Succeeded()); - target_process.vm_manager.Reprotect(vma.Unwrap(), ConvertPermissions(permissions)); - interval_target += interval.second; - } - - return RESULT_SUCCESS; -} - -ResultCode SharedMemory::Unmap(Process& target_process, VAddr address) { - // TODO(Subv): Verify what happens if the application tries to unmap an address that is not - // mapped to a SharedMemory. - return target_process.vm_manager.UnmapRange(address, size); -} - -VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) { - u32 masked_permissions = - static_cast(permission) & static_cast(MemoryPermission::ReadWriteExecute); - return static_cast(masked_permissions); -}; - -u8* SharedMemory::GetPointer(u32 offset) { - if (backing_blocks.size() != 1) { - LOG_WARNING(Kernel, "Unsafe GetPointer on discontinuous SharedMemory"); - } - return backing_blocks[0].first + offset; -} - -const u8* SharedMemory::GetPointer(u32 offset) const { - if (backing_blocks.size() != 1) { - LOG_WARNING(Kernel, "Unsafe GetPointer on discontinuous SharedMemory"); - } - return backing_blocks[0].first + offset; -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h deleted file mode 100644 index d5cec7678..000000000 --- a/src/core/hle/kernel/shared_memory.h +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include "common/common_types.h" -#include "common/memory_ref.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/process.h" -#include "core/hle/result.h" - -namespace Kernel { - -class SharedMemory final : public Object { -public: - explicit SharedMemory(KernelSystem& kernel); - ~SharedMemory() override; - - std::string GetTypeName() const override { - return "SharedMemory"; - } - std::string GetName() const override { - return name; - } - void SetName(std::string name_) { - name = std::move(name_); - } - - static constexpr HandleType HANDLE_TYPE = HandleType::SharedMemory; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - /// Gets the size of the underlying memory block in bytes. - u64 GetSize() const { - return size; - } - - /// Gets the linear heap physical offset - u64 GetLinearHeapPhysicalOffset() const { - return linear_heap_phys_offset; - } - - /** - * Converts the specified MemoryPermission into the equivalent VMAPermission. - * @param permission The MemoryPermission to convert. - */ - static VMAPermission ConvertPermissions(MemoryPermission permission); - - /** - * Maps a shared memory block to an address in the target process' address space - * @param target_process Process on which to map the memory block. - * @param address Address in system memory to map shared memory block to - * @param permissions Memory block map permissions (specified by SVC field) - * @param other_permissions Memory block map other permissions (specified by SVC field) - */ - ResultCode Map(Process& target_process, VAddr address, MemoryPermission permissions, - MemoryPermission other_permissions); - - /** - * Unmaps a shared memory block from the specified address in system memory - * @param target_process Process from which to unmap the memory block. - * @param address Address in system memory where the shared memory block is mapped - * @return Result code of the unmap operation - */ - ResultCode Unmap(Process& target_process, VAddr address); - - /** - * Gets a pointer to the shared memory block - * @param offset Offset from the start of the shared memory block to get pointer - * @return A pointer to the shared memory block from the specified offset - */ - u8* GetPointer(u32 offset = 0); - - /** - * Gets a constant pointer to the shared memory block - * @param offset Offset from the start of the shared memory block to get pointer - * @return A constant pointer to the shared memory block from the specified offset - */ - const u8* GetPointer(u32 offset = 0) const; - -private: - /// Offset in FCRAM of the shared memory block in the linear heap if no address was specified - /// during creation. - PAddr linear_heap_phys_offset = 0; - /// Backing memory for this shared memory block. - std::vector> backing_blocks; - /// Size of the memory block. Page-aligned. - u32 size = 0; - /// Permission restrictions applied to the process which created the block. - MemoryPermission permissions{}; - /// Permission restrictions applied to other processes mapping the block. - MemoryPermission other_permissions{}; - /// Process that created this shared memory block. - std::weak_ptr owner_process; - /// Address of shared memory block in the owner process if specified. - VAddr base_address = 0; - /// Name of shared memory object. - std::string name; - - MemoryRegionInfo::IntervalSet holding_memory; - - friend class KernelSystem; - KernelSystem& kernel; - - template - void serialize(Archive& ar, const unsigned int file_version) { - ar& boost::serialization::base_object(*this); - ar& linear_heap_phys_offset; - ar& backing_blocks; - ar& size; - ar& permissions; - ar& other_permissions; - ar& owner_process; - ar& base_address; - ar& name; - ar& holding_memory; - } - friend class boost::serialization::access; -}; - -} // namespace Kernel - -BOOST_CLASS_EXPORT_KEY(Kernel::SharedMemory) -CONSTRUCT_KERNEL_OBJECT(Kernel::SharedMemory) diff --git a/src/core/hle/kernel/slab_helpers.h b/src/core/hle/kernel/slab_helpers.h index 3821d8126..a3d89581f 100644 --- a/src/core/hle/kernel/slab_helpers.h +++ b/src/core/hle/kernel/slab_helpers.h @@ -52,10 +52,8 @@ public: } }; -template -class KAutoObjectWithSlabHeap : public Base { - static_assert(std::is_base_of::value); - +template +class KAutoObjectWithSlabHeap : public KAutoObject { private: static Derived* Allocate(KernelSystem& kernel) { return kernel.SlabHeap().Allocate(kernel); @@ -66,7 +64,7 @@ private: } public: - explicit KAutoObjectWithSlabHeap(KernelSystem& kernel) : Base(kernel) {} + explicit KAutoObjectWithSlabHeap(KernelSystem& kernel) : KAutoObject(kernel) {} virtual ~KAutoObjectWithSlabHeap() = default; virtual void Destroy() override { @@ -76,7 +74,7 @@ public: arg = this->GetPostDestroyArgument(); this->Finalize(); } - Free(Base::m_kernel, static_cast(this)); + Free(KAutoObject::m_kernel, static_cast(this)); if (is_initialized) { Derived::PostDestroy(arg); } @@ -90,7 +88,8 @@ public: } size_t GetSlabIndex() const { - return SlabHeap(Base::m_kernel).GetObjectIndex(static_cast(this)); + return SlabHeap(KAutoObject::m_kernel) + .GetObjectIndex(static_cast(this)); } public: @@ -127,4 +126,86 @@ public: } }; +template +class KAutoObjectWithSlabHeapAndContainer : public Base { + static_assert(std::is_base_of_v); + +private: + static Derived* Allocate(KernelSystem& kernel) { + return kernel.SlabHeap().Allocate(kernel); + } + + static void Free(KernelSystem& kernel, Derived* obj) { + kernel.SlabHeap().Free(obj); + } + +public: + KAutoObjectWithSlabHeapAndContainer(KernelSystem& kernel) : KAutoObject(kernel) {} + virtual ~KAutoObjectWithSlabHeapAndContainer() {} + + virtual void Destroy() override { + const bool is_initialized = this->IsInitialized(); + uintptr_t arg = 0; + if (is_initialized) { + KAutoObject::m_kernel.ObjectListContainer().Unregister(this); + arg = this->GetPostDestroyArgument(); + this->Finalize(); + } + Free(KAutoObject::m_kernel, static_cast(this)); + if (is_initialized) { + Derived::PostDestroy(arg); + } + } + + virtual bool IsInitialized() const { + return true; + } + virtual uintptr_t GetPostDestroyArgument() const { + return 0; + } + + size_t GetSlabIndex() const { + return SlabHeap(KAutoObject::m_kernel) + .GetObjectIndex(static_cast(this)); + } + +public: + static void InitializeSlabHeap(KernelSystem& kernel, void* memory, size_t memory_size) { + kernel.SlabHeap().Initialize(memory, memory_size); + kernel.ObjectListContainer().Initialize(); + } + + static Derived* Create(KernelSystem& kernel) { + Derived* obj = Allocate(kernel); + if (obj != nullptr) { + KAutoObject::Create(obj); + } + return obj; + } + + static void Register(KernelSystem& kernel, Derived* obj) { + return kernel.ObjectListContainer().Register(obj); + } + + static size_t GetObjectSize(KernelSystem& kernel) { + return kernel.SlabHeap().GetObjectSize(); + } + + static size_t GetSlabHeapSize(KernelSystem& kernel) { + return kernel.SlabHeap().GetSlabHeapSize(); + } + + static size_t GetPeakIndex(KernelSystem& kernel) { + return kernel.SlabHeap().GetPeakIndex(); + } + + static uintptr_t GetSlabHeapAddress(KernelSystem& kernel) { + return kernel.SlabHeap().GetSlabHeapAddress(); + } + + static size_t GetNumRemaining(KernelSystem& kernel) { + return kernel.SlabHeap().GetNumRemaining(); + } +}; + } // namespace Kernel diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index a9a1a618f..f3f5e655e 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -5,39 +5,42 @@ #include #include #include +#include "common/alignment.h" #include "common/archives.h" #include "common/logging/log.h" #include "common/microprofile.h" #include "common/scm_rev.h" +#include "common/scope_exit.h" #include "core/arm/arm_interface.h" #include "core/core.h" #include "core/core_timing.h" #include "core/gdbstub/hio.h" -#include "core/hle/kernel/address_arbiter.h" -#include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/client_session.h" #include "core/hle/kernel/config_mem.h" #include "core/hle/kernel/errors.h" -#include "core/hle/kernel/event.h" -#include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/ipc.h" #include "core/hle/kernel/ipc_debugger/recorder.h" +#include "core/hle/kernel/k_address_arbiter.h" +#include "core/hle/kernel/k_client_port.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_handle_table.h" +#include "core/hle/kernel/k_mutex.h" +#include "core/hle/kernel/k_object_name.h" +#include "core/hle/kernel/k_port.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" +#include "core/hle/kernel/k_semaphore.h" +#include "core/hle/kernel/k_server_port.h" +#include "core/hle/kernel/k_server_session.h" +#include "core/hle/kernel/k_session.h" +#include "core/hle/kernel/k_shared_memory.h" +#include "core/hle/kernel/k_timer.h" #include "core/hle/kernel/memory.h" -#include "core/hle/kernel/mutex.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/resource_limit.h" -#include "core/hle/kernel/semaphore.h" -#include "core/hle/kernel/server_port.h" -#include "core/hle/kernel/server_session.h" -#include "core/hle/kernel/session.h" -#include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/svc.h" #include "core/hle/kernel/svc_wrapper.h" #include "core/hle/kernel/thread.h" -#include "core/hle/kernel/timer.h" #include "core/hle/kernel/vm_manager.h" -#include "core/hle/kernel/wait_object.h" -#include "core/hle/result.h" #include "core/hle/service/plgldr/plgldr.h" namespace Kernel { @@ -347,6 +350,12 @@ enum class ControlProcessOP { PROCESSOP_DISABLE_CREATE_THREAD_RESTRICTIONS, }; +enum class BreakReason : u8 { + Panic = 0, + Assert = 1, + User = 2, +}; + class SVC : public SVCWrapper { public: SVC(Core::System& system); @@ -542,28 +551,32 @@ void SVC::ExitProcess() { } ResultCode SVC::TerminateProcess(Handle handle) { - std::shared_ptr process = - kernel.GetCurrentProcess()->handle_table.Get(handle); - if (process == nullptr) { - return ERR_INVALID_HANDLE; - } + // Get the current process and handle table. + auto current_process = kernel.GetCurrentProcess(); + auto& handle_table = current_process->handle_table; - kernel.TerminateProcess(process); - return RESULT_SUCCESS; + // Get the process to terminate. + KScopedAutoObject process = handle_table.GetObject(handle); + R_UNLESS(process.IsNotNull(), ERR_INVALID_HANDLE); + + // Terminate it. + kernel.TerminateProcess(process.GetPointerUnsafe()); + R_SUCCEED(); } -/// Maps a memory block to specified address ResultCode SVC::MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 other_permissions) { - LOG_TRACE(Kernel_SVC, - "called memblock=0x{:08X}, addr=0x{:08X}, mypermissions=0x{:08X}, " - "otherpermission={}", - handle, addr, permissions, other_permissions); + // Get the current process and handle table. + auto current_process = kernel.GetCurrentProcess(); + auto& handle_table = current_process->handle_table; - std::shared_ptr shared_memory = - kernel.GetCurrentProcess()->handle_table.Get(handle); - if (shared_memory == nullptr) - return ERR_INVALID_HANDLE; + // Validate the address alignment. + R_UNLESS(Common::Is4KBAligned(addr), ERR_MISALIGNED_ADDRESS); + // Get the shared memory. + KScopedAutoObject shared_memory = handle_table.GetObject(handle); + R_UNLESS(shared_memory.IsNotNull(), ERR_INVALID_HANDLE); + + // Map shared memory. MemoryPermission permissions_type = static_cast(permissions); switch (permissions_type) { case MemoryPermission::Read: @@ -574,140 +587,148 @@ ResultCode SVC::MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 oth case MemoryPermission::WriteExecute: case MemoryPermission::ReadWriteExecute: case MemoryPermission::DontCare: - return shared_memory->Map(*kernel.GetCurrentProcess(), addr, permissions_type, - static_cast(other_permissions)); + R_RETURN(shared_memory->Map(*current_process, addr, permissions_type, + static_cast(other_permissions))); default: LOG_ERROR(Kernel_SVC, "unknown permissions=0x{:08X}", permissions); } - return ERR_INVALID_COMBINATION; + R_RETURN(ERR_INVALID_COMBINATION); } ResultCode SVC::UnmapMemoryBlock(Handle handle, u32 addr) { - LOG_TRACE(Kernel_SVC, "called memblock=0x{:08X}, addr=0x{:08X}", handle, addr); + // Get the current process and handle table. + auto current_process = kernel.GetCurrentProcess(); + auto& handle_table = current_process->handle_table; + + // Validate the address alignment. + R_UNLESS(Common::Is4KBAligned(addr), ERR_MISALIGNED_ADDRESS); // TODO(Subv): Return E0A01BF5 if the address is not in the application's heap - std::shared_ptr current_process = kernel.GetCurrentProcess(); - std::shared_ptr shared_memory = - current_process->handle_table.Get(handle); - if (shared_memory == nullptr) - return ERR_INVALID_HANDLE; + // Get the shared memory. + KScopedAutoObject shared_memory = handle_table.GetObject(handle); + R_UNLESS(shared_memory.IsNotNull(), ERR_INVALID_HANDLE); - return shared_memory->Unmap(*current_process, addr); + // Unmap the shared memory. + R_RETURN(shared_memory->Unmap(*current_process, addr)); } -/// Connect to an OS service given the port name, returns the handle to the port to out ResultCode SVC::ConnectToPort(Handle* out_handle, VAddr port_name_address) { - if (!memory.IsValidVirtualAddress(*kernel.GetCurrentProcess(), port_name_address)) { - return ERR_NOT_FOUND; - } + // Get the current process and handle table. + auto current_process = kernel.GetCurrentProcess(); + auto& handle_table = current_process->handle_table; + + // Ensure the port virtual address is valid. + R_UNLESS(memory.IsValidVirtualAddress(*current_process, port_name_address), ERR_NOT_FOUND); - static constexpr std::size_t PortNameMaxLength = 11; // Read 1 char beyond the max allowed port name to detect names that are too long. - std::string port_name = memory.ReadCString(port_name_address, PortNameMaxLength + 1); - if (port_name.size() > PortNameMaxLength) { - return ERR_PORT_NAME_TOO_LONG; - } + static constexpr std::size_t PortNameMaxLength = 11; + const auto port_name = memory.ReadCString(port_name_address, PortNameMaxLength + 1); + R_UNLESS(port_name.size() <= PortNameMaxLength, ERR_PORT_NAME_TOO_LONG); - LOG_TRACE(Kernel_SVC, "called port_name={}", port_name); + // Find the client port. + auto port = KObjectName::Find(system.Kernel(), port_name.data()); + R_UNLESS(port.IsNotNull(), ERR_NOT_FOUND); - auto it = kernel.named_ports.find(port_name); - if (it == kernel.named_ports.end()) { - LOG_WARNING(Kernel_SVC, "tried to connect to unknown port: {}", port_name); - return ERR_NOT_FOUND; - } + // Create a session. + KClientSession* session; + R_TRY(port->CreateSession(std::addressof(session))); - auto client_port = it->second; + // Add the session in the table, close the extra reference. + handle_table.Add(out_handle, session); + session->Close(); - std::shared_ptr client_session; - CASCADE_RESULT(client_session, client_port->Connect()); - - // Return the client session - CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(client_session)); - return RESULT_SUCCESS; + // We succeeded. + R_SUCCEED(); } -/// Makes a blocking IPC call to an OS service. ResultCode SVC::SendSyncRequest(Handle handle) { - std::shared_ptr session = - kernel.GetCurrentProcess()->handle_table.Get(handle); - if (session == nullptr) { - return ERR_INVALID_HANDLE; - } + // Get the current process and handle table. + auto current_process = kernel.GetCurrentProcess(); + auto& handle_table = current_process->handle_table; - LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName()); + // Get the client session. + KScopedAutoObject session = handle_table.GetObject(handle); + R_UNLESS(session.IsNotNull(), ERR_INVALID_HANDLE); + // Reschedule. system.PrepareReschedule(); - auto thread = SharedFrom(kernel.GetCurrentThreadManager().GetCurrentThread()); - - if (kernel.GetIPCRecorder().IsEnabled()) { - kernel.GetIPCRecorder().RegisterRequest(session, thread); + // Record the sync request in the IPC recorder. + auto thread = kernel.GetCurrentThreadManager().GetCurrentThread(); + auto& ipc_recorder = kernel.GetIPCRecorder(); + if (ipc_recorder.IsEnabled()) { + ipc_recorder.RegisterRequest(session.GetPointerUnsafe(), thread); } - return session->SendSyncRequest(thread); + // Perform the sync request. + R_RETURN(session->SendSyncRequest(thread)); } ResultCode SVC::OpenProcess(Handle* out_handle, u32 process_id) { - std::shared_ptr process = kernel.GetProcessById(process_id); - if (!process) { - // Result 0xd9001818 (process not found?) - return ResultCode(24, ErrorModule::OS, ErrorSummary::WrongArgument, ErrorLevel::Permanent); - } - auto result_handle = kernel.GetCurrentProcess()->handle_table.Create(process); - if (!result_handle) { - return result_handle.Code(); - } - *out_handle = result_handle.Unwrap(); - return RESULT_SUCCESS; + // Find the process with the provided pid. + Process* process = kernel.GetProcessById(process_id); + R_UNLESS(process, ERR_PROCESS_NOT_FOUND); + + // Open a reference to the process. + process->Open(); + SCOPE_EXIT({ process->Close(); }); + + // Add the process to the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; + R_RETURN(handle_table.Add(out_handle, process)); } -ResultCode SVC::OpenThread(Handle* out_handle, Handle process_handle, u32 thread_id) { - if (process_handle == 0) { - LOG_ERROR(Kernel_SVC, "Uninplemented svcOpenThread process_handle=0"); - // Result 0xd9001819 (thread not found?) - return ResultCode(25, ErrorModule::OS, ErrorSummary::WrongArgument, ErrorLevel::Permanent); - } +ResultCode SVC::OpenThread(Handle* out_handle, Handle handle, u32 thread_id) { + static constexpr ResultCode ResultThreadNotFound = + ResultCode(25, ErrorModule::OS, ErrorSummary::WrongArgument, ErrorLevel::Permanent); - std::shared_ptr process = - kernel.GetCurrentProcess()->handle_table.Get(process_handle); - if (!process) { - return ERR_INVALID_HANDLE; - } + // TODO: Implement OpenThread with process_handle = 0 + R_UNLESS(handle != 0, ResultThreadNotFound); + + // Get the current process and handle table. + auto current_process = kernel.GetCurrentProcess(); + auto& handle_table = current_process->handle_table; + + // Get the process. + KScopedAutoObject process = handle_table.GetObject(handle); + R_UNLESS(process.IsNotNull(), ERR_INVALID_HANDLE); for (u32 core_id = 0; core_id < system.GetNumCores(); core_id++) { const auto thread_list = kernel.GetThreadManager(core_id).GetThreadList(); for (auto& thread : thread_list) { - if (thread->owner_process.lock() == process && thread.get()->thread_id == thread_id) { - auto result_handle = kernel.GetCurrentProcess()->handle_table.Create(thread); - if (!result_handle) { - return result_handle.Code(); - } - *out_handle = result_handle.Unwrap(); - return RESULT_SUCCESS; + if (thread->GetOwner() != process.GetPointerUnsafe() || + thread->GetThreadId() != thread_id) { + continue; } + + // Add the found thread to the provided processes handle table. + R_RETURN(handle_table.Add(out_handle, thread)); } } - // Result 0xd9001819 (thread not found?) - return ResultCode(25, ErrorModule::OS, ErrorSummary::WrongArgument, ErrorLevel::Permanent); + + // Did not find any thread + R_THROW(ResultThreadNotFound); } -/// Close a handle ResultCode SVC::CloseHandle(Handle handle) { - LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle); - return kernel.GetCurrentProcess()->handle_table.Close(handle); + // Get the current process and handle table. + auto current_process = kernel.GetCurrentProcess(); + auto& handle_table = current_process->handle_table; + + // Close the handle + R_UNLESS(handle_table.Remove(handle), ERR_INVALID_HANDLE); + R_SUCCEED(); } static ResultCode ReceiveIPCRequest(Kernel::KernelSystem& kernel, Memory::MemorySystem& memory, - std::shared_ptr server_session, - std::shared_ptr thread); + KServerSession* server_session, Thread* thread); class SVC_SyncCallback : public Kernel::WakeupCallback { public: explicit SVC_SyncCallback(bool do_output_) : do_output(do_output_) {} - void WakeUp(ThreadWakeupReason reason, std::shared_ptr thread, - std::shared_ptr object) { + void WakeUp(ThreadWakeupReason reason, Thread* thread, KSynchronizationObject* object) { if (reason == ThreadWakeupReason::Timeout) { thread->SetWaitSynchronizationResult(RESULT_TIMEOUT); @@ -715,12 +736,11 @@ public: } ASSERT(reason == ThreadWakeupReason::Signal); - thread->SetWaitSynchronizationResult(RESULT_SUCCESS); // The wait_all case does not update the output index. if (do_output) { - thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(object.get())); + thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(object)); } } @@ -740,21 +760,19 @@ class SVC_IPCCallback : public Kernel::WakeupCallback { public: explicit SVC_IPCCallback(Core::System& system_) : system(system_) {} - void WakeUp(ThreadWakeupReason reason, std::shared_ptr thread, - std::shared_ptr object) { + void WakeUp(ThreadWakeupReason reason, Thread* thread, KSynchronizationObject* object) { - ASSERT(thread->status == ThreadStatus::WaitSynchAny); + ASSERT(thread->GetStatus() == ThreadStatus::WaitSynchAny); ASSERT(reason == ThreadWakeupReason::Signal); ResultCode result = RESULT_SUCCESS; - - if (object->GetHandleType() == HandleType::ServerSession) { - auto server_session = DynamicObjectCast(object); + if (object->GetTypeObj().GetClassToken() == ClassTokenType::KServerSession) { + auto server_session = object->DynamicCast(); result = ReceiveIPCRequest(system.Kernel(), system.Memory(), server_session, thread); } thread->SetWaitSynchronizationResult(result); - thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(object.get())); + thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(object)); } private: @@ -771,184 +789,172 @@ private: /// Wait for a handle to synchronize, timeout after the specified nanoseconds ResultCode SVC::WaitSynchronization1(Handle handle, s64 nano_seconds) { - auto object = kernel.GetCurrentProcess()->handle_table.Get(handle); + KScopedAutoObject object = + kernel.GetCurrentProcess()->handle_table.GetObject(handle); + R_UNLESS(object.IsNotNull(), ERR_INVALID_HANDLE); + Thread* thread = kernel.GetCurrentThreadManager().GetCurrentThread(); - - if (object == nullptr) - return ERR_INVALID_HANDLE; - - LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({}:{}), nanoseconds={}", handle, - object->GetTypeName(), object->GetName(), nano_seconds); - if (object->ShouldWait(thread)) { + R_UNLESS(nano_seconds != 0, RESULT_TIMEOUT); - if (nano_seconds == 0) - return RESULT_TIMEOUT; - - thread->wait_objects = {object}; - object->AddWaitingThread(SharedFrom(thread)); - thread->status = ThreadStatus::WaitSynchAny; + // Add the object to the threads wait list. + thread->m_wait_objects = {object.GetPointerUnsafe()}; + object->AddWaitingThread(thread); + thread->m_status = ThreadStatus::WaitSynchAny; // Create an event to wake the thread up after the specified nanosecond delay has passed thread->WakeAfterDelay(nano_seconds); - - thread->wakeup_callback = std::make_shared(false); + thread->m_wakeup_callback = std::make_shared(false); system.PrepareReschedule(); // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread // resumes due to a signal in its wait objects. // Otherwise we retain the default value of timeout. - return RESULT_TIMEOUT; + R_THROW(RESULT_TIMEOUT); } object->Acquire(thread); - - return RESULT_SUCCESS; + R_SUCCEED(); } /// Wait for the given handles to synchronize, timeout after the specified nanoseconds -ResultCode SVC::WaitSynchronizationN(s32* out, VAddr handles_address, s32 handle_count, +ResultCode SVC::WaitSynchronizationN(s32* out, VAddr handles_address, s32 num_handles, bool wait_all, s64 nano_seconds) { + // Get handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; Thread* thread = kernel.GetCurrentThreadManager().GetCurrentThread(); - if (!memory.IsValidVirtualAddress(*kernel.GetCurrentProcess(), handles_address)) { - return ERR_INVALID_POINTER; + // Ensure address and handle count are valid. + R_UNLESS(memory.IsValidVirtualAddress(*kernel.GetCurrentProcess(), handles_address), + ERR_INVALID_POINTER); + R_UNLESS(num_handles >= 0, ERR_OUT_OF_RANGE); + + std::vector handles(num_handles); + std::vector objects(num_handles); + + // Copy user handles. + if (num_handles > 0) { + // Get the handles. + memory.ReadBlock(num_handles, handles.data(), sizeof(Handle) * num_handles); + + // Convert the handles to objects. + R_UNLESS(handle_table.GetMultipleObjects( + objects.data(), handles.data(), num_handles), + ERR_INVALID_HANDLE); } - // NOTE: on real hardware, there is no nullptr check for 'out' (tested with firmware 4.4). If - // this happens, the running application will crash. - ASSERT_MSG(out != nullptr, "invalid output pointer specified!"); - - // Check if 'handle_count' is invalid - if (handle_count < 0) { - return ERR_OUT_OF_RANGE; - } - - using ObjectPtr = std::shared_ptr; - std::vector objects(handle_count); - - for (int i = 0; i < handle_count; ++i) { - Handle handle = memory.Read32(handles_address + i * sizeof(Handle)); - auto object = kernel.GetCurrentProcess()->handle_table.Get(handle); - if (object == nullptr) - return ERR_INVALID_HANDLE; - objects[i] = object; - } + // Ensure handles are closed when we're done. + SCOPE_EXIT({ + for (auto i = 0; i < num_handles; ++i) { + objects[i]->Close(); + } + }); if (wait_all) { - bool all_available = - std::all_of(objects.begin(), objects.end(), - [thread](const ObjectPtr& object) { return !object->ShouldWait(thread); }); + const bool all_available = std::ranges::all_of( + objects, [thread](const auto object) { return !object->ShouldWait(thread); }); if (all_available) { // We can acquire all objects right now, do so. - for (auto& object : objects) + for (auto& object : objects) { object->Acquire(thread); + } // Note: In this case, the `out` parameter is not set, // and retains whatever value it had before. - return RESULT_SUCCESS; + R_SUCCEED(); } // Not all objects were available right now, prepare to suspend the thread. // If a timeout value of 0 was provided, just return the Timeout error code instead of // suspending the thread. - if (nano_seconds == 0) - return RESULT_TIMEOUT; + R_UNLESS(nano_seconds != 0, RESULT_TIMEOUT); // Put the thread to sleep - thread->status = ThreadStatus::WaitSynchAll; + thread->m_status = ThreadStatus::WaitSynchAll; // Add the thread to each of the objects' waiting threads. for (auto& object : objects) { - object->AddWaitingThread(SharedFrom(thread)); + object->AddWaitingThread(thread); } - thread->wait_objects = std::move(objects); + thread->m_wait_objects = std::move(objects); // Create an event to wake the thread up after the specified nanosecond delay has passed thread->WakeAfterDelay(nano_seconds); - - thread->wakeup_callback = std::make_shared(false); - + thread->m_wakeup_callback = std::make_shared(false); system.PrepareReschedule(); // This value gets set to -1 by default in this case, it is not modified after this. *out = -1; // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to // a signal in one of its wait objects. - return RESULT_TIMEOUT; + R_THROW(RESULT_TIMEOUT); } else { // Find the first object that is acquirable in the provided list of objects - auto itr = std::find_if(objects.begin(), objects.end(), [thread](const ObjectPtr& object) { - return !object->ShouldWait(thread); - }); + auto itr = std::ranges::find_if( + objects, [thread](const auto object) { return !object->ShouldWait(thread); }); if (itr != objects.end()) { // We found a ready object, acquire it and set the result value - WaitObject* object = itr->get(); + KSynchronizationObject* object = *itr; object->Acquire(thread); *out = static_cast(std::distance(objects.begin(), itr)); - return RESULT_SUCCESS; + R_SUCCEED(); } // No objects were ready to be acquired, prepare to suspend the thread. // If a timeout value of 0 was provided, just return the Timeout error code instead of // suspending the thread. - if (nano_seconds == 0) - return RESULT_TIMEOUT; + R_UNLESS(nano_seconds != 0, RESULT_TIMEOUT); // Put the thread to sleep - thread->status = ThreadStatus::WaitSynchAny; + thread->m_status = ThreadStatus::WaitSynchAny; // Add the thread to each of the objects' waiting threads. - for (std::size_t i = 0; i < objects.size(); ++i) { - WaitObject* object = objects[i].get(); - object->AddWaitingThread(SharedFrom(thread)); + for (KSynchronizationObject* object : objects) { + object->AddWaitingThread(thread); } - thread->wait_objects = std::move(objects); + thread->m_wait_objects = std::move(objects); // Note: If no handles and no timeout were given, then the thread will deadlock, this is // consistent with hardware behavior. // Create an event to wake the thread up after the specified nanosecond delay has passed thread->WakeAfterDelay(nano_seconds); - - thread->wakeup_callback = std::make_shared(true); - + thread->m_wakeup_callback = std::make_shared(true); system.PrepareReschedule(); // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a // signal in one of its wait objects. // Otherwise we retain the default value of timeout, and -1 in the out parameter *out = -1; - return RESULT_TIMEOUT; + R_THROW(RESULT_TIMEOUT); } } static ResultCode ReceiveIPCRequest(Kernel::KernelSystem& kernel, Memory::MemorySystem& memory, - std::shared_ptr server_session, - std::shared_ptr thread) { - if (server_session->parent->client == nullptr) { - return ERR_SESSION_CLOSED_BY_REMOTE; - } + KServerSession* server_session, Thread* thread) { + R_UNLESS(server_session, ERR_SESSION_CLOSED_BY_REMOTE); + Thread* currently_handling = server_session->GetCurrent(); VAddr target_address = thread->GetCommandBufferAddress(); - VAddr source_address = server_session->currently_handling->GetCommandBufferAddress(); + VAddr source_address = currently_handling->GetCommandBufferAddress(); - ResultCode translation_result = TranslateCommandBuffer( - kernel, memory, server_session->currently_handling, thread, source_address, target_address, - server_session->mapped_buffer_context, false); + ResultCode translation_result = + TranslateCommandBuffer(kernel, memory, currently_handling, thread, source_address, + target_address, server_session->GetMappedBufferContext(), false); // If a translation error occurred, immediately resume the client thread. if (translation_result.IsError()) { // Set the output of SendSyncRequest in the client thread to the translation output. - server_session->currently_handling->SetWaitSynchronizationResult(translation_result); + currently_handling->SetWaitSynchronizationResult(translation_result); - server_session->currently_handling->ResumeFromWait(); - server_session->currently_handling = nullptr; + // Wake up the thread. + currently_handling->ResumeFromWait(); + currently_handling = nullptr; // TODO(Subv): This path should try to wait again on the same objects. ASSERT_MSG(false, "ReplyAndReceive translation error behavior unimplemented"); @@ -958,58 +964,61 @@ static ResultCode ReceiveIPCRequest(Kernel::KernelSystem& kernel, Memory::Memory } /// In a single operation, sends a IPC reply and waits for a new request. -ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_count, +ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 num_handles, Handle reply_target) { - if (!memory.IsValidVirtualAddress(*kernel.GetCurrentProcess(), handles_address)) { - return ERR_INVALID_POINTER; + // Retrieve current process and handle table. + auto current_process = kernel.GetCurrentProcess(); + auto& handle_table = current_process->handle_table; + + // Ensure number of handles and handles array are valid. + R_UNLESS(memory.IsValidVirtualAddress(*current_process, handles_address), ERR_INVALID_POINTER); + R_UNLESS(0 <= num_handles, ERR_OUT_OF_RANGE); + + // Retrieve the waitable objects from the handle table. + std::vector handles(num_handles); + std::vector objects(num_handles); + + // Copy user handles. + if (num_handles > 0) { + // Get the handles. + memory.ReadBlock(handles_address, handles.data(), sizeof(Handle) * num_handles); + + // Convert the handles to objects. + R_UNLESS(handle_table.GetMultipleObjects( + objects.data(), handles.data(), num_handles), + ERR_INVALID_HANDLE); } - // Check if 'handle_count' is invalid - if (handle_count < 0) { - return ERR_OUT_OF_RANGE; - } - - using ObjectPtr = std::shared_ptr; - std::vector objects(handle_count); - - std::shared_ptr current_process = kernel.GetCurrentProcess(); - - for (int i = 0; i < handle_count; ++i) { - Handle handle = memory.Read32(handles_address + i * sizeof(Handle)); - auto object = current_process->handle_table.Get(handle); - if (object == nullptr) - return ERR_INVALID_HANDLE; - objects[i] = object; - } + // Ensure handles are closed when we're done. + SCOPE_EXIT({ + for (auto i = 0; i < num_handles; ++i) { + objects[i]->Close(); + } + }); // We are also sending a command reply. // Do not send a reply if the command id in the command buffer is 0xFFFF. Thread* thread = kernel.GetCurrentThreadManager().GetCurrentThread(); - u32 cmd_buff_header = memory.Read32(thread->GetCommandBufferAddress()); - IPC::Header header{cmd_buff_header}; + const u32 cmd_buff_header = memory.Read32(thread->GetCommandBufferAddress()); + const IPC::Header header{cmd_buff_header}; if (reply_target != 0 && header.command_id != 0xFFFF) { - auto session = current_process->handle_table.Get(reply_target); - if (session == nullptr) - return ERR_INVALID_HANDLE; - - auto request_thread = std::move(session->currently_handling); + // Get the session from its handle. + KScopedAutoObject session = handle_table.GetObject(reply_target); + R_UNLESS(session.IsNotNull(), ERR_INVALID_HANDLE); // Mark the request as "handled". - session->currently_handling = nullptr; + auto request_thread = std::exchange(session->currently_handling, nullptr); // Error out if there's no request thread or the session was closed. - // TODO(Subv): Is the same error code (ClosedByRemote) returned for both of these cases? - if (request_thread == nullptr || session->parent->client == nullptr) { - *index = -1; - return ERR_SESSION_CLOSED_BY_REMOTE; - } + R_UNLESS(request_thread, ERR_SESSION_CLOSED_BY_REMOTE); + R_UNLESS(session->GetParent()->GetState() == KSessionState::Normal, ERR_SESSION_CLOSED_BY_REMOTE); VAddr source_address = thread->GetCommandBufferAddress(); VAddr target_address = request_thread->GetCommandBufferAddress(); - ResultCode translation_result = TranslateCommandBuffer( - kernel, memory, SharedFrom(thread), request_thread, source_address, target_address, - session->mapped_buffer_context, true); + ResultCode translation_result = + TranslateCommandBuffer(kernel, memory, thread, request_thread, source_address, + target_address, session->GetMappedBufferContext(), true); // Note: The real kernel seems to always panic if the Server->Client buffer translation // fails for whatever reason. @@ -1019,119 +1028,128 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co request_thread->ResumeFromWait(); } - if (handle_count == 0) { + if (num_handles == 0) { *index = 0; // The kernel uses this value as a placeholder for the real error, and returns it when we // pass no handles and do not perform any reply. - if (reply_target == 0 || header.command_id == 0xFFFF) + if (reply_target == 0 || header.command_id == 0xFFFF) { return ResultCode(0xE7E3FFFF); + } return RESULT_SUCCESS; } // Find the first object that is acquirable in the provided list of objects - auto itr = std::find_if(objects.begin(), objects.end(), [thread](const ObjectPtr& object) { - return !object->ShouldWait(thread); - }); + auto it = std::find_if(objects.begin(), objects.end(), + [thread](const auto* object) { return !object->ShouldWait(thread); }); - if (itr != objects.end()) { + if (it != objects.end()) { // We found a ready object, acquire it and set the result value - WaitObject* object = itr->get(); + KSynchronizationObject* object = *it; object->Acquire(thread); - *index = static_cast(std::distance(objects.begin(), itr)); + *index = static_cast(std::distance(objects.begin(), it)); - if (object->GetHandleType() != HandleType::ServerSession) - return RESULT_SUCCESS; + // If not a server session we are done. + R_SUCCEED_IF(object->GetTypeObj().GetClassToken() != ClassTokenType::KServerSession); - auto server_session = static_cast(object); - return ReceiveIPCRequest(kernel, memory, SharedFrom(server_session), SharedFrom(thread)); + // Otherwise receive the IPC request. + auto server_session = object->DynamicCast(); + return ReceiveIPCRequest(kernel, memory, server_session, thread); } // No objects were ready to be acquired, prepare to suspend the thread. // Put the thread to sleep - thread->status = ThreadStatus::WaitSynchAny; + thread->m_status = ThreadStatus::WaitSynchAny; // Add the thread to each of the objects' waiting threads. - for (std::size_t i = 0; i < objects.size(); ++i) { - WaitObject* object = objects[i].get(); - object->AddWaitingThread(SharedFrom(thread)); + for (KSynchronizationObject* object : objects) { + object->AddWaitingThread(thread); } - thread->wait_objects = std::move(objects); - - thread->wakeup_callback = std::make_shared(system); - + // Set the wakeup callback. + thread->m_wait_objects = std::move(objects); + thread->m_wakeup_callback = std::make_shared(system); system.PrepareReschedule(); // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a // signal in one of its wait objects, or to 0xC8A01836 if there was a translation error. // By default the index is set to -1. *index = -1; - return RESULT_SUCCESS; + R_SUCCEED(); } -/// Create an address arbiter (to allocate access to shared resources) ResultCode SVC::CreateAddressArbiter(Handle* out_handle) { - // Update address arbiter count in resource limit. - const auto current_process = kernel.GetCurrentProcess(); - const auto& resource_limit = current_process->resource_limit; - if (!resource_limit->Reserve(ResourceLimitType::AddressArbiter, 1)) { - return ResultCode(ErrCodes::OutOfAddressArbiters, ErrorModule::OS, - ErrorSummary::OutOfResource, ErrorLevel::Status); - } + static constexpr ResultCode ResultOutOfArbiters = + ResultCode(ErrCodes::OutOfAddressArbiters, ErrorModule::OS, ErrorSummary::OutOfResource, + ErrorLevel::Status); - // Create address arbiter. - const auto arbiter = kernel.CreateAddressArbiter(); - arbiter->resource_limit = resource_limit; - CASCADE_RESULT(*out_handle, current_process->handle_table.Create(std::move(arbiter))); - LOG_TRACE(Kernel_SVC, "returned handle=0x{:08X}", *out_handle); - return RESULT_SUCCESS; + // Get the current process and handle table. + auto process = kernel.GetCurrentProcess(); + auto& handle_table = process->handle_table; + + // Reserve a new address arbiter from the process resource limit. + KScopedResourceReservation arb_reservation(process, ResourceLimitType::AddressArbiter); + R_UNLESS(arb_reservation.Succeeded(), ResultOutOfArbiters); + + // Create the address arbiter. + KAddressArbiter* arbiter = KAddressArbiter::Create(kernel); + R_UNLESS(arbiter != nullptr, ResultCode{0xC8601801}); + + // Ensure the only reference is in the handle table when we're done. + SCOPE_EXIT({ arbiter->Close(); }); + + // Initialize the address arbiter. + arbiter->Initialize(process); + + // Commit the reservation. + arb_reservation.Commit(); + + // Register the address arbiter. + KAddressArbiter::Register(kernel, arbiter); + + // Add the address arbiter to the handle table. + R_RETURN(handle_table.Add(out_handle, arbiter)); } -/// Arbitrate address ResultCode SVC::ArbitrateAddress(Handle handle, u32 address, u32 type, u32 value, s64 nanoseconds) { - LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}, address=0x{:08X}, type=0x{:08X}, value=0x{:08X}", - handle, address, type, value); + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - std::shared_ptr arbiter = - kernel.GetCurrentProcess()->handle_table.Get(handle); - if (arbiter == nullptr) - return ERR_INVALID_HANDLE; - - auto res = - arbiter->ArbitrateAddress(SharedFrom(kernel.GetCurrentThreadManager().GetCurrentThread()), - static_cast(type), address, value, nanoseconds); + // Get the address arbiter. + KScopedAutoObject arbiter = handle_table.GetObject(handle); + R_UNLESS(arbiter.IsNotNull(), ERR_INVALID_HANDLE); // TODO(Subv): Identify in which specific cases this call should cause a reschedule. system.PrepareReschedule(); - return res; + // Arbitrate the address. + Thread* thread = kernel.GetCurrentThreadManager().GetCurrentThread(); + R_RETURN(arbiter->ArbitrateAddress(thread, static_cast(type), address, value, + nanoseconds)); } void SVC::Break(u8 break_reason) { LOG_CRITICAL(Debug_Emulated, "Emulated program broke execution!"); - std::string reason_str; - switch (break_reason) { - case 0: - reason_str = "PANIC"; - break; - case 1: - reason_str = "ASSERT"; - break; - case 2: - reason_str = "USER"; - break; - default: - reason_str = "UNKNOWN"; - break; - } - LOG_CRITICAL(Debug_Emulated, "Break reason: {}", reason_str); + + const auto reason = static_cast(break_reason); + const auto reason_name = [&] { + switch (reason) { + case BreakReason::Panic: + return "PANIC"; + case BreakReason::Assert: + return "ASSERT"; + case BreakReason::User: + return "USER"; + default: + return "UNKNOWN"; + } + }(); + + LOG_CRITICAL(Debug_Emulated, "Break reason: {}", reason_name); system.SetStatus(Core::System::ResultStatus::ErrorUnknown); } -/// Used to output a message on a debug hardware unit, or for the GDB file I/O -/// (HIO) protocol - does nothing on a retail unit. void SVC::OutputDebugString(VAddr address, s32 len) { if (!memory.IsValidVirtualAddress(*kernel.GetCurrentProcess(), address)) { LOG_WARNING(Kernel_SVC, "OutputDebugString called with invalid address {:X}", address); @@ -1153,86 +1171,84 @@ void SVC::OutputDebugString(VAddr address, s32 len) { LOG_DEBUG(Debug_Emulated, "{}", string); } -/// Get resource limit ResultCode SVC::GetResourceLimit(Handle* resource_limit, Handle process_handle) { - LOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle); + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - std::shared_ptr current_process = kernel.GetCurrentProcess(); - std::shared_ptr process = current_process->handle_table.Get(process_handle); - if (process == nullptr) - return ERR_INVALID_HANDLE; + // Get the process. + KScopedAutoObject process = handle_table.GetObject(process_handle); + R_UNLESS(process.IsNotNull(), ERR_INVALID_HANDLE); - CASCADE_RESULT(*resource_limit, current_process->handle_table.Create(process->resource_limit)); - - return RESULT_SUCCESS; + // Add the resource limit + R_RETURN(handle_table.Add(resource_limit, process->resource_limit)); } -/// Get resource limit current values ResultCode SVC::GetResourceLimitCurrentValues(VAddr values, Handle resource_limit_handle, VAddr names, u32 name_count) { - LOG_TRACE(Kernel_SVC, "called resource_limit={:08X}, names={:08X}, name_count={}", - resource_limit_handle, names, name_count); + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - const auto resource_limit = - kernel.GetCurrentProcess()->handle_table.Get(resource_limit_handle); - if (!resource_limit) { - return ERR_INVALID_HANDLE; - } + // Get the resource limit. + KScopedAutoObject resource_limit = + handle_table.GetObject(resource_limit_handle); + R_UNLESS(resource_limit.IsNotNull(), ERR_INVALID_HANDLE); for (u32 i = 0; i < name_count; ++i) { - const u32 name = memory.Read32(names + i * sizeof(u32)); - const s64 value = resource_limit->GetCurrentValue(static_cast(name)); + // Get resource limit type. + const auto name = static_cast(memory.Read32(names + i * sizeof(u32))); + R_UNLESS(name < ResourceLimitType::Max, ERR_INVALID_ENUM_VALUE); + + // Write the current value. + const s64 value = resource_limit->GetCurrentValue(name); memory.Write64(values + i * sizeof(u64), value); } - return RESULT_SUCCESS; + R_SUCCEED(); } -/// Get resource limit max values ResultCode SVC::GetResourceLimitLimitValues(VAddr values, Handle resource_limit_handle, VAddr names, u32 name_count) { - LOG_TRACE(Kernel_SVC, "called resource_limit={:08X}, names={:08X}, name_count={}", - resource_limit_handle, names, name_count); + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - const auto resource_limit = - kernel.GetCurrentProcess()->handle_table.Get(resource_limit_handle); - if (!resource_limit) { - return ERR_INVALID_HANDLE; - } + // Get the resource limit. + KScopedAutoObject resource_limit = + handle_table.GetObject(resource_limit_handle); + R_UNLESS(resource_limit.IsNotNull(), ERR_INVALID_HANDLE); for (u32 i = 0; i < name_count; ++i) { + // Get resource limit type. const auto name = static_cast(memory.Read32(names + i * sizeof(u32))); - if (name >= ResourceLimitType::Max) { - return ERR_INVALID_ENUM_VALUE; - } + R_UNLESS(name < ResourceLimitType::Max, ERR_INVALID_ENUM_VALUE); + + // Write the limit value. const s64 value = resource_limit->GetLimitValue(name); memory.Write64(values + i * sizeof(u64), value); } - return RESULT_SUCCESS; + R_SUCCEED(); } ResultCode SVC::SetResourceLimitLimitValues(Handle res_limit, VAddr names, VAddr resource_list, u32 name_count) { - LOG_TRACE(Kernel_SVC, "called resource_limit={:08X}, names={:08X}, name_count={}", res_limit, - names, name_count); + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - const auto resource_limit = - kernel.GetCurrentProcess()->handle_table.Get(res_limit); - if (!resource_limit) { - return ERR_INVALID_HANDLE; - } + // Get the resource limit. + KScopedAutoObject resource_limit = handle_table.GetObject(res_limit); + R_UNLESS(resource_limit.IsNotNull(), ERR_INVALID_HANDLE); for (u32 i = 0; i < name_count; ++i) { + // Get resource limit type. const auto name = static_cast(memory.Read32(names + i * sizeof(u32))); - if (name >= ResourceLimitType::Max) { - return ERR_INVALID_ENUM_VALUE; - } - const s64 value = memory.Read64(resource_list + i * sizeof(u64)); + R_UNLESS(name < ResourceLimitType::Max, ERR_INVALID_ENUM_VALUE); + + // Get new resource limit value and validate it. + const u64 value = memory.Read64(resource_list + i * sizeof(u64)); const s32 value_high = value >> 32; - if (value_high < 0) { - return ERR_OUT_OF_RANGE_KERNEL; - } + R_UNLESS(value_high >= 0, ERR_OUT_OF_RANGE_KERNEL); + + // Set the new value. if (name == ResourceLimitType::Commit && value_high != 0) { auto& config_mem = kernel.GetConfigMemHandler().GetConfigMem(); config_mem.app_mem_alloc = value_high; @@ -1240,31 +1256,37 @@ ResultCode SVC::SetResourceLimitLimitValues(Handle res_limit, VAddr names, VAddr resource_limit->SetLimitValue(name, static_cast(value)); } - return RESULT_SUCCESS; + R_SUCCEED(); } /// Creates a new thread ResultCode SVC::CreateThread(Handle* out_handle, u32 entry_point, u32 arg, VAddr stack_top, u32 priority, s32 processor_id) { - std::string name = fmt::format("thread-{:08X}", entry_point); + static constexpr ResultCode ResultOutOfThreads(ErrCodes::OutOfThreads, ErrorModule::OS, + ErrorSummary::OutOfResource, ErrorLevel::Status); - if (priority > ThreadPrioLowest) { - return ERR_OUT_OF_RANGE; - } + // Validate priority. + R_UNLESS(priority <= ThreadPrioLowest, ERR_OUT_OF_RANGE); - const auto current_process = kernel.GetCurrentProcess(); - const auto& resource_limit = current_process->resource_limit; - const u32 max_priority = resource_limit->GetLimitValue(ResourceLimitType::Priority); - if (max_priority > priority && !current_process->no_thread_restrictions) { - return ERR_NOT_AUTHORIZED; - } + // Get the current process and handle table. + const auto process = kernel.GetCurrentProcess(); + auto& handle_table = process->handle_table; + // Reserve a new address arbiter from the process resource limit. + KScopedResourceReservation thread_reservation(process, ResourceLimitType::Thread); + R_UNLESS(thread_reservation.Succeeded(), ResultOutOfThreads); + + // Validate priority limit + const u32 max_priority = process->resource_limit->GetLimitValue(ResourceLimitType::Priority); + R_UNLESS(priority >= max_priority || !process->no_thread_restrictions, ERR_NOT_AUTHORIZED); + + // Set the target CPU to the one specified in the process' exheader. if (processor_id == ThreadProcessorIdDefault) { - // Set the target CPU to the one specified in the process' exheader. - processor_id = current_process->ideal_processor; + processor_id = process->ideal_processor; ASSERT(processor_id != ThreadProcessorIdDefault); } + // Handle the provided processor id. switch (processor_id) { case ThreadProcessorId0: break; @@ -1283,34 +1305,26 @@ ResultCode SVC::CreateThread(Handle* out_handle, u32 entry_point, u32 arg, VAddr // processorid. If this is implemented, make sure to check process->no_thread_restrictions. break; default: - return ERR_OUT_OF_RANGE; - break; + R_THROW(ERR_OUT_OF_RANGE); } - // Update thread count in resource limit. - if (!resource_limit->Reserve(ResourceLimitType::Thread, 1)) { - return ResultCode(ErrCodes::OutOfThreads, ErrorModule::OS, ErrorSummary::OutOfResource, - ErrorLevel::Status); - } + // Create the address arbiter. + Thread* thread = Thread::Create(kernel); + R_UNLESS(thread != nullptr, ResultCode{0xC8601803}); - // Create thread. - CASCADE_RESULT(std::shared_ptr thread, - kernel.CreateThread(name, entry_point, priority, arg, processor_id, stack_top, - current_process)); + // Initialize the thread. + const std::string name = fmt::format("thread-{:08X}", entry_point); + thread->Initialize(name, entry_point, priority, arg, processor_id, stack_top, process); - thread->context.fpscr = - FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO; // 0x03C00000 + // Commit the reservation. + thread_reservation.Commit(); - CASCADE_RESULT(*out_handle, current_process->handle_table.Create(std::move(thread))); + // Register the thread. + Thread::Register(kernel, thread); + // Add to the handle table. system.PrepareReschedule(); - - LOG_TRACE(Kernel_SVC, - "called entrypoint=0x{:08X} ({}), arg=0x{:08X}, stacktop=0x{:08X}, " - "threadpriority=0x{:08X}, processorid=0x{:08X} : created handle=0x{:08X}", - entry_point, name, arg, stack_top, priority, processor_id, *out_handle); - - return RESULT_SUCCESS; + R_RETURN(handle_table.Add(out_handle, thread)); } /// Called when a thread exits @@ -1321,157 +1335,178 @@ void SVC::ExitThread() { system.PrepareReschedule(); } -/// Gets the priority for the specified thread -ResultCode SVC::GetThreadPriority(u32* priority, Handle handle) { - const std::shared_ptr thread = - kernel.GetCurrentProcess()->handle_table.Get(handle); - if (thread == nullptr) - return ERR_INVALID_HANDLE; +ResultCode SVC::GetThreadPriority(u32* out_priority, Handle handle) { + // Get the thread from its handle. + KScopedAutoObject thread = kernel.GetCurrentProcess()->handle_table.GetObject(handle); + R_UNLESS(thread.IsNotNull(), ERR_INVALID_HANDLE); - *priority = thread->GetPriority(); - return RESULT_SUCCESS; + // Get the thread's priority. + *out_priority = thread->GetPriority(); + R_SUCCEED(); } /// Sets the priority for the specified thread -ResultCode SVC::SetThreadPriority(Handle handle, u32 priority) { - if (priority > ThreadPrioLowest) { - return ERR_OUT_OF_RANGE; - } +ResultCode SVC::SetThreadPriority(Handle thread_handle, u32 priority) { + // Get the current process. + Process* process = kernel.GetCurrentProcess(); - const auto thread = kernel.GetCurrentProcess()->handle_table.Get(handle); - if (!thread) { - return ERR_INVALID_HANDLE; - } + // Validate the priority. + const u32 max_priority = process->resource_limit->GetLimitValue(ResourceLimitType::Priority); + R_UNLESS(priority <= ThreadPrioLowest, ERR_OUT_OF_RANGE); + R_UNLESS(priority >= max_priority, ERR_NOT_AUTHORIZED); - // Note: The kernel uses the current process's resource limit instead of - // the one from the thread owner's resource limit. - const auto& resource_limit = kernel.GetCurrentProcess()->resource_limit; - const u32 max_priority = resource_limit->GetLimitValue(ResourceLimitType::Priority); - if (max_priority > priority) { - return ERR_NOT_AUTHORIZED; - } + // Get the thread from its handle. + KScopedAutoObject thread = process->handle_table.GetObject(thread_handle); + R_UNLESS(thread.IsNotNull(), ERR_INVALID_HANDLE); + // Update thread priority. thread->SetPriority(priority); thread->UpdatePriority(); // Update the mutexes that this thread is waiting for - for (auto& mutex : thread->pending_mutexes) { + for (KMutex* mutex : thread->m_pending_mutexes) { mutex->UpdatePriority(); } system.PrepareReschedule(); - return RESULT_SUCCESS; + R_SUCCEED(); } -/// Create a mutex ResultCode SVC::CreateMutex(Handle* out_handle, u32 initial_locked) { - // Update mutex count in resource limit. - const auto current_process = kernel.GetCurrentProcess(); - const auto& resource_limit = current_process->resource_limit; - if (!resource_limit->Reserve(ResourceLimitType::Mutex, 1)) { - return ResultCode(ErrCodes::OutOfMutexes, ErrorModule::OS, ErrorSummary::OutOfResource, - ErrorLevel::Status); - } + static constexpr ResultCode ResultOutOfMutexes(ErrCodes::OutOfMutexes, ErrorModule::OS, + ErrorSummary::OutOfResource, ErrorLevel::Status); - // Create mutex. - const auto mutex = kernel.CreateMutex(initial_locked != 0); - mutex->name = fmt::format("mutex-{:08x}", system.GetRunningCore().GetReg(14)); - mutex->resource_limit = resource_limit; - CASCADE_RESULT(*out_handle, current_process->handle_table.Create(std::move(mutex))); + // Get the current process and handle table. + auto process = kernel.GetCurrentProcess(); + auto& handle_table = process->handle_table; - LOG_TRACE(Kernel_SVC, "called initial_locked={} : created handle=0x{:08X}", - initial_locked ? "true" : "false", *out_handle); + // Reserve a new mutex from the process resource limit. + KScopedResourceReservation mutex_reservation(process, ResourceLimitType::Mutex); + R_UNLESS(mutex_reservation.Succeeded(), ResultOutOfMutexes); - return RESULT_SUCCESS; + // Create the mutex. + KMutex* mutex = KMutex::Create(kernel); + R_UNLESS(mutex != nullptr, ResultCode{0xC8601801}); + + // Ensure the only reference is in the handle table when we're done. + SCOPE_EXIT({ mutex->Close(); }); + + // Initialize the mutex. + mutex->Initialize(process, initial_locked); + + // Commit the reservation. + mutex_reservation.Commit(); + + // Register the mutex. + KMutex::Register(kernel, mutex); + + // Add the mutex to the handle table. + R_RETURN(handle_table.Add(out_handle, mutex)); } /// Release a mutex ResultCode SVC::ReleaseMutex(Handle handle) { - LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}", handle); + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - std::shared_ptr mutex = kernel.GetCurrentProcess()->handle_table.Get(handle); - if (mutex == nullptr) - return ERR_INVALID_HANDLE; + // Get the address arbiter. + KScopedAutoObject mutex = handle_table.GetObject(handle); + R_UNLESS(mutex.IsNotNull(), ERR_INVALID_HANDLE); - return mutex->Release(kernel.GetCurrentThreadManager().GetCurrentThread()); + // Release the mutex + Thread* thread = kernel.GetCurrentThreadManager().GetCurrentThread(); + R_RETURN(mutex->Release(thread)); } /// Get the ID of the specified process ResultCode SVC::GetProcessId(u32* process_id, Handle process_handle) { - LOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle); + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - const std::shared_ptr process = - kernel.GetCurrentProcess()->handle_table.Get(process_handle); - if (process == nullptr) - return ERR_INVALID_HANDLE; + // Get the address arbiter. + KScopedAutoObject process = handle_table.GetObject(process_handle); + R_UNLESS(process.IsNotNull(), ERR_INVALID_HANDLE); + // Retrieve process id. *process_id = process->process_id; - return RESULT_SUCCESS; + R_SUCCEED(); } /// Get the ID of the process that owns the specified thread ResultCode SVC::GetProcessIdOfThread(u32* process_id, Handle thread_handle) { - LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle); + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - const std::shared_ptr thread = - kernel.GetCurrentProcess()->handle_table.Get(thread_handle); - if (thread == nullptr) - return ERR_INVALID_HANDLE; + // Get the address arbiter. + KScopedAutoObject thread = handle_table.GetObject(thread_handle); + R_UNLESS(thread.IsNotNull(), ERR_INVALID_HANDLE); - const std::shared_ptr process = thread->owner_process.lock(); - ASSERT_MSG(process != nullptr, "Invalid parent process for thread={:#010X}", thread_handle); - - *process_id = process->process_id; - return RESULT_SUCCESS; + // Retrieve process id of the thread. + Process* owner = thread->GetOwner(); + *process_id = owner ? owner->process_id : -1; + R_SUCCEED(); } /// Get the ID for the specified thread. -ResultCode SVC::GetThreadId(u32* thread_id, Handle handle) { - LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", handle); +ResultCode SVC::GetThreadId(u32* thread_id, Handle thread_handle) { + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - const std::shared_ptr thread = - kernel.GetCurrentProcess()->handle_table.Get(handle); - if (thread == nullptr) - return ERR_INVALID_HANDLE; + // Get the address arbiter. + KScopedAutoObject thread = handle_table.GetObject(thread_handle); + R_UNLESS(thread.IsNotNull(), ERR_INVALID_HANDLE); + // Retrieve thread id. *thread_id = thread->GetThreadId(); - return RESULT_SUCCESS; + R_SUCCEED(); } /// Creates a semaphore ResultCode SVC::CreateSemaphore(Handle* out_handle, s32 initial_count, s32 max_count) { - // Update semaphore count in resource limit. - const auto current_process = kernel.GetCurrentProcess(); - const auto& resource_limit = current_process->resource_limit; - if (!resource_limit->Reserve(ResourceLimitType::Semaphore, 1)) { - return ResultCode(ErrCodes::OutOfSemaphores, ErrorModule::OS, ErrorSummary::OutOfResource, - ErrorLevel::Status); - } + static constexpr ResultCode ResultOutOfSemaphores(ErrCodes::OutOfSemaphores, ErrorModule::OS, + ErrorSummary::OutOfResource, + ErrorLevel::Status); - // Create semaphore - CASCADE_RESULT(std::shared_ptr semaphore, - kernel.CreateSemaphore(initial_count, max_count)); - semaphore->name = fmt::format("semaphore-{:08x}", system.GetRunningCore().GetReg(14)); - semaphore->resource_limit = resource_limit; - CASCADE_RESULT(*out_handle, current_process->handle_table.Create(std::move(semaphore))); + // Get the current process and handle table. + auto process = kernel.GetCurrentProcess(); + auto& handle_table = process->handle_table; - LOG_TRACE(Kernel_SVC, "called initial_count={}, max_count={}, created handle=0x{:08X}", - initial_count, max_count, *out_handle); - return RESULT_SUCCESS; + // Reserve a new semaphore from the process resource limit. + KScopedResourceReservation semaphore_reservation(process, ResourceLimitType::Semaphore); + R_UNLESS(semaphore_reservation.Succeeded(), ResultOutOfSemaphores); + + // Create the semaphore. + KSemaphore* semaphore = KSemaphore::Create(kernel); + R_UNLESS(semaphore != nullptr, ResultCode{0xC8601801}); + + // Ensure the only reference is in the handle table when we're done. + SCOPE_EXIT({ semaphore->Close(); }); + + // Initialize the semaphore. + const auto name = fmt::format("semaphore-{:08x}", system.GetRunningCore().GetReg(14)); + semaphore->Initialize(process, initial_count, max_count, name); + + // Commit the reservation. + semaphore_reservation.Commit(); + + // Register the semaphore. + KSemaphore::Register(kernel, semaphore); + + // Add the semaphore to the handle table. + R_RETURN(handle_table.Add(out_handle, semaphore)); } /// Releases a certain number of slots in a semaphore ResultCode SVC::ReleaseSemaphore(s32* count, Handle handle, s32 release_count) { - LOG_TRACE(Kernel_SVC, "called release_count={}, handle=0x{:08X}", release_count, handle); + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - std::shared_ptr semaphore = - kernel.GetCurrentProcess()->handle_table.Get(handle); - if (semaphore == nullptr) - return ERR_INVALID_HANDLE; + // Get the semaphore. + KScopedAutoObject semaphore = handle_table.GetObject(handle); + R_UNLESS(semaphore.IsNotNull(), ERR_INVALID_HANDLE); - CASCADE_RESULT(*count, semaphore->Release(release_count)); - - return RESULT_SUCCESS; + // Release the semaphore + R_RETURN(semaphore->Release(count, release_count)); } /// Sets the kernel state @@ -1487,21 +1522,23 @@ ResultCode SVC::KernelSetState(u32 kernel_state, u32 varg1, u32 varg2) { LOG_ERROR(Kernel_SVC, "Unknown KernelSetState state={} varg1={} varg2={}", kernel_state, varg1, varg2); } - return RESULT_SUCCESS; + R_SUCCEED(); } /// Query process memory ResultCode SVC::QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_info, Handle process_handle, u32 addr) { - std::shared_ptr process = - kernel.GetCurrentProcess()->handle_table.Get(process_handle); - if (process == nullptr) - return ERR_INVALID_HANDLE; + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - auto vma = process->vm_manager.FindVMA(addr); + // Get the address arbiter. + KScopedAutoObject process = handle_table.GetObject(process_handle); + R_UNLESS(process.IsNotNull(), ERR_INVALID_HANDLE); - if (vma == process->vm_manager.vma_map.end()) - return ERR_INVALID_ADDRESS; + // Ensure a VMA for the provided address exists. + auto& vm_manager = process->vm_manager; + auto vma = vm_manager.FindVMA(addr); + R_UNLESS(vma != vm_manager.vma_map.end(), ERR_INVALID_ADDRESS); auto permissions = vma->second.permissions; auto state = vma->second.meminfo_state; @@ -1527,128 +1564,164 @@ ResultCode SVC::QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_info, page_info->flags = 0; LOG_TRACE(Kernel_SVC, "called process=0x{:08X} addr=0x{:08X}", process_handle, addr); - return RESULT_SUCCESS; + R_SUCCEED(); } /// Query memory ResultCode SVC::QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, u32 addr) { - return QueryProcessMemory(memory_info, page_info, CurrentProcess, addr); + return QueryProcessMemory(memory_info, page_info, KernelHandle::CurrentProcess, addr); } /// Create an event ResultCode SVC::CreateEvent(Handle* out_handle, u32 reset_type) { - // Update event count in resource limit. - const auto current_process = kernel.GetCurrentProcess(); - const auto& resource_limit = current_process->resource_limit; - if (!resource_limit->Reserve(ResourceLimitType::Event, 1)) { - return ResultCode(ErrCodes::OutOfEvents, ErrorModule::OS, ErrorSummary::OutOfResource, - ErrorLevel::Status); - } + static constexpr ResultCode ResultOutOfEvents(ErrCodes::OutOfEvents, ErrorModule::OS, + ErrorSummary::OutOfResource, ErrorLevel::Status); - // Create event. - const auto name = fmt::format("event-{:08x}", system.GetRunningCore().GetReg(14)); - const auto event = kernel.CreateEvent(static_cast(reset_type), name); - event->resource_limit = resource_limit; - CASCADE_RESULT(*out_handle, current_process->handle_table.Create(std::move(event))); + // Get the current process and handle table. + auto process = kernel.GetCurrentProcess(); + auto& handle_table = process->handle_table; - LOG_TRACE(Kernel_SVC, "called reset_type=0x{:08X} : created handle=0x{:08X}", reset_type, - *out_handle); - return RESULT_SUCCESS; + // Reserve a new event from the process resource limit. + KScopedResourceReservation event_reservation(process, ResourceLimitType::Event); + R_UNLESS(event_reservation.Succeeded(), ResultOutOfEvents); + + // Create the event. + KEvent* event = KEvent::Create(kernel); + R_UNLESS(event != nullptr, ResultCode{0xC8601801}); + + // Ensure the only reference is in the handle table when we're done. + SCOPE_EXIT({ event->Close(); }); + + // Initialize the event. + event->Initialize(process, static_cast(reset_type)); + + // Commit the event. + event_reservation.Commit(); + + // Register the event. + KEvent::Register(kernel, event); + + // Add the event to the handle table. + R_RETURN(handle_table.Add(out_handle, event)); } /// Duplicates a kernel handle ResultCode SVC::DuplicateHandle(Handle* out, Handle handle) { - CASCADE_RESULT(*out, kernel.GetCurrentProcess()->handle_table.Duplicate(handle)); - LOG_TRACE(Kernel_SVC, "duplicated 0x{:08X} to 0x{:08X}", handle, *out); - return RESULT_SUCCESS; + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; + + // Get the provided object + KScopedAutoObject object = handle_table.GetObject(handle); + R_UNLESS(object.IsNotNull(), ERR_INVALID_HANDLE); + + // Add it to the handle table again. + R_RETURN(handle_table.Add(out, object.GetPointerUnsafe())); } /// Signals an event ResultCode SVC::SignalEvent(Handle handle) { - LOG_TRACE(Kernel_SVC, "called event=0x{:08X}", handle); + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - std::shared_ptr evt = kernel.GetCurrentProcess()->handle_table.Get(handle); - if (evt == nullptr) - return ERR_INVALID_HANDLE; + // Get the event. + KScopedAutoObject event = handle_table.GetObject(handle); + R_UNLESS(event.IsNotNull(), ERR_INVALID_HANDLE); - evt->Signal(); - - return RESULT_SUCCESS; + // Signal the event + event->Signal(); + R_SUCCEED(); } /// Clears an event ResultCode SVC::ClearEvent(Handle handle) { - LOG_TRACE(Kernel_SVC, "called event=0x{:08X}", handle); + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - std::shared_ptr evt = kernel.GetCurrentProcess()->handle_table.Get(handle); - if (evt == nullptr) - return ERR_INVALID_HANDLE; + // Get the event. + KScopedAutoObject event = handle_table.GetObject(handle); + R_UNLESS(event.IsNotNull(), ERR_INVALID_HANDLE); - evt->Clear(); - return RESULT_SUCCESS; + // Clear the event + event->Clear(); + R_SUCCEED(); } /// Creates a timer ResultCode SVC::CreateTimer(Handle* out_handle, u32 reset_type) { - // Update timer count in resource limit. - const auto current_process = kernel.GetCurrentProcess(); - const auto& resource_limit = current_process->resource_limit; - if (!resource_limit->Reserve(ResourceLimitType::Timer, 1)) { - return ResultCode(ErrCodes::OutOfTimers, ErrorModule::OS, ErrorSummary::OutOfResource, - ErrorLevel::Status); - } + static constexpr ResultCode ResultOutOfTimers(ErrCodes::OutOfTimers, ErrorModule::OS, + ErrorSummary::OutOfResource, ErrorLevel::Status); - // Create timer. - const auto name = fmt::format("timer-{:08x}", system.GetRunningCore().GetReg(14)); - const auto timer = kernel.CreateTimer(static_cast(reset_type), name); - timer->resource_limit = resource_limit; - CASCADE_RESULT(*out_handle, current_process->handle_table.Create(std::move(timer))); + // Get the current process and handle table. + auto process = kernel.GetCurrentProcess(); + auto& handle_table = process->handle_table; - LOG_TRACE(Kernel_SVC, "called reset_type=0x{:08X} : created handle=0x{:08X}", reset_type, - *out_handle); - return RESULT_SUCCESS; + // Reserve a new event from the process resource limit. + KScopedResourceReservation timer_reservation(process, ResourceLimitType::Timer); + R_UNLESS(timer_reservation.Succeeded(), ResultOutOfTimers); + + // Create the event. + KTimer* timer = KTimer::Create(kernel); + R_UNLESS(timer != nullptr, ResultCode{0xC8601801}); + + // Ensure the only reference is in the handle table when we're done. + SCOPE_EXIT({ timer->Close(); }); + + // Initialize the event. + timer->Initialize(process, static_cast(reset_type)); + + // Commit the event. + timer_reservation.Commit(); + + // Register the event. + KTimer::Register(kernel, timer); + + // Add the event to the handle table. + R_RETURN(handle_table.Add(out_handle, timer)); } /// Clears a timer ResultCode SVC::ClearTimer(Handle handle) { - LOG_TRACE(Kernel_SVC, "called timer=0x{:08X}", handle); + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - std::shared_ptr timer = kernel.GetCurrentProcess()->handle_table.Get(handle); - if (timer == nullptr) - return ERR_INVALID_HANDLE; + // Get the timer. + KScopedAutoObject timer = handle_table.GetObject(handle); + R_UNLESS(timer.IsNotNull(), ERR_INVALID_HANDLE); + // Clear the timer timer->Clear(); - return RESULT_SUCCESS; + R_SUCCEED(); } /// Starts a timer ResultCode SVC::SetTimer(Handle handle, s64 initial, s64 interval) { - LOG_TRACE(Kernel_SVC, "called timer=0x{:08X}", handle); + // Validate interval + R_UNLESS(initial >= 0 && interval >= 0, ERR_OUT_OF_RANGE_KERNEL); - if (initial < 0 || interval < 0) { - return ERR_OUT_OF_RANGE_KERNEL; - } + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - std::shared_ptr timer = kernel.GetCurrentProcess()->handle_table.Get(handle); - if (timer == nullptr) - return ERR_INVALID_HANDLE; + // Get the timer. + KScopedAutoObject timer = handle_table.GetObject(handle); + R_UNLESS(timer.IsNotNull(), ERR_INVALID_HANDLE); + // Set the timer timer->Set(initial, interval); - - return RESULT_SUCCESS; + R_SUCCEED(); } /// Cancels a timer ResultCode SVC::CancelTimer(Handle handle) { - LOG_TRACE(Kernel_SVC, "called timer=0x{:08X}", handle); + // Get the handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - std::shared_ptr timer = kernel.GetCurrentProcess()->handle_table.Get(handle); - if (timer == nullptr) - return ERR_INVALID_HANDLE; + // Get the timer. + KScopedAutoObject timer = handle_table.GetObject(handle); + R_UNLESS(timer.IsNotNull(), ERR_INVALID_HANDLE); + // Cancel the timer timer->Cancel(); - - return RESULT_SUCCESS; + R_SUCCEED(); } /// Sleep the current thread @@ -1659,8 +1732,9 @@ void SVC::SleepThread(s64 nanoseconds) { // Don't attempt to yield execution if there are no available threads to run, // this way we avoid a useless reschedule to the idle thread. - if (nanoseconds == 0 && !thread_manager.HaveReadyThreads()) + if (nanoseconds == 0 && !thread_manager.HaveReadyThreads()) { return; + } // Sleep current thread and check for next thread to schedule thread_manager.WaitCurrentThread_Sleep(); @@ -1681,51 +1755,50 @@ s64 SVC::GetSystemTick() { return result; } -// Returns information of the specified handle +/// Returns information of the specified handle ResultCode SVC::GetHandleInfo(s64* out, Handle handle, u32 type) { - std::shared_ptr KObject = kernel.GetCurrentProcess()->handle_table.GetGeneric(handle); - if (!KObject) { - return ERR_INVALID_HANDLE; - } + // Get the object. + KScopedAutoObject object = + kernel.GetCurrentProcess()->handle_table.GetObject(handle); + R_UNLESS(object.IsNotNull(), ERR_INVALID_HANDLE); // Not initialized in real kernel, but we don't want to leak memory. s64 value = 0; - std::shared_ptr process; switch (static_cast(type)) { - case HandleInfoType::KPROCESS_ELAPSED_TICKS: - process = DynamicObjectCast(KObject); + case HandleInfoType::KPROCESS_ELAPSED_TICKS: { + Process* process = object->DynamicCast(); if (process) { value = process->creation_time_ticks; } break; + } case HandleInfoType::REFERENCE_COUNT: - // This is the closest approximation we can get without a full KObject impl. - value = KObject.use_count() - 1; + value = object->GetReferenceCount() - 1; break; - // These values are stubbed in real kernel, they do nothing. case HandleInfoType::STUBBED_1: case HandleInfoType::STUBBED_2: break; default: - return ERR_INVALID_ENUM_VALUE; + R_THROW(ERR_INVALID_ENUM_VALUE); } + + // Write the result. *out = value; - return RESULT_SUCCESS; + R_SUCCEED(); } /// Creates a memory block at the specified address with the specified permissions and size ResultCode SVC::CreateMemoryBlock(Handle* out_handle, u32 addr, u32 size, u32 my_permission, u32 other_permission) { - if (size % Memory::CITRA_PAGE_SIZE != 0) - return ERR_MISALIGNED_SIZE; + static constexpr ResultCode ResultOutOfSharedMems(ErrCodes::OutOfSharedMems, ErrorModule::OS, + ErrorSummary::OutOfResource, + ErrorLevel::Status); - std::shared_ptr shared_memory = nullptr; - - auto VerifyPermissions = [](MemoryPermission permission) { - // SharedMemory blocks can not be created with Execute permissions - switch (permission) { + const auto IsNotExecute = [](u32 permission) { + const auto perm = static_cast(permission); + switch (perm) { case MemoryPermission::None: case MemoryPermission::Read: case MemoryPermission::Write: @@ -1737,97 +1810,162 @@ ResultCode SVC::CreateMemoryBlock(Handle* out_handle, u32 addr, u32 size, u32 my } }; - if (!VerifyPermissions(static_cast(my_permission)) || - !VerifyPermissions(static_cast(other_permission))) - return ERR_INVALID_COMBINATION; + // Get current process and handle table. + Process* process = kernel.GetCurrentProcess(); + auto& handle_table = process->handle_table; - // TODO(Subv): Processes with memory type APPLICATION are not allowed - // to create memory blocks with addr = 0, any attempts to do so - // should return error 0xD92007EA. - if ((addr < Memory::PROCESS_IMAGE_VADDR || addr + size > Memory::SHARED_MEMORY_VADDR_END) && - addr != 0) { - return ERR_INVALID_ADDRESS; - } + // Validate the size alignment. + R_UNLESS(Common::Is4KBAligned(size), ERR_MISALIGNED_SIZE); - // Update shared memory count in resource limit. - const auto current_process = kernel.GetCurrentProcess(); - const auto& resource_limit = current_process->resource_limit; - if (!resource_limit->Reserve(ResourceLimitType::SharedMemory, 1)) { - return ResultCode(ErrCodes::OutOfSharedMems, ErrorModule::OS, ErrorSummary::OutOfResource, - ErrorLevel::Status); - } + // SharedMemory blocks can not be created with Execute permissions + R_UNLESS(IsNotExecute(my_permission) && IsNotExecute(other_permission), + ERR_INVALID_COMBINATION); + + // Processes with memory type APPLICATION are not allowed to create memory blocks with addr = 0 + R_UNLESS(process->flags.memory_region != MemoryRegion::APPLICATION || addr != 0, + ResultCode{0xD92007EA}); + R_UNLESS( + (addr >= Memory::PROCESS_IMAGE_VADDR && addr + size <= Memory::SHARED_MEMORY_VADDR_END) || + addr == 0, + ERR_INVALID_ADDRESS); + + // Reserve a new shared memory from the process resource limit. + KScopedResourceReservation shared_mem_reservation(process, ResourceLimitType::SharedMemory); + R_UNLESS(shared_mem_reservation.Succeeded(), ResultOutOfSharedMems); // When trying to create a memory block with address = 0, // if the process has the Shared Device Memory flag in the exheader, // then we have to allocate from the same region as the caller process instead of the BASE // region. - MemoryRegion region = MemoryRegion::BASE; - if (addr == 0 && current_process->flags.shared_device_mem) { - region = current_process->flags.memory_region; - } + const MemoryRegion region = [&] { + if (addr == 0 && process->flags.shared_device_mem) { + return static_cast(process->flags.memory_region); + } + return MemoryRegion::BASE; + }(); - CASCADE_RESULT(shared_memory, - kernel.CreateSharedMemory( - current_process, size, static_cast(my_permission), - static_cast(other_permission), addr, region)); - CASCADE_RESULT(*out_handle, current_process->handle_table.Create(std::move(shared_memory))); + // Create the shared memory. + KSharedMemory* shared_memory = KSharedMemory::Create(kernel); + R_UNLESS(shared_memory != nullptr, ResultCode{0xC8601802}); - LOG_WARNING(Kernel_SVC, "called addr=0x{:08X}", addr); - return RESULT_SUCCESS; + // Ensure the only reference is in the handle table when we're done. + SCOPE_EXIT({ shared_memory->Close(); }); + + // Initialize the shared memory. + const auto my_perm = static_cast(my_permission); + const auto other_perm = static_cast(other_permission); + R_TRY(shared_memory->Initialize(process, size, my_perm, other_perm, addr, region)); + + // Commit the shared memory. + shared_mem_reservation.Commit(); + + // Register the shared memory. + KSharedMemory::Register(kernel, shared_memory); + + // Add the shared memory to the handle table. + R_RETURN(handle_table.Add(out_handle, shared_memory)); } ResultCode SVC::CreatePort(Handle* server_port, Handle* client_port, VAddr name_address, u32 max_sessions) { - // TODO(Subv): Implement named ports. - ASSERT_MSG(name_address == 0, "Named ports are currently unimplemented"); + // Copy the provided name from user memory to kernel memory. + const auto string_name = memory.ReadCString(name_address, KObjectName::NameLengthMax); - std::shared_ptr current_process = kernel.GetCurrentProcess(); + std::array name{}; + std::strncpy(name.data(), string_name.c_str(), KObjectName::NameLengthMax - 1); - auto [server, client] = kernel.CreatePortPair(max_sessions); - CASCADE_RESULT(*client_port, current_process->handle_table.Create(std::move(client))); - // Note: The 3DS kernel also leaks the client port handle if the server port handle fails to be - // created. - CASCADE_RESULT(*server_port, current_process->handle_table.Create(std::move(server))); + // Validate that the name is valid. + R_UNLESS(name[sizeof(name) - 1] == '\x00', ERR_PORT_NAME_TOO_LONG); - LOG_TRACE(Kernel_SVC, "called max_sessions={}", max_sessions); - return RESULT_SUCCESS; + // Get the current handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; + + // Create the port. + KPort* port = KPort::Create(kernel); + R_UNLESS(port, ResultCode{0xC8601808}); + + // Initialize the port. + port->Initialize(max_sessions); + + // Register the port. + KPort::Register(kernel, port); + + // Ensure that our only reference to the port is in the handle table when we're done. + SCOPE_EXIT({ + port->GetClientPort().Close(); + port->GetServerPort().Close(); + }); + + // Create a new object name. + if (name[0]) { + *client_port = 0; + R_RETURN(KObjectName::NewFromName(kernel, port, name.data())); + } + + // Add the client and server ports to the handle table. + // Note: The 3DS kernel also leaks the client port handle + // if the server port handle fails to be created. + R_TRY(handle_table.Add(client_port, std::addressof(port->GetClientPort()))); + R_TRY(handle_table.Add(server_port, std::addressof(port->GetServerPort()))); + R_SUCCEED(); } ResultCode SVC::CreateSessionToPort(Handle* out_client_session, Handle client_port_handle) { - std::shared_ptr current_process = kernel.GetCurrentProcess(); - std::shared_ptr client_port = - current_process->handle_table.Get(client_port_handle); - if (client_port == nullptr) - return ERR_INVALID_HANDLE; + // Get the current handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - CASCADE_RESULT(auto session, client_port->Connect()); - CASCADE_RESULT(*out_client_session, current_process->handle_table.Create(std::move(session))); - return RESULT_SUCCESS; + // Get the client port. + KScopedAutoObject port = handle_table.GetObject(client_port_handle); + R_UNLESS(port.IsNotNull(), ERR_INVALID_HANDLE); + + // Create a session. + KClientSession* session; + R_TRY(port->CreateSession(std::addressof(session))); + + // Close the extra session reference on return. + SCOPE_EXIT({ session->Close(); }); + + // Register the session in the table, close the extra reference. + R_RETURN(handle_table.Add(out_client_session, session)); } ResultCode SVC::CreateSession(Handle* server_session, Handle* client_session) { - auto [server, client] = kernel.CreateSessionPair(); + // Get the current handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - std::shared_ptr current_process = kernel.GetCurrentProcess(); + // Create the session. + KSession* session = KSession::Create(kernel); + R_UNLESS(session, ResultCode{0xC8601809}); - CASCADE_RESULT(*server_session, current_process->handle_table.Create(std::move(server))); + // Initialize the port. + session->Initialize(nullptr); - CASCADE_RESULT(*client_session, current_process->handle_table.Create(std::move(client))); + // Register the port. + KSession::Register(kernel, session); - LOG_TRACE(Kernel_SVC, "called"); - return RESULT_SUCCESS; + // Add the client and server session to the handle table. + R_TRY(handle_table.Add(client_session, std::addressof(session->GetClientSession()))); + R_TRY(handle_table.Add(server_session, std::addressof(session->GetServerSession()))); + R_SUCCEED(); } ResultCode SVC::AcceptSession(Handle* out_server_session, Handle server_port_handle) { - std::shared_ptr current_process = kernel.GetCurrentProcess(); - std::shared_ptr server_port = - current_process->handle_table.Get(server_port_handle); - if (server_port == nullptr) - return ERR_INVALID_HANDLE; + // Get the current handle table. + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - CASCADE_RESULT(auto session, server_port->Accept()); - CASCADE_RESULT(*out_server_session, current_process->handle_table.Create(std::move(session))); - return RESULT_SUCCESS; + // Get the server port. + KScopedAutoObject port = handle_table.GetObject(server_port_handle); + R_UNLESS(port.IsNotNull(), ERR_INVALID_HANDLE); + + // Accept the session. + KServerSession* session = port->AcceptSession(); + R_UNLESS(session, ERR_NO_PENDING_SESSIONS); + + // Close the extra session reference on return. + SCOPE_EXIT({ session->Close(); }); + + // Add session to the handle table. + R_RETURN(handle_table.Add(out_server_session, session)); } static void CopyStringPart(char* out, const char* in, size_t offset, size_t max_length) { @@ -1941,10 +2079,10 @@ ResultCode SVC::GetSystemInfo(s64* out, u32 type, s32 param) { ResultCode SVC::GetProcessInfo(s64* out, Handle process_handle, u32 type) { LOG_TRACE(Kernel_SVC, "called process=0x{:08X} type={}", process_handle, type); - std::shared_ptr process = - kernel.GetCurrentProcess()->handle_table.Get(process_handle); - if (process == nullptr) - return ERR_INVALID_HANDLE; + // Get the process. + KScopedAutoObject process = + kernel.GetCurrentProcess()->handle_table.GetObject(process_handle); + R_UNLESS(process.IsNotNull(), ERR_INVALID_HANDLE); switch (static_cast(type)) { case ProcessInfoType::PRIVATE_AND_SHARED_USED_MEMORY: @@ -1954,7 +2092,7 @@ ResultCode SVC::GetProcessInfo(s64* out, Handle process_handle, u32 type) { *out = process->memory_used; if (*out % Memory::CITRA_PAGE_SIZE != 0) { LOG_ERROR(Kernel_SVC, "called, memory size not page-aligned"); - return ERR_MISALIGNED_SIZE; + R_THROW(ERR_MISALIGNED_SIZE); } break; case ProcessInfoType::SUPERVISOR_AND_HANDLE_USED_MEMORY: @@ -1975,11 +2113,11 @@ ResultCode SVC::GetProcessInfo(s64* out, Handle process_handle, u32 type) { case ProcessInfoType::QTM_MEMORY_SIZE: // These return a different error value than higher invalid values LOG_ERROR(Kernel_SVC, "unknown GetProcessInfo type={}", type); - return ERR_NOT_IMPLEMENTED; + R_THROW(ERR_NOT_IMPLEMENTED); // Here start the custom ones, taken from Luma3DS for 3GX support case ProcessInfoType::LUMA_CUSTOM_PROCESS_NAME: // Get process name - strncpy(reinterpret_cast(out), process->codeset->GetName().c_str(), 8); + std::strncpy(reinterpret_cast(out), process->codeset->name.c_str(), 8); break; case ProcessInfoType::LUMA_CUSTOM_PROCESS_TITLE_ID: // Get process TID @@ -2006,20 +2144,19 @@ ResultCode SVC::GetProcessInfo(s64* out, Handle process_handle, u32 type) { default: LOG_ERROR(Kernel_SVC, "unknown GetProcessInfo type={}", type); - return ERR_INVALID_ENUM_VALUE; + R_THROW(ERR_INVALID_ENUM_VALUE); } - return RESULT_SUCCESS; + R_SUCCEED(); } ResultCode SVC::GetThreadInfo(s64* out, Handle thread_handle, u32 type) { LOG_TRACE(Kernel_SVC, "called thread=0x{:08X} type={}", thread_handle, type); - std::shared_ptr thread = - kernel.GetCurrentProcess()->handle_table.Get(thread_handle); - if (thread == nullptr) { - return ERR_INVALID_HANDLE; - } + // Get the thread. + KScopedAutoObject thread = + kernel.GetCurrentProcess()->handle_table.GetObject(thread_handle); + R_UNLESS(thread.IsNotNull(), ERR_INVALID_HANDLE); switch (type) { case 0x10000: @@ -2027,20 +2164,21 @@ ResultCode SVC::GetThreadInfo(s64* out, Handle thread_handle, u32 type) { break; default: LOG_ERROR(Kernel_SVC, "unknown GetThreadInfo type={}", type); - return ERR_INVALID_ENUM_VALUE; + R_THROW(ERR_INVALID_ENUM_VALUE); } - return RESULT_SUCCESS; + R_SUCCEED(); } ResultCode SVC::GetProcessList(s32* process_count, VAddr out_process_array, s32 out_process_array_count) { - if (!memory.IsValidVirtualAddress(*kernel.GetCurrentProcess(), out_process_array)) { - return ERR_INVALID_POINTER; - } + // Validate virtual address + Process* process = kernel.GetCurrentProcess(); + R_UNLESS(memory.IsValidVirtualAddress(*process, out_process_array), ERR_INVALID_POINTER); + // Retrieve process list. s32 written = 0; - for (const auto& process : kernel.GetProcessList()) { + for (const Process* process : kernel.GetProcessList()) { if (written >= out_process_array_count) { break; } @@ -2048,150 +2186,144 @@ ResultCode SVC::GetProcessList(s32* process_count, VAddr out_process_array, memory.Write32(out_process_array + written++ * sizeof(u32), process->process_id); } } + *process_count = written; - return RESULT_SUCCESS; + R_SUCCEED(); } ResultCode SVC::InvalidateInstructionCacheRange(u32 addr, u32 size) { system.GetRunningCore().InvalidateCacheRange(addr, size); - return RESULT_SUCCESS; + R_SUCCEED(); } ResultCode SVC::InvalidateEntireInstructionCache() { system.GetRunningCore().ClearInstructionCache(); - return RESULT_SUCCESS; + R_SUCCEED(); } u32 SVC::ConvertVaToPa(u32 addr) { - auto vma = kernel.GetCurrentProcess()->vm_manager.FindVMA(addr); - if (vma == kernel.GetCurrentProcess()->vm_manager.vma_map.end() || - vma->second.type != VMAType::BackingMemory) { + auto& vm_manager = kernel.GetCurrentProcess()->vm_manager; + auto vma = vm_manager.FindVMA(addr); + if (vma == vm_manager.vma_map.end() || vma->second.type != VMAType::BackingMemory) { return 0; } - return kernel.memory.GetFCRAMOffset(vma->second.backing_memory.GetPtr() + addr - - vma->second.base) + + const u8* backing_mem = vma->second.backing_memory.GetPtr(); + return kernel.memory.GetFCRAMOffset(backing_mem + addr - vma->second.base) + Memory::FCRAM_PADDR; } ResultCode SVC::MapProcessMemoryEx(Handle dst_process_handle, u32 dst_address, Handle src_process_handle, u32 src_address, u32 size) { - std::shared_ptr dst_process = - kernel.GetCurrentProcess()->handle_table.Get(dst_process_handle); - std::shared_ptr src_process = - kernel.GetCurrentProcess()->handle_table.Get(src_process_handle); + // Get handle table + auto& handle_table = kernel.GetCurrentProcess()->handle_table; - if (dst_process == nullptr || src_process == nullptr) { - return ERR_INVALID_HANDLE; - } + // Get the source processs. + KScopedAutoObject src_process = handle_table.GetObject(src_process_handle); + R_UNLESS(src_process.IsNotNull(), ERR_INVALID_HANDLE); - if (size & 0xFFF) { - size = (size & ~0xFFF) + Memory::CITRA_PAGE_SIZE; - } + // Get the destination processs. + KScopedAutoObject dst_process = handle_table.GetObject(dst_process_handle); + R_UNLESS(dst_process.IsNotNull(), ERR_INVALID_HANDLE); + + // Align size to page size + size = Common::AlignUp(size, Memory::CITRA_PAGE_SIZE); // Only linear memory supported - auto vma = src_process->vm_manager.FindVMA(src_address); - if (vma == src_process->vm_manager.vma_map.end() || - vma->second.type != VMAType::BackingMemory || - vma->second.meminfo_state != MemoryState::Continuous) { + auto src_vma = src_process->vm_manager.FindVMA(src_address); + if (src_vma == src_process->vm_manager.vma_map.end() || + src_vma->second.type != VMAType::BackingMemory || + src_vma->second.meminfo_state != MemoryState::Continuous) { return ERR_INVALID_ADDRESS; } - u32 offset = src_address - vma->second.base; - if (offset + size > vma->second.size) { - return ERR_INVALID_ADDRESS; - } + // Validate offset is within bounds + const u32 offset = src_address - src_vma->second.base; + R_UNLESS(offset + size <= src_vma->second.size, ERR_INVALID_ADDRESS); auto vma_res = dst_process->vm_manager.MapBackingMemory( dst_address, - memory.GetFCRAMRef(vma->second.backing_memory.GetPtr() + offset - + memory.GetFCRAMRef(src_vma->second.backing_memory.GetPtr() + offset - kernel.memory.GetFCRAMPointer(0)), size, Kernel::MemoryState::Continuous); - if (!vma_res.Succeeded()) { - return ERR_INVALID_ADDRESS_STATE; - } + R_UNLESS(vma_res.Succeeded(), ERR_INVALID_ADDRESS_STATE); dst_process->vm_manager.Reprotect(vma_res.Unwrap(), Kernel::VMAPermission::ReadWriteExecute); - return RESULT_SUCCESS; + R_SUCCEED(); } -ResultCode SVC::UnmapProcessMemoryEx(Handle process, u32 dst_address, u32 size) { - std::shared_ptr dst_process = - kernel.GetCurrentProcess()->handle_table.Get(process); +ResultCode SVC::UnmapProcessMemoryEx(Handle process_handle, u32 dst_address, u32 size) { + // Get the destination processs. + KScopedAutoObject process = + kernel.GetCurrentProcess()->handle_table.GetObject(process_handle); + R_UNLESS(process.IsNotNull(), ERR_INVALID_HANDLE); - if (dst_process == nullptr) { - return ERR_INVALID_HANDLE; - } - - if (size & 0xFFF) { - size = (size & ~0xFFF) + Memory::CITRA_PAGE_SIZE; - } + // Align size to page size + size = Common::AlignUp(size, Memory::CITRA_PAGE_SIZE); // Only linear memory supported - auto vma = dst_process->vm_manager.FindVMA(dst_address); - if (vma == dst_process->vm_manager.vma_map.end() || - vma->second.type != VMAType::BackingMemory || + auto vma = process->vm_manager.FindVMA(dst_address); + if (vma == process->vm_manager.vma_map.end() || vma->second.type != VMAType::BackingMemory || vma->second.meminfo_state != MemoryState::Continuous) { return ERR_INVALID_ADDRESS; } - dst_process->vm_manager.UnmapRange(dst_address, size); - return RESULT_SUCCESS; + // Unmap + process->vm_manager.UnmapRange(dst_address, size); + R_SUCCEED(); } ResultCode SVC::ControlProcess(Handle process_handle, u32 process_OP, u32 varg2, u32 varg3) { - std::shared_ptr process = - kernel.GetCurrentProcess()->handle_table.Get(process_handle); - - if (process == nullptr) { - return ERR_INVALID_HANDLE; - } + // Get the destination processs. + KScopedAutoObject process = + kernel.GetCurrentProcess()->handle_table.GetObject(process_handle); + R_UNLESS(process.IsNotNull(), ERR_INVALID_HANDLE); switch (static_cast(process_OP)) { case ControlProcessOP::PROCESSOP_SET_MMU_TO_RWX: { - for (auto it = process->vm_manager.vma_map.cbegin(); - it != process->vm_manager.vma_map.cend(); it++) { - if (it->second.meminfo_state != MemoryState::Free) + auto& vm_manager = process->vm_manager; + for (auto it = vm_manager.vma_map.cbegin(); it != vm_manager.vma_map.cend(); it++) { + if (it->second.meminfo_state != MemoryState::Free) { process->vm_manager.Reprotect(it, Kernel::VMAPermission::ReadWriteExecute); + } } - return RESULT_SUCCESS; + R_SUCCEED(); } case ControlProcessOP::PROCESSOP_GET_ON_MEMORY_CHANGE_EVENT: { + // Get plgldr service auto plgldr = Service::PLGLDR::GetService(system); - if (!plgldr) { - return ERR_NOT_FOUND; - } + R_UNLESS(plgldr, ERR_NOT_FOUND); - ResultVal out = plgldr->GetMemoryChangedHandle(kernel); - if (out.Failed()) { - return out.Code(); - } + // Get memory changed handle. + const auto out = plgldr->GetMemoryChangedHandle(kernel); + R_UNLESS(out.Succeeded(), out.Code()); + // Write it. memory.Write32(varg2, out.Unwrap()); - return RESULT_SUCCESS; + R_SUCCEED(); } case ControlProcessOP::PROCESSOP_SCHEDULE_THREADS_WITHOUT_TLS_MAGIC: { for (u32 core_id = 0; core_id < system.GetNumCores(); core_id++) { const auto thread_list = kernel.GetThreadManager(core_id).GetThreadList(); for (auto& thread : thread_list) { - if (thread->owner_process.lock() != process) { + if (thread->GetOwner() != process.GetPointerUnsafe()) { continue; } - if (memory.Read32(thread.get()->GetTLSAddress()) == varg3) { + if (memory.Read32(thread->GetTLSAddress()) == varg3) { continue; } - if (thread.get()->thread_id == - kernel.GetCurrentThreadManager().GetCurrentThread()->thread_id) { + Thread* current = kernel.GetCurrentThreadManager().GetCurrentThread(); + if (thread->GetThreadId() == current->GetThreadId()) { continue; } - thread.get()->can_schedule = !varg2; + thread->m_can_schedule = !varg2; } } - return RESULT_SUCCESS; + R_SUCCEED(); } case ControlProcessOP::PROCESSOP_DISABLE_CREATE_THREAD_RESTRICTIONS: { process->no_thread_restrictions = varg2 == 1; - return RESULT_SUCCESS; + R_SUCCEED(); } case ControlProcessOP::PROCESSOP_GET_ALL_HANDLES: case ControlProcessOP::PROCESSOP_GET_PA_FROM_VA: @@ -2199,7 +2331,7 @@ ResultCode SVC::ControlProcess(Handle process_handle, u32 process_OP, u32 varg2, case ControlProcessOP::PROCESSOP_SCHEDULE_THREADS: default: LOG_ERROR(Kernel_SVC, "Unknown ControlProcessOp type={}", process_OP); - return ERR_NOT_IMPLEMENTED; + R_THROW(ERR_NOT_IMPLEMENTED); } } diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 1daa7a185..0296236ab 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -1,10 +1,13 @@ -// Copyright 2014 Citra Emulator Project / PPSSPP Project +// Copyright 2023 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include -#include +#include #include +#include +#include +#include #include "common/archives.h" #include "common/assert.h" #include "common/common_types.h" @@ -12,9 +15,10 @@ #include "common/serialization/boost_flat_set.h" #include "core/arm/arm_interface.h" #include "core/arm/skyeye_common/armstate.h" +#include "core/core_timing.h" #include "core/hle/kernel/errors.h" +#include "core/hle/kernel/k_mutex.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/mutex.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/thread.h" @@ -25,124 +29,56 @@ SERIALIZE_EXPORT_IMPL(Kernel::Thread) namespace Kernel { -template -void Thread::serialize(Archive& ar, const unsigned int file_version) { - ar& boost::serialization::base_object(*this); - ar& context; - ar& thread_id; - ar& status; - ar& entry_point; - ar& stack_top; - ar& nominal_priority; - ar& current_priority; - ar& last_running_ticks; - ar& processor_id; - ar& tls_address; - ar& held_mutexes; - ar& pending_mutexes; - ar& owner_process; - ar& wait_objects; - ar& wait_address; - ar& name; - ar& wakeup_callback; +ThreadManager::ThreadManager(Kernel::KernelSystem& kernel, u32 core_id) : kernel(kernel) { + ThreadWakeupEventType = kernel.timing.RegisterEvent( + "ThreadWakeupCallback_" + std::to_string(core_id), + [this](u64 thread_id, s64 cycle_late) { ThreadWakeupCallback(thread_id, cycle_late); }); } -SERIALIZE_IMPL(Thread) - -bool Thread::ShouldWait(const Thread* thread) const { - return status != ThreadStatus::Dead; -} - -void Thread::Acquire(Thread* thread) { - ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); -} - -Thread::Thread(KernelSystem& kernel, u32 core_id) - : WaitObject(kernel), core_id(core_id), thread_manager(kernel.GetThreadManager(core_id)) {} - -Thread::~Thread() { - auto process = owner_process.lock(); - if (process) { - process->resource_limit->Release(ResourceLimitType::Thread, 1); - } -} - -Thread* ThreadManager::GetCurrentThread() const { - return current_thread.get(); -} - -void Thread::Stop() { - // Cancel any outstanding wakeup events for this thread - thread_manager.kernel.timing.UnscheduleEvent(thread_manager.ThreadWakeupEventType, thread_id); - thread_manager.wakeup_callback_table.erase(thread_id); - - // Clean up thread from ready queue - // This is only needed when the thread is termintated forcefully (SVC TerminateProcess) - if (status == ThreadStatus::Ready) { - thread_manager.ready_queue.remove(current_priority, this); - } - - status = ThreadStatus::Dead; - - WakeupAllWaitingThreads(); - - // Clean up any dangling references in objects that this thread was waiting for - for (auto& wait_object : wait_objects) { - wait_object->RemoveWaitingThread(this); - } - wait_objects.clear(); - - // Release all the mutexes that this thread holds - ReleaseThreadMutexes(this); - - // Mark the TLS slot in the thread's page as free. - u32 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::CITRA_PAGE_SIZE; - u32 tls_slot = - ((tls_address - Memory::TLS_AREA_VADDR) % Memory::CITRA_PAGE_SIZE) / Memory::TLS_ENTRY_SIZE; - if (auto process = owner_process.lock()) { - process->tls_slots[tls_page].reset(tls_slot); +ThreadManager::~ThreadManager() { + for (auto& t : thread_list) { + t->Stop(); } } void ThreadManager::SwitchContext(Thread* new_thread) { + auto& timing = kernel.timing; Thread* previous_thread = GetCurrentThread(); - std::shared_ptr previous_process = nullptr; - - Core::Timing& timing = kernel.timing; + Process* previous_process = nullptr; // Save context for previous thread if (previous_thread) { - previous_process = previous_thread->owner_process.lock(); - previous_thread->last_running_ticks = cpu->GetTimer().GetTicks(); - cpu->SaveContext(previous_thread->context); + previous_process = previous_thread->GetOwner(); + previous_thread->m_last_running_ticks = cpu->GetTimer().GetTicks(); + cpu->SaveContext(previous_thread->m_context); - if (previous_thread->status == ThreadStatus::Running) { + if (previous_thread->m_status == ThreadStatus::Running) { // This is only the case when a reschedule is triggered without the current thread // yielding execution (i.e. an event triggered, system core time-sliced, etc) - ready_queue.push_front(previous_thread->current_priority, previous_thread); - previous_thread->status = ThreadStatus::Ready; + ready_queue.push_front(previous_thread->m_current_priority, previous_thread); + previous_thread->m_status = ThreadStatus::Ready; } } // Load context of new thread if (new_thread) { - ASSERT_MSG(new_thread->status == ThreadStatus::Ready, + ASSERT_MSG(new_thread->m_status == ThreadStatus::Ready, "Thread must be ready to become running."); // Cancel any outstanding wakeup events for this thread - timing.UnscheduleEvent(ThreadWakeupEventType, new_thread->thread_id); + timing.UnscheduleEvent(ThreadWakeupEventType, new_thread->m_thread_id); - current_thread = SharedFrom(new_thread); + current_thread = new_thread; - ready_queue.remove(new_thread->current_priority, new_thread); - new_thread->status = ThreadStatus::Running; + ready_queue.remove(new_thread->m_current_priority, new_thread); + new_thread->m_status = ThreadStatus::Running; - ASSERT(current_thread->owner_process.lock()); - if (previous_process != current_thread->owner_process.lock()) { - kernel.SetCurrentProcessForCPU(current_thread->owner_process.lock(), cpu->GetID()); + ASSERT(current_thread->GetOwner()); + if (previous_process != current_thread->GetOwner()) { + kernel.SetCurrentProcessForCPU(current_thread->GetOwner(), cpu->GetID()); } - cpu->LoadContext(new_thread->context); + cpu->LoadContext(new_thread->m_context); cpu->SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress()); } else { current_thread = nullptr; @@ -155,29 +91,29 @@ Thread* ThreadManager::PopNextReadyThread() { Thread* next = nullptr; Thread* thread = GetCurrentThread(); - if (thread && thread->status == ThreadStatus::Running) { + if (thread && thread->m_status == ThreadStatus::Running) { do { // We have to do better than the current thread. // This call returns null when that's not possible. - next = ready_queue.pop_first_better(thread->current_priority); + next = ready_queue.pop_first_better(thread->m_current_priority); if (!next) { // Otherwise just keep going with the current thread next = thread; break; - } else if (!next->can_schedule) + } else if (!next->m_can_schedule) unscheduled_ready_queue.push_back(next); - } while (!next->can_schedule); + } while (!next->m_can_schedule); } else { do { next = ready_queue.pop_first(); - if (next && !next->can_schedule) + if (next && !next->m_can_schedule) unscheduled_ready_queue.push_back(next); - } while (next && !next->can_schedule); + } while (next && !next->m_can_schedule); } while (!unscheduled_ready_queue.empty()) { auto t = std::move(unscheduled_ready_queue.back()); - ready_queue.push_back(t->current_priority, t); + ready_queue.push_back(t->m_current_priority, t); unscheduled_ready_queue.pop_back(); } @@ -186,7 +122,7 @@ Thread* ThreadManager::PopNextReadyThread() { void ThreadManager::WaitCurrentThread_Sleep() { Thread* thread = GetCurrentThread(); - thread->status = ThreadStatus::WaitSleep; + thread->m_status = ThreadStatus::WaitSleep; } void ThreadManager::ExitCurrentThread() { @@ -195,19 +131,19 @@ void ThreadManager::ExitCurrentThread() { kernel.PrepareReschedule(); } -void ThreadManager::TerminateProcessThreads(std::shared_ptr process) { +void ThreadManager::TerminateProcessThreads(Process* process) { auto iter = thread_list.begin(); while (iter != thread_list.end()) { auto& thread = *iter; - if (thread == current_thread || thread->owner_process.lock() != process) { + if (thread == current_thread || thread->GetOwner() != process) { iter++; continue; } - if (thread->status != ThreadStatus::WaitSynchAny && - thread->status != ThreadStatus::WaitSynchAll) { + if (thread->m_status != ThreadStatus::WaitSynchAny && + thread->m_status != ThreadStatus::WaitSynchAll) { // TODO: How does the real kernel handle non-waiting threads? - LOG_WARNING(Kernel, "Terminating non-waiting thread {}", thread->thread_id); + LOG_WARNING(Kernel, "Terminating non-waiting thread {}", thread->m_thread_id); } thread->Stop(); @@ -215,214 +151,38 @@ void ThreadManager::TerminateProcessThreads(std::shared_ptr process) { } // Kill the current thread last, if applicable. - if (current_thread != nullptr && current_thread->owner_process.lock() == process) { + if (current_thread != nullptr && current_thread->GetOwner() == process) { ExitCurrentThread(); } } void ThreadManager::ThreadWakeupCallback(u64 thread_id, s64 cycles_late) { - std::shared_ptr thread = SharedFrom(wakeup_callback_table.at(thread_id)); + Thread* thread = wakeup_callback_table.at(thread_id); if (thread == nullptr) { LOG_CRITICAL(Kernel, "Callback fired for invalid thread {:08X}", thread_id); return; } - if (thread->status == ThreadStatus::WaitSynchAny || - thread->status == ThreadStatus::WaitSynchAll || thread->status == ThreadStatus::WaitArb || - thread->status == ThreadStatus::WaitHleEvent) { + if (thread->m_status == ThreadStatus::WaitSynchAny || + thread->m_status == ThreadStatus::WaitSynchAll || + thread->m_status == ThreadStatus::WaitArb || + thread->m_status == ThreadStatus::WaitHleEvent) { // Invoke the wakeup callback before clearing the wait objects - if (thread->wakeup_callback) - thread->wakeup_callback->WakeUp(ThreadWakeupReason::Timeout, thread, nullptr); + if (thread->m_wakeup_callback) { + thread->m_wakeup_callback->WakeUp(ThreadWakeupReason::Timeout, thread, nullptr); + } // Remove the thread from each of its waiting objects' waitlists - for (auto& object : thread->wait_objects) - object->RemoveWaitingThread(thread.get()); - thread->wait_objects.clear(); + for (KSynchronizationObject* object : thread->m_wait_objects) { + object->RemoveWaitingThread(thread); + } + thread->m_wait_objects.clear(); } thread->ResumeFromWait(); } -void Thread::WakeAfterDelay(s64 nanoseconds, bool thread_safe_mode) { - // Don't schedule a wakeup if the thread wants to wait forever - if (nanoseconds == -1) - return; - size_t core = thread_safe_mode ? core_id : std::numeric_limits::max(); - - thread_manager.kernel.timing.ScheduleEvent(nsToCycles(nanoseconds), - thread_manager.ThreadWakeupEventType, thread_id, - core, thread_safe_mode); -} - -void Thread::ResumeFromWait() { - ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects"); - - switch (status) { - case ThreadStatus::WaitSynchAll: - case ThreadStatus::WaitSynchAny: - case ThreadStatus::WaitHleEvent: - case ThreadStatus::WaitArb: - case ThreadStatus::WaitSleep: - case ThreadStatus::WaitIPC: - case ThreadStatus::Dormant: - break; - - case ThreadStatus::Ready: - // The thread's wakeup callback must have already been cleared when the thread was first - // awoken. - ASSERT(wakeup_callback == nullptr); - // If the thread is waiting on multiple wait objects, it might be awoken more than once - // before actually resuming. We can ignore subsequent wakeups if the thread status has - // already been set to ThreadStatus::Ready. - return; - - case ThreadStatus::Running: - DEBUG_ASSERT_MSG(false, "Thread with object id {} has already resumed.", GetObjectId()); - return; - case ThreadStatus::Dead: - // This should never happen, as threads must complete before being stopped. - DEBUG_ASSERT_MSG(false, "Thread with object id {} cannot be resumed because it's DEAD.", - GetObjectId()); - return; - } - - wakeup_callback = nullptr; - - thread_manager.ready_queue.push_back(current_priority, this); - status = ThreadStatus::Ready; - thread_manager.kernel.PrepareReschedule(); -} - -void ThreadManager::DebugThreadQueue() { - Thread* thread = GetCurrentThread(); - if (!thread) { - LOG_DEBUG(Kernel, "Current: NO CURRENT THREAD"); - } else { - LOG_DEBUG(Kernel, "0x{:02X} {} (current)", thread->current_priority, - GetCurrentThread()->GetObjectId()); - } - - for (auto& t : thread_list) { - u32 priority = ready_queue.contains(t.get()); - if (priority != UINT_MAX) { - LOG_DEBUG(Kernel, "0x{:02X} {}", priority, t->GetObjectId()); - } - } -} - -/** - * Resets a thread context, making it ready to be scheduled and run by the CPU - * @param context Thread context to reset - * @param stack_top Address of the top of the stack - * @param entry_point Address of entry point for execution - * @param arg User argument for thread - */ -static void ResetThreadContext(Core::ARM_Interface::ThreadContext& context, u32 stack_top, - u32 entry_point, u32 arg) { - context.cpu_registers[0] = arg; - context.SetProgramCounter(entry_point); - context.SetStackPointer(stack_top); - context.cpsr = USER32MODE | ((entry_point & 1) << 5); // Usermode and THUMB mode -} - -ResultVal> KernelSystem::CreateThread( - std::string name, VAddr entry_point, u32 priority, u32 arg, s32 processor_id, VAddr stack_top, - std::shared_ptr owner_process) { - // Check if priority is in ranged. Lowest priority -> highest priority id. - if (priority > ThreadPrioLowest) { - LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority); - return ERR_OUT_OF_RANGE; - } - - if (processor_id > ThreadProcessorIdMax) { - LOG_ERROR(Kernel_SVC, "Invalid processor id: {}", processor_id); - return ERR_OUT_OF_RANGE_KERNEL; - } - - // TODO(yuriks): Other checks, returning 0xD9001BEA - if (!memory.IsValidVirtualAddress(*owner_process, entry_point)) { - LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:08x}", name, entry_point); - // TODO: Verify error - return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel, - ErrorSummary::InvalidArgument, ErrorLevel::Permanent); - } - - auto thread = std::make_shared(*this, processor_id); - - thread_managers[processor_id]->thread_list.push_back(thread); - thread_managers[processor_id]->ready_queue.prepare(priority); - - thread->thread_id = NewThreadId(); - thread->status = ThreadStatus::Dormant; - thread->entry_point = entry_point; - thread->stack_top = stack_top; - thread->nominal_priority = thread->current_priority = priority; - thread->last_running_ticks = timing.GetTimer(processor_id)->GetTicks(); - thread->processor_id = processor_id; - thread->wait_objects.clear(); - thread->wait_address = 0; - thread->name = std::move(name); - thread_managers[processor_id]->wakeup_callback_table[thread->thread_id] = thread.get(); - thread->owner_process = owner_process; - CASCADE_RESULT(thread->tls_address, owner_process->AllocateThreadLocalStorage()); - - // TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used - // to initialize the context - ResetThreadContext(thread->context, stack_top, entry_point, arg); - - thread_managers[processor_id]->ready_queue.push_back(thread->current_priority, thread.get()); - thread->status = ThreadStatus::Ready; - - return thread; -} - -void Thread::SetPriority(u32 priority) { - ASSERT_MSG(priority <= ThreadPrioLowest && priority >= ThreadPrioHighest, - "Invalid priority value."); - // If thread was ready, adjust queues - if (status == ThreadStatus::Ready) - thread_manager.ready_queue.move(this, current_priority, priority); - else - thread_manager.ready_queue.prepare(priority); - - nominal_priority = current_priority = priority; -} - -void Thread::UpdatePriority() { - u32 best_priority = nominal_priority; - for (auto& mutex : held_mutexes) { - if (mutex->priority < best_priority) - best_priority = mutex->priority; - } - BoostPriority(best_priority); -} - -void Thread::BoostPriority(u32 priority) { - // If thread was ready, adjust queues - if (status == ThreadStatus::Ready) - thread_manager.ready_queue.move(this, current_priority, priority); - else - thread_manager.ready_queue.prepare(priority); - current_priority = priority; -} - -std::shared_ptr SetupMainThread(KernelSystem& kernel, u32 entry_point, u32 priority, - std::shared_ptr owner_process) { - // Initialize new "main" thread - auto thread_res = - kernel.CreateThread("main", entry_point, priority, 0, owner_process->ideal_processor, - Memory::HEAP_VADDR_END, owner_process); - - std::shared_ptr thread = std::move(thread_res).Unwrap(); - - thread->context.fpscr = - FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO | FPSCR_IXC; // 0x03C00010 - - // Note: The newly created thread will be run when the scheduler fires. - return thread; -} - bool ThreadManager::HaveReadyThreads() { return ready_queue.get_first() != nullptr; } @@ -445,19 +205,205 @@ void ThreadManager::Reschedule() { SwitchContext(next); } +Thread::Thread(KernelSystem& kernel) : KAutoObjectWithSlabHeapAndContainer(kernel) {} + +Thread::~Thread() = default; + +void Thread::Stop() { + // Cancel any outstanding wakeup events for this thread + auto& timing = m_kernel.timing; + timing.UnscheduleEvent(m_manager->ThreadWakeupEventType, m_thread_id); + m_manager->wakeup_callback_table.erase(m_thread_id); + + // Clean up thread from ready queue + // This is only needed when the thread is termintated forcefully (SVC TerminateProcess) + if (m_status == ThreadStatus::Ready) { + m_manager->ready_queue.remove(m_current_priority, this); + } + + // Wake all threads waiting on this thread. + m_status = ThreadStatus::Dead; + this->WakeupAllWaitingThreads(); + + // Clean up any dangling references in objects that this thread was waiting for + for (KSynchronizationObject* object : m_wait_objects) { + object->RemoveWaitingThread(this); + } + m_wait_objects.clear(); + + // Release all the mutexes that this thread holds + ReleaseThreadMutexes(this); + + // Mark the TLS slot in the thread's page as free. + const u32 tls_page = (m_tls_address - Memory::TLS_AREA_VADDR) / Memory::CITRA_PAGE_SIZE; + const u32 tls_slot = ((m_tls_address - Memory::TLS_AREA_VADDR) % Memory::CITRA_PAGE_SIZE) / + Memory::TLS_ENTRY_SIZE; + m_owner->tls_slots[tls_page].reset(tls_slot); +} + +void Thread::WakeAfterDelay(s64 nanoseconds, bool thread_safe_mode) { + // Don't schedule a wakeup if the thread wants to wait forever + if (nanoseconds == -1) { + return; + } + auto& timing = m_kernel.timing; + const size_t core = thread_safe_mode ? m_core_id : std::numeric_limits::max(); + timing.ScheduleEvent(nsToCycles(nanoseconds), m_manager->ThreadWakeupEventType, m_thread_id, + core, thread_safe_mode); +} + +void Thread::ResumeFromWait() { + ASSERT_MSG(m_wait_objects.empty(), "Thread is waking up while waiting for objects"); + + switch (m_status) { + case ThreadStatus::WaitSynchAll: + case ThreadStatus::WaitSynchAny: + case ThreadStatus::WaitHleEvent: + case ThreadStatus::WaitArb: + case ThreadStatus::WaitSleep: + case ThreadStatus::WaitIPC: + case ThreadStatus::Dormant: + break; + + case ThreadStatus::Ready: + // The thread's wakeup callback must have already been cleared when the thread was first + // awoken. + ASSERT(m_wakeup_callback == nullptr); + // If the thread is waiting on multiple wait objects, it might be awoken more than once + // before actually resuming. We can ignore subsequent wakeups if the thread status has + // already been set to ThreadStatus::Ready. + return; + + case ThreadStatus::Running: + DEBUG_ASSERT_MSG(false, "Thread with object id {} has already resumed.", GetObjectId()); + return; + case ThreadStatus::Dead: + // This should never happen, as threads must complete before being stopped. + DEBUG_ASSERT_MSG(false, "Thread with object id {} cannot be resumed because it's DEAD.", + GetObjectId()); + return; + } + + // Mark as ready and reschedule. + m_wakeup_callback = nullptr; + m_manager->ready_queue.push_back(m_current_priority, this); + m_status = ThreadStatus::Ready; + m_kernel.PrepareReschedule(); +} + +/** + * Resets a thread context, making it ready to be scheduled and run by the CPU + * @param context Thread context to reset + * @param stack_top Address of the top of the stack + * @param entry_point Address of entry point for execution + * @param arg User argument for thread + */ +static void ResetThreadContext(Core::ARM_Interface::ThreadContext& context, u32 stack_top, + u32 entry_point, u32 arg) { + context.cpu_registers[0] = arg; + context.SetProgramCounter(entry_point); + context.SetStackPointer(stack_top); + context.cpsr = USER32MODE | ((entry_point & 1) << 5); // Usermode and THUMB mode + context.fpscr = FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO | FPSCR_IXC; +} + +ResultCode Thread::Initialize(std::string name, VAddr entry_point, u32 priority, u32 arg, + s32 processor_id, VAddr stack_top, Process* owner_process) { + R_UNLESS(priority <= ThreadPrioLowest, ERR_OUT_OF_RANGE); + R_UNLESS(processor_id <= ThreadProcessorIdMax, ERR_OUT_OF_RANGE_KERNEL); + + // Open a reference to our owner process + m_owner = owner_process; + m_owner->Open(); + + // Set last running ticks. + auto& timing = m_kernel.timing; + m_last_running_ticks = timing.GetTimer(processor_id)->GetTicks(); + + // Set member variables. + m_thread_id = m_kernel.NewThreadId(); + m_status = ThreadStatus::Ready; + m_entry_point = entry_point; + m_stack_top = stack_top; + m_nominal_priority = m_current_priority = priority; + m_processor_id = processor_id; + m_wait_objects.clear(); + m_wait_address = 0; + m_name = std::move(name); + + // Register thread in the thread manager. + auto& thread_manager = m_kernel.GetThreadManager(processor_id); + m_manager = std::addressof(thread_manager); + m_manager->thread_list.push_back(this); + m_manager->ready_queue.prepare(priority); + m_manager->wakeup_callback_table[m_thread_id] = this; + + // Allocate the thread local region. + R_TRY(m_owner->AllocateThreadLocalStorage(std::addressof(m_tls_address))); + + // Reset the thread context. + ResetThreadContext(m_context, stack_top, entry_point, arg); + + // Mark thread as ready and return + m_manager->ready_queue.push_back(m_current_priority, this); + R_SUCCEED(); +} + +void Thread::SetPriority(u32 priority) { + ASSERT_MSG(priority <= ThreadPrioLowest && priority >= ThreadPrioHighest, + "Invalid priority value."); + + // If thread was ready, adjust queues + if (m_status == ThreadStatus::Ready) { + m_manager->ready_queue.move(this, m_current_priority, priority); + } else { + m_manager->ready_queue.prepare(priority); + } + + // Set the priority + m_nominal_priority = m_current_priority = priority; +} + +void Thread::UpdatePriority() { + u32 best_priority = m_nominal_priority; + for (KMutex* mutex : m_held_mutexes) { + if (mutex->GetPriority() < best_priority) { + best_priority = mutex->GetPriority(); + } + } + this->BoostPriority(best_priority); +} + +void Thread::BoostPriority(u32 priority) { + // If thread was ready, adjust queues + if (m_status == ThreadStatus::Ready) { + m_manager->ready_queue.move(this, m_current_priority, priority); + } else { + m_manager->ready_queue.prepare(priority); + } + m_current_priority = priority; +} + +void Thread::PostDestroy(uintptr_t arg) { + Process* owner = reinterpret_cast(arg); + if (owner != nullptr) { + owner->ReleaseResource(ResourceLimitType::Thread, 1); + owner->Close(); + } +} + void Thread::SetWaitSynchronizationResult(ResultCode result) { - context.cpu_registers[0] = result.raw; + m_context.cpu_registers[0] = result.raw; } void Thread::SetWaitSynchronizationOutput(s32 output) { - context.cpu_registers[1] = output; + m_context.cpu_registers[1] = output; } -s32 Thread::GetWaitObjectIndex(const WaitObject* object) const { - ASSERT_MSG(!wait_objects.empty(), "Thread is not waiting for anything"); - const auto match = std::find_if(wait_objects.rbegin(), wait_objects.rend(), - [object](const auto& p) { return p.get() == object; }); - return static_cast(std::distance(match, wait_objects.rend()) - 1); +s32 Thread::GetWaitObjectIndex(const KSynchronizationObject* object) const { + ASSERT_MSG(!m_wait_objects.empty(), "Thread is not waiting for anything"); + const auto match = std::find(m_wait_objects.rbegin(), m_wait_objects.rend(), object); + return static_cast(std::distance(match, m_wait_objects.rend()) - 1); } VAddr Thread::GetCommandBufferAddress() const { @@ -466,20 +412,28 @@ VAddr Thread::GetCommandBufferAddress() const { return GetTLSAddress() + command_header_offset; } -ThreadManager::ThreadManager(Kernel::KernelSystem& kernel, u32 core_id) : kernel(kernel) { - ThreadWakeupEventType = kernel.timing.RegisterEvent( - "ThreadWakeupCallback_" + std::to_string(core_id), - [this](u64 thread_id, s64 cycle_late) { ThreadWakeupCallback(thread_id, cycle_late); }); +template +void Thread::serialize(Archive& ar, const unsigned int file_version) { + ar& boost::serialization::base_object(*this); + ar& m_context; + ar& m_thread_id; + ar& m_status; + ar& m_entry_point; + ar& m_stack_top; + ar& m_nominal_priority; + ar& m_current_priority; + ar& m_last_running_ticks; + ar& m_processor_id; + ar& m_tls_address; + ar& m_held_mutexes; + ar& m_pending_mutexes; + ar& m_owner; + ar& m_wait_objects; + ar& m_wait_address; + ar& m_name; + ar& m_wakeup_callback; } -ThreadManager::~ThreadManager() { - for (auto& t : thread_list) { - t->Stop(); - } -} - -std::span> ThreadManager::GetThreadList() { - return thread_list; -} +SERIALIZE_IMPL(Thread) } // namespace Kernel diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 86c25a424..31892fbb8 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -1,30 +1,25 @@ -// Copyright 2014 Citra Emulator Project / PPSSPP Project +// Copyright 2023 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once #include -#include #include #include #include #include #include -#include -#include -#include #include "common/common_types.h" #include "common/thread_queue_list.h" #include "core/arm/arm_interface.h" -#include "core/core_timing.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/wait_object.h" +#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/kernel/slab_helpers.h" #include "core/hle/result.h" namespace Kernel { -class Mutex; +class KMutex; class Process; enum ThreadPriority : u32 { @@ -67,8 +62,8 @@ class Thread; class WakeupCallback { public: virtual ~WakeupCallback() = default; - virtual void WakeUp(ThreadWakeupReason reason, std::shared_ptr thread, - std::shared_ptr object) = 0; + virtual void WakeUp(ThreadWakeupReason reason, Thread* thread, + KSynchronizationObject* object) = 0; private: template @@ -81,21 +76,15 @@ public: explicit ThreadManager(Kernel::KernelSystem& kernel, u32 core_id); ~ThreadManager(); - /** - * Gets the current thread - */ - Thread* GetCurrentThread() const; + Thread* GetCurrentThread() const { + return current_thread; + } /** * Reschedules to the next available thread (call after current thread is suspended) */ void Reschedule(); - /** - * Prints the thread queue for debugging purposes - */ - void DebugThreadQueue(); - /** * Returns whether there are any threads that are ready to run. */ @@ -114,12 +103,14 @@ public: /** * Terminates all threads belonging to a specific process. */ - void TerminateProcessThreads(std::shared_ptr process); + void TerminateProcessThreads(Process* process); /** * Get a const reference to the thread list for debug use */ - std::span> GetThreadList(); + std::vector& GetThreadList() { + return thread_list; + } void SetCPU(Core::ARM_Interface& cpu_) { cpu = &cpu_; @@ -148,7 +139,7 @@ private: Kernel::KernelSystem& kernel; Core::ARM_Interface* cpu; - std::shared_ptr current_thread; + Thread* current_thread; Common::ThreadQueueList ready_queue; std::deque unscheduled_ready_queue; std::unordered_map wakeup_callback_table; @@ -157,7 +148,7 @@ private: Core::TimingEventType* ThreadWakeupEventType = nullptr; // Lists all threadsthat aren't deleted. - std::vector> thread_list; + std::vector thread_list; friend class Thread; friend class KernelSystem; @@ -172,38 +163,64 @@ private: } }; -class Thread final : public WaitObject { +class Thread final : public KAutoObjectWithSlabHeapAndContainer { + KERNEL_AUTOOBJECT_TRAITS(Thread, KSynchronizationObject); + + using ThreadContext = Core::ARM_Interface::ThreadContext; + public: - explicit Thread(KernelSystem&, u32 core_id); + explicit Thread(KernelSystem&); ~Thread() override; - std::string GetName() const override { - return name; - } - std::string GetTypeName() const override { - return "Thread"; + ResultCode Initialize(std::string name, VAddr entry_point, u32 priority, u32 arg, + s32 processor_id, VAddr stack_top, Process* owner); + + static void PostDestroy(uintptr_t arg); + + bool ShouldWait(const Thread* thread) const override { + return m_status != ThreadStatus::Dead; } - static constexpr HandleType HANDLE_TYPE = HandleType::Thread; - HandleType GetHandleType() const override { - return HANDLE_TYPE; + void Acquire(Thread* thread) override { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); } - bool ShouldWait(const Thread* thread) const override; - void Acquire(Thread* thread) override; + u32 GetThreadId() const { + return m_thread_id; + } + + u32 GetCurrentPriority() const { + return m_current_priority; + } + + VAddr GetWaitAddress() const { + return m_wait_address; + } + + VAddr GetTLSAddress() const { + return m_tls_address; + } + + ThreadContext& GetContext() { + return m_context; + } + + ThreadStatus GetStatus() const { + return m_status; + } + + Process* GetOwner() const override { + return m_owner; + } + + void SetWakeupCallback(std::shared_ptr&& callback) { + m_wakeup_callback = callback; + } - /** - * Gets the thread's current priority - * @return The current thread's priority - */ u32 GetPriority() const { - return current_priority; + return m_current_priority; } - /** - * Sets the thread's current priority - * @param priority The new priority - */ void SetPriority(u32 priority); /** @@ -218,14 +235,6 @@ public: */ void BoostPriority(u32 priority); - /** - * Gets the thread's thread ID - * @return The thread's ID - */ - u32 GetThreadId() const { - return thread_id; - } - /** * Resumes a thread from waiting */ @@ -260,25 +269,13 @@ public: * object in the list. * @param object Object to query the index of. */ - s32 GetWaitObjectIndex(const WaitObject* object) const; + s32 GetWaitObjectIndex(const KSynchronizationObject* object) const; /** * Stops a thread, invalidating it from further use */ void Stop(); - /** - * Returns the Thread Local Storage address of the current thread - * @returns VAddr of the thread's TLS - */ - VAddr GetTLSAddress() const { - return tls_address; - } - - /** - * Returns the address of the current thread's command buffer, located in the TLS. - * @returns VAddr of the thread's command buffer. - */ VAddr GetCommandBufferAddress() const; /** @@ -287,86 +284,37 @@ public: * with wait_all = true. */ bool IsSleepingOnWaitAll() const { - return status == ThreadStatus::WaitSynchAll; + return m_status == ThreadStatus::WaitSynchAll; } - Core::ARM_Interface::ThreadContext context{}; - - u32 thread_id; - - bool can_schedule{true}; - ThreadStatus status; - VAddr entry_point; - VAddr stack_top; - - u32 nominal_priority; ///< Nominal thread priority, as set by the emulated application - u32 current_priority; ///< Current thread priority, can be temporarily changed - - u64 last_running_ticks; ///< CPU tick when thread was last running - - s32 processor_id; - - VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread - - /// Mutexes currently held by this thread, which will be released when it exits. - boost::container::flat_set> held_mutexes{}; - - /// Mutexes that this thread is currently waiting for. - boost::container::flat_set> pending_mutexes{}; - - std::weak_ptr owner_process{}; ///< Process that owns this thread - - /// Objects that the thread is waiting on, in the same order as they were - /// passed to WaitSynchronization1/N. - std::vector> wait_objects{}; - - VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address - - std::string name{}; - - /// Callback that will be invoked when the thread is resumed from a waiting state. If the thread - /// was waiting via WaitSynchronizationN then the object will be the last object that became - /// available. In case of a timeout, the object will be nullptr. - std::shared_ptr wakeup_callback{}; - - const u32 core_id; - -private: - ThreadManager& thread_manager; +public: + ThreadManager* m_manager{}; + ThreadContext m_context{}; + u32 m_thread_id; + u32 m_core_id; + bool m_can_schedule{true}; + ThreadStatus m_status; + VAddr m_entry_point; + VAddr m_stack_top; + u32 m_nominal_priority; + u32 m_current_priority; + u64 m_last_running_ticks; + s32 m_processor_id; + VAddr m_tls_address; + boost::container::flat_set m_held_mutexes; + boost::container::flat_set m_pending_mutexes; + Process* m_owner{}; + std::vector m_wait_objects; + VAddr m_wait_address; + std::string m_name{}; + std::shared_ptr m_wakeup_callback{}; friend class boost::serialization::access; template - void serialize(Archive& ar, const unsigned int file_version); + void serialize(Archive& ar, const u32 file_version); }; -/** - * Sets up the primary application thread - * @param kernel The kernel instance on which the thread is created - * @param entry_point The address at which the thread should start execution - * @param priority The priority to give the main thread - * @param owner_process The parent process for the main thread - * @return A shared pointer to the main thread - */ -std::shared_ptr SetupMainThread(KernelSystem& kernel, u32 entry_point, u32 priority, - std::shared_ptr owner_process); - } // namespace Kernel BOOST_CLASS_EXPORT_KEY(Kernel::Thread) - -namespace boost::serialization { - -template -inline void save_construct_data(Archive& ar, const Kernel::Thread* t, - const unsigned int file_version) { - ar << t->core_id; -} - -template -inline void load_construct_data(Archive& ar, Kernel::Thread* t, const unsigned int file_version) { - u32 core_id; - ar >> core_id; - ::new (t) Kernel::Thread(Core::Global(), core_id); -} - -} // namespace boost::serialization +CONSTRUCT_KERNEL_OBJECT(Kernel::Thread) diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp deleted file mode 100644 index 41ce6ef1a..000000000 --- a/src/core/hle/kernel/timer.cpp +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/archives.h" -#include "common/assert.h" -#include "common/logging/log.h" -#include "core/core.h" -#include "core/hle/kernel/handle_table.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/process.h" -#include "core/hle/kernel/resource_limit.h" -#include "core/hle/kernel/thread.h" -#include "core/hle/kernel/timer.h" - -SERIALIZE_EXPORT_IMPL(Kernel::Timer) - -namespace Kernel { - -Timer::Timer(KernelSystem& kernel) - : WaitObject(kernel), kernel(kernel), timer_manager(kernel.GetTimerManager()) {} - -Timer::~Timer() { - Cancel(); - timer_manager.timer_callback_table.erase(callback_id); - if (resource_limit) { - resource_limit->Release(ResourceLimitType::Timer, 1); - } -} - -std::shared_ptr KernelSystem::CreateTimer(ResetType reset_type, std::string name) { - auto timer = std::make_shared(*this); - timer->reset_type = reset_type; - timer->signaled = false; - timer->name = std::move(name); - timer->initial_delay = 0; - timer->interval_delay = 0; - timer->callback_id = ++timer_manager->next_timer_callback_id; - timer_manager->timer_callback_table[timer->callback_id] = timer.get(); - return timer; -} - -bool Timer::ShouldWait(const Thread* thread) const { - return !signaled; -} - -void Timer::Acquire(Thread* thread) { - ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); - - if (reset_type == ResetType::OneShot) - signaled = false; -} - -void Timer::Set(s64 initial, s64 interval) { - // Ensure we get rid of any previous scheduled event - Cancel(); - - initial_delay = initial; - interval_delay = interval; - - if (initial == 0) { - // Immediately invoke the callback - Signal(0); - } else { - kernel.timing.ScheduleEvent(nsToCycles(initial), timer_manager.timer_callback_event_type, - callback_id); - } -} - -void Timer::Cancel() { - kernel.timing.UnscheduleEvent(timer_manager.timer_callback_event_type, callback_id); -} - -void Timer::Clear() { - signaled = false; -} - -void Timer::WakeupAllWaitingThreads() { - WaitObject::WakeupAllWaitingThreads(); - - if (reset_type == ResetType::Pulse) - signaled = false; -} - -void Timer::Signal(s64 cycles_late) { - LOG_TRACE(Kernel, "Timer {} fired", GetObjectId()); - - signaled = true; - - // Resume all waiting threads - WakeupAllWaitingThreads(); - - if (interval_delay != 0) { - // Reschedule the timer with the interval delay - kernel.timing.ScheduleEvent(nsToCycles(interval_delay) - cycles_late, - timer_manager.timer_callback_event_type, callback_id); - } -} - -/// The timer callback event, called when a timer is fired -void TimerManager::TimerCallback(u64 callback_id, s64 cycles_late) { - std::shared_ptr timer = SharedFrom(timer_callback_table.at(callback_id)); - - if (timer == nullptr) { - LOG_CRITICAL(Kernel, "Callback fired for invalid timer {:016x}", callback_id); - return; - } - - timer->Signal(cycles_late); -} - -TimerManager::TimerManager(Core::Timing& timing) : timing(timing) { - timer_callback_event_type = - timing.RegisterEvent("TimerCallback", [this](u64 thread_id, s64 cycle_late) { - TimerCallback(thread_id, cycle_late); - }); -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h deleted file mode 100644 index 9eedb6a85..000000000 --- a/src/core/hle/kernel/timer.h +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include "common/common_types.h" -#include "core/core_timing.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/wait_object.h" - -namespace Core { -class Timing; -} - -namespace Kernel { - -class TimerManager { -public: - TimerManager(Core::Timing& timing); - -private: - /// The timer callback event, called when a timer is fired - void TimerCallback(u64 callback_id, s64 cycles_late); - - Core::Timing& timing; - - /// The event type of the generic timer callback event - Core::TimingEventType* timer_callback_event_type = nullptr; - - u64 next_timer_callback_id = 0; - std::unordered_map timer_callback_table; - - friend class Timer; - friend class KernelSystem; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int file_version) { - ar& next_timer_callback_id; - ar& timer_callback_table; - } -}; - -class ResourceLimit; - -class Timer final : public WaitObject { -public: - explicit Timer(KernelSystem& kernel); - ~Timer() override; - - std::string GetTypeName() const override { - return "Timer"; - } - std::string GetName() const override { - return name; - } - - static constexpr HandleType HANDLE_TYPE = HandleType::Timer; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - ResetType GetResetType() const { - return reset_type; - } - - u64 GetInitialDelay() const { - return initial_delay; - } - - u64 GetIntervalDelay() const { - return interval_delay; - } - - bool ShouldWait(const Thread* thread) const override; - void Acquire(Thread* thread) override; - - void WakeupAllWaitingThreads() override; - - /** - * Starts the timer, with the specified initial delay and interval. - * @param initial Delay until the timer is first fired - * @param interval Delay until the timer is fired after the first time - */ - void Set(s64 initial, s64 interval); - - void Cancel(); - void Clear(); - - /** - * Signals the timer, waking up any waiting threads and rescheduling it - * for the next interval. - * This method should not be called from outside the timer callback handler, - * lest multiple callback events get scheduled. - */ - void Signal(s64 cycles_late); - - std::shared_ptr resource_limit; - -private: - ResetType reset_type; ///< The ResetType of this timer - - u64 initial_delay; ///< The delay until the timer fires for the first time - u64 interval_delay; ///< The delay until the timer fires after the first time - - bool signaled; ///< Whether the timer has been signaled or not - std::string name; ///< Name of timer (optional) - - /// ID used as userdata to reference this object when inserting into the CoreTiming queue. - u64 callback_id; - - KernelSystem& kernel; - TimerManager& timer_manager; - - friend class KernelSystem; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int file_version) { - ar& boost::serialization::base_object(*this); - ar& reset_type; - ar& initial_delay; - ar& interval_delay; - ar& signaled; - ar& name; - ar& callback_id; - ar& resource_limit; - } -}; - -} // namespace Kernel - -BOOST_CLASS_EXPORT_KEY(Kernel::Timer) -CONSTRUCT_KERNEL_OBJECT(Kernel::Timer) diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp deleted file mode 100644 index 2cd4331c5..000000000 --- a/src/core/hle/kernel/wait_object.cpp +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include "common/archives.h" -#include "common/assert.h" -#include "common/logging/log.h" -#include "core/hle/kernel/errors.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/memory.h" -#include "core/hle/kernel/process.h" -#include "core/hle/kernel/resource_limit.h" -#include "core/hle/kernel/thread.h" -#include "core/hle/kernel/timer.h" - -namespace Kernel { - -template -void WaitObject::serialize(Archive& ar, const unsigned int file_version) { - ar& boost::serialization::base_object(*this); - ar& waiting_threads; - // NB: hle_notifier *not* serialized since it's a callback! - // Fortunately it's only used in one place (DSP) so we can reconstruct it there -} -SERIALIZE_IMPL(WaitObject) - -void WaitObject::AddWaitingThread(std::shared_ptr thread) { - auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); - if (itr == waiting_threads.end()) - waiting_threads.push_back(std::move(thread)); -} - -void WaitObject::RemoveWaitingThread(Thread* thread) { - auto itr = std::find_if(waiting_threads.begin(), waiting_threads.end(), - [thread](const auto& p) { return p.get() == thread; }); - // If a thread passed multiple handles to the same object, - // the kernel might attempt to remove the thread from the object's - // waiting threads list multiple times. - if (itr != waiting_threads.end()) - waiting_threads.erase(itr); -} - -std::shared_ptr WaitObject::GetHighestPriorityReadyThread() const { - Thread* candidate = nullptr; - u32 candidate_priority = ThreadPrioLowest + 1; - - for (const auto& thread : waiting_threads) { - // The list of waiting threads must not contain threads that are not waiting to be awakened. - ASSERT_MSG(thread->status == ThreadStatus::WaitSynchAny || - thread->status == ThreadStatus::WaitSynchAll || - thread->status == ThreadStatus::WaitHleEvent, - "Inconsistent thread statuses in waiting_threads"); - - if (thread->current_priority >= candidate_priority) - continue; - - if (ShouldWait(thread.get())) - continue; - - // A thread is ready to run if it's either in ThreadStatus::WaitSynchAny or - // in ThreadStatus::WaitSynchAll and the rest of the objects it is waiting on are ready. - bool ready_to_run = true; - if (thread->status == ThreadStatus::WaitSynchAll) { - ready_to_run = std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), - [&thread](const std::shared_ptr& object) { - return object->ShouldWait(thread.get()); - }); - } - - if (ready_to_run) { - candidate = thread.get(); - candidate_priority = thread->current_priority; - } - } - - return SharedFrom(candidate); -} - -void WaitObject::WakeupAllWaitingThreads() { - while (auto thread = GetHighestPriorityReadyThread()) { - if (!thread->IsSleepingOnWaitAll()) { - Acquire(thread.get()); - } else { - for (auto& object : thread->wait_objects) { - object->Acquire(thread.get()); - } - } - - // Invoke the wakeup callback before clearing the wait objects - if (thread->wakeup_callback) - thread->wakeup_callback->WakeUp(ThreadWakeupReason::Signal, thread, SharedFrom(this)); - - for (auto& object : thread->wait_objects) - object->RemoveWaitingThread(thread.get()); - thread->wait_objects.clear(); - - thread->ResumeFromWait(); - } - - if (hle_notifier) - hle_notifier(); -} - -const std::vector>& WaitObject::GetWaitingThreads() const { - return waiting_threads; -} - -void WaitObject::SetHLENotifier(std::function callback) { - hle_notifier = std::move(callback); -} - -} // namespace Kernel diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 9f115086c..6ab3c3e61 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -18,7 +18,7 @@ #include "core/file_sys/cia_container.h" #include "core/file_sys/file_backend.h" #include "core/global.h" -#include "core/hle/kernel/mutex.h" +#include "core/hle/kernel/k_mutex.h" #include "core/hle/result.h" #include "core/hle/service/service.h" diff --git a/src/core/hle/service/apt/applet_manager.cpp b/src/core/hle/service/apt/applet_manager.cpp index db55a6d10..d9bb575d4 100644 --- a/src/core/hle/service/apt/applet_manager.cpp +++ b/src/core/hle/service/apt/applet_manager.cpp @@ -3,7 +3,6 @@ // Refer to the license.txt file included. #include "common/archives.h" -#include "common/file_util.h" #include "common/settings.h" #include "core/core.h" #include "core/frontend/input.h" @@ -357,7 +356,7 @@ ResultVal AppletManager::Initialize(AppletId ap slot_data->applet_id = static_cast(app_id); // Note: In the real console the title id of a given applet slot is set by the APT module when // calling StartApplication. - slot_data->title_id = system.Kernel().GetCurrentProcess()->codeset->program_id; + slot_data->title_id = system.Kernel().GetCurrentProcess()->codeset.program_id; slot_data->attributes.raw = attributes.raw; // Applications need to receive a Wakeup signal to actually start up, this signal is usually @@ -1475,8 +1474,8 @@ void AppletManager::ButtonUpdateEvent(std::uintptr_t user_data, s64 cycles_late) button_update_event); } -AppletManager::AppletManager(Core::System& system) : system(system) { - lock = system.Kernel().CreateMutex(false, "APT_U:Lock"); +AppletManager::AppletManager(Core::System& system) : system(system), service_context{system, "AppletManager"} { + lock = service_context.Create("APT_U:Lock", false); for (std::size_t slot = 0; slot < applet_slots.size(); ++slot) { auto& slot_data = applet_slots[slot]; slot_data.slot = static_cast(slot); @@ -1485,9 +1484,9 @@ AppletManager::AppletManager(Core::System& system) : system(system) { slot_data.registered = false; slot_data.loaded = false; slot_data.notification_event = - system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "APT:Notification"); + service_context.Create("APT:Notification", Kernel::ResetType::OneShot); slot_data.parameter_event = - system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "APT:Parameter"); + service_context.Create("APT:Parameter", Kernel::ResetType::OneShot); } HLE::Applets::Init(); button_update_event = system.CoreTiming().RegisterEvent( diff --git a/src/core/hle/service/apt/applet_manager.h b/src/core/hle/service/apt/applet_manager.h index 8794b7713..15fbcba9a 100644 --- a/src/core/hle/service/apt/applet_manager.h +++ b/src/core/hle/service/apt/applet_manager.h @@ -15,9 +15,10 @@ #include #include "core/frontend/input.h" #include "core/global.h" -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/k_event.h" #include "core/hle/result.h" #include "core/hle/service/fs/archive.h" +#include "core/hle/service/kernel_helpers.h" namespace Core { class System; @@ -119,7 +120,7 @@ struct MessageParameter { AppletId sender_id = AppletId::None; AppletId destination_id = AppletId::None; SignalType signal = SignalType::None; - std::shared_ptr object = nullptr; + Kernel::KAutoObject* object = nullptr; std::vector buffer; private: @@ -267,13 +268,13 @@ public: struct GetLockHandleResult { AppletAttributes corrected_attributes; u32 state; - std::shared_ptr lock; + Kernel::KMutex* lock; }; ResultVal GetLockHandle(AppletAttributes attributes); struct InitializeResult { - std::shared_ptr notification_event; - std::shared_ptr parameter_event; + Kernel::KEvent* notification_event; + Kernel::KEvent* parameter_event; }; ResultVal Initialize(AppletId app_id, AppletAttributes attributes); @@ -398,7 +399,7 @@ public: private: /// APT lock retrieved via GetLockHandle. - std::shared_ptr lock; + Kernel::KMutex* lock; /// Parameter data to be returned in the next call to Glance/ReceiveParameter. // NOTE: A bug in gcc prevents serializing std::optional @@ -435,8 +436,8 @@ private: bool loaded; AppletAttributes attributes; Notification notification; - std::shared_ptr notification_event; - std::shared_ptr parameter_event; + Kernel::KEvent* notification_event; + Kernel::KEvent* parameter_event; void Reset() { applet_id = AppletId::None; @@ -487,6 +488,7 @@ private: bool last_power_button_state = false; Core::System& system; + KernelHelpers::ServiceContext service_context; AppletSlotData* GetAppletSlot(AppletSlot slot) { return &applet_slots[static_cast(slot)]; diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index 3a4251691..cae418129 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -8,9 +8,7 @@ #include #include #include "common/archives.h" -#include "common/common_funcs.h" #include "common/common_types.h" -#include "common/swap.h" #include "core/global.h" #include "core/hle/kernel/kernel.h" #include "core/hle/service/service.h" @@ -20,8 +18,8 @@ class System; } namespace Kernel { -class Mutex; -class SharedMemory; +class KMutex; +class KSharedMemory; } // namespace Kernel namespace Service::APT { @@ -1046,7 +1044,7 @@ private: Core::System& system; /// Handle to shared memory region designated to for shared system font - std::shared_ptr shared_font_mem; + Kernel::KSharedMemory* shared_font_mem{}; bool shared_font_loaded = false; bool shared_font_relocated = false; diff --git a/src/core/hle/service/apt/ns.cpp b/src/core/hle/service/apt/ns.cpp index 5e859799b..f41d5a767 100644 --- a/src/core/hle/service/apt/ns.cpp +++ b/src/core/hle/service/apt/ns.cpp @@ -3,13 +3,15 @@ // Refer to the license.txt file included. #include "core/core.h" +#include "core/hle/kernel/process.h" #include "core/hle/service/am/am.h" +#include "core/hle/service/fs/archive.h" #include "core/hle/service/apt/ns.h" #include "core/loader/loader.h" namespace Service::NS { -std::shared_ptr LaunchTitle(FS::MediaType media_type, u64 title_id) { +Kernel::Process* LaunchTitle(FS::MediaType media_type, u64 title_id) { std::string path = AM::GetTitleContentPath(media_type, title_id); auto loader = Loader::GetLoader(path); @@ -18,7 +20,7 @@ std::shared_ptr LaunchTitle(FS::MediaType media_type, u64 title return nullptr; } - std::shared_ptr process; + Kernel::Process* process{}; Loader::ResultStatus result = loader->Load(process); if (result != Loader::ResultStatus::Success) { diff --git a/src/core/hle/service/apt/ns.h b/src/core/hle/service/apt/ns.h index 89fddbd33..9f7c81f9f 100644 --- a/src/core/hle/service/apt/ns.h +++ b/src/core/hle/service/apt/ns.h @@ -5,18 +5,23 @@ #pragma once #include "common/common_types.h" -#include "core/hle/kernel/process.h" -#include "core/hle/service/fs/archive.h" -#include "core/hle/service/service.h" namespace Core { class System; } +namespace Kernel { +class Process; +} + +namespace Service::FS { +enum class MediaType : u32; +} + namespace Service::NS { /// Loads and launches the title identified by title_id in the specified media type. -std::shared_ptr LaunchTitle(FS::MediaType media_type, u64 title_id); +Kernel::Process* LaunchTitle(FS::MediaType media_type, u64 title_id); /// Reboots the system to the specified title. void RebootToTitle(Core::System& system, FS::MediaType media_type, u64 title_id); diff --git a/src/core/hle/service/gsp/gsp_gpu.cpp b/src/core/hle/service/gsp/gsp_gpu.cpp index 100e03f52..30a9f0ae7 100644 --- a/src/core/hle/service/gsp/gsp_gpu.cpp +++ b/src/core/hle/service/gsp/gsp_gpu.cpp @@ -12,7 +12,7 @@ #include "core/file_sys/plugin_3gx.h" #include "core/hle/ipc.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/shared_memory.h" +#include "core/hle/kernel/k_shared_memory.h" #include "core/hle/kernel/shared_page.h" #include "core/hle/result.h" #include "core/hle/service/gsp/gsp_gpu.h" @@ -874,7 +874,7 @@ SessionData* GSP_GPU::FindRegisteredThreadData(u32 thread_id) { return nullptr; } -GSP_GPU::GSP_GPU(Core::System& system) : ServiceFramework("gsp::Gpu", 4), system(system) { +GSP_GPU::GSP_GPU(Core::System& system) : ServiceFramework("gsp::Gpu", 4), system(system), service_context(system, "GSP") { static const FunctionInfo functions[] = { // clang-format off {0x0001, &GSP_GPU::WriteHWRegs, "WriteHWRegs"}, @@ -913,11 +913,9 @@ GSP_GPU::GSP_GPU(Core::System& system) : ServiceFramework("gsp::Gpu", 4), system RegisterHandlers(functions); using Kernel::MemoryPermission; - shared_memory = system.Kernel() - .CreateSharedMemory(nullptr, 0x1000, MemoryPermission::ReadWrite, + shared_memory = service_context.Create("GSP:SharedMemory", 0x1000, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, 0, - Kernel::MemoryRegion::BASE, "GSP:SharedMemory") - .Unwrap(); + Kernel::MemoryRegion::BASE); first_initialization = true; }; diff --git a/src/core/hle/service/gsp/gsp_gpu.h b/src/core/hle/service/gsp/gsp_gpu.h index 7de8191a2..b746df98c 100644 --- a/src/core/hle/service/gsp/gsp_gpu.h +++ b/src/core/hle/service/gsp/gsp_gpu.h @@ -12,8 +12,9 @@ #include #include "common/bit_field.h" #include "common/common_types.h" -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/k_event.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/service/kernel_helpers.h" #include "core/hle/result.h" #include "core/hle/service/service.h" @@ -22,7 +23,7 @@ class System; } namespace Kernel { -class SharedMemory; +class KSharedMemory; } // namespace Kernel namespace Service::GSP { @@ -95,12 +96,8 @@ struct FrameBufferUpdate { u32 pad2; }; static_assert(sizeof(FrameBufferUpdate) == 0x40, "Struct has incorrect size"); -// TODO: Not sure if this padding is correct. -// Chances are the second block is stored at offset 0x24 rather than 0x20. -#ifndef _MSC_VER static_assert(offsetof(FrameBufferUpdate, framebuffer_info[1]) == 0x20, "FrameBufferInfo element has incorrect alignment"); -#endif /// GSP command struct Command { @@ -208,7 +205,7 @@ public: GSP_GPU* gsp; /// Event triggered when GSP interrupt has been signalled - std::shared_ptr interrupt_event; + Kernel::KEvent* interrupt_event; /// Thread index into interrupt relay queue u32 thread_id; /// Whether RegisterInterruptRelayQueue was called for this session @@ -232,7 +229,7 @@ public: explicit GSP_GPU(Core::System& system); ~GSP_GPU() = default; - void ClientDisconnected(std::shared_ptr server_session) override; + void ClientDisconnected(Kernel::KServerSession* server_session) override; /** * Signals that the specified interrupt type has occurred to userland code @@ -475,8 +472,10 @@ private: Core::System& system; + KernelHelpers::ServiceContext service_context; + /// GSP shared memory - std::shared_ptr shared_memory; + Kernel::KSharedMemory* shared_memory; /// Thread id that currently has GPU rights or UINT32_MAX if none. u32 active_thread_id = UINT32_MAX; diff --git a/src/core/hle/service/kernel_helpers.cpp b/src/core/hle/service/kernel_helpers.cpp new file mode 100644 index 000000000..a39c53944 --- /dev/null +++ b/src/core/hle/service/kernel_helpers.cpp @@ -0,0 +1,41 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/core.h" +#include "core/hle/kernel/k_shared_memory.h" +#include "core/hle/service/kernel_helpers.h" + +namespace Service::KernelHelpers { + +ServiceContext::ServiceContext(Core::System& system_, std::string name_) : kernel{system_.Kernel()} {} + +ServiceContext::~ServiceContext() { + // Close references to tracked objects. + for (Kernel::KAutoObject* object : objects) { + object->Close(); + } +} + +Kernel::KSharedMemory* ServiceContext::CreateSharedMemoryForApplet(std::string&& name, u32 offset, u32 size, + Kernel::MemoryPermission permissions, + Kernel::MemoryPermission other_permissions) { + // Create a new object. + Kernel::KSharedMemory* shared_mem = Kernel::KSharedMemory::Create(kernel); + if (!shared_mem) { + LOG_CRITICAL(Service, "Unable to create object!"); + return {}; + } + + // Initialize the object. + shared_mem->InitializeForApplet(offset, size, permissions, other_permissions); + + // Register the object. + Kernel::KSharedMemory::Register(kernel, shared_mem); + objects.push_back(shared_mem); + + // Return it + return shared_mem; +} + +} // namespace Service::KernelHelpers diff --git a/src/core/hle/service/kernel_helpers.h b/src/core/hle/service/kernel_helpers.h new file mode 100644 index 000000000..c4746f45c --- /dev/null +++ b/src/core/hle/service/kernel_helpers.h @@ -0,0 +1,63 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "core/hle/kernel/k_auto_object.h" + +namespace Core { +class System; +} + +namespace Kernel { +class KSharedMemory; +class KernelSystem; +class Process; +enum class MemoryPermission : u32; +} // namespace Kernel + +namespace Service::KernelHelpers { + +template +concept IsKAutoObject = std::is_base_of_v; + +class ServiceContext { +public: + explicit ServiceContext(Core::System& system_, std::string name_); + ~ServiceContext(); + + template + T* Create(std::string&& name, Args&&... args) { + // Create a new object. + auto* object = T::Create(kernel); + if (!object) { + LOG_CRITICAL(Service, "Unable to create object!"); + return {}; + } + + // Initialize the object. + object->Initialize(nullptr, args...); + + // Register the object. + T::Register(kernel, object); + objects.push_back(object); + + // Return it + return object; + } + + Kernel::KSharedMemory* CreateSharedMemoryForApplet(std::string&& name, u32 offset, u32 size, + Kernel::MemoryPermission permissions, + Kernel::MemoryPermission other_permissions); + +private: + Kernel::KernelSystem& kernel; + std::vector objects; +}; + +} // namespace Service::KernelHelpers diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 0b4b3ec54..ddccb0c6b 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -15,7 +15,6 @@ #include "common/common_types.h" #include "common/construct.h" #include "core/hle/kernel/hle_ipc.h" -#include "core/hle/kernel/object.h" #include "core/hle/service/sm/sm.h" namespace Core { diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index 25ca1d248..e301ffdd4 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -2,10 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include #include "common/assert.h" #include "core/core.h" -#include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/k_port.h" #include "core/hle/result.h" #include "core/hle/service/sm/sm.h" #include "core/hle/service/sm/srv.h" @@ -13,13 +12,9 @@ namespace Service::SM { static ResultCode ValidateServiceName(const std::string& name) { - if (name.size() <= 0 || name.size() > 8) { - return ERR_INVALID_NAME_SIZE; - } - if (name.find('\0') != std::string::npos) { - return ERR_NAME_CONTAINS_NUL; - } - return RESULT_SUCCESS; + R_UNLESS(name.size() > 0 && name.size() <= 8, ERR_INVALID_NAME_SIZE); + R_UNLESS(name.find('\0') == std::string::npos, ERR_NAME_CONTAINS_NUL); + R_SUCCEED(); } ServiceManager::ServiceManager(Core::System& system) : system(system) {} @@ -32,38 +27,46 @@ void ServiceManager::InstallInterfaces(Core::System& system) { system.ServiceManager().srv_interface = srv; } -ResultVal> ServiceManager::RegisterService( - std::string name, unsigned int max_sessions) { +ResultCode ServiceManager::RegisterService(Kernel::KServerPort** out_port, std::string name, + u32 max_sessions) { + // Validate service name. + R_UNLESS(name.size() > 0 && name.size() <= 8, ERR_INVALID_NAME_SIZE); + R_UNLESS(name.find('\0') == std::string::npos, ERR_NAME_CONTAINS_NUL); + R_UNLESS(registered_services.find(name) == registered_services.end(), ERR_ALREADY_REGISTERED); - CASCADE_CODE(ValidateServiceName(name)); + // Create the port + auto& kernel = system.Kernel(); + auto* port = Kernel::KPort::Create(kernel); - if (registered_services.find(name) != registered_services.end()) - return ERR_ALREADY_REGISTERED; + // Initialize and register the port. + port->Initialize(max_sessions); + Kernel::KPort::Register(kernel, port); - auto [server_port, client_port] = system.Kernel().CreatePortPair(max_sessions, name); - - registered_services_inverse.emplace(client_port->GetObjectId(), name); - registered_services.emplace(std::move(name), std::move(client_port)); - return server_port; + // Register port. + service_ports.emplace(name, port); + *out_port = &port->GetServerPort(); + R_SUCCEED(); } -ResultVal> ServiceManager::GetServicePort( - const std::string& name) { +ResultCode ServiceManager::GetServicePort(Kernel::KClientPort** out_port, const std::string& name) { + // Validate service name. + R_UNLESS(name.size() > 0 && name.size() <= 8, ERR_INVALID_NAME_SIZE); + R_UNLESS(name.find('\0') == std::string::npos, ERR_NAME_CONTAINS_NUL); - CASCADE_CODE(ValidateServiceName(name)); - auto it = registered_services.find(name); - if (it == registered_services.end()) { - return ERR_SERVICE_NOT_REGISTERED; - } + // Get the port. + auto it = service_ports.find(name); + R_UNLESS(it != service_ports.end(), ERR_SERVICE_NOT_REGISTERED); - return it->second; + // Return it. + *out_port = &it->second->GetClientPort(); + R_SUCCEED(); } -ResultVal> ServiceManager::ConnectToService( - const std::string& name) { - - CASCADE_RESULT(auto client_port, GetServicePort(name)); - return client_port->Connect(); +ResultCode ServiceManager::ConnectToService(Kernel::KClientSession** out_session, + const std::string& name) { + Kernel::KClientPort* client_port; + R_TRY(this->GetServicePort(std::addressof(client_port), name)); + R_RETURN(client_port->CreateSession(out_session)); } std::string ServiceManager::GetServiceNameByPortId(u32 port) const { diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index 659e14afb..527e92be2 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -12,9 +12,8 @@ #include #include #include -#include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/server_port.h" +#include "core/hle/kernel/k_client_port.h" +#include "core/hle/kernel/k_server_port.h" #include "core/hle/result.h" #include "core/hle/service/service.h" @@ -23,8 +22,9 @@ class System; } namespace Kernel { -class ClientSession; +class KClientSession; class SessionRequestHandler; +class KPort; } // namespace Kernel namespace Service::SM { @@ -51,10 +51,10 @@ public: explicit ServiceManager(Core::System& system); - ResultVal> RegisterService(std::string name, - unsigned int max_sessions); - ResultVal> GetServicePort(const std::string& name); - ResultVal> ConnectToService(const std::string& name); + ResultCode RegisterService(Kernel::KServerPort** out_port, std::string name, u32 max_sessions); + ResultCode GetServicePort(Kernel::KClientPort** out_port, const std::string& name); + ResultCode ConnectToService(Kernel::KClientSession** out_session, const std::string& name); + // For IPC Recorder std::string GetServiceNameByPortId(u32 port) const; @@ -67,10 +67,8 @@ public: LOG_DEBUG(Service, "Can't find service: {}", service_name); return nullptr; } - auto port = service->second->GetServerPort(); - if (port == nullptr) { - return nullptr; - } + auto port = service->second->GetParent(); + ASSERT(port); return std::static_pointer_cast(port->hle_handler); } @@ -78,20 +76,22 @@ private: Core::System& system; std::weak_ptr srv_interface; - /// Map of registered services, retrieved using GetServicePort or ConnectToService. - std::unordered_map> registered_services; + std::unordered_map service_ports; - // For IPC Recorder + /// Map of registered services, retrieved using GetServicePort or ConnectToService. + std::unordered_map> registered_services; + + /// For IPC Recorder /// client port Object id -> service name std::unordered_map registered_services_inverse; template - void save(Archive& ar, const unsigned int file_version) const { + void save(Archive& ar, const u32 file_version) const { ar << registered_services; } template - void load(Archive& ar, const unsigned int file_version) { + void load(Archive& ar, const u32 file_version) { ar >> registered_services; registered_services_inverse.clear(); for (const auto& pair : registered_services) { diff --git a/src/core/hle/service/sm/srv.cpp b/src/core/hle/service/sm/srv.cpp index 1960bb7e4..b5dddb3f4 100644 --- a/src/core/hle/service/sm/srv.cpp +++ b/src/core/hle/service/sm/srv.cpp @@ -2,7 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include #include #include #include @@ -12,15 +11,14 @@ #include "core/core.h" #include "core/hle/ipc.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/client_session.h" #include "core/hle/kernel/errors.h" -#include "core/hle/kernel/event.h" #include "core/hle/kernel/hle_ipc.h" -#include "core/hle/kernel/resource_limit.h" -#include "core/hle/kernel/semaphore.h" -#include "core/hle/kernel/server_port.h" -#include "core/hle/kernel/server_session.h" +#include "core/hle/kernel/k_client_port.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_semaphore.h" +#include "core/hle/kernel/k_server_port.h" +#include "core/hle/kernel/k_server_session.h" #include "core/hle/service/sm/sm.h" #include "core/hle/service/sm/srv.h" @@ -76,8 +74,8 @@ void SRV::RegisterClient(Kernel::HLERequestContext& ctx) { void SRV::EnableNotification(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - notification_semaphore = - system.Kernel().CreateSemaphore(0, MAX_PENDING_NOTIFICATIONS, "SRV:Notification").Unwrap(); + notification_semaphore = Kernel::KSemaphore::Create(system.Kernel()); + notification_semaphore->Initialize(nullptr, 0, MAX_PENDING_NOTIFICATIONS, "SRV:Notification"); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); @@ -91,25 +89,26 @@ public: explicit ThreadCallback(Core::System& system_, std::string name_) : system(system_), name(name_) {} - void WakeUp(std::shared_ptr thread, Kernel::HLERequestContext& ctx, + void WakeUp(Kernel::Thread* thread, Kernel::HLERequestContext& ctx, Kernel::ThreadWakeupReason reason) { LOG_ERROR(Service_SRV, "called service={} wakeup", name); - auto client_port = system.ServiceManager().GetServicePort(name); + Kernel::KClientPort* client_port{}; + auto result = system.ServiceManager().GetServicePort(std::addressof(client_port), name); + ASSERT(result.IsSuccess()); - auto session = client_port.Unwrap()->Connect(); - if (session.Succeeded()) { - LOG_DEBUG(Service_SRV, "called service={} -> session={}", name, - (*session)->GetObjectId()); + Kernel::KClientSession* session{}; + result = client_port->CreateSession(std::addressof(session)); + if (result.IsSuccess()) { IPC::RequestBuilder rb(ctx, 0x5, 1, 2); - rb.Push(session.Code()); - rb.PushMoveObjects(std::move(session).Unwrap()); - } else if (session.Code() == Kernel::ERR_MAX_CONNECTIONS_REACHED) { + rb.Push(result); + rb.PushMoveObjects(session); + } else if (result == Kernel::ERR_MAX_CONNECTIONS_REACHED) { LOG_ERROR(Service_SRV, "called service={} -> ERR_MAX_CONNECTIONS_REACHED", name); UNREACHABLE(); } else { - LOG_ERROR(Service_SRV, "called service={} -> error 0x{:08X}", name, session.Code().raw); + LOG_ERROR(Service_SRV, "called service={} -> error 0x{:08X}", name, result.raw); IPC::RequestBuilder rb(ctx, 0x5, 1, 0); - rb.Push(session.Code()); + rb.Push(result); } } @@ -158,37 +157,37 @@ void SRV::GetServiceHandle(Kernel::HLERequestContext& ctx) { auto get_handle = std::make_shared(system, name); - auto client_port = system.ServiceManager().GetServicePort(name); - if (client_port.Failed()) { - if (wait_until_available && client_port.Code() == ERR_SERVICE_NOT_REGISTERED) { + Kernel::KClientPort* client_port{}; + auto result = system.ServiceManager().GetServicePort(std::addressof(client_port), name); + if (result.IsError()) { + if (wait_until_available && result == ERR_SERVICE_NOT_REGISTERED) { LOG_INFO(Service_SRV, "called service={} delayed", name); - std::shared_ptr get_service_handle_event = + Kernel::KEvent* get_service_handle_event = ctx.SleepClientThread("GetServiceHandle", std::chrono::nanoseconds(-1), get_handle); - get_service_handle_delayed_map[name] = std::move(get_service_handle_event); + get_service_handle_delayed_map[name] = get_service_handle_event; return; } else { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(client_port.Code()); - LOG_ERROR(Service_SRV, "called service={} -> error 0x{:08X}", name, - client_port.Code().raw); + rb.Push(result); + LOG_ERROR(Service_SRV, "called service={} -> error 0x{:08X}", name, result.raw); return; } } - auto session = client_port.Unwrap()->Connect(); - if (session.Succeeded()) { - LOG_DEBUG(Service_SRV, "called service={} -> session={}", name, (*session)->GetObjectId()); + Kernel::KClientSession* session{}; + result = client_port->CreateSession(std::addressof(session)); + if (result.IsSuccess()) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); - rb.Push(session.Code()); - rb.PushMoveObjects(std::move(session).Unwrap()); - } else if (session.Code() == Kernel::ERR_MAX_CONNECTIONS_REACHED && wait_until_available) { + rb.Push(result); + rb.PushMoveObjects(session); + } else if (result == Kernel::ERR_MAX_CONNECTIONS_REACHED && wait_until_available) { LOG_WARNING(Service_SRV, "called service={} -> ERR_MAX_CONNECTIONS_REACHED", name); // TODO(Subv): Put the caller guest thread to sleep until this port becomes available again. UNIMPLEMENTED_MSG("Unimplemented wait until port {} is available.", name); } else { - LOG_ERROR(Service_SRV, "called service={} -> error 0x{:08X}", name, session.Code().raw); + LOG_ERROR(Service_SRV, "called service={} -> error 0x{:08X}", name, result.raw); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(session.Code()); + rb.Push(result); } } @@ -266,12 +265,13 @@ void SRV::RegisterService(Kernel::HLERequestContext& ctx) { std::string name(name_buf.data(), std::min(name_len, name_buf.size())); - auto port = system.ServiceManager().RegisterService(name, max_sessions); + Kernel::KServerPort* port{}; + auto result = system.ServiceManager().RegisterService(std::addressof(port), name, max_sessions); - if (port.Failed()) { + if (result.IsError()) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(port.Code()); - LOG_ERROR(Service_SRV, "called service={} -> error 0x{:08X}", name, port.Code().raw); + rb.Push(result); + LOG_ERROR(Service_SRV, "called service={} -> error 0x{:08X}", name, result.raw); return; } @@ -283,7 +283,7 @@ void SRV::RegisterService(Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); - rb.PushMoveObjects(port.Unwrap()); + rb.PushMoveObjects(port); } SRV::SRV(Core::System& system) : ServiceFramework("srv:", 64), system(system) { diff --git a/src/core/hle/service/sm/srv.h b/src/core/hle/service/sm/srv.h index 753218dca..d7c58a72e 100644 --- a/src/core/hle/service/sm/srv.h +++ b/src/core/hle/service/sm/srv.h @@ -4,7 +4,6 @@ #pragma once -#include #include #include #include "core/hle/service/service.h" @@ -15,7 +14,8 @@ class System; namespace Kernel { class HLERequestContext; -class Semaphore; +class KSemaphore; +class KEvent; } // namespace Kernel namespace Service::SM { @@ -38,8 +38,8 @@ private: void RegisterService(Kernel::HLERequestContext& ctx); Core::System& system; - std::shared_ptr notification_semaphore; - std::unordered_map> get_service_handle_delayed_map; + Kernel::KSemaphore* notification_semaphore; + std::unordered_map get_service_handle_delayed_map; template void serialize(Archive& ar, const unsigned int);