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
This commit is contained in:
Stephen Miller
2025-09-26 04:28:32 -05:00
committed by GitHub
parent 6e27842562
commit 528a060709
6 changed files with 250 additions and 109 deletions

View File

@@ -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<u8*>(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());
}

View File

@@ -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 <bit>
@@ -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<VAddr>(*addr);
const auto mem_prot = static_cast<Core::MemoryProt>(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<Core::MemoryMapFlags>(flags);
const VAddr in_addr = reinterpret_cast<VAddr>(*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<Core::MemoryProt>(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<Core::MemoryMapFlags>(flags);
const VAddr in_addr = reinterpret_cast<VAddr>(*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<VAddr>(*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<VAddr>(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<Core::MemoryProt>(prot);
return memory_manager->Protect(std::bit_cast<VAddr>(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<VAddr>(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<Core::MemoryProt>(prot);
return memory_manager->Protect(std::bit_cast<VAddr>(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<VAddr>(addr);
const auto mem_prot = static_cast<Core::MemoryProt>(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<Core::MemoryProt>(prot);
const auto mem_flags = static_cast<Core::MemoryMapFlags>(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<VAddr>(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<VAddr>(addr), len, mem_prot, mem_flags,
fd, phys_addr);

View File

@@ -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);

View File

@@ -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<void*>(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<Core::FileSys::HandleTable>::Instance();
VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
ASSERT_MSG(IsValidAddress(reinterpret_cast<void*>(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<Core::FileSys::HandleTable>::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<void*>(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<void*>(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<void*>(area.base);
*directMemoryEndOut = reinterpret_cast<void*>(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<void*>(dmem_area.base);
*directMemoryEndOut = reinterpret_cast<void*>(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<void*>(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;

View File

@@ -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);

View File

@@ -113,7 +113,8 @@ void Module::LoadModuleToMemory(u32& max_tls_index) {
// Map module segments (and possible TLS trampolines)
void** out_addr = reinterpret_cast<void**>(&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