From d87a9f011cf8551da04b02aeee2199b464e689ca Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Thu, 23 Jul 2020 00:44:33 -0700 Subject: [PATCH] kern: implement transfer memory (and SVCs) --- .../arch/arm64/kern_k_process_page_table.hpp | 16 +++ .../mesosphere/kern_k_page_table_base.hpp | 5 + .../mesosphere/kern_k_shared_memory.hpp | 2 - .../mesosphere/kern_k_transfer_memory.hpp | 28 ++++- .../source/kern_k_page_table_base.cpp | 46 ++++++- .../source/kern_k_transfer_memory.cpp | 116 ++++++++++++++++++ .../source/svc/kern_svc_shared_memory.cpp | 2 +- .../source/svc/kern_svc_transfer_memory.cpp | 116 ++++++++++++++++-- 8 files changed, 312 insertions(+), 19 deletions(-) create mode 100644 libraries/libmesosphere/source/kern_k_transfer_memory.cpp 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 feca2e94a..a46e4b1cf 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 @@ -136,6 +136,22 @@ namespace ams::kern::arch::arm64 { return this->page_table.UnlockForIpcUserBuffer(address, size); } + Result LockForTransferMemory(KPageGroup *out, KProcessAddress address, size_t size, KMemoryPermission perm) { + return this->page_table.LockForTransferMemory(out, address, size, perm); + } + + Result UnlockForTransferMemory(KProcessAddress address, size_t size, const KPageGroup &pg) { + return this->page_table.UnlockForTransferMemory(address, size, pg); + } + + Result LockForCodeMemory(KPageGroup *out, KProcessAddress address, size_t size, KMemoryPermission perm) { + return this->page_table.LockForCodeMemory(out, address, size, perm); + } + + Result UnlockForCodeMemory(KProcessAddress address, size_t size, const KPageGroup &pg) { + return this->page_table.UnlockForCodeMemory(address, size, pg); + } + Result CopyMemoryFromLinearToUser(KProcessAddress dst_addr, size_t size, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr) { return this->page_table.CopyMemoryFromLinearToUser(dst_addr, size, src_addr, src_state_mask, src_state, src_test_perm, src_attr_mask, src_attr); } 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 4e11a5861..d166e67e6 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp @@ -316,6 +316,11 @@ namespace ams::kern { Result LockForIpcUserBuffer(KPhysicalAddress *out, KProcessAddress address, size_t size); Result UnlockForIpcUserBuffer(KProcessAddress address, size_t size); + Result LockForTransferMemory(KPageGroup *out, KProcessAddress address, size_t size, KMemoryPermission perm); + Result UnlockForTransferMemory(KProcessAddress address, size_t size, const KPageGroup &pg); + Result LockForCodeMemory(KPageGroup *out, KProcessAddress address, size_t size, KMemoryPermission perm); + Result UnlockForCodeMemory(KProcessAddress address, size_t size, const KPageGroup &pg); + Result CopyMemoryFromLinearToUser(KProcessAddress dst_addr, size_t size, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr); Result CopyMemoryFromLinearToKernel(KProcessAddress dst_addr, size_t size, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr); Result CopyMemoryFromUserToLinear(KProcessAddress dst_addr, size_t size, u32 dst_state_mask, u32 dst_state, KMemoryPermission dst_test_perm, u32 dst_attr_mask, u32 dst_attr, KProcessAddress src_addr); diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_shared_memory.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_shared_memory.hpp index 4da9e59e5..82fb2432e 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_shared_memory.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_shared_memory.hpp @@ -54,8 +54,6 @@ namespace ams::kern { 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/include/mesosphere/kern_k_transfer_memory.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_transfer_memory.hpp index 9dd0d98bc..a0000202a 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_transfer_memory.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_transfer_memory.hpp @@ -22,8 +22,34 @@ namespace ams::kern { class KTransferMemory final : public KAutoObjectWithSlabHeapAndContainer { MESOSPHERE_AUTOOBJECT_TRAITS(KTransferMemory, KAutoObject); + private: + TYPED_STORAGE(KPageGroup) page_group; + KProcess *owner; + KProcessAddress address; + KLightLock lock; + ams::svc::MemoryPermission owner_perm; + bool is_initialized; + bool is_mapped; public: - /* TODO: This is a placeholder definition. */ + explicit KTransferMemory() : owner(nullptr), address(Null), owner_perm(ams::svc::MemoryPermission_None), is_initialized(false), is_mapped(false) { + /* ... */ + } + + virtual ~KTransferMemory() { /* ... */ } + + Result Initialize(KProcessAddress addr, size_t size, ams::svc::MemoryPermission own_perm); + virtual void Finalize() override; + + virtual bool IsInitialized() const override { return this->is_initialized; } + virtual uintptr_t GetPostDestroyArgument() const override { return reinterpret_cast(this->owner); } + static void PostDestroy(uintptr_t arg); + + Result Map(KProcessAddress address, size_t size, ams::svc::MemoryPermission map_perm); + Result Unmap(KProcessAddress address, size_t size); + + KProcess *GetOwner() const { return this->owner; } + KProcessAddress GetSourceAddress() { return this->address; } + size_t GetSize() const { return this->is_initialized ? GetReference(this->page_group).GetNumPages() * PageSize : 0; } }; } diff --git a/libraries/libmesosphere/source/kern_k_page_table_base.cpp b/libraries/libmesosphere/source/kern_k_page_table_base.cpp index a0bb33934..4075ce182 100644 --- a/libraries/libmesosphere/source/kern_k_page_table_base.cpp +++ b/libraries/libmesosphere/source/kern_k_page_table_base.cpp @@ -1665,11 +1665,11 @@ namespace ams::kern { Result KPageTableBase::LockForIpcUserBuffer(KPhysicalAddress *out, KProcessAddress address, size_t size) { return this->LockMemoryAndOpen(nullptr, out, address, size, - KMemoryState_FlagCanIpcUserBuffer, KMemoryState_FlagCanIpcUserBuffer, - KMemoryPermission_All, KMemoryPermission_UserReadWrite, - KMemoryAttribute_All, KMemoryAttribute_None, - static_cast(KMemoryPermission_NotMapped | KMemoryPermission_KernelReadWrite), - KMemoryAttribute_AnyLocked | KMemoryAttribute_Locked); + KMemoryState_FlagCanIpcUserBuffer, KMemoryState_FlagCanIpcUserBuffer, + KMemoryPermission_All, KMemoryPermission_UserReadWrite, + KMemoryAttribute_All, KMemoryAttribute_None, + static_cast(KMemoryPermission_NotMapped | KMemoryPermission_KernelReadWrite), + KMemoryAttribute_AnyLocked | KMemoryAttribute_Locked); } Result KPageTableBase::UnlockForIpcUserBuffer(KProcessAddress address, size_t size) { @@ -1681,6 +1681,42 @@ namespace ams::kern { KMemoryAttribute_AnyLocked | KMemoryAttribute_Locked, nullptr); } + Result KPageTableBase::LockForTransferMemory(KPageGroup *out, KProcessAddress address, size_t size, KMemoryPermission perm) { + return this->LockMemoryAndOpen(out, nullptr, address, size, + KMemoryState_FlagCanTransfer, KMemoryState_FlagCanTransfer, + KMemoryPermission_All, KMemoryPermission_UserReadWrite, + KMemoryAttribute_All, KMemoryAttribute_None, + perm, + KMemoryAttribute_AnyLocked | KMemoryAttribute_Locked); + } + + Result KPageTableBase::UnlockForTransferMemory(KProcessAddress address, size_t size, const KPageGroup &pg) { + return this->UnlockMemory(address, size, + KMemoryState_FlagCanTransfer, KMemoryState_FlagCanTransfer, + KMemoryPermission_None, KMemoryPermission_None, + KMemoryAttribute_All, KMemoryAttribute_AnyLocked | KMemoryAttribute_Locked, + KMemoryPermission_UserReadWrite, + KMemoryAttribute_AnyLocked | KMemoryAttribute_Locked, std::addressof(pg)); + } + + Result KPageTableBase::LockForCodeMemory(KPageGroup *out, KProcessAddress address, size_t size, KMemoryPermission perm) { + return this->LockMemoryAndOpen(out, nullptr, address, size, + KMemoryState_FlagCanCodeMemory, KMemoryState_FlagCanCodeMemory, + KMemoryPermission_All, KMemoryPermission_UserReadWrite, + KMemoryAttribute_All, KMemoryAttribute_None, + static_cast(KMemoryPermission_NotMapped | KMemoryPermission_KernelReadWrite), + KMemoryAttribute_AnyLocked | KMemoryAttribute_Locked); + } + + Result KPageTableBase::UnlockForCodeMemory(KProcessAddress address, size_t size, const KPageGroup &pg) { + return this->UnlockMemory(address, size, + KMemoryState_FlagCanCodeMemory, KMemoryState_FlagCanCodeMemory, + KMemoryPermission_None, KMemoryPermission_None, + KMemoryAttribute_All, KMemoryAttribute_AnyLocked | KMemoryAttribute_Locked, + KMemoryPermission_UserReadWrite, + KMemoryAttribute_AnyLocked | KMemoryAttribute_Locked, std::addressof(pg)); + } + Result KPageTableBase::CopyMemoryFromLinearToUser(KProcessAddress dst_addr, size_t size, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr) { /* Lightly validate the range before doing anything else. */ R_UNLESS(this->Contains(src_addr, size), svc::ResultInvalidCurrentMemory()); diff --git a/libraries/libmesosphere/source/kern_k_transfer_memory.cpp b/libraries/libmesosphere/source/kern_k_transfer_memory.cpp new file mode 100644 index 000000000..da9e4086e --- /dev/null +++ b/libraries/libmesosphere/source/kern_k_transfer_memory.cpp @@ -0,0 +1,116 @@ +/* + * 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 KTransferMemory::Initialize(KProcessAddress addr, size_t size, ams::svc::MemoryPermission own_perm) { + MESOSPHERE_ASSERT_THIS(); + + /* Set members. */ + this->owner = GetCurrentProcessPointer(); + + /* Initialize the page group. */ + auto &page_table = this->owner->GetPageTable(); + new (GetPointer(this->page_group)) KPageGroup(page_table.GetBlockInfoManager()); + + /* Ensure that our page group's state is valid on exit. */ + auto pg_guard = SCOPE_GUARD { GetReference(this->page_group).~KPageGroup(); }; + + /* Lock the memory. */ + R_TRY(page_table.LockForTransferMemory(GetPointer(this->page_group), addr, size, ConvertToKMemoryPermission(own_perm))); + + /* Set remaining tracking members. */ + this->owner->Open(); + this->owner_perm = own_perm; + this->address = addr; + this->is_initialized = true; + this->is_mapped = false; + + /* We succeeded. */ + pg_guard.Cancel(); + return ResultSuccess(); + } + + void KTransferMemory::Finalize() { + MESOSPHERE_ASSERT_THIS(); + + /* Unmap. */ + if (!this->is_mapped) { + const size_t size = GetReference(this->page_group).GetNumPages() * PageSize; + MESOSPHERE_R_ABORT_UNLESS(this->owner->GetPageTable().UnlockForTransferMemory(this->address, size, GetReference(this->page_group))); + } + + /* Close the page group. */ + GetReference(this->page_group).Close(); + GetReference(this->page_group).Finalize(); + + /* Perform inherited finalization. */ + KAutoObjectWithSlabHeapAndContainer::Finalize(); + } + + void KTransferMemory::PostDestroy(uintptr_t arg) { + KProcess *owner = reinterpret_cast(arg); + owner->ReleaseResource(ams::svc::LimitableResource_TransferMemoryCountMax, 1); + owner->Close(); + } + + Result KTransferMemory::Map(KProcessAddress address, size_t size, ams::svc::MemoryPermission map_perm) { + MESOSPHERE_ASSERT_THIS(); + + /* Validate the size. */ + R_UNLESS(GetReference(this->page_group).GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize()); + + /* Validate the permission. */ + R_UNLESS(this->owner_perm == map_perm, svc::ResultInvalidState()); + + /* Lock ourselves. */ + KScopedLightLock lk(this->lock); + + /* Ensure we're not already mapped. */ + R_UNLESS(!this->is_mapped, svc::ResultInvalidState()); + + /* Map the memory. */ + const KMemoryState state = (this->owner_perm == ams::svc::MemoryPermission_None) ? KMemoryState_Transfered : KMemoryState_SharedTransfered; + R_TRY(GetCurrentProcess().GetPageTable().MapPageGroup(address, GetReference(this->page_group), state, KMemoryPermission_UserReadWrite)); + + /* Mark ourselves as mapped. */ + this->is_mapped = true; + + return ResultSuccess(); + } + + Result KTransferMemory::Unmap(KProcessAddress address, size_t size) { + MESOSPHERE_ASSERT_THIS(); + + /* Validate the size. */ + R_UNLESS(GetReference(this->page_group).GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize()); + + /* Lock ourselves. */ + KScopedLightLock lk(this->lock); + + /* Unmap the memory. */ + const KMemoryState state = (this->owner_perm == ams::svc::MemoryPermission_None) ? KMemoryState_Transfered : KMemoryState_SharedTransfered; + R_TRY(GetCurrentProcess().GetPageTable().UnmapPageGroup(address, GetReference(this->page_group), state)); + + /* Mark ourselves as unmapped. */ + MESOSPHERE_ASSERT(this->is_mapped); + this->is_mapped = false; + + return ResultSuccess(); + } + +} diff --git a/libraries/libmesosphere/source/svc/kern_svc_shared_memory.cpp b/libraries/libmesosphere/source/svc/kern_svc_shared_memory.cpp index 8cfd1e014..91326a975 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_shared_memory.cpp +++ b/libraries/libmesosphere/source/svc/kern_svc_shared_memory.cpp @@ -88,7 +88,7 @@ namespace ams::kern::svc { /* Verify that the mapping is in range. */ R_UNLESS(page_table.CanContain(address, size, KMemoryState_Shared), svc::ResultInvalidMemoryRegion()); - /* Map the shared memory. */ + /* Unmap the shared memory. */ R_TRY(shmem->Unmap(std::addressof(page_table), address, size, std::addressof(process))); /* Remove the shared memory from the process. */ diff --git a/libraries/libmesosphere/source/svc/kern_svc_transfer_memory.cpp b/libraries/libmesosphere/source/svc/kern_svc_transfer_memory.cpp index 922c06ba7..ce435d44e 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_transfer_memory.cpp +++ b/libraries/libmesosphere/source/svc/kern_svc_transfer_memory.cpp @@ -21,36 +21,132 @@ namespace ams::kern::svc { namespace { + constexpr bool IsValidTransferMemoryPermission(ams::svc::MemoryPermission perm) { + switch (perm) { + case ams::svc::MemoryPermission_None: + case ams::svc::MemoryPermission_Read: + case ams::svc::MemoryPermission_ReadWrite: + return true; + default: + return false; + } + } + Result MapTransferMemory(ams::svc::Handle trmem_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(IsValidTransferMemoryPermission(map_perm), svc::ResultInvalidState()); + + /* Get the transfer memory. */ + KScopedAutoObject trmem = GetCurrentProcess().GetHandleTable().GetObject(trmem_handle); + R_UNLESS(trmem.IsNotNull(), svc::ResultInvalidHandle()); + + /* Verify that the mapping is in range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().CanContain(address, size, KMemoryState_Transfered), svc::ResultInvalidMemoryRegion()); + + /* Map the transfer memory. */ + R_TRY(trmem->Map(address, size, map_perm)); + + /* We succeeded. */ + return ResultSuccess(); + } + + Result UnmapTransferMemory(ams::svc::Handle trmem_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 transfer memory. */ + KScopedAutoObject trmem = GetCurrentProcess().GetHandleTable().GetObject(trmem_handle); + R_UNLESS(trmem.IsNotNull(), svc::ResultInvalidHandle()); + + /* Verify that the mapping is in range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().CanContain(address, size, KMemoryState_Transfered), svc::ResultInvalidMemoryRegion()); + + /* Unmap the transfer memory. */ + R_TRY(trmem->Unmap(address, size)); + + return ResultSuccess(); + } + + Result CreateTransferMemory(ams::svc::Handle *out, uintptr_t address, size_t size, ams::svc::MemoryPermission map_perm) { + /* Validate the 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 permissions. */ + R_UNLESS(IsValidTransferMemoryPermission(map_perm), svc::ResultInvalidNewMemoryPermission()); + + /* Get the current process and handle table. */ + auto &process = GetCurrentProcess(); + auto &handle_table = process.GetHandleTable(); + + /* Reserve a new transfer memory from the process resource limit. */ + KScopedResourceReservation trmem_reservation(std::addressof(process), ams::svc::LimitableResource_TransferMemoryCountMax); + R_UNLESS(trmem_reservation.Succeeded(), svc::ResultLimitReached()); + + /* Create the transfer memory. */ + KTransferMemory *trmem = KTransferMemory::Create(); + R_UNLESS(trmem != nullptr, svc::ResultOutOfResource()); + + /* Ensure the only reference is in the handle table when we're done. */ + ON_SCOPE_EXIT { trmem->Close(); }; + + /* Ensure that the region is in range. */ + R_UNLESS(process.GetPageTable().Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Initialize the transfer memory. */ + R_TRY(trmem->Initialize(address, size, map_perm)); + + /* Commit the reservation. */ + trmem_reservation.Commit(); + + /* Register the transfer memory. */ + R_TRY(KTransferMemory::Register(trmem)); + + /* Add the transfer memory to the handle table. */ + R_TRY(handle_table.Add(out, trmem)); + + return ResultSuccess(); + } } /* ============================= 64 ABI ============================= */ - Result MapTransferMemory64From32(ams::svc::Handle trmem_handle, ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission owner_perm) { - MESOSPHERE_PANIC("Stubbed SvcMapTransferMemory64From32 was called."); + Result MapTransferMemory64(ams::svc::Handle trmem_handle, ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission owner_perm) { + return MapTransferMemory(trmem_handle, address, size, owner_perm); } - Result UnmapTransferMemory64From32(ams::svc::Handle trmem_handle, ams::svc::Address address, ams::svc::Size size) { - MESOSPHERE_PANIC("Stubbed SvcUnmapTransferMemory64From32 was called."); + Result UnmapTransferMemory64(ams::svc::Handle trmem_handle, ams::svc::Address address, ams::svc::Size size) { + return UnmapTransferMemory(trmem_handle, address, size); } Result CreateTransferMemory64(ams::svc::Handle *out_handle, ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission map_perm) { - MESOSPHERE_PANIC("Stubbed SvcCreateTransferMemory64 was called."); + return CreateTransferMemory(out_handle, address, size, map_perm); } /* ============================= 64From32 ABI ============================= */ - Result MapTransferMemory64(ams::svc::Handle trmem_handle, ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission owner_perm) { - MESOSPHERE_PANIC("Stubbed SvcMapTransferMemory64 was called."); + Result MapTransferMemory64From32(ams::svc::Handle trmem_handle, ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission owner_perm) { + return MapTransferMemory(trmem_handle, address, size, owner_perm); } - Result UnmapTransferMemory64(ams::svc::Handle trmem_handle, ams::svc::Address address, ams::svc::Size size) { - MESOSPHERE_PANIC("Stubbed SvcUnmapTransferMemory64 was called."); + Result UnmapTransferMemory64From32(ams::svc::Handle trmem_handle, ams::svc::Address address, ams::svc::Size size) { + return UnmapTransferMemory(trmem_handle, address, size); } Result CreateTransferMemory64From32(ams::svc::Handle *out_handle, ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission map_perm) { - MESOSPHERE_PANIC("Stubbed SvcCreateTransferMemory64From32 was called."); + return CreateTransferMemory(out_handle, address, size, map_perm); } }