diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index ad953cdbf7..1db7639998 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() {} diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 83d3aceae5..567d5df18d 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -15,6 +15,7 @@ #include "common/common_types.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/vm_manager.h" namespace Kernel { @@ -48,7 +49,6 @@ union ProcessFlags { }; class ResourceLimit; -class VMManager; struct CodeSet final : public Object { static SharedPtr Create(std::string name, u64 program_id); @@ -108,10 +108,6 @@ public: /// The id of this process u32 process_id = next_process_id++; - /// Bitmask of the used TLS slots - std::bitset<300> used_tls_slots; - std::unique_ptr address_space; - /** * Parses a list of kernel capability descriptors (as found in the ExHeader) and applies them * to this process. @@ -123,6 +119,31 @@ public: */ void Run(s32 main_thread_priority, u32 stack_size); + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Memory Management + + VMManager vm_manager; + + // Memory used to back the allocations in the regular heap. A single vector is used to cover + // the entire virtual address space extents that bound the allocations, including any holes. + // This makes deallocation and reallocation of holes fast and keeps process memory contiguous + // in the emulator address space, allowing Memory::GetPointer to be reasonably safe. + std::shared_ptr> heap_memory; + // The left/right bounds of the address space covered by heap_memory. + VAddr heap_start = 0, heap_end = 0; + + std::shared_ptr> linear_heap_memory; + + /// Bitmask of the used TLS slots + std::bitset<300> used_tls_slots; + + ResultVal HeapAllocate(VAddr target, u32 size, VMAPermission perms); + ResultCode HeapFree(VAddr target, u32 size); + + ResultVal LinearAllocate(VAddr target, u32 size, VMAPermission perms); + ResultCode LinearFree(VAddr target, u32 size); + private: Process(); ~Process() override; diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 65395476b1..2610acf76a 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -60,7 +60,11 @@ void VMManager::Reset() { } VMManager::VMAHandle VMManager::FindVMA(VAddr target) const { - return std::prev(vma_map.upper_bound(target)); + if (target >= MAX_ADDRESS) { + return vma_map.end(); + } else { + return std::prev(vma_map.upper_bound(target)); + } } ResultVal VMManager::MapMemoryBlock(VAddr target, @@ -115,10 +119,8 @@ ResultVal VMManager::MapMMIO(VAddr target, PAddr paddr, u3 return MakeResult(MergeAdjacent(vma_handle)); } -void VMManager::Unmap(VMAHandle vma_handle) { - VMAIter iter = StripIterConstness(vma_handle); - - VirtualMemoryArea& vma = iter->second; +VMManager::VMAIter VMManager::Unmap(VMAIter vma_handle) { + VirtualMemoryArea& vma = vma_handle->second; vma.type = VMAType::Free; vma.permissions = VMAPermission::None; vma.meminfo_state = MemoryState::Free; @@ -130,17 +132,57 @@ void VMManager::Unmap(VMAHandle vma_handle) { UpdatePageTableForVMA(vma); - MergeAdjacent(iter); + return MergeAdjacent(vma_handle); } -void VMManager::Reprotect(VMAHandle vma_handle, VMAPermission new_perms) { +ResultCode VMManager::UnmapRange(VAddr target, u32 size) { + CASCADE_RESULT(VMAIter vma, CarveVMARange(target, size)); + VAddr target_end = target + size; + + VMAIter end = vma_map.end(); + // The comparison against the end of the range must be done using addresses since VMAs can be + // merged during this process, causing invalidation of the iterators. + while (vma != end && vma->second.base < target_end) { + vma = std::next(Unmap(vma)); + } + + ASSERT(FindVMA(target)->second.size >= size); + return RESULT_SUCCESS; +} + +VMManager::VMAHandle VMManager::Reprotect(VMAHandle vma_handle, VMAPermission new_perms) { VMAIter iter = StripIterConstness(vma_handle); VirtualMemoryArea& vma = iter->second; vma.permissions = new_perms; UpdatePageTableForVMA(vma); - MergeAdjacent(iter); + return MergeAdjacent(iter); +} + +ResultCode VMManager::ReprotectRange(VAddr target, u32 size, VMAPermission new_perms) { + CASCADE_RESULT(VMAIter vma, CarveVMARange(target, size)); + VAddr target_end = target + size; + + VMAIter end = vma_map.end(); + // The comparison against the end of the range must be done using addresses since VMAs can be + // merged during this process, causing invalidation of the iterators. + while (vma != end && vma->second.base < target_end) { + vma = std::next(StripIterConstness(Reprotect(vma, new_perms))); + } + + return RESULT_SUCCESS; +} + +void VMManager::RefreshMemoryBlockMappings(const std::vector* block) { + // If this ever proves to have a noticeable performance impact, allow users of the function to + // specify a specific range of addresses to limit the scan to. + for (const auto& p : vma_map) { + const VirtualMemoryArea& vma = p.second; + if (block == vma.backing_block.get()) { + UpdatePageTableForVMA(vma); + } + } } void VMManager::LogLayout(Log::Level log_level) const { @@ -161,8 +203,8 @@ VMManager::VMAIter VMManager::StripIterConstness(const VMAHandle & iter) { } ResultVal VMManager::CarveVMA(VAddr base, u32 size) { - ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: %8X", size); - ASSERT_MSG((base & Memory::PAGE_MASK) == 0, "non-page aligned base: %08X", base); + ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%8X", size); + ASSERT_MSG((base & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%08X", base); VMAIter vma_handle = StripIterConstness(FindVMA(base)); if (vma_handle == vma_map.end()) { @@ -196,6 +238,35 @@ ResultVal VMManager::CarveVMA(VAddr base, u32 size) { return MakeResult(vma_handle); } +ResultVal VMManager::CarveVMARange(VAddr target, u32 size) { + ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%8X", size); + ASSERT_MSG((target & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%08X", target); + + VAddr target_end = target + size; + ASSERT(target_end >= target); + ASSERT(target_end <= MAX_ADDRESS); + ASSERT(size > 0); + + VMAIter begin_vma = StripIterConstness(FindVMA(target)); + VMAIter i_end = vma_map.lower_bound(target_end); + for (auto i = begin_vma; i != i_end; ++i) { + if (i->second.type == VMAType::Free) { + return ERR_INVALID_ADDRESS_STATE; + } + } + + if (target != begin_vma->second.base) { + begin_vma = SplitVMA(begin_vma, target - begin_vma->second.base); + } + + VMAIter end_vma = StripIterConstness(FindVMA(target_end)); + if (end_vma != vma_map.end() && target_end != end_vma->second.base) { + end_vma = SplitVMA(end_vma, target_end - end_vma->second.base); + } + + return MakeResult(begin_vma); +} + VMManager::VMAIter VMManager::SplitVMA(VMAIter vma_handle, u32 offset_in_vma) { VirtualMemoryArea& old_vma = vma_handle->second; VirtualMemoryArea new_vma = old_vma; // Make a copy of the VMA diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index 15c10e4136..4e95f1f0c8 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -171,11 +171,20 @@ public: */ ResultVal MapMMIO(VAddr target, PAddr paddr, u32 size, MemoryState state); - /// Unmaps the given VMA. - void Unmap(VMAHandle vma); + /// Unmaps a range of addresses, splitting VMAs as necessary. + ResultCode UnmapRange(VAddr target, u32 size); /// Changes the permissions of the given VMA. - void Reprotect(VMAHandle vma, VMAPermission new_perms); + VMAHandle Reprotect(VMAHandle vma, VMAPermission new_perms); + + /// Changes the permissions of a range of addresses, splitting VMAs as necessary. + ResultCode ReprotectRange(VAddr target, u32 size, VMAPermission new_perms); + + /** + * Scans all VMAs and updates the page table range of any that use the given vector as backing + * memory. This should be called after any operation that causes reallocation of the vector. + */ + void RefreshMemoryBlockMappings(const std::vector* block); /// Dumps the address space layout to the log, for debugging void LogLayout(Log::Level log_level) const; @@ -186,12 +195,21 @@ private: /// Converts a VMAHandle to a mutable VMAIter. VMAIter StripIterConstness(const VMAHandle& iter); + /// Unmaps the given VMA. + VMAIter Unmap(VMAIter vma); + /** * Carves a VMA of a specific size at the specified address by splitting Free VMAs while doing * the appropriate error checking. */ ResultVal CarveVMA(VAddr base, u32 size); + /** + * Splits the edges of the given range of non-Free VMAs so that there is a VMA split at each + * end of the range. + */ + ResultVal CarveVMARange(VAddr base, u32 size); + /** * Splits a VMA in two, at the specified offset. * @returns the right side of the split, with the original iterator becoming the left side. diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index bb64fdfb76..91260fe714 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -41,32 +41,114 @@ const ResultCode ERR_NOT_FOUND(ErrorDescription::NotFound, ErrorModule::Kernel, const ResultCode ERR_PORT_NAME_TOO_LONG(ErrorDescription(30), ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E0181E +const ResultCode ERR_MISALIGNED_ADDRESS{ // 0xE0E01BF1 + ErrorDescription::MisalignedAddress, ErrorModule::OS, + ErrorSummary::InvalidArgument, ErrorLevel::Usage}; +const ResultCode ERR_MISALIGNED_SIZE{ // 0xE0E01BF2 + ErrorDescription::MisalignedSize, ErrorModule::OS, + ErrorSummary::InvalidArgument, ErrorLevel::Usage}; +const ResultCode ERR_INVALID_COMBINATION{ // 0xE0E01BEE + ErrorDescription::InvalidCombination, ErrorModule::OS, + ErrorSummary::InvalidArgument, ErrorLevel::Usage}; + enum ControlMemoryOperation { - MEMORY_OPERATION_HEAP = 0x00000003, - MEMORY_OPERATION_GSP_HEAP = 0x00010003, + MEMOP_FREE = 1, + MEMOP_RESERVE = 2, // This operation seems to be unsupported in the kernel + MEMOP_COMMIT = 3, + MEMOP_MAP = 4, + MEMOP_UNMAP = 5, + MEMOP_PROTECT = 6, + MEMOP_OPERATION_MASK = 0xFF, + + MEMOP_REGION_APP = 0x100, + MEMOP_REGION_SYSTEM = 0x200, + MEMOP_REGION_BASE = 0x300, + MEMOP_REGION_MASK = 0xF00, + + MEMOP_LINEAR = 0x10000, }; /// Map application or GSP heap memory static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 addr1, u32 size, u32 permissions) { - LOG_TRACE(Kernel_SVC,"called operation=0x%08X, addr0=0x%08X, addr1=0x%08X, size=%08X, permissions=0x%08X", + using namespace Kernel; + + LOG_DEBUG(Kernel_SVC,"called operation=0x%08X, addr0=0x%08X, addr1=0x%08X, size=0x%X, permissions=0x%08X", operation, addr0, addr1, size, permissions); - switch (operation) { + if ((addr0 & Memory::PAGE_MASK) != 0 || (addr1 & Memory::PAGE_MASK) != 0) { + return ERR_MISALIGNED_ADDRESS; + } + if ((size & Memory::PAGE_MASK) != 0) { + return ERR_MISALIGNED_SIZE; + } - // Map normal heap memory - case MEMORY_OPERATION_HEAP: - *out_addr = Memory::MapBlock_Heap(size, operation, permissions); + u32 region = operation & MEMOP_REGION_MASK; + operation &= ~MEMOP_REGION_MASK; + + if (region != 0) { + LOG_WARNING(Kernel_SVC, "ControlMemory with specified region not supported, region=%X", region); + } + + if ((permissions & (u32)MemoryPermission::ReadWrite) != permissions) { + return ERR_INVALID_COMBINATION; + } + VMAPermission vma_permissions = (VMAPermission)permissions; + + auto& process = *g_current_process; + + switch (operation & MEMOP_OPERATION_MASK) { + case MEMOP_FREE: + { + if (addr0 >= Memory::HEAP_VADDR && addr0 < Memory::HEAP_VADDR_END) { + ResultCode result = process.HeapFree(addr0, size); + if (result.IsError()) return result; + } else if (addr0 >= Memory::LINEAR_HEAP_VADDR && addr0 < Memory::LINEAR_HEAP_VADDR_END) { + ResultCode result = process.LinearFree(addr0, size); + if (result.IsError()) return result; + } else { + return ERR_INVALID_ADDRESS; + } + *out_addr = addr0; break; + } - // Map GSP heap memory - case MEMORY_OPERATION_GSP_HEAP: - *out_addr = Memory::MapBlock_HeapLinear(size, operation, permissions); + case MEMOP_COMMIT: + { + if (operation & MEMOP_LINEAR) { + CASCADE_RESULT(*out_addr, process.LinearAllocate(addr0, size, vma_permissions)); + } else { + CASCADE_RESULT(*out_addr, process.HeapAllocate(addr0, size, vma_permissions)); + } break; + } + + case MEMOP_MAP: // TODO: This is just a hack to avoid regressions until memory aliasing is implemented + { + CASCADE_RESULT(*out_addr, process.HeapAllocate(addr0, size, vma_permissions)); + break; + } + + case MEMOP_UNMAP: // TODO: This is just a hack to avoid regressions until memory aliasing is implemented + { + ResultCode result = process.HeapFree(addr0, size); + if (result.IsError()) return result; + break; + } + + case MEMOP_PROTECT: + { + ResultCode result = process.vm_manager.ReprotectRange(addr0, size, vma_permissions); + if (result.IsError()) return result; + break; + } - // Unknown ControlMemory operation default: LOG_ERROR(Kernel_SVC, "unknown operation=0x%08X", operation); + return ERR_INVALID_COMBINATION; } + + process.vm_manager.LogLayout(Log::Level::Trace); + return RESULT_SUCCESS; } @@ -537,9 +619,9 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_inf if (process == nullptr) return ERR_INVALID_HANDLE; - auto vma = process->address_space->FindVMA(addr); + auto vma = process->vm_manager.FindVMA(addr); - if (vma == process->address_space->vma_map.end()) + if (vma == Kernel::g_current_process->vm_manager.vma_map.end()) return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); memory_info->base_address = vma->second.base; diff --git a/src/core/mem_map.cpp b/src/core/mem_map.cpp index cbe993fbe0..c95dea0649 100644 --- a/src/core/mem_map.cpp +++ b/src/core/mem_map.cpp @@ -32,9 +32,7 @@ struct MemoryArea { // We don't declare the IO regions in here since its handled by other means. static MemoryArea memory_areas[] = { - {HEAP_VADDR, HEAP_SIZE, "Heap"}, // Application heap (main memory) {SHARED_MEMORY_VADDR, SHARED_MEMORY_SIZE, "Shared Memory"}, // Shared memory - {LINEAR_HEAP_VADDR, LINEAR_HEAP_SIZE, "Linear Heap"}, // Linear heap (main memory) {VRAM_VADDR, VRAM_SIZE, "VRAM"}, // Video memory (VRAM) {DSP_RAM_VADDR, DSP_RAM_SIZE, "DSP RAM"}, // DSP memory {TLS_AREA_VADDR, TLS_AREA_SIZE, "TLS Area"}, // TLS memory