diff options
| -rw-r--r-- | src/common/host_memory.cpp | 60 | ||||
| -rw-r--r-- | src/common/host_memory.h | 18 | ||||
| -rw-r--r-- | src/core/hle/kernel/k_memory_manager.cpp | 24 | ||||
| -rw-r--r-- | src/core/hle/kernel/k_memory_manager.h | 6 | ||||
| -rw-r--r-- | src/core/hle/kernel/k_shared_memory.cpp | 5 |
5 files changed, 107 insertions, 6 deletions
diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index 75a9da80e9..868e9c68f2 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp @@ -56,6 +56,7 @@ #include "common/alignment.h" #include "common/assert.h" +#include "common/div_ceil.h" #include "common/free_region_manager.h" #include "common/host_memory.h" #include "common/logging.h" @@ -707,7 +708,9 @@ private: HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_) : backing_size(backing_size_) , virtual_size(virtual_size_) + , num_granules(Common::DivCeil(backing_size_, size_t{1} << GranuleShift)) { + granules = std::make_unique<std::atomic<u8>[]>(num_granules); #if defined(__OPENORBIS__) || defined(__managarm__) LOG_WARNING(HW_Memory, "Platform doesn't support fastmem"); backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); @@ -791,7 +794,62 @@ void HostMemory::Protect(size_t virtual_offset, size_t length, MemoryPermission } void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 fill_value) { - std::memset(backing_base + physical_offset, fill_value, length); + if (length == 0) { + return; + } + MarkUsed(physical_offset, length); + if (fill_value != 0) { + std::memset(backing_base + physical_offset, fill_value, length); + MarkDirty(physical_offset, length); + return; + } + // Fresh backing memory is zero and the host gives it no RAM until it's touched: clear only + // the granules that may hold data (title 1's heap made the host commit every page of it). + // A granule cleared in full is zero again. + const size_t end = physical_offset + length; + for (size_t offset = physical_offset; offset < end;) { + const size_t index = offset >> GranuleShift; + const size_t granule_begin = index << GranuleShift; + const size_t granule_end = granule_begin + (size_t{1} << GranuleShift); + const size_t clear_end = (std::min)(granule_end, end); + if ((granules[index].load(std::memory_order_acquire) & GranuleDirty) != 0) { + std::memset(backing_base + offset, 0, clear_end - offset); + if (offset == granule_begin && clear_end == granule_end) { + granules[index].fetch_and(static_cast<u8>(~GranuleDirty), std::memory_order_release); + } + } + offset = clear_end; + } +} + +void HostMemory::MarkUsed(size_t physical_offset, size_t length) { + if (length == 0) { + return; + } + const size_t last = (physical_offset + length - 1) >> GranuleShift; + for (size_t index = physical_offset >> GranuleShift; index <= last; ++index) { + if ((granules[index].load(std::memory_order_relaxed) & GranuleUsed) == 0) { + granules[index].fetch_or(GranuleUsed, std::memory_order_release); + } + } +} + +void HostMemory::MarkDirty(size_t physical_offset, size_t length) { + if (length == 0) { + return; + } + const size_t last = (physical_offset + length - 1) >> GranuleShift; + for (size_t index = physical_offset >> GranuleShift; index <= last; ++index) { + granules[index].fetch_or(GranuleDirty, std::memory_order_release); + } +} + +void HostMemory::ForgetContents() { + for (size_t index = 0; index < num_granules; ++index) { + if ((granules[index].load(std::memory_order_relaxed) & GranuleUsed) != 0) { + granules[index].fetch_or(GranuleDirty, std::memory_order_release); + } + } } void HostMemory::EnableDirectMappedAddress() { diff --git a/src/common/host_memory.h b/src/common/host_memory.h index 8747a1850f..3e0142eed8 100644 --- a/src/common/host_memory.h +++ b/src/common/host_memory.h @@ -6,6 +6,7 @@ #pragma once +#include <atomic> #include <memory> #include <optional> #include "common/common_funcs.h" @@ -52,8 +53,19 @@ public: void EnableDirectMappedAddress(); + /// Clears guest memory being handed out. Memory still zero since it was created (or since its + /// last full clear) is skipped: clearing it only made the host give every page real memory. void ClearBackingRegion(size_t physical_offset, size_t length, u32 fill_value); + /// Pages the kernel hands out (cleared or not). + void MarkUsed(size_t physical_offset, size_t length); + + /// Pages given back to the kernel: their contents are no longer known to be zero. + void MarkDirty(size_t physical_offset, size_t length); + + /// A new kernel on this memory: everything a previous one used may hold data. + void ForgetContents(); + [[nodiscard]] u8* BackingBasePointer() noexcept { return backing_base; } @@ -88,6 +100,12 @@ private: u8* backing_base{}; u8* virtual_base{}; size_t virtual_base_offset{}; + + static constexpr size_t GranuleShift = 16; ///< 64 KiB of backing per state byte + static constexpr u8 GranuleUsed = 1 << 0; ///< Handed out at least once + static constexpr u8 GranuleDirty = 1 << 1; ///< May hold non-zero data + std::unique_ptr<std::atomic<u8>[]> granules; + size_t num_granules{}; // Windows requires it for kernels whom lack proper support for some functions! bool fallback_buffer{false}; }; diff --git a/src/core/hle/kernel/k_memory_manager.cpp b/src/core/hle/kernel/k_memory_manager.cpp index 743e290541..bed3dd819d 100644 --- a/src/core/hle/kernel/k_memory_manager.cpp +++ b/src/core/hle/kernel/k_memory_manager.cpp @@ -48,6 +48,8 @@ KMemoryManager::KMemoryManager(Core::System& system) } {} void KMemoryManager::Initialize(KVirtualAddress management_region, size_t management_region_size) { + // Memory a previous kernel handed out may still hold its data. + m_system.DeviceMemory().buffer.ForgetContents(); // Clear the management region to zero. const KVirtualAddress management_region_end = management_region + management_region_size; @@ -242,10 +244,21 @@ KPhysicalAddress KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, siz // Open the first reference to the pages. chosen_manager->OpenFirst(allocated_block, num_pages); + this->MarkUsed(allocated_block, num_pages); return allocated_block; } +void KMemoryManager::MarkUsed(KPhysicalAddress address, size_t num_pages) { + m_system.DeviceMemory().buffer.MarkUsed(GetInteger(address) - Core::DramMemoryMap::Base, + num_pages * PageSize); +} + +void KMemoryManager::MarkDirty(KPhysicalAddress address, size_t num_pages) { + m_system.DeviceMemory().buffer.MarkDirty(GetInteger(address) - Core::DramMemoryMap::Base, + num_pages * PageSize); +} + Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages, Pool pool, Direction dir, bool unoptimized, bool random) { // Choose a heap based on our page size request. @@ -297,6 +310,10 @@ Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages, // Only succeed if we allocated as many pages as we wanted. R_UNLESS(num_pages == 0, ResultOutOfMemory); + for (const auto& block : *out) { + this->MarkUsed(block.GetAddress(), block.GetNumPages()); + } + // We succeeded! R_SUCCEED(); } @@ -525,9 +542,10 @@ bool KMemoryManager::Impl::ProcessOptimizedAllocation(KernelCore& kernel, KPhysi // If not, it's new. any_new = true; - // Fill the page. - auto* ptr = device_memory.GetPointer<u8>(m_heap.GetAddress()); - std::memset(ptr + offset * PageSize, fill_pattern, PageSize); + // Fill the page (a zero fill skips pages that are still zero). + device_memory.buffer.ClearBackingRegion( + GetInteger(m_heap.GetAddress()) + offset * PageSize - Core::DramMemoryMap::Base, + PageSize, fill_pattern); } offset++; diff --git a/src/core/hle/kernel/k_memory_manager.h b/src/core/hle/kernel/k_memory_manager.h index 2f4ae7f04f..66b97fef8a 100644 --- a/src/core/hle/kernel/k_memory_manager.h +++ b/src/core/hle/kernel/k_memory_manager.h @@ -100,6 +100,9 @@ public: } void Close(KPhysicalAddress address, size_t num_pages) { + // Pages that may be freed here may hold data: the next allocation clears them. + this->MarkDirty(address, num_pages); + // Repeatedly close references until we've done so for all pages. while (num_pages) { auto& manager = this->GetManager(address); @@ -333,6 +336,9 @@ private: }; private: + void MarkUsed(KPhysicalAddress address, size_t num_pages); + void MarkDirty(KPhysicalAddress address, size_t num_pages); + Impl& GetManager(KPhysicalAddress address) { return m_managers[m_memory_layout.GetPhysicalLinearRegion(address).GetAttributes()]; } diff --git a/src/core/hle/kernel/k_shared_memory.cpp b/src/core/hle/kernel/k_shared_memory.cpp index 61f2872560..cb8877b8b2 100644 --- a/src/core/hle/kernel/k_shared_memory.cpp +++ b/src/core/hle/kernel/k_shared_memory.cpp @@ -59,9 +59,10 @@ Result KSharedMemory::Initialize(KernelCore& kernel, Core::DeviceMemory& device_ // Mark initialized. m_is_initialized = true; - // Clear all pages in the memory. + // Clear all pages in the memory (pages still zero are skipped). for (const auto& block : *m_page_group) { - std::memset(m_device_memory->GetPointer<void>(block.GetAddress()), 0, block.GetSize()); + m_device_memory->buffer.ClearBackingRegion( + GetInteger(block.GetAddress()) - Core::DramMemoryMap::Base, block.GetSize(), 0); } R_SUCCEED(); |
