From 528a060709d1f80fc8e779d80b86757f8cc807ba Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Fri, 26 Sep 2025 04:28:32 -0500 Subject: [PATCH] Core: Memory code cleanup and further direct memory fixes (#3655) * Remove mapped dmem type Since physical addresses can be mapped multiple times, tracking mapped pages is not necessary. This also allows me to significantly simplify the MapMemory physical address validation logic. * Proper implementation for sceKernelMtypeprotect I've rewritten SetDirectMemoryType to use virtual addresses instead of physical addresses, allowing it to be used in sceKernelMtypeprotect. To accommodate this change, I've also moved address and size alignment out of MemoryManager::Protect * Apply memory type in sceKernelMemoryPoolCommit * Organization Some potentially important missing mutexes, removed some unnecessary mutexes, moved some mutexes after early error returns, and updated copyright dates * Iterator logic cleanup Missing end check in ClampRangeSize, and adjusted VirtualQuery and DirectMemoryQuery. * Clang * Adjustments * Properly account for behavior differences in MapDirectMemory2 Undid the changes to direct memory areas, added more robust logic for changing dma types, and fixed DirectMemoryQuery to return hardware-accurate direct memory information in cases where dmas split here, but not on real hardware. I've also changed MapMemory's is_exec flag to a validate_dmem flag, used to handle alternate behavior in MapDirectMemory2. is_exec is now determined by the use of MemoryProt::CpuExec instead. * Clang * Add execute permissions to physical backing Needed for executable mappings to work properly on Windows, fixes regression in RE2 with prior commit. * Minor variable cleanup * Update memory.h * Prohibit direct memory mappings with exec protections Did a quick hardware test to confirm, only seems to be prohibited for dmem mappings though. * Update memory.cpp --- src/core/address_space.cpp | 11 +- src/core/libraries/kernel/memory.cpp | 109 ++++++++++--- src/core/libraries/kernel/memory.h | 9 +- src/core/memory.cpp | 219 ++++++++++++++++++--------- src/core/memory.h | 8 +- src/core/module.cpp | 3 +- 6 files changed, 250 insertions(+), 109 deletions(-) diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index 1140f6720..69f8d4cc1 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -103,17 +103,18 @@ struct AddressSpace::Impl { MemoryRegion{system_managed_addr, virtual_size, false}); // Allocate backing file that represents the total physical memory. - backing_handle = - CreateFileMapping2(INVALID_HANDLE_VALUE, nullptr, FILE_MAP_WRITE | FILE_MAP_READ, - PAGE_READWRITE, SEC_COMMIT, BackingSize, nullptr, nullptr, 0); + backing_handle = CreateFileMapping2(INVALID_HANDLE_VALUE, nullptr, FILE_MAP_ALL_ACCESS, + PAGE_EXECUTE_READWRITE, SEC_COMMIT, BackingSize, + nullptr, nullptr, 0); ASSERT_MSG(backing_handle, "{}", Common::GetLastErrorMsg()); // Allocate a virtual memory for the backing file map as placeholder backing_base = static_cast(VirtualAlloc2(process, nullptr, BackingSize, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, nullptr, 0)); // Map backing placeholder. This will commit the pages - void* const ret = MapViewOfFile3(backing_handle, process, backing_base, 0, BackingSize, - MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0); + void* const ret = + MapViewOfFile3(backing_handle, process, backing_base, 0, BackingSize, + MEM_REPLACE_PLACEHOLDER, PAGE_EXECUTE_READWRITE, nullptr, 0); ASSERT_MSG(ret == backing_base, "{}", Common::GetLastErrorMsg()); } diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp index 7e95fd124..d52e53940 100644 --- a/src/core/libraries/kernel/memory.cpp +++ b/src/core/libraries/kernel/memory.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -159,19 +159,18 @@ s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, s32 flags, u } s32 PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, s32 prot, s32 flags, - s64 directMemoryStart, u64 alignment, - const char* name) { + s64 phys_addr, u64 alignment, const char* name) { LOG_INFO(Kernel_Vmm, "in_addr = {}, len = {:#x}, prot = {:#x}, flags = {:#x}, " - "directMemoryStart = {:#x}, alignment = {:#x}, name = '{}'", - fmt::ptr(*addr), len, prot, flags, directMemoryStart, alignment, name); + "phys_addr = {:#x}, alignment = {:#x}, name = '{}'", + fmt::ptr(*addr), len, prot, flags, phys_addr, alignment, name); if (len == 0 || !Common::Is16KBAligned(len)) { LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 16KB aligned!"); return ORBIS_KERNEL_ERROR_EINVAL; } - if (!Common::Is16KBAligned(directMemoryStart)) { + if (!Common::Is16KBAligned(phys_addr)) { LOG_ERROR(Kernel_Vmm, "Start address is not 16KB aligned!"); return ORBIS_KERNEL_ERROR_EINVAL; } @@ -188,35 +187,72 @@ s32 PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, s32 prot, s return ORBIS_KERNEL_ERROR_ENAMETOOLONG; } - const VAddr in_addr = reinterpret_cast(*addr); const auto mem_prot = static_cast(prot); + if (True(mem_prot & Core::MemoryProt::CpuExec)) { + LOG_ERROR(Kernel_Vmm, "Executable permissions are not allowed."); + return ORBIS_KERNEL_ERROR_EACCES; + } + const auto map_flags = static_cast(flags); + const VAddr in_addr = reinterpret_cast(*addr); auto* memory = Core::Memory::Instance(); - const auto ret = - memory->MapMemory(addr, in_addr, len, mem_prot, map_flags, Core::VMAType::Direct, name, - false, directMemoryStart, alignment); + const auto ret = memory->MapMemory(addr, in_addr, len, mem_prot, map_flags, + Core::VMAType::Direct, name, false, phys_addr, alignment); LOG_INFO(Kernel_Vmm, "out_addr = {}", fmt::ptr(*addr)); return ret; } -s32 PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, s32 prot, s32 flags, - s64 directMemoryStart, u64 alignment) { - LOG_INFO(Kernel_Vmm, "called, redirected to sceKernelMapNamedDirectMemory"); - return sceKernelMapNamedDirectMemory(addr, len, prot, flags, directMemoryStart, alignment, - "anon"); +s32 PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, s32 prot, s32 flags, s64 phys_addr, + u64 alignment) { + LOG_TRACE(Kernel_Vmm, "called, redirected to sceKernelMapNamedDirectMemory"); + return sceKernelMapNamedDirectMemory(addr, len, prot, flags, phys_addr, alignment, "anon"); } s32 PS4_SYSV_ABI sceKernelMapDirectMemory2(void** addr, u64 len, s32 type, s32 prot, s32 flags, s64 phys_addr, u64 alignment) { - LOG_INFO(Kernel_Vmm, "called, redirected to sceKernelMapNamedDirectMemory"); - const s32 ret = - sceKernelMapNamedDirectMemory(addr, len, prot, flags, phys_addr, alignment, "anon"); + LOG_INFO(Kernel_Vmm, + "in_addr = {}, len = {:#x}, prot = {:#x}, flags = {:#x}, " + "phys_addr = {:#x}, alignment = {:#x}", + fmt::ptr(*addr), len, prot, flags, phys_addr, alignment); + + if (len == 0 || !Common::Is16KBAligned(len)) { + LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 16KB aligned!"); + return ORBIS_KERNEL_ERROR_EINVAL; + } + + if (!Common::Is16KBAligned(phys_addr)) { + LOG_ERROR(Kernel_Vmm, "Start address is not 16KB aligned!"); + return ORBIS_KERNEL_ERROR_EINVAL; + } + + if (alignment != 0) { + if ((!std::has_single_bit(alignment) && !Common::Is16KBAligned(alignment))) { + LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!"); + return ORBIS_KERNEL_ERROR_EINVAL; + } + } + + const auto mem_prot = static_cast(prot); + if (True(mem_prot & Core::MemoryProt::CpuExec)) { + LOG_ERROR(Kernel_Vmm, "Executable permissions are not allowed."); + return ORBIS_KERNEL_ERROR_EACCES; + } + + const auto map_flags = static_cast(flags); + const VAddr in_addr = reinterpret_cast(*addr); + + auto* memory = Core::Memory::Instance(); + const auto ret = memory->MapMemory(addr, in_addr, len, mem_prot, map_flags, + Core::VMAType::Direct, "anon", true, phys_addr, alignment); if (ret == 0) { + // If the map call succeeds, set the direct memory type using the output address. auto* memory = Core::Memory::Instance(); - memory->SetDirectMemoryType(phys_addr, type); + const auto out_addr = reinterpret_cast(*addr); + memory->SetDirectMemoryType(out_addr, len, type); + LOG_INFO(Kernel_Vmm, "out_addr = {:#x}", out_addr); } return ret; } @@ -262,9 +298,20 @@ s32 PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** s32 PS4_SYSV_ABI sceKernelMprotect(const void* addr, u64 size, s32 prot) { LOG_INFO(Kernel_Vmm, "called addr = {}, size = {:#x}, prot = {:#x}", fmt::ptr(addr), size, prot); + // Align addr and size to the nearest page boundary. + const VAddr in_addr = reinterpret_cast(addr); + auto aligned_addr = Common::AlignDown(in_addr, 16_KB); + auto aligned_size = Common::AlignUp(size + in_addr - aligned_addr, 16_KB); + + if (aligned_size == 0) { + // Nothing to do. + return ORBIS_OK; + } + Core::MemoryManager* memory_manager = Core::Memory::Instance(); Core::MemoryProt protection_flags = static_cast(prot); - return memory_manager->Protect(std::bit_cast(addr), size, protection_flags); + + return memory_manager->Protect(aligned_addr, aligned_size, protection_flags); } s32 PS4_SYSV_ABI posix_mprotect(const void* addr, u64 size, s32 prot) { @@ -279,9 +326,24 @@ s32 PS4_SYSV_ABI posix_mprotect(const void* addr, u64 size, s32 prot) { s32 PS4_SYSV_ABI sceKernelMtypeprotect(const void* addr, u64 size, s32 mtype, s32 prot) { LOG_INFO(Kernel_Vmm, "called addr = {}, size = {:#x}, prot = {:#x}", fmt::ptr(addr), size, prot); + // Align addr and size to the nearest page boundary. + const VAddr in_addr = reinterpret_cast(addr); + auto aligned_addr = Common::AlignDown(in_addr, 16_KB); + auto aligned_size = Common::AlignUp(size + in_addr - aligned_addr, 16_KB); + + if (aligned_size == 0) { + // Nothing to do. + return ORBIS_OK; + } + Core::MemoryManager* memory_manager = Core::Memory::Instance(); Core::MemoryProt protection_flags = static_cast(prot); - return memory_manager->Protect(std::bit_cast(addr), size, protection_flags); + + s32 result = memory_manager->Protect(aligned_addr, aligned_size, protection_flags); + if (result == ORBIS_OK) { + memory_manager->SetDirectMemoryType(aligned_addr, aligned_size, mtype); + } + return result; } s32 PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, s32 flags, OrbisQueryInfo* query_info, @@ -481,7 +543,7 @@ s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, u64 len, s32 type, s32 pr const VAddr in_addr = reinterpret_cast(addr); const auto mem_prot = static_cast(prot); auto* memory = Core::Memory::Instance(); - return memory->PoolCommit(in_addr, len, mem_prot); + return memory->PoolCommit(in_addr, len, mem_prot, type); } s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, u64 len, s32 flags) { @@ -588,12 +650,11 @@ void* PS4_SYSV_ABI posix_mmap(void* addr, u64 len, s32 prot, s32 flags, s32 fd, auto* memory = Core::Memory::Instance(); const auto mem_prot = static_cast(prot); const auto mem_flags = static_cast(flags); - const auto is_exec = True(mem_prot & Core::MemoryProt::CpuExec); s32 result = ORBIS_OK; if (fd == -1) { result = memory->MapMemory(&addr_out, std::bit_cast(addr), len, mem_prot, mem_flags, - Core::VMAType::Flexible, "anon", is_exec); + Core::VMAType::Flexible, "anon", false); } else { result = memory->MapFile(&addr_out, std::bit_cast(addr), len, mem_prot, mem_flags, fd, phys_addr); diff --git a/src/core/libraries/kernel/memory.h b/src/core/libraries/kernel/memory.h index 2f624d7b1..9f78eb84f 100644 --- a/src/core/libraries/kernel/memory.h +++ b/src/core/libraries/kernel/memory.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -137,10 +137,9 @@ u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize(); s32 PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, u64 alignment, s32 memoryType, s64* physAddrOut); s32 PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, s32 prot, s32 flags, - s64 directMemoryStart, u64 alignment, - const char* name); -s32 PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, s32 prot, s32 flags, - s64 directMemoryStart, u64 alignment); + s64 phys_addr, u64 alignment, const char* name); +s32 PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, s32 prot, s32 flags, s64 phys_addr, + u64 alignment); s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(u64 len, u64 alignment, s32 memoryType, s64* physAddrOut); s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, u64 len); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 51ae29d92..8b081b3a9 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/alignment.h" @@ -83,7 +83,7 @@ u64 MemoryManager::ClampRangeSize(VAddr virtual_addr, u64 size) { ++vma; // Keep adding to the size while there is contigious virtual address space. - while (vma->second.IsMapped() && clamped_size < size) { + while (vma != vma_map.end() && vma->second.IsMapped() && clamped_size < size) { clamped_size += vma->second.size; ++vma; } @@ -253,14 +253,13 @@ void MemoryManager::Free(PAddr phys_addr, u64 size) { MergeAdjacent(dmem_map, dmem_area); } -s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot) { +s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32 mtype) { ASSERT_MSG(IsValidAddress(reinterpret_cast(virtual_addr)), "Attempted to access invalid address {:#x}", virtual_addr); std::scoped_lock lk{mutex}; + // Input addresses to PoolCommit are treated as fixed, and have a constant alignment. const u64 alignment = 64_KB; - - // Input addresses to PoolCommit are treated as fixed. VAddr mapped_addr = Common::AlignUp(virtual_addr, alignment); auto& vma = FindVMA(mapped_addr)->second; @@ -309,6 +308,7 @@ s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot) { const auto new_dmem_handle = CarveDmemArea(handle->second.base, size); auto& new_dmem_area = new_dmem_handle->second; new_dmem_area.dma_type = DMAType::Committed; + new_dmem_area.memory_type = mtype; new_vma.phys_base = new_dmem_area.base; MergeAdjacent(dmem_map, new_dmem_handle); @@ -325,9 +325,7 @@ s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot) { s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot, MemoryMapFlags flags, VMAType type, std::string_view name, - bool is_exec, PAddr phys_addr, u64 alignment) { - std::scoped_lock lk{mutex}; - + bool validate_dmem, PAddr phys_addr, u64 alignment) { // Certain games perform flexible mappings on loop to determine // the available flexible memory size. Questionable but we need to handle this. if (type == VMAType::Flexible && flexible_usage + size > total_flexible_size) { @@ -338,6 +336,8 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo return ORBIS_KERNEL_ERROR_ENOMEM; } + std::scoped_lock lk{mutex}; + // Validate the requested physical address range if (phys_addr != -1) { if (total_direct_size < phys_addr + size) { @@ -346,26 +346,53 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo return ORBIS_KERNEL_ERROR_ENOMEM; } - u64 validated_size = 0; - do { - auto dmem_area = FindDmemArea(phys_addr + validated_size)->second; + // Validate direct memory areas involved in this call. + auto dmem_area = FindDmemArea(phys_addr); + while (dmem_area != dmem_map.end() && dmem_area->second.base < phys_addr + size) { // If any requested dmem area is not allocated, return an error. - if (dmem_area.dma_type != DMAType::Allocated && dmem_area.dma_type != DMAType::Mapped) { + if (dmem_area->second.dma_type != DMAType::Allocated && + dmem_area->second.dma_type != DMAType::Mapped) { LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at physical address {:#x}", size, phys_addr); return ORBIS_KERNEL_ERROR_ENOMEM; } - // Track how much we've validated. - validated_size += dmem_area.size - (phys_addr + validated_size - dmem_area.base); - } while (validated_size < size); - // Carve a new dmem area for this mapping - const auto new_dmem_handle = CarveDmemArea(phys_addr, size); - auto& new_dmem_area = new_dmem_handle->second; - new_dmem_area.dma_type = DMAType::Mapped; + // If we need to perform extra validation, then check for Mapped dmem areas too. + if (validate_dmem && dmem_area->second.dma_type == DMAType::Mapped) { + LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at physical address {:#x}", size, + phys_addr); + return ORBIS_KERNEL_ERROR_EBUSY; + } - // Coalesce direct memory areas if possible - MergeAdjacent(dmem_map, new_dmem_handle); + dmem_area++; + } + + // If the prior loop succeeds, we need to loop through again and carve out mapped dmas. + // This needs to be a separate loop to avoid modifying dmem map during failed calls. + auto phys_addr_to_search = phys_addr; + auto remaining_size = size; + dmem_area = FindDmemArea(phys_addr); + while (dmem_area != dmem_map.end() && remaining_size > 0) { + // Carve a new dmem area in place of this one with the appropriate type. + // Ensure the carved area only covers the current dmem area. + const auto start_phys_addr = + phys_addr > dmem_area->second.base ? phys_addr : dmem_area->second.base; + const auto offset_in_dma = start_phys_addr - dmem_area->second.base; + const auto size_in_dma = dmem_area->second.size - offset_in_dma > remaining_size + ? remaining_size + : dmem_area->second.size - offset_in_dma; + const auto dmem_handle = CarveDmemArea(start_phys_addr, size_in_dma); + auto& new_dmem_area = dmem_handle->second; + new_dmem_area.dma_type = DMAType::Mapped; + + // Merge the new dmem_area with dmem_map + MergeAdjacent(dmem_map, dmem_handle); + + // Get the next relevant dmem area. + phys_addr_to_search = phys_addr + size_in_dma; + remaining_size -= size_in_dma; + dmem_area = FindDmemArea(phys_addr_to_search); + } } // Limit the minimum address to SystemManagedVirtualBase to prevent hardware-specific issues. @@ -438,6 +465,8 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo MergeAdjacent(fmem_map, new_fmem_handle); } + const bool is_exec = True(prot & MemoryProt::CpuExec); + new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce); new_vma.prot = prot; new_vma.name = name; @@ -470,11 +499,11 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot, MemoryMapFlags flags, s32 fd, s64 phys_addr) { - auto* h = Common::Singleton::Instance(); - VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr; ASSERT_MSG(IsValidAddress(reinterpret_cast(mapped_addr)), "Attempted to access invalid address {:#x}", mapped_addr); + + std::scoped_lock lk{mutex}; const u64 size_aligned = Common::AlignUp(size, 16_KB); // Find first free area to map the file. @@ -495,6 +524,7 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory } // Get the file to map + auto* h = Common::Singleton::Instance(); auto file = h->GetFile(fd); if (file == nullptr) { return ORBIS_KERNEL_ERROR_EBADF; @@ -603,6 +633,28 @@ u64 MemoryManager::UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma return adjusted_size; } + if (type == VMAType::Direct) { + // Unmap all direct memory areas covered by this unmap. + auto phys_addr = phys_base + start_in_vma; + auto remaining_size = adjusted_size; + DMemHandle dmem_handle = FindDmemArea(phys_addr); + while (dmem_handle != dmem_map.end() && remaining_size > 0) { + const auto start_in_dma = phys_base - dmem_handle->second.base; + const auto size_in_dma = dmem_handle->second.size - start_in_dma > remaining_size + ? remaining_size + : dmem_handle->second.size - start_in_dma; + dmem_handle = CarveDmemArea(phys_addr, size_in_dma); + auto& dmem_area = dmem_handle->second; + dmem_area.dma_type = DMAType::Allocated; + remaining_size -= dmem_area.size; + phys_addr += dmem_area.size; + + // Check if we can coalesce any dmem areas. + MergeAdjacent(dmem_map, dmem_handle); + dmem_handle = FindDmemArea(phys_addr); + } + } + if (type == VMAType::Flexible) { flexible_usage -= adjusted_size; @@ -620,16 +672,6 @@ u64 MemoryManager::UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma MergeAdjacent(fmem_map, new_fmem_handle); } - if (type == VMAType::Direct) { - // Since we're unmapping the memory - // make sure the corresponding direct memory area is updated correctly. - const auto unmap_phys_base = phys_base + start_in_vma; - const auto new_dmem_handle = CarveDmemArea(unmap_phys_base, adjusted_size); - auto& new_dmem_area = new_dmem_handle->second; - new_dmem_area.dma_type = DMAType::Allocated; - MergeAdjacent(dmem_map, new_dmem_handle); - } - // Mark region as free and attempt to coalesce it with neighbours. const auto new_it = CarveVMA(virtual_addr, adjusted_size); auto& vma = new_it->second; @@ -750,19 +792,14 @@ s32 MemoryManager::Protect(VAddr addr, u64 size, MemoryProt prot) { return ORBIS_KERNEL_ERROR_EINVAL; } - // Align addr and size to the nearest page boundary. - auto aligned_addr = Common::AlignDown(addr, 16_KB); - auto aligned_size = Common::AlignUp(size + addr - aligned_addr, 16_KB); - - // Protect all VMAs between aligned_addr and aligned_addr + aligned_size. + // Protect all VMAs between addr and addr + size. s64 protected_bytes = 0; - while (protected_bytes < aligned_size) { - ASSERT_MSG(IsValidAddress(reinterpret_cast(aligned_addr)), - "Attempted to access invalid address {:#x}", aligned_addr); - auto it = FindVMA(aligned_addr + protected_bytes); + while (protected_bytes < size) { + ASSERT_MSG(IsValidAddress(reinterpret_cast(addr)), + "Attempted to access invalid address {:#x}", addr); + auto it = FindVMA(addr + protected_bytes); auto& vma_base = it->second; - auto result = ProtectBytes(aligned_addr + protected_bytes, vma_base, - aligned_size - protected_bytes, prot); + auto result = ProtectBytes(addr + protected_bytes, vma_base, size - protected_bytes, prot); if (result < 0) { // ProtectBytes returned an error, return it return result; @@ -786,10 +823,10 @@ s32 MemoryManager::VirtualQuery(VAddr addr, s32 flags, } auto it = FindVMA(query_addr); - while (it->second.type == VMAType::Free && flags == 1 && it != --vma_map.end()) { + while (it != vma_map.end() && it->second.type == VMAType::Free && flags == 1) { ++it; } - if (it->second.type == VMAType::Free) { + if (it == vma_map.end() || it->second.type == VMAType::Free) { LOG_WARNING(Kernel_Vmm, "VirtualQuery on free memory region"); return ORBIS_KERNEL_ERROR_EACCES; } @@ -828,20 +865,28 @@ s32 MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next, } auto dmem_area = FindDmemArea(addr); - while (dmem_area != --dmem_map.end() && dmem_area->second.dma_type == DMAType::Free && + while (dmem_area != dmem_map.end() && dmem_area->second.dma_type == DMAType::Free && find_next) { dmem_area++; } - if (dmem_area->second.dma_type == DMAType::Free) { + if (dmem_area == dmem_map.end() || dmem_area->second.dma_type == DMAType::Free) { LOG_WARNING(Kernel_Vmm, "Unable to find allocated direct memory region to query!"); return ORBIS_KERNEL_ERROR_EACCES; } - const auto& area = dmem_area->second; - out_info->start = area.base; - out_info->end = area.GetEnd(); - out_info->memoryType = area.memory_type; + out_info->start = dmem_area->second.base; + out_info->memoryType = dmem_area->second.memory_type; + + // Loop through all sequential mapped or allocated dmem areas + // to determine the hardware accurate end. + while (dmem_area != dmem_map.end() && dmem_area->second.memory_type == out_info->memoryType && + (dmem_area->second.dma_type == DMAType::Mapped || + dmem_area->second.dma_type == DMAType::Allocated)) { + out_info->end = dmem_area->second.GetEnd(); + dmem_area++; + } + return ORBIS_OK; } @@ -892,20 +937,53 @@ s32 MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, u6 return ORBIS_OK; } -s32 MemoryManager::SetDirectMemoryType(s64 phys_addr, s32 memory_type) { +s32 MemoryManager::SetDirectMemoryType(VAddr addr, u64 size, s32 memory_type) { std::scoped_lock lk{mutex}; - auto& dmem_area = FindDmemArea(phys_addr)->second; + // Search through all VMAs covered by the provided range. + // We aren't modifying these VMAs, so it's safe to iterate through them. + auto remaining_size = size; + auto vma_handle = FindVMA(addr); + while (vma_handle != vma_map.end() && vma_handle->second.base < addr + size) { + // Direct and Pooled mappings are the only ones with a memory type. + if (vma_handle->second.type == VMAType::Direct || + vma_handle->second.type == VMAType::Pooled) { + // Calculate position in vma + const auto start_in_vma = addr - vma_handle->second.base; + const auto size_in_vma = vma_handle->second.size - start_in_vma; + auto phys_addr = vma_handle->second.phys_base + start_in_vma; + auto size_to_modify = remaining_size > size_in_vma ? size_in_vma : remaining_size; - ASSERT_MSG(phys_addr <= dmem_area.GetEnd() && dmem_area.dma_type == DMAType::Mapped, - "Direct memory area is not mapped"); + // Loop through remaining dmem areas until the physical addresses represented + // are all adjusted. + DMemHandle dmem_handle = FindDmemArea(phys_addr); + while (dmem_handle != dmem_map.end() && size_in_vma >= size_to_modify && + size_to_modify > 0) { + const auto start_in_dma = phys_addr - dmem_handle->second.base; + const auto size_in_dma = dmem_handle->second.size - start_in_dma > size_to_modify + ? size_to_modify + : dmem_handle->second.size - start_in_dma; + dmem_handle = CarveDmemArea(phys_addr, size_in_dma); + auto& dmem_area = dmem_handle->second; + dmem_area.memory_type = memory_type; + size_to_modify -= dmem_area.size; + phys_addr += dmem_area.size; - dmem_area.memory_type = memory_type; + // Check if we can coalesce any dmem areas now that the types are different. + MergeAdjacent(dmem_map, dmem_handle); + dmem_handle = FindDmemArea(phys_addr); + } + } + remaining_size -= vma_handle->second.size; + vma_handle++; + } return ORBIS_OK; } void MemoryManager::NameVirtualRange(VAddr virtual_addr, u64 size, std::string_view name) { + std::scoped_lock lk{mutex}; + // Sizes are aligned up to the nearest 16_KB auto aligned_size = Common::AlignUp(size, 16_KB); // Addresses are aligned down to the nearest 16_KB @@ -935,28 +1013,27 @@ void MemoryManager::NameVirtualRange(VAddr virtual_addr, u64 size, std::string_v s32 MemoryManager::GetDirectMemoryType(PAddr addr, s32* directMemoryTypeOut, void** directMemoryStartOut, void** directMemoryEndOut) { - std::scoped_lock lk{mutex}; - - auto dmem_area = FindDmemArea(addr); - - if (addr > dmem_area->second.GetEnd() || dmem_area->second.dma_type == DMAType::Free) { - LOG_ERROR(Core, "Unable to find allocated direct memory region to check type!"); + if (addr >= total_direct_size) { + LOG_ERROR(Kernel_Vmm, "Unable to find allocated direct memory region to check type!"); return ORBIS_KERNEL_ERROR_ENOENT; } - const auto& area = dmem_area->second; - *directMemoryStartOut = reinterpret_cast(area.base); - *directMemoryEndOut = reinterpret_cast(area.GetEnd()); - *directMemoryTypeOut = area.memory_type; + const auto& dmem_area = FindDmemArea(addr)->second; + if (dmem_area.dma_type == DMAType::Free) { + LOG_ERROR(Kernel_Vmm, "Unable to find allocated direct memory region to check type!"); + return ORBIS_KERNEL_ERROR_ENOENT; + } + + *directMemoryStartOut = reinterpret_cast(dmem_area.base); + *directMemoryEndOut = reinterpret_cast(dmem_area.GetEnd()); + *directMemoryTypeOut = dmem_area.memory_type; return ORBIS_OK; } s32 MemoryManager::IsStack(VAddr addr, void** start, void** end) { ASSERT_MSG(IsValidAddress(reinterpret_cast(addr)), "Attempted to access invalid address {:#x}", addr); - auto vma_handle = FindVMA(addr); - - const VirtualMemoryArea& vma = vma_handle->second; + const auto& vma = FindVMA(addr)->second; if (vma.IsFree()) { return ORBIS_KERNEL_ERROR_EACCES; } @@ -980,6 +1057,8 @@ s32 MemoryManager::IsStack(VAddr addr, void** start, void** end) { } s32 MemoryManager::GetMemoryPoolStats(::Libraries::Kernel::OrbisKernelMemoryPoolBlockStats* stats) { + std::scoped_lock lk{mutex}; + // Run through dmem_map, determine how much physical memory is currently committed constexpr u64 block_size = 64_KB; u64 committed_size = 0; diff --git a/src/core/memory.h b/src/core/memory.h index f729e0207..830015068 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -222,11 +222,11 @@ public: void Free(PAddr phys_addr, u64 size); - s32 PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot); + s32 PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32 mtype); s32 MapMemory(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot, MemoryMapFlags flags, VMAType type, std::string_view name = "anon", - bool is_exec = false, PAddr phys_addr = -1, u64 alignment = 0); + bool validate_dmem = false, PAddr phys_addr = -1, u64 alignment = 0); s32 MapFile(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot, MemoryMapFlags flags, s32 fd, s64 phys_addr); @@ -254,7 +254,7 @@ public: s32 IsStack(VAddr addr, void** start, void** end); - s32 SetDirectMemoryType(s64 phys_addr, s32 memory_type); + s32 SetDirectMemoryType(VAddr addr, u64 size, s32 memory_type); void NameVirtualRange(VAddr virtual_addr, u64 size, std::string_view name); diff --git a/src/core/module.cpp b/src/core/module.cpp index fbfb92976..127e74293 100644 --- a/src/core/module.cpp +++ b/src/core/module.cpp @@ -113,7 +113,8 @@ void Module::LoadModuleToMemory(u32& max_tls_index) { // Map module segments (and possible TLS trampolines) void** out_addr = reinterpret_cast(&base_virtual_addr); memory->MapMemory(out_addr, ModuleLoadBase, aligned_base_size + TrampolineSize, - MemoryProt::CpuReadWrite, MemoryMapFlags::NoFlags, VMAType::Code, name, true); + MemoryProt::CpuReadWrite | MemoryProt::CpuExec, MemoryMapFlags::NoFlags, + VMAType::Code, name); LOG_INFO(Core_Linker, "Loading module {} to {}", name, fmt::ptr(*out_addr)); #ifdef ARCH_X86_64