memory: write GPU publishes to backed pages and skip reserved holes like sparse reads

This commit is contained in:
nmzik
2026-10-20 15:53:23 +02:00
parent f492027be2
commit 6ad393775f
2 changed files with 63 additions and 56 deletions
+48 -44
View File
@@ -28,9 +28,9 @@ static bool CoalescePlaceholders(uint64_t vaddr, uint64_t size) {
} }
#endif #endif
template <typename Regions, typename Source, typename Gap> template <typename Regions, typename Region, typename Gap>
static void CopySparseRange(const Regions& regions, uint64_t vaddr, void* data, uint64_t size, static void ForEachSparseRange(const Regions& regions, uint64_t vaddr, uint64_t size,
Source&& source, Gap&& gap) { Region&& region, Gap&& gap) {
const auto end = vaddr + size; const auto end = vaddr + size;
auto mapping = regions.lower_bound(vaddr); auto mapping = regions.lower_bound(vaddr);
if (mapping != regions.begin()) { if (mapping != regions.begin()) {
@@ -40,23 +40,34 @@ static void CopySparseRange(const Regions& regions, uint64_t vaddr, void* data,
} }
} }
auto* destination = static_cast<uint8_t*>(data);
auto cursor = vaddr; auto cursor = vaddr;
for (; mapping != regions.end() && mapping->second.vaddr < end; ++mapping) { for (; mapping != regions.end() && mapping->second.vaddr < end; ++mapping) {
const auto& entry = mapping->second; const auto& entry = mapping->second;
const auto start = std::max(vaddr, entry.vaddr); const auto start = std::max(vaddr, entry.vaddr);
const auto bytes = std::min(end, entry.vaddr + entry.size) - start; const auto bytes = std::min(end, entry.vaddr + entry.size) - start;
if (cursor < start) { if (cursor < start) {
gap(cursor, destination + (cursor - vaddr), start - cursor); gap(cursor, start - cursor);
} }
std::memcpy(destination + (start - vaddr), source(entry, start), bytes); region(entry, start, bytes);
cursor = start + bytes; cursor = start + bytes;
} }
if (cursor < end) { if (cursor < end) {
gap(cursor, destination + (cursor - vaddr), end - cursor); gap(cursor, end - cursor);
} }
} }
template <typename Regions, typename Source, typename Gap>
static void CopySparseRange(const Regions& regions, uint64_t vaddr, void* data, uint64_t size,
Source&& source, Gap&& gap) {
auto* destination = static_cast<uint8_t*>(data);
ForEachSparseRange(
regions, vaddr, size,
[&](const auto& entry, uint64_t start, uint64_t bytes) {
std::memcpy(destination + (start - vaddr), source(entry, start), bytes);
},
[&](uint64_t start, uint64_t bytes) { gap(start, destination + (start - vaddr), bytes); });
}
class GuestBackingStore { class GuestBackingStore {
public: public:
enum class FailureReason { enum class FailureReason {
@@ -200,36 +211,16 @@ public:
return false; return false;
} }
// Skip unbacked gaps, mirroring sparse reads that zero-fill them.
std::lock_guard lock(m_mutex); std::lock_guard lock(m_mutex);
const auto end = vaddr + size;
auto first = m_maps.upper_bound(vaddr);
if (first == m_maps.begin()) {
return false;
}
--first;
auto current = vaddr;
for (auto mapping = first; current < end; ++mapping) {
if (mapping == m_maps.end() || current < mapping->second.vaddr ||
current >= mapping->second.vaddr + mapping->second.size) {
return false;
}
const auto& entry = mapping->second;
const auto bytes = std::min(end, entry.vaddr + entry.size) - current;
if (!RangeOk(entry.backing_offset + current - entry.vaddr, bytes)) {
return false;
}
current += bytes;
}
const auto* source = static_cast<const uint8_t*>(data); const auto* source = static_cast<const uint8_t*>(data);
current = vaddr; ForEachSparseRange(
for (auto mapping = first; current < end; ++mapping) { m_maps, vaddr, size,
const auto& entry = mapping->second; [&](const auto& entry, uint64_t start, uint64_t bytes) {
const auto bytes = std::min(end, entry.vaddr + entry.size) - current; std::memcpy(m_backing_base + (entry.backing_offset + start - entry.vaddr),
const auto offset = entry.backing_offset + current - entry.vaddr; source + (start - vaddr), bytes);
std::memcpy(m_backing_base + offset, source + (current - vaddr), bytes); },
current += bytes; [](uint64_t, uint64_t) {});
}
return true; return true;
} }
@@ -775,8 +766,14 @@ public:
}); });
} }
// Writes the backed bytes of a range that holds no private mappings.
bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size) { bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size) {
return m_backing->TryWriteBacking(vaddr, data, size); if (!Owns(vaddr, size)) {
return false;
}
std::shared_lock lock(m_mutex);
return !OverlapsPrivateUnlocked(vaddr, size) &&
m_backing->TryWriteBacking(vaddr, data, size);
} }
bool TryReadBacking(uint64_t vaddr, void* data, uint64_t size) { bool TryReadBacking(uint64_t vaddr, void* data, uint64_t size) {
@@ -790,17 +787,10 @@ public:
} }
std::shared_lock lock(m_mutex); std::shared_lock lock(m_mutex);
if constexpr (!copy_private) { if constexpr (!copy_private) {
auto mapping = m_mapped.upper_bound(vaddr); if (OverlapsPrivateUnlocked(vaddr, size)) {
if (mapping != m_mapped.begin()) {
--mapping;
}
for (; mapping != m_mapped.end() && mapping->second.vaddr < vaddr + size; ++mapping) {
const auto& region = mapping->second;
if (region.type == MappingType::Private && region.vaddr + region.size > vaddr) {
return false; return false;
} }
} }
}
return m_backing->TryReadSparseBacking( return m_backing->TryReadSparseBacking(
vaddr, data, size, [&](uint64_t start, uint8_t* destination, uint64_t bytes) { vaddr, data, size, [&](uint64_t start, uint8_t* destination, uint64_t bytes) {
const auto zero = [](uint64_t, uint8_t* output, uint64_t length) { const auto zero = [](uint64_t, uint8_t* output, uint64_t length) {
@@ -1111,6 +1101,20 @@ private:
return true; return true;
} }
[[nodiscard]] bool OverlapsPrivateUnlocked(uint64_t vaddr, uint64_t size) const {
auto mapping = m_mapped.upper_bound(vaddr);
if (mapping != m_mapped.begin()) {
--mapping;
}
for (; mapping != m_mapped.end() && mapping->second.vaddr < vaddr + size; ++mapping) {
const auto& region = mapping->second;
if (region.type == MappingType::Private && region.vaddr + region.size > vaddr) {
return true;
}
}
return false;
}
bool AddMappedUnlocked(uint64_t vaddr, uint64_t size, MappingType type) { bool AddMappedUnlocked(uint64_t vaddr, uint64_t size, MappingType type) {
if (size == 0 || UINT64_MAX - vaddr < size) { if (size == 0 || UINT64_MAX - vaddr < size) {
return false; return false;
+12 -9
View File
@@ -1596,20 +1596,23 @@ void TestDirectMapQueryOffsetAndPartialMunmap() {
ExpectUnmapped(test, base + SceKernelPageSize); ExpectUnmapped(test, base + SceKernelPageSize);
constexpr uint64_t transaction_sentinel = 0x5452414e53414354ull; // "TRANSACT" constexpr uint64_t transaction_sentinel = 0x5452414e53414354ull; // "TRANSACT"
constexpr uint64_t rejected_write = 0x4e4f504152544941ull; // "NOPARTIA" constexpr uint64_t sparse_write = 0x4e4f504152544941ull; // "NOPARTIA"
constexpr uint64_t backed_prefix_mask = 0xffffffffull;
const auto crossing_address = base + SceKernelPageSize - sizeof(uint32_t); const auto crossing_address = base + SceKernelPageSize - sizeof(uint32_t);
std::memcpy(reinterpret_cast<void*>(alias_base + SceKernelPageSize - sizeof(uint32_t)), std::memcpy(reinterpret_cast<void*>(alias_base + SceKernelPageSize - sizeof(uint32_t)),
&transaction_sentinel, sizeof(transaction_sentinel)); &transaction_sentinel, sizeof(transaction_sentinel));
Check(test, Check(test,
!Libs::LibKernel::Memory::TryWriteBacking(crossing_address, &rejected_write, Libs::LibKernel::Memory::TryWriteBacking(crossing_address, &sparse_write,
sizeof(rejected_write)), sizeof(sparse_write)),
"TryWriteBacking should reject a range crossing an unmapped span"); "TryWriteBacking should write a range crossing an unmapped span");
uint64_t backing_after_rejected_write = 0; uint64_t backing_after_sparse_write = 0;
std::memcpy(&backing_after_rejected_write, std::memcpy(&backing_after_sparse_write,
reinterpret_cast<const void*>(alias_base + SceKernelPageSize - sizeof(uint32_t)), reinterpret_cast<const void*>(alias_base + SceKernelPageSize - sizeof(uint32_t)),
sizeof(backing_after_rejected_write)); sizeof(backing_after_sparse_write));
Check(test, backing_after_rejected_write == transaction_sentinel, Check(test,
"failed backing writes must not modify a validated prefix"); backing_after_sparse_write == ((sparse_write & backed_prefix_mask) |
(transaction_sentinel & ~backed_prefix_mask)),
"sparse backing writes must fill the backed prefix and skip the unmapped span");
uint64_t rejected_read = transaction_sentinel; uint64_t rejected_read = transaction_sentinel;
Check(test, Check(test,
!Libs::LibKernel::Memory::TryReadBacking(crossing_address, &rejected_read, !Libs::LibKernel::Memory::TryReadBacking(crossing_address, &rejected_read,