mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-12-10 05:38:49 +00:00
Core: Refactor direct memory handling (#3645)
* Refactor direct memory areas At this point, swapping the multiple booleans for an enum is cleaner, and makes it easier to track the state of a direct memory area. I've also sped up the logic for mapping direct memory by checking for out-of-bounds physical addresses before looping, and made the logic more solid using my dma type logic. * Fix PoolCommit assert Windows devices will throw an access violation if we don't check for iterator reaching end.
This commit is contained in:
@@ -44,7 +44,7 @@ bool MemoryMapViewer::Iterator::DrawLine() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto m = dmem.it->second;
|
auto m = dmem.it->second;
|
||||||
if (m.is_free) {
|
if (m.dma_type == DMAType::Free) {
|
||||||
++dmem.it;
|
++dmem.it;
|
||||||
return DrawLine();
|
return DrawLine();
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ bool MemoryMapViewer::Iterator::DrawLine() {
|
|||||||
auto type = static_cast<::Libraries::Kernel::MemoryTypes>(m.memory_type);
|
auto type = static_cast<::Libraries::Kernel::MemoryTypes>(m.memory_type);
|
||||||
Text("%s", magic_enum::enum_name(type).data());
|
Text("%s", magic_enum::enum_name(type).data());
|
||||||
TableNextColumn();
|
TableNextColumn();
|
||||||
Text("%d", m.is_pooled);
|
Text("%d", m.dma_type == DMAType::Pooled || m.dma_type == DMAType::Committed);
|
||||||
++dmem.it;
|
++dmem.it;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,7 +154,8 @@ PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, u64 size,
|
|||||||
auto mapping_end = mapping_start + size;
|
auto mapping_end = mapping_start + size;
|
||||||
|
|
||||||
// Find the first free, large enough dmem area in the range.
|
// Find the first free, large enough dmem area in the range.
|
||||||
while (!dmem_area->second.is_free || dmem_area->second.GetEnd() < mapping_end) {
|
while (dmem_area->second.dma_type != DMAType::Free ||
|
||||||
|
dmem_area->second.GetEnd() < mapping_end) {
|
||||||
// The current dmem_area isn't suitable, move to the next one.
|
// The current dmem_area isn't suitable, move to the next one.
|
||||||
dmem_area++;
|
dmem_area++;
|
||||||
if (dmem_area == dmem_map.end()) {
|
if (dmem_area == dmem_map.end()) {
|
||||||
@@ -174,8 +175,8 @@ PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, u64 size,
|
|||||||
|
|
||||||
// Add the allocated region to the list and commit its pages.
|
// Add the allocated region to the list and commit its pages.
|
||||||
auto& area = CarveDmemArea(mapping_start, size)->second;
|
auto& area = CarveDmemArea(mapping_start, size)->second;
|
||||||
area.is_free = false;
|
area.dma_type = DMAType::Pooled;
|
||||||
area.is_pooled = true;
|
area.memory_type = 3;
|
||||||
|
|
||||||
// Track how much dmem was allocated for pools.
|
// Track how much dmem was allocated for pools.
|
||||||
pool_budget += size;
|
pool_budget += size;
|
||||||
@@ -195,7 +196,8 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, u64 size, u6
|
|||||||
auto mapping_end = mapping_start + size;
|
auto mapping_end = mapping_start + size;
|
||||||
|
|
||||||
// Find the first free, large enough dmem area in the range.
|
// Find the first free, large enough dmem area in the range.
|
||||||
while (!dmem_area->second.is_free || dmem_area->second.GetEnd() < mapping_end) {
|
while (dmem_area->second.dma_type != DMAType::Free ||
|
||||||
|
dmem_area->second.GetEnd() < mapping_end) {
|
||||||
// The current dmem_area isn't suitable, move to the next one.
|
// The current dmem_area isn't suitable, move to the next one.
|
||||||
dmem_area++;
|
dmem_area++;
|
||||||
if (dmem_area == dmem_map.end()) {
|
if (dmem_area == dmem_map.end()) {
|
||||||
@@ -216,7 +218,7 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, u64 size, u6
|
|||||||
// Add the allocated region to the list and commit its pages.
|
// Add the allocated region to the list and commit its pages.
|
||||||
auto& area = CarveDmemArea(mapping_start, size)->second;
|
auto& area = CarveDmemArea(mapping_start, size)->second;
|
||||||
area.memory_type = memory_type;
|
area.memory_type = memory_type;
|
||||||
area.is_free = false;
|
area.dma_type = DMAType::Allocated;
|
||||||
MergeAdjacent(dmem_map, dmem_area);
|
MergeAdjacent(dmem_map, dmem_area);
|
||||||
return mapping_start;
|
return mapping_start;
|
||||||
}
|
}
|
||||||
@@ -246,7 +248,7 @@ void MemoryManager::Free(PAddr phys_addr, u64 size) {
|
|||||||
|
|
||||||
// Mark region as free and attempt to coalesce it with neighbours.
|
// Mark region as free and attempt to coalesce it with neighbours.
|
||||||
auto& area = dmem_area->second;
|
auto& area = dmem_area->second;
|
||||||
area.is_free = true;
|
area.dma_type = DMAType::Free;
|
||||||
area.memory_type = 0;
|
area.memory_type = 0;
|
||||||
MergeAdjacent(dmem_map, dmem_area);
|
MergeAdjacent(dmem_map, dmem_area);
|
||||||
}
|
}
|
||||||
@@ -296,18 +298,17 @@ s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot) {
|
|||||||
|
|
||||||
// Find a suitable physical address
|
// Find a suitable physical address
|
||||||
auto handle = dmem_map.begin();
|
auto handle = dmem_map.begin();
|
||||||
while (handle != dmem_map.end() && (!handle->second.is_pooled || handle->second.size < size)) {
|
while (handle != dmem_map.end() &&
|
||||||
|
(handle->second.dma_type != Core::DMAType::Pooled || handle->second.size < size)) {
|
||||||
handle++;
|
handle++;
|
||||||
}
|
}
|
||||||
|
ASSERT_MSG(handle != dmem_map.end() && handle->second.dma_type == Core::DMAType::Pooled,
|
||||||
ASSERT_MSG(handle->second.is_pooled, "Out of pooled memory");
|
"No suitable physical memory areas to map");
|
||||||
|
|
||||||
// Use the start of this area as the physical backing for this mapping.
|
// Use the start of this area as the physical backing for this mapping.
|
||||||
const auto new_dmem_handle = CarveDmemArea(handle->second.base, size);
|
const auto new_dmem_handle = CarveDmemArea(handle->second.base, size);
|
||||||
auto& new_dmem_area = new_dmem_handle->second;
|
auto& new_dmem_area = new_dmem_handle->second;
|
||||||
new_dmem_area.is_free = false;
|
new_dmem_area.dma_type = DMAType::Committed;
|
||||||
new_dmem_area.is_pooled = false;
|
|
||||||
new_dmem_area.is_committed = true;
|
|
||||||
new_vma.phys_base = new_dmem_area.base;
|
new_vma.phys_base = new_dmem_area.base;
|
||||||
MergeAdjacent(dmem_map, new_dmem_handle);
|
MergeAdjacent(dmem_map, new_dmem_handle);
|
||||||
|
|
||||||
@@ -339,24 +340,32 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo
|
|||||||
|
|
||||||
// Validate the requested physical address range
|
// Validate the requested physical address range
|
||||||
if (phys_addr != -1) {
|
if (phys_addr != -1) {
|
||||||
|
if (total_direct_size < phys_addr + size) {
|
||||||
|
LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at physical address {:#x}", size,
|
||||||
|
phys_addr);
|
||||||
|
return ORBIS_KERNEL_ERROR_ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
u64 validated_size = 0;
|
u64 validated_size = 0;
|
||||||
do {
|
do {
|
||||||
auto dmem_area = FindDmemArea(phys_addr + validated_size)->second;
|
auto dmem_area = FindDmemArea(phys_addr + validated_size)->second;
|
||||||
// If any requested dmem area is not allocated, return an error.
|
// If any requested dmem area is not allocated, return an error.
|
||||||
if (dmem_area.is_free) {
|
if (dmem_area.dma_type != DMAType::Allocated) {
|
||||||
LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at physical address {:#x}", size,
|
LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at physical address {:#x}", size,
|
||||||
phys_addr);
|
phys_addr);
|
||||||
return ORBIS_KERNEL_ERROR_ENOMEM;
|
return ORBIS_KERNEL_ERROR_ENOMEM;
|
||||||
}
|
}
|
||||||
// Track how much we've validated.
|
// Track how much we've validated.
|
||||||
validated_size += dmem_area.size - (phys_addr + validated_size - dmem_area.base);
|
validated_size += dmem_area.size - (phys_addr + validated_size - dmem_area.base);
|
||||||
} while (validated_size < size && phys_addr + validated_size < GetTotalDirectSize());
|
} while (validated_size < size);
|
||||||
// If the requested range goes outside the dmem map, return an error.
|
|
||||||
if (validated_size < size) {
|
// Carve a new dmem area for this mapping
|
||||||
LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at physical address {:#x}", size,
|
const auto new_dmem_handle = CarveDmemArea(phys_addr, size);
|
||||||
phys_addr);
|
auto& new_dmem_area = new_dmem_handle->second;
|
||||||
return ORBIS_KERNEL_ERROR_ENOMEM;
|
new_dmem_area.dma_type = DMAType::Mapped;
|
||||||
}
|
|
||||||
|
// Coalesce direct memory areas if possible
|
||||||
|
MergeAdjacent(dmem_map, new_dmem_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit the minimum address to SystemManagedVirtualBase to prevent hardware-specific issues.
|
// Limit the minimum address to SystemManagedVirtualBase to prevent hardware-specific issues.
|
||||||
@@ -546,9 +555,9 @@ s32 MemoryManager::PoolDecommit(VAddr virtual_addr, u64 size) {
|
|||||||
const auto unmap_phys_base = phys_base + start_in_vma;
|
const auto unmap_phys_base = phys_base + start_in_vma;
|
||||||
const auto new_dmem_handle = CarveDmemArea(unmap_phys_base, size);
|
const auto new_dmem_handle = CarveDmemArea(unmap_phys_base, size);
|
||||||
auto& new_dmem_area = new_dmem_handle->second;
|
auto& new_dmem_area = new_dmem_handle->second;
|
||||||
new_dmem_area.is_free = false;
|
new_dmem_area.dma_type = DMAType::Pooled;
|
||||||
new_dmem_area.is_pooled = true;
|
|
||||||
new_dmem_area.is_committed = false;
|
// Coalesce with nearby direct memory areas.
|
||||||
MergeAdjacent(dmem_map, new_dmem_handle);
|
MergeAdjacent(dmem_map, new_dmem_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,6 +620,16 @@ u64 MemoryManager::UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma
|
|||||||
MergeAdjacent(fmem_map, new_fmem_handle);
|
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.
|
// Mark region as free and attempt to coalesce it with neighbours.
|
||||||
const auto new_it = CarveVMA(virtual_addr, adjusted_size);
|
const auto new_it = CarveVMA(virtual_addr, adjusted_size);
|
||||||
auto& vma = new_it->second;
|
auto& vma = new_it->second;
|
||||||
@@ -803,12 +822,18 @@ s32 MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next,
|
|||||||
::Libraries::Kernel::OrbisQueryInfo* out_info) {
|
::Libraries::Kernel::OrbisQueryInfo* out_info) {
|
||||||
std::scoped_lock lk{mutex};
|
std::scoped_lock lk{mutex};
|
||||||
|
|
||||||
|
if (addr >= total_direct_size) {
|
||||||
|
LOG_WARNING(Kernel_Vmm, "Unable to find allocated direct memory region to query!");
|
||||||
|
return ORBIS_KERNEL_ERROR_EACCES;
|
||||||
|
}
|
||||||
|
|
||||||
auto dmem_area = FindDmemArea(addr);
|
auto dmem_area = FindDmemArea(addr);
|
||||||
while (dmem_area != --dmem_map.end() && dmem_area->second.is_free && find_next) {
|
while (dmem_area != --dmem_map.end() && dmem_area->second.dma_type == DMAType::Free &&
|
||||||
|
find_next) {
|
||||||
dmem_area++;
|
dmem_area++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dmem_area->second.is_free || addr >= total_direct_size) {
|
if (dmem_area->second.dma_type == DMAType::Free) {
|
||||||
LOG_WARNING(Kernel_Vmm, "Unable to find allocated direct memory region to query!");
|
LOG_WARNING(Kernel_Vmm, "Unable to find allocated direct memory region to query!");
|
||||||
return ORBIS_KERNEL_ERROR_EACCES;
|
return ORBIS_KERNEL_ERROR_EACCES;
|
||||||
}
|
}
|
||||||
@@ -829,7 +854,7 @@ s32 MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, u6
|
|||||||
u64 max_size{};
|
u64 max_size{};
|
||||||
|
|
||||||
while (dmem_area != dmem_map.end()) {
|
while (dmem_area != dmem_map.end()) {
|
||||||
if (!dmem_area->second.is_free) {
|
if (dmem_area->second.dma_type != DMAType::Free) {
|
||||||
dmem_area++;
|
dmem_area++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -872,7 +897,7 @@ s32 MemoryManager::SetDirectMemoryType(s64 phys_addr, s32 memory_type) {
|
|||||||
|
|
||||||
auto& dmem_area = FindDmemArea(phys_addr)->second;
|
auto& dmem_area = FindDmemArea(phys_addr)->second;
|
||||||
|
|
||||||
ASSERT_MSG(phys_addr <= dmem_area.GetEnd() && !dmem_area.is_free,
|
ASSERT_MSG(phys_addr <= dmem_area.GetEnd() && dmem_area.dma_type == DMAType::Mapped,
|
||||||
"Direct memory area is not mapped");
|
"Direct memory area is not mapped");
|
||||||
|
|
||||||
dmem_area.memory_type = memory_type;
|
dmem_area.memory_type = memory_type;
|
||||||
@@ -914,7 +939,7 @@ s32 MemoryManager::GetDirectMemoryType(PAddr addr, s32* directMemoryTypeOut,
|
|||||||
|
|
||||||
auto dmem_area = FindDmemArea(addr);
|
auto dmem_area = FindDmemArea(addr);
|
||||||
|
|
||||||
if (addr > dmem_area->second.GetEnd() || dmem_area->second.is_free) {
|
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!");
|
LOG_ERROR(Core, "Unable to find allocated direct memory region to check type!");
|
||||||
return ORBIS_KERNEL_ERROR_ENOENT;
|
return ORBIS_KERNEL_ERROR_ENOENT;
|
||||||
}
|
}
|
||||||
@@ -1113,6 +1138,7 @@ MemoryManager::DMemHandle MemoryManager::Split(DMemHandle dmem_handle, u64 offse
|
|||||||
|
|
||||||
auto new_area = old_area;
|
auto new_area = old_area;
|
||||||
old_area.size = offset_in_area;
|
old_area.size = offset_in_area;
|
||||||
|
new_area.memory_type = old_area.memory_type;
|
||||||
new_area.base += offset_in_area;
|
new_area.base += offset_in_area;
|
||||||
new_area.size -= offset_in_area;
|
new_area.size -= offset_in_area;
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,14 @@ enum class MemoryMapFlags : u32 {
|
|||||||
};
|
};
|
||||||
DECLARE_ENUM_FLAG_OPERATORS(MemoryMapFlags)
|
DECLARE_ENUM_FLAG_OPERATORS(MemoryMapFlags)
|
||||||
|
|
||||||
|
enum class DMAType : u32 {
|
||||||
|
Free = 0,
|
||||||
|
Allocated = 1,
|
||||||
|
Mapped = 2,
|
||||||
|
Pooled = 3,
|
||||||
|
Committed = 4,
|
||||||
|
};
|
||||||
|
|
||||||
enum class VMAType : u32 {
|
enum class VMAType : u32 {
|
||||||
Free = 0,
|
Free = 0,
|
||||||
Reserved = 1,
|
Reserved = 1,
|
||||||
@@ -66,9 +74,7 @@ struct DirectMemoryArea {
|
|||||||
PAddr base = 0;
|
PAddr base = 0;
|
||||||
u64 size = 0;
|
u64 size = 0;
|
||||||
s32 memory_type = 0;
|
s32 memory_type = 0;
|
||||||
bool is_pooled = false;
|
DMAType dma_type = DMAType::Free;
|
||||||
bool is_committed = false;
|
|
||||||
bool is_free = true;
|
|
||||||
|
|
||||||
PAddr GetEnd() const {
|
PAddr GetEnd() const {
|
||||||
return base + size;
|
return base + size;
|
||||||
@@ -81,8 +87,7 @@ struct DirectMemoryArea {
|
|||||||
if (memory_type != next.memory_type) {
|
if (memory_type != next.memory_type) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (is_free != next.is_free || is_pooled != next.is_pooled ||
|
if (dma_type != next.dma_type) {
|
||||||
is_committed != next.is_committed) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user