From cdeeecf0807d0005356f30db0f7164c5891a9245 Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Fri, 17 Jul 2015 23:19:16 -0300 Subject: Kernel: Properly implement ControlMemory FREE and COMMIT --- src/core/hle/kernel/process.cpp | 120 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 5 deletions(-) (limited to 'src/core/hle/kernel/process.cpp') diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index ad953cdbf..1db763999 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -36,8 +36,7 @@ SharedPtr Process::Create(SharedPtr code_set) { process->codeset = std::move(code_set); process->flags.raw = 0; process->flags.memory_region = MemoryRegion::APPLICATION; - process->address_space = Common::make_unique(); - Memory::InitLegacyAddressSpace(*process->address_space); + Memory::InitLegacyAddressSpace(process->vm_manager); return process; } @@ -104,19 +103,130 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) { void Process::Run(s32 main_thread_priority, u32 stack_size) { auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions, MemoryState memory_state) { - auto vma = address_space->MapMemoryBlock(segment.addr, codeset->memory, + auto vma = vm_manager.MapMemoryBlock(segment.addr, codeset->memory, segment.offset, segment.size, memory_state).Unwrap(); - address_space->Reprotect(vma, permissions); + vm_manager.Reprotect(vma, permissions); }; + // Map CodeSet segments MapSegment(codeset->code, VMAPermission::ReadExecute, MemoryState::Code); MapSegment(codeset->rodata, VMAPermission::Read, MemoryState::Code); MapSegment(codeset->data, VMAPermission::ReadWrite, MemoryState::Private); - address_space->LogLayout(Log::Level::Debug); + // Allocate and map stack + vm_manager.MapMemoryBlock(Memory::HEAP_VADDR_END - stack_size, + std::make_shared>(stack_size, 0), 0, stack_size, MemoryState::Locked + ).Unwrap(); + + vm_manager.LogLayout(Log::Level::Debug); Kernel::SetupMainThread(codeset->entrypoint, main_thread_priority); } +ResultVal Process::HeapAllocate(VAddr target, u32 size, VMAPermission perms) { + if (target < Memory::HEAP_VADDR || target + size > Memory::HEAP_VADDR_END || target + size < target) { + return ERR_INVALID_ADDRESS; + } + + if (heap_memory == nullptr) { + // Initialize heap + heap_memory = std::make_shared>(); + heap_start = heap_end = target; + } + + // If necessary, expand backing vector to cover new heap extents. + if (target < heap_start) { + heap_memory->insert(begin(*heap_memory), heap_start - target, 0); + heap_start = target; + vm_manager.RefreshMemoryBlockMappings(heap_memory.get()); + } + if (target + size > heap_end) { + heap_memory->insert(end(*heap_memory), (target + size) - heap_end, 0); + heap_end = target + size; + vm_manager.RefreshMemoryBlockMappings(heap_memory.get()); + } + ASSERT(heap_end - heap_start == heap_memory->size()); + + CASCADE_RESULT(auto vma, vm_manager.MapMemoryBlock(target, heap_memory, target - heap_start, size, MemoryState::Private)); + vm_manager.Reprotect(vma, perms); + + return MakeResult(heap_end - size); +} + +ResultCode Process::HeapFree(VAddr target, u32 size) { + if (target < Memory::HEAP_VADDR || target + size > Memory::HEAP_VADDR_END || target + size < target) { + return ERR_INVALID_ADDRESS; + } + + ResultCode result = vm_manager.UnmapRange(target, size); + if (result.IsError()) return result; + + return RESULT_SUCCESS; +} + +ResultVal Process::LinearAllocate(VAddr target, u32 size, VMAPermission perms) { + if (linear_heap_memory == nullptr) { + // Initialize heap + linear_heap_memory = std::make_shared>(); + } + + VAddr heap_end = Memory::LINEAR_HEAP_VADDR + (u32)linear_heap_memory->size(); + // Games and homebrew only ever seem to pass 0 here (which lets the kernel decide the address), + // but explicit addresses are also accepted and respected. + if (target == 0) { + target = heap_end; + } + + if (target < Memory::LINEAR_HEAP_VADDR || target + size > Memory::LINEAR_HEAP_VADDR_END || + target > heap_end || target + size < target) { + + return ERR_INVALID_ADDRESS; + } + + // Expansion of the linear heap is only allowed if you do an allocation immediatelly at its + // end. It's possible to free gaps in the middle of the heap and then reallocate them later, + // but expansions are only allowed at the end. + if (target == heap_end) { + linear_heap_memory->insert(linear_heap_memory->end(), size, 0); + vm_manager.RefreshMemoryBlockMappings(linear_heap_memory.get()); + } + + size_t offset = target - Memory::LINEAR_HEAP_VADDR; + CASCADE_RESULT(auto vma, vm_manager.MapMemoryBlock(target, linear_heap_memory, offset, size, MemoryState::Continuous)); + vm_manager.Reprotect(vma, perms); + + return MakeResult(target); +} + +ResultCode Process::LinearFree(VAddr target, u32 size) { + if (linear_heap_memory == nullptr || target < Memory::LINEAR_HEAP_VADDR || + target + size > Memory::LINEAR_HEAP_VADDR_END || target + size < target) { + + return ERR_INVALID_ADDRESS; + } + + VAddr heap_end = Memory::LINEAR_HEAP_VADDR + (u32)linear_heap_memory->size(); + if (target + size > heap_end) { + return ERR_INVALID_ADDRESS_STATE; + } + + ResultCode result = vm_manager.UnmapRange(target, size); + if (result.IsError()) return result; + + if (target + size == heap_end) { + // End of linear heap has been freed, so check what's the last allocated block in it and + // reduce the size. + auto vma = vm_manager.FindVMA(target); + ASSERT(vma != vm_manager.vma_map.end()); + ASSERT(vma->second.type == VMAType::Free); + VAddr new_end = vma->second.base; + if (new_end >= Memory::LINEAR_HEAP_VADDR) { + linear_heap_memory->resize(new_end - Memory::LINEAR_HEAP_VADDR); + } + } + + return RESULT_SUCCESS; +} + Kernel::Process::Process() {} Kernel::Process::~Process() {} -- cgit v1.2.3