From 01a7606f95f8009886c0e9905cff3f7254a0156f Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Wed, 15 Jul 2020 03:07:00 -0700 Subject: [PATCH] kern: implement SvcSetHeapSize --- ...kern_k_memory_layout.board.nintendo_nx.hpp | 23 ++++ .../mesosphere/kern_k_memory_layout.hpp | 6 + .../source/kern_k_page_table_base.cpp | 114 +++++++++++++++++- .../source/svc/kern_svc_physical_memory.cpp | 20 ++- .../include/vapours/svc/svc_types_common.hpp | 2 + 5 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_memory_layout.board.nintendo_nx.hpp diff --git a/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_memory_layout.board.nintendo_nx.hpp b/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_memory_layout.board.nintendo_nx.hpp new file mode 100644 index 000000000..beb1775f9 --- /dev/null +++ b/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_memory_layout.board.nintendo_nx.hpp @@ -0,0 +1,23 @@ +/* + * 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 + +namespace ams::kern { + + constexpr inline size_t MainMemorySize = 4_GB; + +} diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_memory_layout.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_memory_layout.hpp index 690679a3b..d70485b62 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_memory_layout.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_memory_layout.hpp @@ -17,6 +17,12 @@ #include #include +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + #include +#else + #error "Unknown board for KMemoryLayout" +#endif + namespace ams::kern { constexpr size_t KernelAslrAlignment = 2_MB; diff --git a/libraries/libmesosphere/source/kern_k_page_table_base.cpp b/libraries/libmesosphere/source/kern_k_page_table_base.cpp index 82235b7fe..13fb231d8 100644 --- a/libraries/libmesosphere/source/kern_k_page_table_base.cpp +++ b/libraries/libmesosphere/source/kern_k_page_table_base.cpp @@ -1077,7 +1077,119 @@ namespace ams::kern { } Result KPageTableBase::SetHeapSize(KProcessAddress *out, size_t size) { - MESOSPHERE_UNIMPLEMENTED(); + /* Lock the physical memory mutex. */ + KScopedLightLock map_phys_mem_lk(this->map_physical_memory_lock); + + /* Try to perform a reduction in heap, instead of an extension. */ + KProcessAddress cur_address; + size_t allocation_size; + { + /* Lock the table. */ + KScopedLightLock lk(this->general_lock); + + /* Validate that setting heap size is possible at all. */ + R_UNLESS(!this->is_kernel, svc::ResultOutOfMemory()); + R_UNLESS(size <= static_cast(this->heap_region_end - this->heap_region_start), svc::ResultOutOfMemory()); + R_UNLESS(size <= this->max_heap_size, svc::ResultOutOfMemory()); + + if (size < static_cast(this->current_heap_end - this->heap_region_start)) { + /* The size being requested is less than the current size, so we need to free the end of the heap. */ + + /* Create an update allocator. */ + KMemoryBlockManagerUpdateAllocator allocator(this->memory_block_slab_manager); + R_TRY(allocator.GetResult()); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Validate memory state. */ + R_TRY(this->CheckMemoryState(this->heap_region_start + size, (this->heap_region_end - this->heap_region_start) - size, + KMemoryState_All, KMemoryState_Normal, + KMemoryPermission_All, KMemoryPermission_UserReadWrite, + KMemoryAttribute_All, KMemoryAttribute_None)); + + /* Unmap the end of the heap. */ + const size_t num_pages = ((this->current_heap_end - this->heap_region_start) - size) / PageSize; + const KPageProperties unmap_properties = { KMemoryPermission_None, false, false, false }; + R_TRY(this->Operate(updater.GetPageList(), this->heap_region_start + size, num_pages, Null, false, unmap_properties, OperationType_Unmap, false)); + + /* Release the memory from the resource limit. */ + GetCurrentProcess().ReleaseResource(ams::svc::LimitableResource_PhysicalMemoryMax, num_pages * PageSize); + + /* Apply the memory block update. */ + this->memory_block_manager.Update(std::addressof(allocator), this->heap_region_start + size, num_pages, KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None); + + /* Update the current heap end. */ + this->current_heap_end = this->heap_region_start + size; + + /* Set the output. */ + *out = this->heap_region_start; + return ResultSuccess(); + } else if (size == static_cast(this->current_heap_end - this->heap_region_start)) { + /* The size requested is exactly the current size. */ + *out = this->heap_region_start; + return ResultSuccess(); + } else { + /* We have to allocate memory. Determine how much to allocate and where while the table is locked. */ + cur_address = this->current_heap_end; + allocation_size = size - (this->current_heap_end - this->heap_region_start); + } + } + + /* Reserve memory for the heap extension. */ + KScopedResourceReservation memory_reservation(GetCurrentProcess().GetResourceLimit(), ams::svc::LimitableResource_PhysicalMemoryMax, allocation_size); + R_UNLESS(memory_reservation.Succeeded(), svc::ResultLimitReached()); + + /* Allocate pages for the heap extension. */ + KPageGroup pg(this->block_info_manager); + R_TRY(Kernel::GetMemoryManager().Allocate(std::addressof(pg), allocation_size / PageSize, this->allocate_option)); + + /* Open the pages in the group for the duration of the call, and close them at the end. */ + /* If the mapping succeeds, each page will gain an extra reference, otherwise they will be freed automatically. */ + pg.Open(); + ON_SCOPE_EXIT { pg.Close(); }; + + /* Clear all the newly allocated pages. */ + for (const auto &it : pg) { + std::memset(GetVoidPointer(it.GetAddress()), this->heap_fill_value, it.GetSize()); + } + + /* Map the pages. */ + { + /* Lock the table. */ + KScopedLightLock lk(this->general_lock); + + /* Create an update allocator. */ + KMemoryBlockManagerUpdateAllocator allocator(this->memory_block_slab_manager); + R_TRY(allocator.GetResult()); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Ensure that the heap hasn't changed since we began executing. */ + MESOSPHERE_ABORT_UNLESS(cur_address == this->current_heap_end); + + /* Check the memory state. */ + R_TRY(this->CheckMemoryState(this->current_heap_end, allocation_size, KMemoryState_All, KMemoryState_Free, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Map the pages. */ + const size_t num_pages = allocation_size / PageSize; + const KPageProperties map_properties = { KMemoryPermission_UserReadWrite, false, false, false }; + R_TRY(this->Operate(updater.GetPageList(), this->current_heap_end, num_pages, pg, map_properties, OperationType_MapGroup, false)); + + /* We succeeded, so commit our memory reservation. */ + memory_reservation.Commit(); + + /* Apply the memory block update. */ + this->memory_block_manager.Update(std::addressof(allocator), this->current_heap_end, num_pages, KMemoryState_Normal, KMemoryPermission_UserReadWrite, KMemoryAttribute_None); + + /* Update the current heap end. */ + this->current_heap_end = this->heap_region_start + size; + + /* Set the output. */ + *out = this->heap_region_start; + return ResultSuccess(); + } } Result KPageTableBase::SetMaxHeapSize(size_t size) { diff --git a/libraries/libmesosphere/source/svc/kern_svc_physical_memory.cpp b/libraries/libmesosphere/source/svc/kern_svc_physical_memory.cpp index c7cb9a7c6..07e1f136e 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_physical_memory.cpp +++ b/libraries/libmesosphere/source/svc/kern_svc_physical_memory.cpp @@ -21,6 +21,20 @@ namespace ams::kern::svc { namespace { + Result SetHeapSize(uintptr_t *out_address, size_t size) { + /* Validate size. */ + R_UNLESS(util::IsAligned(size, ams::svc::HeapSizeAlignment), svc::ResultInvalidSize()); + R_UNLESS(size < ams::kern::MainMemorySize, svc::ResultInvalidSize()); + + /* Set the heap size. */ + KProcessAddress address; + R_TRY(GetCurrentProcess().GetPageTable().SetHeapSize(std::addressof(address), size)); + + /* Set the output. */ + *out_address = GetInteger(address); + return ResultSuccess(); + } + Result SetUnsafeLimit(size_t limit) { /* Ensure the size is aligned. */ R_UNLESS(util::IsAligned(limit, PageSize), svc::ResultInvalidSize()); @@ -37,7 +51,8 @@ namespace ams::kern::svc { /* ============================= 64 ABI ============================= */ Result SetHeapSize64(ams::svc::Address *out_address, ams::svc::Size size) { - MESOSPHERE_PANIC("Stubbed SvcSetHeapSize64 was called."); + static_assert(sizeof(*out_address) == sizeof(uintptr_t)); + return SetHeapSize(reinterpret_cast(out_address), size); } Result MapPhysicalMemory64(ams::svc::Address address, ams::svc::Size size) { @@ -63,7 +78,8 @@ namespace ams::kern::svc { /* ============================= 64From32 ABI ============================= */ Result SetHeapSize64From32(ams::svc::Address *out_address, ams::svc::Size size) { - MESOSPHERE_PANIC("Stubbed SvcSetHeapSize64From32 was called."); + static_assert(sizeof(*out_address) == sizeof(uintptr_t)); + return SetHeapSize(reinterpret_cast(out_address), size); } Result MapPhysicalMemory64From32(ams::svc::Address address, ams::svc::Size size) { diff --git a/libraries/libvapours/include/vapours/svc/svc_types_common.hpp b/libraries/libvapours/include/vapours/svc/svc_types_common.hpp index 48e7941de..bf4530a64 100644 --- a/libraries/libvapours/include/vapours/svc/svc_types_common.hpp +++ b/libraries/libvapours/include/vapours/svc/svc_types_common.hpp @@ -116,6 +116,8 @@ namespace ams::svc { MemoryAttribute_Uncached = (1 << 3), }; + constexpr inline size_t HeapSizeAlignment = 2_MB; + struct PageInfo { u32 flags; };