Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 80 additions & 12 deletions core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -348,15 +348,77 @@ struct DirectMapping {

std::map<std::uintptr_t, DirectMapping> g_directMappings;

#if defined(__linux__)
struct PhysicalSpan {
std::uint64_t end;
std::vector<std::uintptr_t> bases;
};

std::map<std::uint64_t, PhysicalSpan> g_physicalSpans;

void SplitPhysicalSpan(std::uint64_t at) {
auto span = g_physicalSpans.upper_bound(at);
if (span == g_physicalSpans.begin()) return;
--span;
if (span->first == at || span->second.end <= at) return;
g_physicalSpans.emplace_hint(std::next(span), at, PhysicalSpan{span->second.end, span->second.bases});
span->second.end = at;
}
#endif

std::map<std::uintptr_t, DirectMapping>::iterator InsertMapping(std::uintptr_t base, DirectMapping mapping) {
const auto [inserted, added] = g_directMappings.emplace(base, std::move(mapping));
if (!added) throw std::runtime_error("direct mapping inserted over a live one");
#if defined(__linux__)
const auto first = inserted->second.phys;
const auto last = first + (inserted->second.end - base);
SplitPhysicalSpan(first);
SplitPhysicalSpan(last);
auto span = g_physicalSpans.lower_bound(first);
for (auto cursor = first; cursor < last; ++span) {
if (span == g_physicalSpans.end() || span->first > cursor) span = g_physicalSpans.emplace_hint(span, cursor, PhysicalSpan{span == g_physicalSpans.end() ? last : std::min(last, span->first), {}});
auto& bases = span->second.bases;
bases.insert(std::upper_bound(bases.begin(), bases.end(), base), base);
cursor = span->second.end;
}
#endif
return inserted;
}

std::map<std::uintptr_t, DirectMapping>::iterator RemoveMapping(std::map<std::uintptr_t, DirectMapping>::iterator mapping) {
#if defined(__linux__)
const auto base = mapping->first;
const auto first = mapping->second.phys;
const auto last = first + (mapping->second.end - base);
for (auto span = g_physicalSpans.lower_bound(first); span != g_physicalSpans.end() && span->first < last;) {
auto& bases = span->second.bases;
bases.erase(std::lower_bound(bases.begin(), bases.end(), base));
span = bases.empty() ? g_physicalSpans.erase(span) : std::next(span);
}
for (auto span = g_physicalSpans.lower_bound(first); span != g_physicalSpans.end() && span->first <= last;) {
if (span != g_physicalSpans.begin()) {
const auto before = std::prev(span);
if (before->second.end == span->first && before->second.bases == span->second.bases) {
before->second.end = span->second.end;
span = g_physicalSpans.erase(span);
continue;
}
}
++span;
}
#endif
return g_directMappings.erase(mapping);
}

void EraseMappings(std::uintptr_t start, std::uintptr_t end) {
auto it = g_directMappings.lower_bound(start);
if (it != g_directMappings.begin() && std::prev(it)->second.end > start) --it;
while (it != g_directMappings.end() && it->first < end) {
const auto base = it->first;
const auto mapping = it->second;
it = g_directMappings.erase(it);
if (base < start) g_directMappings.emplace(base, DirectMapping{start, mapping.phys, mapping.memoryType, mapping.backing});
if (mapping.end > end) it = g_directMappings.emplace(end, DirectMapping{mapping.end, mapping.phys + end - base, mapping.memoryType, mapping.backing}).first;
it = RemoveMapping(it);
if (base < start) InsertMapping(base, DirectMapping{start, mapping.phys, mapping.memoryType, mapping.backing});
if (mapping.end > end) it = InsertMapping(end, DirectMapping{mapping.end, mapping.phys + end - base, mapping.memoryType, mapping.backing});
}
}

Expand All @@ -369,14 +431,14 @@ void ErasePhysMappings(std::uint64_t first, std::uint64_t last) {
++it;
continue;
}
it = g_directMappings.erase(it);
it = RemoveMapping(it);
if (mapping.phys < first) {
const auto keep = first - mapping.phys;
g_directMappings.emplace(base, DirectMapping{base + keep, mapping.phys, mapping.memoryType, mapping.backing});
InsertMapping(base, DirectMapping{base + keep, mapping.phys, mapping.memoryType, mapping.backing});
}
if (mappingLast > last) {
const auto skip = last - mapping.phys;
it = g_directMappings.emplace(base + skip, DirectMapping{mapping.end, last, mapping.memoryType, mapping.backing}).first;
it = InsertMapping(base + skip, DirectMapping{mapping.end, last, mapping.memoryType, mapping.backing});
}
}
}
Expand All @@ -396,10 +458,16 @@ void ValidatePhysicalRange(std::uint64_t phys, std::size_t len) {
void WatchMapping(std::uintptr_t address, std::size_t len) {
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(reinterpret_cast<const void*>(address), len);
const auto end = address + len;
std::vector<std::uintptr_t> sharing;
for (auto added = g_directMappings.lower_bound(address); added != g_directMappings.end() && added->first < end; ++added) {
const auto addedPhys = added->second.phys;
const auto addedPhysEnd = addedPhys + (added->second.end - added->first);
for (const auto& [base, other] : g_directMappings) {
sharing.clear();
for (auto span = g_physicalSpans.lower_bound(addedPhys); span != g_physicalSpans.end() && span->first < addedPhysEnd; ++span) sharing.insert(sharing.end(), span->second.bases.begin(), span->second.bases.end());
std::sort(sharing.begin(), sharing.end());
sharing.erase(std::unique(sharing.begin(), sharing.end()), sharing.end());
for (const auto base : sharing) {
const auto& other = g_directMappings.at(base);
if ((base >= address && base < end) || other.backing != added->second.backing) continue;
const auto first = std::max(addedPhys, other.phys);
const auto last = std::min(addedPhysEnd, other.phys + (other.end - base));
Expand Down Expand Up @@ -481,7 +549,7 @@ void AddMapping(std::uintptr_t address, std::size_t len, std::uint64_t phys, int
bytes += PS5_PAGE_SIZE;
}
page.backing->Map(address + offset, bytes, page.offset, nativeProt);
g_directMappings.emplace(address + offset, DirectMapping{address + offset + bytes, phys + offset, page.backing->MemoryType(), page.backing});
InsertMapping(address + offset, DirectMapping{address + offset + bytes, phys + offset, page.backing->MemoryType(), page.backing});
offset += bytes;
}
#if defined(__linux__)
Expand Down Expand Up @@ -945,13 +1013,13 @@ int DoMtypeprotect(const void* addr, size_t len, int type, int prot) {
while (it != g_directMappings.end() && it->first < end) {
const auto base = it->first;
const auto mapping = it->second;
it = g_directMappings.erase(it);
if (base < first) g_directMappings.emplace(base, DirectMapping{first, mapping.phys, mapping.memoryType, mapping.backing});
it = RemoveMapping(it);
if (base < first) InsertMapping(base, DirectMapping{first, mapping.phys, mapping.memoryType, mapping.backing});
const auto low = std::max(base, first);
const auto high = std::min(mapping.end, end);
g_directMappings.emplace(low, DirectMapping{high, mapping.phys + low - base, type, mapping.backing});
InsertMapping(low, DirectMapping{high, mapping.phys + low - base, type, mapping.backing});
physical.emplace_back(mapping.phys + low - base, high - low);
if (mapping.end > end) it = g_directMappings.emplace(end, DirectMapping{mapping.end, mapping.phys + end - base, mapping.memoryType, mapping.backing}).first;
if (mapping.end > end) it = InsertMapping(end, DirectMapping{mapping.end, mapping.phys + end - base, mapping.memoryType, mapping.backing});
}
}
for (const auto& [phys, bytes] : physical) DirectMemoryRetype(static_cast<int64_t>(phys), bytes, type);
Expand Down
87 changes: 87 additions & 0 deletions core/libs/tests/GuestMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <fstream>
#include <string>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#endif
#ifdef _WIN32
Expand Down Expand Up @@ -1604,6 +1605,90 @@ static void CheckDirectMemoryWriteWatch() {
Require(sceKernelMunmap(flexible, page * 3) == 0);
Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0);
}

static void CheckDirectMemoryWriteWatchFollowsRemovedMappings() {
if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) {
std::puts("write watch unavailable: removed direct mappings not tested");
return;
}
constexpr std::size_t page = 0x4000;
std::int64_t phys = 0;
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 4, 0, 0, &phys) == 0);
void* whole = nullptr;
Require(sceKernelMapDirectMemory(&whole, page * 4, 3, 0, phys, 0) == 0);
const auto address = reinterpret_cast<std::uintptr_t>(whole);
const auto watched = [](const void* pointer, std::size_t offset) { return GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(reinterpret_cast<std::uintptr_t>(pointer) + offset, page); };
void* pair = nullptr;
Require(sceKernelMapDirectMemory(&pair, page * 2, 3, 0, phys + page, 0) == 0);
Require(watched(whole, 0) && !watched(whole, page) && !watched(whole, page * 2) && watched(whole, page * 3));
Require(!watched(pair, 0) && !watched(pair, page));
Require(sceKernelMunmap(pair, page) == 0);
void* single = nullptr;
Require(sceKernelMapDirectMemory(&single, page, 3, 0, phys + page, 0) == 0);
Require(!watched(single, 0) && watched(whole, 0) && watched(whole, page * 3));
Require(sceKernelMunmap(single, page) == 0);
Require(sceKernelMunmap(static_cast<unsigned char*>(pair) + page, page) == 0);
void* replaced = whole;
Require(sceKernelMapDirectMemory(&replaced, page, 3, 0x10, phys + page * 3, 0) == 0 && replaced == whole);
Require(!watched(whole, 0) && !watched(whole, page * 3));
std::uintptr_t start = 0;
std::uintptr_t end = 0;
std::uint64_t offset = 0;
int type = 0;
Require(QueryDirectMapping(address, &start, &end, &offset, &type) && start == address && end == address + page && offset == static_cast<std::uint64_t>(phys) + page * 3);
void* alone = nullptr;
Require(sceKernelMapDirectMemory(&alone, page, 3, 0, phys, 0) == 0);
Require(watched(alone, 0));
void* again = nullptr;
Require(sceKernelMapDirectMemory(&again, page, 3, 0, phys, 0) == 0);
Require(!watched(alone, 0) && !watched(again, 0));
Require(sceKernelMunmap(again, page) == 0);
Require(sceKernelMunmap(alone, page) == 0);
Require(sceKernelMunmap(whole, page * 4) == 0);
Require(sceKernelReleaseDirectMemory(phys, page * 4) == 0);
}

