aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common/host_memory.cpp60
-rw-r--r--src/common/host_memory.h18
-rw-r--r--src/core/hle/kernel/k_memory_manager.cpp24
-rw-r--r--src/core/hle/kernel/k_memory_manager.h6
-rw-r--r--src/core/hle/kernel/k_shared_memory.cpp5
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();