diff --git a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp index ba5410e87..cd9bb9c3c 100644 --- a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp +++ b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp @@ -92,6 +92,10 @@ namespace ams::kern::arch::arm64 { return this->page_table.MakeAndOpenPageGroup(out, address, num_pages, state_mask, state, perm_mask, perm, attr_mask, attr); } + Result UnlockForIpcUserBuffer(KProcessAddress address, size_t size) { + return this->page_table.UnlockForIpcUserBuffer(address, size); + } + bool GetPhysicalAddress(KPhysicalAddress *out, KProcessAddress address) const { return this->page_table.GetPhysicalAddress(out, address); } diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_event.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_event.hpp index f1db211e1..15362e8a3 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_event.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_event.hpp @@ -18,13 +18,36 @@ #include #include #include +#include namespace ams::kern { class KEvent final : public KAutoObjectWithSlabHeapAndContainer { MESOSPHERE_AUTOOBJECT_TRAITS(KEvent, KAutoObject); + private: + KReadableEvent readable_event; + KWritableEvent writable_event; + KProcess *owner; + bool initialized; public: - /* TODO: This is a placeholder definition. */ + constexpr KEvent() + : readable_event(), writable_event(), owner(), initialized() + { + /* ... */ + } + + virtual ~KEvent() { /* ... */ } + + void Initialize(); + virtual void Finalize() override; + + virtual bool IsInitialized() const override { return this->initialized; } + virtual uintptr_t GetPostDestroyArgument() const override { return reinterpret_cast(this->owner); } + + static void PostDestroy(uintptr_t arg); + + KReadableEvent &GetReadableEvent() { return this->readable_event; } + KWritableEvent &GetWritableEvent() { return this->writable_event; } }; } diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp index 02e34c359..65d434dba 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp @@ -230,6 +230,8 @@ namespace ams::kern { return this->CheckMemoryState(nullptr, nullptr, nullptr, addr, size, state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr); } + Result UnlockMemory(KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, KMemoryPermission new_perm, u32 lock_attr, const KPageGroup *pg); + Result QueryInfoImpl(KMemoryInfo *out_info, ams::svc::PageInfo *out_page, KProcessAddress address) const; Result AllocateAndMapPagesImpl(PageLinkedList *page_list, KProcessAddress address, size_t num_pages, const KPageProperties properties); Result MapPageGroupImpl(PageLinkedList *page_list, KProcessAddress address, const KPageGroup &pg, const KPageProperties properties, bool reuse_ll); @@ -273,6 +275,8 @@ namespace ams::kern { Result UnmapPageGroup(KProcessAddress address, const KPageGroup &pg, KMemoryState state); Result MakeAndOpenPageGroup(KPageGroup *out, KProcessAddress address, size_t num_pages, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr); + + Result UnlockForIpcUserBuffer(KProcessAddress address, size_t size); public: KProcessAddress GetAddressSpaceStart() const { return this->address_space_start; } KProcessAddress GetHeapRegionStart() const { return this->heap_region_start; } diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_session_request.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_session_request.hpp index 95a1d0a2c..ab9c6325a 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_session_request.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_session_request.hpp @@ -17,13 +17,125 @@ #include #include #include +#include +#include +#include +#include namespace ams::kern { class KSessionRequest final : public KSlabAllocated, public KAutoObject, public util::IntrusiveListBaseNode { MESOSPHERE_AUTOOBJECT_TRAITS(KSessionRequest, KAutoObject); public: - /* TODO: This is a placeholder definition. */ + class SessionMappings { + private: + static constexpr size_t NumStaticMappings = 8; + + class Mapping { + private: + KProcessAddress client_address; + KProcessAddress server_address; + size_t size; + KMemoryState state; + public: + constexpr void Set(KProcessAddress c, KProcessAddress s, size_t sz, KMemoryState st) { + this->client_address = c; + this->server_address = s; + this->size = sz; + this->state = st; + } + + constexpr KProcessAddress GetClientAddress() const { return this->client_address; } + constexpr KProcessAddress GetServerAddress() const { return this->server_address; } + constexpr size_t GetSize() const { return this->size; } + constexpr KMemoryState GetMemoryState() const { return this->state; } + }; + private: + Mapping static_mappings[NumStaticMappings]; + Mapping *mappings; + u8 num_send; + u8 num_recv; + u8 num_exch; + public: + constexpr explicit SessionMappings() : static_mappings(), mappings(), num_send(), num_recv(), num_exch() { /* ... */ } + + void Initialize() { /* ... */ } + void Finalize(); + + size_t GetSendCount() const { return this->num_send; } + size_t GetReceiveCount() const { return this->num_recv; } + size_t GetExchangeCount() const { return this->num_exch; } + + /* TODO: More functionality. */ + }; + private: + SessionMappings mappings; + KThread *thread; + KProcess *server; + KWritableEvent *event; + uintptr_t address; + size_t size; + public: + constexpr KSessionRequest() : mappings(), thread(), server(), event(), address(), size() { /* ... */ } + virtual ~KSessionRequest() { /* ... */ } + + static KSessionRequest *Create() { + KSessionRequest *req = KSessionRequest::Allocate(); + if (req != nullptr) { + KAutoObject::Create(req); + } + return req; + } + + virtual void Destroy() override { + this->Finalize(); + KSessionRequest::Free(this); + } + + void Initialize(KWritableEvent *event, uintptr_t address, size_t size) { + this->mappings.Initialize(); + + this->thread = std::addressof(GetCurrentThread()); + this->event = event; + this->address = address; + this->size = size; + + this->thread->Open(); + if (this->event != nullptr) { + this->event->Open(); + } + } + + virtual void Finalize() override { + this->mappings.Finalize(); + + if (this->thread) { + this->thread->Close(); + } + if (this->event) { + this->event->Close(); + } + if (this->server) { + this->server->Close(); + } + } + + static void PostDestroy(uintptr_t arg) { /* ... */ } + + KThread *GetThread() const { return this->thread; } + KWritableEvent *GetEvent() const { return this->event; } + uintptr_t GetAddress() const { return this->address; } + size_t GetSize() const { return this->size; } + KProcess *GetServerProcess() const { return this->server; } + + void ClearThread() { this->thread = nullptr; } + void ClearEvent() { this->event = nullptr; } + + size_t GetSendCount() const { return this->mappings.GetSendCount(); } + size_t GetReceiveCount() const { return this->mappings.GetReceiveCount(); } + size_t GetExchangeCount() const { return this->mappings.GetExchangeCount(); } + + /* TODO: More functionality. */ }; } diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_writable_event.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_writable_event.hpp new file mode 100644 index 000000000..f67c43979 --- /dev/null +++ b/libraries/libmesosphere/include/mesosphere/kern_k_writable_event.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include + +namespace ams::kern { + + class KEvent; + + class KWritableEvent final : public KAutoObjectWithSlabHeapAndContainer { + MESOSPHERE_AUTOOBJECT_TRAITS(KWritableEvent, KAutoObject); + private: + KEvent *parent; + public: + constexpr explicit KWritableEvent() : parent(nullptr) { /* ... */ } + virtual ~KWritableEvent() { /* ... */ } + + virtual void Destroy() override; + + static void PostDestroy(uintptr_t arg) { /* ... */ } + + void Initialize(KEvent *p); + Result Signal(); + Result Clear(); + + constexpr KEvent *GetParent() const { return this->parent; } + }; + +} diff --git a/libraries/libmesosphere/source/kern_k_event.cpp b/libraries/libmesosphere/source/kern_k_event.cpp new file mode 100644 index 000000000..08dbb3b87 --- /dev/null +++ b/libraries/libmesosphere/source/kern_k_event.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::kern { + + void KEvent::Initialize() { + MESOSPHERE_ASSERT_THIS(); + + /* Increment reference count. */ + /* Because reference count is one on creation, this will result */ + /* in a reference count of two. Thus, when both readable and */ + /* writable events are closed this object will be destroyed. */ + this->Open(); + + /* Create our sub events. */ + KAutoObject::Create(std::addressof(this->readable_event)); + KAutoObject::Create(std::addressof(this->writable_event)); + + /* Initialize our sub sessions. */ + this->readable_event.Initialize(this); + this->writable_event.Initialize(this); + + /* Set our owner process. */ + this->owner = GetCurrentProcessPointer(); + this->owner->Open(); + + /* Mark initialized. */ + this->initialized = true; + } + + void KEvent::Finalize() { + MESOSPHERE_ASSERT_THIS(); + + KAutoObjectWithSlabHeapAndContainer::Finalize(); + } + + void KEvent::PostDestroy(uintptr_t arg) { + /* Release the event count resource the owner process holds. */ + KProcess *owner = reinterpret_cast(arg); + owner->ReleaseResource(ams::svc::LimitableResource_EventCountMax, 1); + owner->Close(); + } + +} diff --git a/libraries/libmesosphere/source/kern_k_page_table_base.cpp b/libraries/libmesosphere/source/kern_k_page_table_base.cpp index c647129f6..e005b9c8d 100644 --- a/libraries/libmesosphere/source/kern_k_page_table_base.cpp +++ b/libraries/libmesosphere/source/kern_k_page_table_base.cpp @@ -439,6 +439,52 @@ namespace ams::kern { return ResultSuccess(); } + Result KPageTableBase::UnlockMemory(KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, KMemoryPermission new_perm, u32 lock_attr, const KPageGroup *pg) { + /* Validate basic preconditions. */ + MESOSPHERE_ASSERT((attr_mask & lock_attr) == lock_attr); + MESOSPHERE_ASSERT((attr & lock_attr) == lock_attr); + + /* Validate the unlock request. */ + const size_t num_pages = size / PageSize; + R_UNLESS(this->Contains(addr, size), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(this->general_lock); + + /* Check the state. */ + KMemoryState old_state; + KMemoryPermission old_perm; + KMemoryAttribute old_attr; + R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm), std::addressof(old_attr), addr, size, state_mask | KMemoryState_FlagReferenceCounted, state | KMemoryState_FlagReferenceCounted, perm_mask, perm, attr_mask, attr)); + + /* Check the page group. */ + if (pg != nullptr) { + R_UNLESS(this->IsValidPageGroup(*pg, addr, num_pages), svc::ResultInvalidMemoryRegion()); + } + + /* Decide on new perm and attr. */ + new_perm = (new_perm != KMemoryPermission_None) ? new_perm : old_perm; + KMemoryAttribute new_attr = static_cast(old_attr & ~lock_attr); + + /* Create an update allocator. */ + KMemoryBlockManagerUpdateAllocator allocator(this->memory_block_slab_manager); + R_TRY(allocator.GetResult()); + + /* Update permission, if we need to. */ + if (new_perm != old_perm) { + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + const KPageProperties properties = { new_perm, false, (old_attr & KMemoryAttribute_Uncached) != 0, false }; + R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, Null, false, properties, OperationType_ChangePermissions, false)); + } + + /* Apply the memory block updates. */ + this->memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm, new_attr); + + return ResultSuccess(); + } + Result KPageTableBase::QueryInfoImpl(KMemoryInfo *out_info, ams::svc::PageInfo *out_page, KProcessAddress address) const { MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); MESOSPHERE_ASSERT(out_info != nullptr); @@ -1086,7 +1132,7 @@ namespace ams::kern { Result KPageTableBase::MakeAndOpenPageGroup(KPageGroup *out, KProcessAddress address, size_t num_pages, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr) { /* Ensure that the page group isn't null. */ - AMS_ASSERT(out != nullptr); + MESOSPHERE_ASSERT(out != nullptr); /* Make sure that the region we're mapping is valid for the table. */ const size_t size = num_pages * PageSize; @@ -1107,4 +1153,13 @@ namespace ams::kern { return ResultSuccess(); } + Result KPageTableBase::UnlockForIpcUserBuffer(KProcessAddress address, size_t size) { + return this->UnlockMemory(address, size, + KMemoryState_FlagCanIpcUserBuffer, KMemoryState_FlagCanIpcUserBuffer, + KMemoryPermission_None, KMemoryPermission_None, + KMemoryAttribute_All, KMemoryAttribute_AnyLocked | KMemoryAttribute_Locked, + KMemoryPermission_UserReadWrite, + KMemoryAttribute_AnyLocked | KMemoryAttribute_Locked, nullptr); + } + } diff --git a/libraries/libmesosphere/source/kern_k_server_session.cpp b/libraries/libmesosphere/source/kern_k_server_session.cpp index d0db87603..cda3fb1d8 100644 --- a/libraries/libmesosphere/source/kern_k_server_session.cpp +++ b/libraries/libmesosphere/source/kern_k_server_session.cpp @@ -17,6 +17,23 @@ namespace ams::kern { + namespace { + + ALWAYS_INLINE void ReplyAsyncError(KProcess *to_process, uintptr_t to_msg_buf, size_t to_msg_buf_size, Result result) { + /* Convert the buffer to a physical address. */ + KPhysicalAddress phys_addr; + to_process->GetPageTable().GetPhysicalAddress(std::addressof(phys_addr), KProcessAddress(to_msg_buf)); + + /* Convert the physical address to a linear pointer. */ + u32 *to_msg = GetPointer(KPageTable::GetHeapVirtualAddress(phys_addr)); + + /* Set the error. */ + ams::svc::ipc::MessageBuffer msg(to_msg, to_msg_buf_size); + msg.SetAsyncResult(result); + } + + } + void KServerSession::Destroy() { MESOSPHERE_ASSERT_THIS(); @@ -33,7 +50,95 @@ namespace ams::kern { void KServerSession::OnClientClosed() { MESOSPHERE_ASSERT_THIS(); - MESOSPHERE_UNIMPLEMENTED(); + + KScopedLightLock lk(this->lock); + + /* Handle any pending requests. */ + KSessionRequest *prev_request = nullptr; + while (true) { + /* Declare variables for processing the request. */ + KSessionRequest *request = nullptr; + KWritableEvent *event = nullptr; + KThread *thread = nullptr; + bool cur_request = false; + bool terminate = false; + + /* Get the next request. */ + { + KScopedSchedulerLock sl; + + if (this->current_request != nullptr && this->current_request != prev_request) { + /* Set the request, open a reference as we process it. */ + request = this->current_request; + request->Open(); + cur_request = true; + + /* Get thread and event for the request. */ + thread = request->GetThread(); + event = request->GetEvent(); + + /* If the thread is terminating, handle that. */ + if (thread->IsTerminationRequested()) { + request->ClearThread(); + request->ClearEvent(); + terminate = true; + } + prev_request = request; + } else if (!this->request_list.empty()) { + /* Pop the request from the front of the list. */ + request = std::addressof(this->request_list.front()); + this->request_list.pop_front(); + + /* Get thread and event for the request. */ + thread = request->GetThread(); + event = request->GetEvent(); + } + } + + /* If there are no requests, we're done. */ + if (request == nullptr) { + break; + } + + /* All requests must have threads. */ + MESOSPHERE_ASSERT(thread != nullptr); + + /* Ensure that we close the request when done. */ + ON_SCOPE_EXIT { request->Close(); }; + + /* If we're terminating, close a reference to the thread and event. */ + if (terminate) { + thread->Close(); + if (event != nullptr) { + event->Close(); + } + } + + /* If we need to, reply. */ + if (event != nullptr && !cur_request) { + /* There must be no mappings. */ + MESOSPHERE_ASSERT(request->GetSendCount() == 0); + MESOSPHERE_ASSERT(request->GetReceiveCount() == 0); + MESOSPHERE_ASSERT(request->GetExchangeCount() == 0); + + /* Get the process and page table. */ + KProcess *client_process = thread->GetOwner(); + auto &client_pt = client_process->GetPageTable(); + + /* Reply to the request. */ + ReplyAsyncError(client_process, request->GetAddress(), request->GetSize(), svc::ResultSessionClosed()); + + /* Unlock the buffer. */ + /* NOTE: Nintendo does not check the result of this. */ + client_pt.UnlockForIpcUserBuffer(request->GetAddress(), request->GetSize()); + + /* Signal the event. */ + event->Signal(); + } + } + + /* Notify. */ + this->NotifyAbort(svc::ResultSessionClosed()); } } diff --git a/libraries/libmesosphere/source/kern_k_writable_event.cpp b/libraries/libmesosphere/source/kern_k_writable_event.cpp new file mode 100644 index 000000000..76950456a --- /dev/null +++ b/libraries/libmesosphere/source/kern_k_writable_event.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::kern { + + void KWritableEvent::Initialize(KEvent *p) { + /* Set parent, open a reference to the readable event. */ + this->parent = p; + this->parent->GetReadableEvent().Open(); + } + + Result KWritableEvent::Signal() { + return this->parent->GetReadableEvent().Signal(); + } + + Result KWritableEvent::Clear() { + return this->parent->GetReadableEvent().Clear(); + } + + void KWritableEvent::Destroy() { + /* Close our references. */ + this->parent->GetReadableEvent().Close(); + this->parent->Close(); + } + +}