// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include "common/alignment.h" #include "common/assert.h" #include "common/common_types.h" #include "common/scope_exit.h" #include "core/hle/kernel/k_memory_manager.h" #include "core/hle/kernel/k_page_linked_list.h" #include "core/hle/kernel/svc_results.h" namespace Kernel { std::size_t KMemoryManager::Impl::Initialize(Pool new_pool, u64 start_address, u64 end_address) { const auto size{end_address - start_address}; // Calculate metadata sizes const auto ref_count_size{(size / PageSize) * sizeof(u16)}; const auto optimize_map_size{(Common::AlignUp((size / PageSize), 64) / 64) * sizeof(u64)}; const auto manager_size{Common::AlignUp(optimize_map_size + ref_count_size, PageSize)}; const auto page_heap_size{KPageHeap::CalculateManagementOverheadSize(size)}; const auto total_metadata_size{manager_size + page_heap_size}; ASSERT(manager_size <= total_metadata_size); ASSERT(Common::IsAligned(total_metadata_size, PageSize)); // Setup region pool = new_pool; // Initialize the manager's KPageHeap heap.Initialize(start_address, size, page_heap_size); // Free the memory to the heap heap.Free(start_address, size / PageSize); // Update the heap's used size heap.UpdateUsedSize(); return total_metadata_size; } void KMemoryManager::InitializeManager(Pool pool, u64 start_address, u64 end_address) { ASSERT(pool < Pool::Count); managers[static_cast(pool)].Initialize(pool, start_address, end_address); } VAddr KMemoryManager::AllocateAndOpenContinuous(std::size_t num_pages, std::size_t align_pages, u32 option) { // Early return if we're allocating no pages if (num_pages == 0) { return {}; } // Lock the pool that we're allocating from const auto [pool, dir] = DecodeOption(option); const auto pool_index{static_cast(pool)}; std::lock_guard lock{pool_locks[pool_index]}; // Choose a heap based on our page size request const s32 heap_index{KPageHeap::GetAlignedBlockIndex(num_pages, align_pages)}; // Loop, trying to iterate from each block // TODO (bunnei): Support multiple managers Impl& chosen_manager{managers[pool_index]}; VAddr allocated_block{chosen_manager.AllocateBlock(heap_index, false)}; // If we failed to allocate, quit now if (!allocated_block) { return {}; } // If we allocated more than we need, free some const auto allocated_pages{KPageHeap::GetBlockNumPages(heap_index)}; if (allocated_pages > num_pages) { chosen_manager.Free(allocated_block + num_pages * PageSize, allocated_pages - num_pages); } return allocated_block; } ResultCode KMemoryManager::Allocate(KPageLinkedList& page_list, std::size_t num_pages, Pool pool, Direction dir) { ASSERT(page_list.GetNumPages() == 0); // Early return if we're allocating no pages if (num_pages == 0) { return ResultSuccess; } // Lock the pool that we're allocating from const auto pool_index{static_cast(pool)}; std::lock_guard lock{pool_locks[pool_index]}; // Choose a heap based on our page size request const s32 heap_index{KPageHeap::GetBlockIndex(num_pages)}; if (heap_index < 0) { return ResultOutOfMemory; } // TODO (bunnei): Support multiple managers Impl& chosen_manager{managers[pool_index]}; // Ensure that we don't leave anything un-freed auto group_guard = detail::ScopeExit([&] { for (const auto& it : page_list.Nodes()) { const auto min_num_pages{std::min( it.GetNumPages(), (chosen_manager.GetEndAddress() - it.GetAddress()) / PageSize)}; chosen_manager.Free(it.GetAddress(), min_num_pages); } }); // Keep allocating until we've allocated all our pages for (s32 index{heap_index}; index >= 0 && num_pages > 0; index--) { const auto pages_per_alloc{KPageHeap::GetBlockNumPages(index)}; while (num_pages >= pages_per_alloc) { // Allocate a block VAddr allocated_block{chosen_manager.AllocateBlock(index, false)}; if (!allocated_block) { break; } // Safely add it to our group { auto block_guard = detail::ScopeExit( [&] { chosen_manager.Free(allocated_block, pages_per_alloc); }); if (const ResultCode result{page_list.AddBlock(allocated_block, pages_per_alloc)}; result.IsError()) { return result; } block_guard.Cancel(); } num_pages -= pages_per_alloc; } } // Only succeed if we allocated as many pages as we wanted if (num_pages) { return ResultOutOfMemory; } // We succeeded! group_guard.Cancel(); return ResultSuccess; } ResultCode KMemoryManager::Free(KPageLinkedList& page_list, std::size_t num_pages, Pool pool, Direction dir) { // Early return if we're freeing no pages if (!num_pages) { return ResultSuccess; } // Lock the pool that we're freeing from const auto pool_index{static_cast(pool)}; std::lock_guard lock{pool_locks[pool_index]}; // TODO (bunnei): Support multiple managers Impl& chosen_manager{managers[pool_index]}; // Free all of the pages for (const auto& it : page_list.Nodes()) { const auto min_num_pages{std::min( it.GetNumPages(), (chosen_manager.GetEndAddress() - it.GetAddress()) / PageSize)}; chosen_manager.Free(it.GetAddress(), min_num_pages); } return ResultSuccess; } std::size_t KMemoryManager::Impl::CalculateManagementOverheadSize(std::size_t region_size) { const std::size_t ref_count_size = (region_size / PageSize) * sizeof(u16); const std::size_t optimize_map_size = (Common::AlignUp((region_size / PageSize), Common::BitSize()) / Common::BitSize()) * sizeof(u64); const std::size_t manager_meta_size = Common::AlignUp(optimize_map_size + ref_count_size, PageSize); const std::size_t page_heap_size = KPageHeap::CalculateManagementOverheadSize(region_size); return manager_meta_size + page_heap_size; } } // namespace Kernel