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 c4fbe143d..f5a2ee96b 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 @@ -195,6 +195,8 @@ namespace ams::kern::arch::arm64 { size_t GetNormalMemorySize() const { return this->page_table.GetNormalMemorySize(); } + u32 GetAllocateOption() const { return this->page_table.GetAllocateOption(); } + KPhysicalAddress GetHeapPhysicalAddress(KVirtualAddress address) const { /* TODO: Better way to convert address type? */ return this->page_table.GetHeapPhysicalAddress(address); 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 3769a2dc1..04b84ce22 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp @@ -345,6 +345,8 @@ namespace ams::kern { return (this->current_heap_end - this->heap_region_start) + this->mapped_physical_memory_size; } + + u32 GetAllocateOption() const { return this->allocate_option; } public: static ALWAYS_INLINE KVirtualAddress GetLinearMappedVirtualAddress(KPhysicalAddress addr) { return KMemoryLayout::GetLinearVirtualAddress(addr); diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp index 6b283ef51..15bc3275a 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp @@ -151,7 +151,6 @@ namespace ams::kern { constexpr KProcessAddress GetEntryPoint() const { return this->code_address; } constexpr u64 GetRandomEntropy(size_t i) const { return this->entropy[i]; } - constexpr bool IsSuspended() const { return this->is_suspended; } @@ -176,6 +175,8 @@ namespace ams::kern { return this->capabilities.CanForceDebug(); } + u32 GetAllocateOption() const { return this->page_table.GetAllocateOption(); } + ThreadList &GetThreadList() { return this->thread_list; } const ThreadList &GetThreadList() const { return this->thread_list; } @@ -218,6 +219,9 @@ namespace ams::kern { size_t GetUsedNonSystemUserPhysicalMemorySize() const; size_t GetTotalNonSystemUserPhysicalMemorySize() const; + Result AddSharedMemory(KSharedMemory *shmem, KProcessAddress address, size_t size); + void RemoveSharedMemory(KSharedMemory *shmem, KProcessAddress address, size_t size); + Result CreateThreadLocalRegion(KProcessAddress *out); Result DeleteThreadLocalRegion(KProcessAddress addr); void *GetThreadLocalRegionPointer(KProcessAddress addr); diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_shared_memory.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_shared_memory.hpp index 796d264e3..4da9e59e5 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_shared_memory.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_shared_memory.hpp @@ -17,11 +17,43 @@ #include #include #include +#include namespace ams::kern { + class KProcess; + class KResourceLimit; + class KSharedMemory final : public KAutoObjectWithSlabHeapAndContainer { MESOSPHERE_AUTOOBJECT_TRAITS(KSharedMemory, KAutoObject); + private: + KPageGroup page_group; + KResourceLimit *resource_limit; + u64 owner_process_id; + ams::svc::MemoryPermission owner_perm; + ams::svc::MemoryPermission remote_perm; + bool is_initialized; + public: + explicit KSharedMemory() + : page_group(std::addressof(Kernel::GetBlockInfoManager())), resource_limit(nullptr), owner_process_id(std::numeric_limits::max()), + owner_perm(ams::svc::MemoryPermission_None), remote_perm(ams::svc::MemoryPermission_None), is_initialized(false) + { + /* ... */ + } + + virtual ~KSharedMemory() { /* ... */ } + + Result Initialize(KProcess *owner, size_t size, ams::svc::MemoryPermission own_perm, ams::svc::MemoryPermission rem_perm); + virtual void Finalize() override; + + virtual bool IsInitialized() const override { return this->is_initialized; } + static void PostDestroy(uintptr_t arg) { /* ... */ } + + Result Map(KProcessPageTable *table, KProcessAddress address, size_t size, KProcess *process, ams::svc::MemoryPermission map_perm); + Result Unmap(KProcessPageTable *table, KProcessAddress address, size_t size, KProcess *process); + + u64 GetOwnerProcessId() const { return this->owner_process_id; } + size_t GetSize() const { return this->page_group.GetNumPages() * PageSize; } public: /* TODO: This is a placeholder definition. */ }; diff --git a/libraries/libmesosphere/source/kern_k_process.cpp b/libraries/libmesosphere/source/kern_k_process.cpp index cd958ace4..4852e96bf 100644 --- a/libraries/libmesosphere/source/kern_k_process.cpp +++ b/libraries/libmesosphere/source/kern_k_process.cpp @@ -277,6 +277,61 @@ namespace ams::kern { MESOSPHERE_UNIMPLEMENTED(); } + Result KProcess::AddSharedMemory(KSharedMemory *shmem, KProcessAddress address, size_t size) { + /* Lock ourselves, to prevent concurrent access. */ + KScopedLightLock lk(this->state_lock); + + /* Try to find an existing info for the memory. */ + KSharedMemoryInfo *info = nullptr; + for (auto it = this->shared_memory_list.begin(); it != this->shared_memory_list.end(); ++it) { + if (it->GetSharedMemory() == shmem) { + info = std::addressof(*it); + break; + } + } + + /* If we didn't find an info, create one. */ + if (info == nullptr) { + /* Allocate a new info. */ + info = KSharedMemoryInfo::Allocate(); + R_UNLESS(info != nullptr, svc::ResultOutOfResource()); + + /* Initialize the info and add it to our list. */ + info->Initialize(shmem); + this->shared_memory_list.push_back(*info); + } + + /* Open a reference to the shared memory and its info. */ + shmem->Open(); + info->Open(); + + return ResultSuccess(); + } + + void KProcess::RemoveSharedMemory(KSharedMemory *shmem, KProcessAddress address, size_t size) { + /* Lock ourselves, to prevent concurrent access. */ + KScopedLightLock lk(this->state_lock); + + /* Find an existing info for the memory. */ + KSharedMemoryInfo *info = nullptr; + auto it = this->shared_memory_list.begin(); + for (/* ... */; it != this->shared_memory_list.end(); ++it) { + if (it->GetSharedMemory() == shmem) { + info = std::addressof(*it); + break; + } + } + MESOSPHERE_ABORT_UNLESS(info != nullptr); + + /* Close a reference to the info and its memory. */ + if (info->Close()) { + this->shared_memory_list.erase(it); + KSharedMemoryInfo::Free(info); + } + + shmem->Close(); + } + Result KProcess::CreateThreadLocalRegion(KProcessAddress *out) { KThreadLocalPage *tlp = nullptr; KProcessAddress tlr = Null; diff --git a/libraries/libmesosphere/source/kern_k_shared_memory.cpp b/libraries/libmesosphere/source/kern_k_shared_memory.cpp new file mode 100644 index 000000000..641e8c4f6 --- /dev/null +++ b/libraries/libmesosphere/source/kern_k_shared_memory.cpp @@ -0,0 +1,110 @@ +/* + * 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 { + + Result KSharedMemory::Initialize(KProcess *owner, size_t size, ams::svc::MemoryPermission own_perm, ams::svc::MemoryPermission rem_perm) { + MESOSPHERE_ASSERT_THIS(); + + /* Set members. */ + this->owner_process_id = owner->GetId(); + this->owner_perm = own_perm; + this->remote_perm = rem_perm; + + /* Get the number of pages. */ + const size_t num_pages = util::DivideUp(size, PageSize); + MESOSPHERE_ASSERT(num_pages > 0); + + /* Get the resource limit. */ + KResourceLimit *reslimit = owner->GetResourceLimit(); + + /* Reserve memory for ourselves. */ + KScopedResourceReservation memory_reservation(reslimit, ams::svc::LimitableResource_PhysicalMemoryMax, size); + R_UNLESS(memory_reservation.Succeeded(), svc::ResultLimitReached()); + + /* Allocate the memory. */ + R_TRY(Kernel::GetMemoryManager().Allocate(std::addressof(this->page_group), num_pages, owner->GetAllocateOption())); + + /* Commit our reservation. */ + memory_reservation.Commit(); + + /* Set our resource limit. */ + this->resource_limit = reslimit; + this->resource_limit->Open(); + + /* Open the memory. */ + this->page_group.Open(); + + /* Mark initialized. */ + this->is_initialized = true; + + /* Clear all pages in the memory. */ + for (const auto &block : this->page_group) { + std::memset(GetVoidPointer(block.GetAddress()), 0, block.GetSize()); + } + + return ResultSuccess(); + } + + void KSharedMemory::Finalize() { + MESOSPHERE_ASSERT_THIS(); + + /* Get the number of pages. */ + const size_t num_pages = this->page_group.GetNumPages(); + const size_t size = num_pages * PageSize; + + /* Close and finalize the page group. */ + this->page_group.Close(); + this->page_group.Finalize(); + + /* Release the memory reservation. */ + this->resource_limit->Release(ams::svc::LimitableResource_PhysicalMemoryMax, size); + this->resource_limit->Close(); + + /* Perform inherited finalization. */ + KAutoObjectWithSlabHeapAndContainer::Finalize(); + } + + Result KSharedMemory::Map(KProcessPageTable *table, KProcessAddress address, size_t size, KProcess *process, ams::svc::MemoryPermission map_perm) { + MESOSPHERE_ASSERT_THIS(); + + /* Validate the size. */ + R_UNLESS(this->page_group.GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize()); + + /* Validate the permission. */ + const ams::svc::MemoryPermission test_perm = (process->GetId() == this->owner_process_id) ? this->owner_perm : this->remote_perm; + if (test_perm == ams::svc::MemoryPermission_DontCare) { + MESOSPHERE_ASSERT(map_perm == ams::svc::MemoryPermission_Read || map_perm == ams::svc::MemoryPermission_ReadWrite); + } else { + R_UNLESS(map_perm == test_perm, svc::ResultInvalidNewMemoryPermission()); + } + + /* Map the memory. */ + return table->MapPageGroup(address, this->page_group, KMemoryState_Shared, ConvertToKMemoryPermission(map_perm)); + } + + Result KSharedMemory::Unmap(KProcessPageTable *table, KProcessAddress address, size_t size, KProcess *process) { + MESOSPHERE_ASSERT_THIS(); + + /* Validate the size. */ + R_UNLESS(this->page_group.GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize()); + + /* Unmap the memory. */ + return table->UnmapPageGroup(address, this->page_group, KMemoryState_Shared); + } + +} diff --git a/libraries/libmesosphere/source/svc/kern_svc_shared_memory.cpp b/libraries/libmesosphere/source/svc/kern_svc_shared_memory.cpp index fbd77c023..8cfd1e014 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_shared_memory.cpp +++ b/libraries/libmesosphere/source/svc/kern_svc_shared_memory.cpp @@ -21,36 +21,138 @@ namespace ams::kern::svc { namespace { + constexpr bool IsValidSharedMemoryPermission(ams::svc::MemoryPermission perm) { + switch (perm) { + case ams::svc::MemoryPermission_Read: + case ams::svc::MemoryPermission_ReadWrite: + return true; + default: + return false; + } + } + constexpr bool IsValidRemoteSharedMemoryPermission(ams::svc::MemoryPermission perm) { + return IsValidSharedMemoryPermission(perm) || perm == ams::svc::MemoryPermission_DontCare; + } + + Result MapSharedMemory(ams::svc::Handle shmem_handle, uintptr_t address, size_t size, ams::svc::MemoryPermission map_perm) { + /* Validate the address/size. */ + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Validate the permission. */ + R_UNLESS(IsValidSharedMemoryPermission(map_perm), svc::ResultInvalidNewMemoryPermission()); + + /* Get the current process. */ + auto &process = GetCurrentProcess(); + auto &page_table = process.GetPageTable(); + + /* Get the shared memory. */ + KScopedAutoObject shmem = process.GetHandleTable().GetObject(shmem_handle); + R_UNLESS(shmem.IsNotNull(), svc::ResultInvalidHandle()); + + /* Verify that the mapping is in range. */ + R_UNLESS(page_table.CanContain(address, size, KMemoryState_Shared), svc::ResultInvalidMemoryRegion()); + + /* Add the shared memory to the process. */ + R_TRY(process.AddSharedMemory(shmem.GetPointerUnsafe(), address, size)); + + /* Ensure that we clean up the shared memory if we fail to map it. */ + auto guard = SCOPE_GUARD { process.RemoveSharedMemory(shmem.GetPointerUnsafe(), address, size); }; + + /* Map the shared memory. */ + R_TRY(shmem->Map(std::addressof(page_table), address, size, std::addressof(process), map_perm)); + + /* We succeeded. */ + guard.Cancel(); + return ResultSuccess(); + } + + Result UnmapSharedMemory(ams::svc::Handle shmem_handle, uintptr_t address, size_t size) { + /* Validate the address/size. */ + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Get the current process. */ + auto &process = GetCurrentProcess(); + auto &page_table = process.GetPageTable(); + + /* Get the shared memory. */ + KScopedAutoObject shmem = process.GetHandleTable().GetObject(shmem_handle); + R_UNLESS(shmem.IsNotNull(), svc::ResultInvalidHandle()); + + /* Verify that the mapping is in range. */ + R_UNLESS(page_table.CanContain(address, size, KMemoryState_Shared), svc::ResultInvalidMemoryRegion()); + + /* Map the shared memory. */ + R_TRY(shmem->Unmap(std::addressof(page_table), address, size, std::addressof(process))); + + /* Remove the shared memory from the process. */ + process.RemoveSharedMemory(shmem.GetPointerUnsafe(), address, size); + + return ResultSuccess(); + } + + Result CreateSharedMemory(ams::svc::Handle *out, size_t size, ams::svc::MemoryPermission owner_perm, ams::svc::MemoryPermission remote_perm) { + /* Validate the size. */ + R_UNLESS(0 < size && size < kern::MainMemorySize, svc::ResultInvalidSize()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + + /* Validate the permissions. */ + R_UNLESS(IsValidSharedMemoryPermission(owner_perm), svc::ResultInvalidNewMemoryPermission()); + R_UNLESS(IsValidRemoteSharedMemoryPermission(remote_perm), svc::ResultInvalidNewMemoryPermission()); + + /* Create the shared memory. */ + KSharedMemory *shmem = KSharedMemory::Create(); + R_UNLESS(shmem != nullptr, svc::ResultOutOfResource()); + + /* Ensure the only reference is in the handle table when we're done. */ + ON_SCOPE_EXIT { shmem->Close(); }; + + /* Initialize the shared memory. */ + R_TRY(shmem->Initialize(GetCurrentProcessPointer(), size, owner_perm, remote_perm)); + + /* Register the shared memory. */ + R_TRY(KSharedMemory::Register(shmem)); + + /* Add the shared memory to the handle table. */ + R_TRY(GetCurrentProcess().GetHandleTable().Add(out, shmem)); + + return ResultSuccess(); + } } /* ============================= 64 ABI ============================= */ Result MapSharedMemory64(ams::svc::Handle shmem_handle, ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission map_perm) { - MESOSPHERE_PANIC("Stubbed SvcMapSharedMemory64 was called."); + return MapSharedMemory(shmem_handle, address, size, map_perm); } Result UnmapSharedMemory64(ams::svc::Handle shmem_handle, ams::svc::Address address, ams::svc::Size size) { - MESOSPHERE_PANIC("Stubbed SvcUnmapSharedMemory64 was called."); + return UnmapSharedMemory(shmem_handle, address, size); } Result CreateSharedMemory64(ams::svc::Handle *out_handle, ams::svc::Size size, ams::svc::MemoryPermission owner_perm, ams::svc::MemoryPermission remote_perm) { - MESOSPHERE_PANIC("Stubbed SvcCreateSharedMemory64 was called."); + return CreateSharedMemory(out_handle, size, owner_perm, remote_perm); } /* ============================= 64From32 ABI ============================= */ Result MapSharedMemory64From32(ams::svc::Handle shmem_handle, ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission map_perm) { - MESOSPHERE_PANIC("Stubbed SvcMapSharedMemory64From32 was called."); + return MapSharedMemory(shmem_handle, address, size, map_perm); } Result UnmapSharedMemory64From32(ams::svc::Handle shmem_handle, ams::svc::Address address, ams::svc::Size size) { - MESOSPHERE_PANIC("Stubbed SvcUnmapSharedMemory64From32 was called."); + return UnmapSharedMemory(shmem_handle, address, size); } Result CreateSharedMemory64From32(ams::svc::Handle *out_handle, ams::svc::Size size, ams::svc::MemoryPermission owner_perm, ams::svc::MemoryPermission remote_perm) { - MESOSPHERE_PANIC("Stubbed SvcCreateSharedMemory64From32 was called."); + return CreateSharedMemory(out_handle, size, owner_perm, remote_perm); } }