static double MapAcrossBackingsSeconds(std::int64_t phys, std::size_t backings) {
constexpr std::size_t page = 0x4000;
const auto threadSeconds = [] {
timespec now{};
Require(clock_gettime(CLOCK_THREAD_CPUTIME_ID, &now) == 0);
return static_cast<double>(now.tv_sec) + static_cast<double>(now.tv_nsec) * 1e-9;
};
auto best = std::numeric_limits<double>::max();
for (int attempt = 0; attempt < 3; ++attempt) {
void* mapped = nullptr;
const auto before = threadSeconds();
Require(sceKernelMapDirectMemory(&mapped, page * backings, 3, 0, phys, 0) == 0);
best = std::min(best, threadSeconds() - before);
const auto last = reinterpret_cast<std::uintptr_t>(mapped) + page * (backings - 1);
std::uintptr_t start = 0;
std::uintptr_t end = 0;
std::uint64_t offset = 0;
int type = 0;
Require(QueryDirectMapping(last, &start, &end, &offset, &type) && start == last && end == last + page);
Require(sceKernelMunmap(mapped, page * backings) == 0);
}
return best;
}

static void CheckMappingCostFollowsTheMappingCount() {
constexpr std::size_t page = 0x4000;
constexpr std::size_t few = 1024;
constexpr std::size_t many = few * 16;
std::int64_t phys = 0;
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * many, 0, 0, &phys) == 0);
Require(sceKernelReleaseDirectMemory(phys, page * many) == 0);
for (std::size_t index = 0; index < many; ++index) {
const auto wanted = phys + static_cast<std::int64_t>(page * index);
std::int64_t piece = 0;
Require(sceKernelAllocateDirectMemory(wanted, wanted + static_cast<std::int64_t>(page), page, 0, 0, &piece) == 0 && piece == wanted);
}
const auto small = MapAcrossBackingsSeconds(phys, few);
const auto large = MapAcrossBackingsSeconds(phys, many);
Require(large <= small * 48);
Require(sceKernelReleaseDirectMemory(phys, page * many) == 0);
}
#endif

static void CheckVirtualQuerySplitFlexibleRanges() {
Expand Down Expand Up @@ -1789,6 +1874,8 @@ int main() {
CheckWriteWatchArmedCollect();
CheckWriteWatchFreshCollect();
CheckDirectMemoryWriteWatch();
CheckDirectMemoryWriteWatchFollowsRemovedMappings();
CheckMappingCostFollowsTheMappingCount();
CheckDirectMemoryBackingNeedsNoFilesystem();
CheckDirectMemorySharedBacking();
#endif
Expand Down
Loading