perf(agc): track guest memory ownership + optimize gpu synchronization
This commit is contained in:
parent
e392506e0a
commit
40df5282e6
|
|
@ -8,6 +8,7 @@
|
|||
namespace AgcDriver::GuestMemory {
|
||||
|
||||
void CheckRange(const void* pointer, std::size_t bytes, std::size_t alignment, bool writable = false);
|
||||
void CheckGpuRange(const void* pointer, std::size_t bytes, std::size_t alignment, bool writable = false);
|
||||
void Read(std::uint64_t address, std::span<std::byte> destination, std::size_t alignment = 1);
|
||||
void Write(std::uint64_t address, std::span<const std::byte> source, std::size_t alignment = 1);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DrawQueue.hpp"
|
||||
#include "prx/libc/include/GuestMemoryTracking.hpp"
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
void VulkanDevice::AcquireGpuMemory() {
|
||||
std::lock_guard memoryLock(GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix());
|
||||
PerformanceTimer timing("Vulkan.AcquireGpuMemory");
|
||||
const auto context = graphicsContext();
|
||||
Graphics::Require(context.drawQueue != nullptr, "GPU memory barrier requires a draw queue");
|
||||
context.drawQueue->RecordMemoryBarrier(context);
|
||||
timing.Mark("barrier_record");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp"
|
||||
#include "prx/libc/include/Shutdown.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/MemoryAccessScope.hpp"
|
||||
#include "prx/libc/include/GuestMemoryTracking.hpp"
|
||||
#include <bit>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
|
@ -279,7 +280,7 @@ private:
|
|||
std::map<std::uint64_t, std::shared_ptr<const ShaderSnapshot>> shaders;
|
||||
std::map<std::uint32_t, QueueState> queues;
|
||||
std::map<std::uint32_t, std::shared_ptr<IVideoOutput>> outputs;
|
||||
std::mutex gpuMutex;
|
||||
std::recursive_mutex& gpuMutex = GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix();
|
||||
std::shared_ptr<VulkanDevice> device;
|
||||
std::uint64_t accepted = 0;
|
||||
std::uint64_t completed = 0;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/MemoryAccessScope.hpp"
|
||||
#include "prx/libc/include/GuestMemoryTracking.hpp"
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace AgcDriver::GuestMemory {
|
||||
|
||||
void CheckGpuRange(const void* pointer, std::size_t bytes, std::size_t alignment, bool writable) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(pointer);
|
||||
if (alignment == 0 || address == 0 || address % alignment != 0 || bytes > std::numeric_limits<std::uintptr_t>::max() - address) throw std::invalid_argument("invalid guest GPU memory range");
|
||||
const MemoryAccessScope suspended(nullptr, nullptr);
|
||||
GuestMemoryTracking::GuestMemoryTrackingValidate_nid_postfix(address, bytes, [writable](std::uint64_t first, std::size_t count) {
|
||||
CheckRange(reinterpret_cast<const void*>(first), count, 1, writable);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libc/include/GuestMemoryTracking.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/MemoryAccessScope.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include <algorithm>
|
||||
|
|
@ -29,6 +30,7 @@ void CheckRange(const void* pointer, std::size_t bytes, std::size_t alignment, b
|
|||
require(address != 0 && address % alignment == 0, "null or misaligned address");
|
||||
require(bytes <= std::numeric_limits<std::uintptr_t>::max() - address, "address range overflow");
|
||||
MemoryAccessScope::Resolve(address, bytes, writable);
|
||||
GuestMemoryTracking::GuestMemoryTrackingResolve_nid_postfix(address, bytes, writable);
|
||||
auto cursor = address;
|
||||
const auto end = address + bytes;
|
||||
#ifdef _WIN32
|
||||
|
|
|
|||
|
|
@ -165,7 +165,10 @@ struct VulkanDevice::State {
|
|||
}
|
||||
|
||||
~State() {
|
||||
std::lock_guard memoryLock(GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix());
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
if (drawQueue) drawQueue->Wait();
|
||||
if (renderCache) renderCache->Flush();
|
||||
const auto idle = reinterpret_cast<PFN_vkDeviceWaitIdle>(deviceProc(device, "vkDeviceWaitIdle"))(device);
|
||||
if (idle != VK_SUCCESS && idle != VK_ERROR_DEVICE_LOST) std::terminate();
|
||||
drawQueue.reset();
|
||||
|
|
@ -490,41 +493,26 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
VulkanDevice::~VulkanDevice() = default;
|
||||
|
||||
void VulkanDevice::WaitIdle() {
|
||||
std::lock_guard memoryLock(GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix());
|
||||
PerformanceTimer timing("Vulkan.WaitIdle");
|
||||
state->drawQueue->Wait();
|
||||
timing.Mark("draw_wait");
|
||||
state->renderCache->Flush();
|
||||
timing.Mark("render_cache_flush");
|
||||
check(state->DeviceFunction<PFN_vkDeviceWaitIdle>("vkDeviceWaitIdle")(state->device), "vkDeviceWaitIdle");
|
||||
timing.Mark("device_wait");
|
||||
}
|
||||
|
||||
void VulkanDevice::WaitDraws() {
|
||||
std::lock_guard memoryLock(GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix());
|
||||
PerformanceTimer timing("Vulkan.WaitDraws");
|
||||
state->drawQueue->Wait();
|
||||
}
|
||||
|
||||
void VulkanDevice::AcquireGpuMemory() {
|
||||
PerformanceTimer timing("Vulkan.AcquireGpuMemory");
|
||||
state->drawQueue->Wait();
|
||||
timing.Mark("draw_wait");
|
||||
const auto context = graphicsContext();
|
||||
Graphics::CommandBatch batch(context);
|
||||
VkMemoryBarrier barrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier")(batch.Handle(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr);
|
||||
batch.SubmitAndWait();
|
||||
timing.Mark("gpu_barrier");
|
||||
state->renderCache->InvalidateClean();
|
||||
timing.Mark("invalidate_clean");
|
||||
}
|
||||
|
||||
void* VulkanDevice::Window() const {
|
||||
return state->window;
|
||||
}
|
||||
|
||||
void VulkanDevice::Resize(std::uint32_t width, std::uint32_t height) {
|
||||
std::lock_guard memoryLock(GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix());
|
||||
require(state->swapchain != VK_NULL_HANDLE, "cannot resize an unavailable swapchain");
|
||||
if (width == 0 || height == 0) {
|
||||
state->extent = {0, 0};
|
||||
|
|
@ -592,7 +580,10 @@ void VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) {
|
|||
}
|
||||
|
||||
void VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaque, std::span<const std::byte> pixels, const DisplayBuffer* display) {
|
||||
std::lock_guard memoryLock(GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix());
|
||||
PerformanceTimer timing("Vulkan.Present");
|
||||
state->drawQueue->Wait();
|
||||
timing.Mark("draw_wait");
|
||||
require(state->swapchain != VK_NULL_HANDLE, "device has no swapchain");
|
||||
require(state->extent.width != 0 && state->extent.height != 0, "output window is minimized");
|
||||
std::shared_ptr<Graphics::ResidentColor> resident;
|
||||
|
|
@ -746,6 +737,7 @@ Graphics::Context VulkanDevice::graphicsContext() const {
|
|||
}
|
||||
|
||||
void VulkanDevice::ResolveMemory(std::uint64_t address, std::size_t bytes, bool writable) {
|
||||
std::lock_guard memoryLock(GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix());
|
||||
state->drawQueue->Resolve(address, bytes);
|
||||
state->renderCache->Resolve(address, bytes, writable);
|
||||
}
|
||||
|
|
@ -756,6 +748,7 @@ void VulkanDevice::Draw(const Graphics::State& graphics, const Pm4::DrawParamete
|
|||
}
|
||||
|
||||
void VulkanDevice::EnqueueDraw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span<const Graphics::CompiledShader> shaders, std::span<const Graphics::GuestMemorySnapshot> snapshots) {
|
||||
std::lock_guard memoryLock(GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix());
|
||||
const GuestMemory::MemoryAccessScope memoryScope(this, [](void* context, std::uint64_t address, std::size_t bytes, bool writable) {
|
||||
static_cast<VulkanDevice*>(context)->ResolveMemory(address, bytes, writable);
|
||||
});
|
||||
|
|
@ -764,6 +757,7 @@ void VulkanDevice::EnqueueDraw(const Graphics::State& graphics, const Pm4::DrawP
|
|||
}
|
||||
|
||||
void VulkanDevice::Dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::span<const Graphics::GuestMemorySnapshot> snapshots) {
|
||||
std::lock_guard memoryLock(GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix());
|
||||
PerformanceTimer timing("Vulkan.Dispatch");
|
||||
const GuestMemory::MemoryAccessScope memoryScope(this, [](void* context, std::uint64_t address, std::size_t bytes, bool writable) {
|
||||
static_cast<VulkanDevice*>(context)->ResolveMemory(address, bytes, writable);
|
||||
|
|
@ -782,6 +776,8 @@ void VulkanDevice::Dispatch(const ShaderRecompiler::RecompileResult& shader, std
|
|||
if (x > limit[0] || y > limit[1] || z > limit[2]) {
|
||||
throw std::runtime_error("Vulkan dispatch: workgroup count exceeds device limits");
|
||||
}
|
||||
state->drawQueue->Wait();
|
||||
timing.Mark("draw_wait");
|
||||
const auto destroyModule = state->DeviceFunction<PFN_vkDestroyShaderModule>("vkDestroyShaderModule");
|
||||
const auto destroyLayout = state->DeviceFunction<PFN_vkDestroyPipelineLayout>("vkDestroyPipelineLayout");
|
||||
const auto destroyPipeline = state->DeviceFunction<PFN_vkDestroyPipeline>("vkDestroyPipeline");
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ public:
|
|||
void Flush();
|
||||
void Resolve(std::uint64_t address, std::size_t bytes);
|
||||
void Wait();
|
||||
void WaitGpu();
|
||||
void Collect();
|
||||
void RecordMemoryBarrier(const Context& context);
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
|
|
@ -22,7 +25,9 @@ private:
|
|||
struct Batch {
|
||||
std::vector<Entry> entries;
|
||||
std::unique_ptr<CommandBatch> commands;
|
||||
bool hasBarrier = false;
|
||||
};
|
||||
void retire(Batch batch);
|
||||
Batch recording;
|
||||
std::vector<Batch> pending;
|
||||
std::vector<std::unique_ptr<CommandBatch>> available;
|
||||
|
|
|
|||
|
|
@ -16,11 +16,13 @@ public:
|
|||
void Detile(VkCommandBuffer commands, bool swapRedBlue = false);
|
||||
void Tile(VkCommandBuffer commands);
|
||||
void WriteBack(std::uint64_t address);
|
||||
void WriteBackTracked(std::uint64_t address);
|
||||
bool MatchesGuest(std::uint64_t address);
|
||||
VkBuffer LinearBuffer() const;
|
||||
RenderTarget& Target(const ColorTarget& color, bool blending);
|
||||
|
||||
private:
|
||||
void writeBack(std::uint64_t address, bool tracked);
|
||||
void prepare(std::uint32_t width, std::uint32_t height, ColorTileMode mode);
|
||||
void convert(VkCommandBuffer commands, bool toTiled, bool swapRedBlue);
|
||||
void release() noexcept;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_RENDERCACHE_HPP
|
||||
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GpuColorTransfer.hpp"
|
||||
#include "prx/libc/include/GuestMemoryTracking.hpp"
|
||||
#include <map>
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
|
@ -9,6 +10,7 @@ namespace AgcDriver::Graphics {
|
|||
class ResidentColor {
|
||||
public:
|
||||
ResidentColor(const Context& context, const ColorTarget& color);
|
||||
~ResidentColor();
|
||||
void Begin(VkCommandBuffer commands);
|
||||
void Download(VkCommandBuffer commands);
|
||||
void Commit();
|
||||
|
|
@ -18,28 +20,30 @@ public:
|
|||
bool Valid() const { return valid; }
|
||||
bool Dirty() const { return dirty; }
|
||||
std::uint64_t Generation() const { return generation; }
|
||||
bool MatchesGuest();
|
||||
void Invalidate() { valid = false; }
|
||||
void Invalidate();
|
||||
void ReleaseMemory();
|
||||
bool SharesPages(const ColorTarget& other) const;
|
||||
|
||||
private:
|
||||
void resolveCpuAccess(GuestMemoryTracking::Access access);
|
||||
Context context;
|
||||
ColorTarget color;
|
||||
GpuColorTransfer transfer;
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
bool valid = false;
|
||||
bool dirty = false;
|
||||
bool hasSnapshot = false;
|
||||
std::uint64_t generation = 0;
|
||||
std::unique_ptr<GuestMemoryTracking::Watch> memoryWatch;
|
||||
};
|
||||
|
||||
class RenderCache {
|
||||
public:
|
||||
explicit RenderCache(const Context& context) : context(context) {}
|
||||
~RenderCache();
|
||||
std::shared_ptr<ResidentColor> Get(const ColorTarget& color, bool blending);
|
||||
std::shared_ptr<ResidentColor> Find(std::uint64_t address) const;
|
||||
void Resolve(std::uint64_t address, std::size_t bytes, bool writable);
|
||||
void Flush();
|
||||
void InvalidateClean();
|
||||
|
||||
private:
|
||||
Context context;
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ public:
|
|||
void SubmitAndWait();
|
||||
void Submit();
|
||||
void Wait();
|
||||
bool IsComplete();
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
#include "prx/libSceAgcDriver/Graphics/include/GpuColorTransfer.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libc/include/GuestMemoryBacking.hpp"
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
void GpuColorTransfer::WriteBack(std::uint64_t address) {
|
||||
writeBack(address, false);
|
||||
}
|
||||
|
||||
void GpuColorTransfer::WriteBackTracked(std::uint64_t address) {
|
||||
writeBack(address, true);
|
||||
}
|
||||
|
||||
void GpuColorTransfer::writeBack(std::uint64_t address, bool tracked) {
|
||||
PerformanceTimer timing("ColorTransfer.WriteBack");
|
||||
Require(tiled != nullptr && readback != nullptr, "color transfer is not prepared for writeback");
|
||||
const ColorTargetLayout layout(width, height, mode);
|
||||
Require(address != 0 && address % layout.Alignment() == 0, "misaligned color writeback address");
|
||||
timing.Mark("validate");
|
||||
readback->Invalidate();
|
||||
if (tracked) GuestMemoryBacking::GuestMemoryBackingWrite_nid_postfix(address, readback->Bytes().data(), readback->Bytes().size());
|
||||
else GuestMemory::Write(address, readback->Bytes(), layout.Alignment());
|
||||
timing.Mark("guest_write", layout.Bytes());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
bool CommandBatch::IsComplete() {
|
||||
Require(submitted, "cannot query an unsubmitted command batch");
|
||||
if (!pending) return true;
|
||||
const auto result = context.Function<PFN_vkGetFenceStatus>("vkGetFenceStatus")(context.device, fence);
|
||||
if (result == VK_NOT_READY) return false;
|
||||
Check(result, "vkGetFenceStatus graphics");
|
||||
pending = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
#include "prx/libSceAgcDriver/Graphics/include/DrawQueue.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/MemoryAccessScope.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
void DrawQueue::retire(Batch batch) {
|
||||
PerformanceTimer timing("Graphics.DrawQueue.Retire");
|
||||
const GuestMemory::MemoryAccessScope suspended(nullptr, nullptr);
|
||||
Require(drawCount >= batch.entries.size(), "draw queue completion count underflow");
|
||||
drawCount -= batch.entries.size();
|
||||
for (auto& entry : batch.entries) entry.resources->WriteBack();
|
||||
timing.Mark("resources_writeback");
|
||||
batch.entries.clear();
|
||||
available.push_back(std::move(batch.commands));
|
||||
timing.Mark("resources_release");
|
||||
}
|
||||
|
||||
void DrawQueue::Collect() {
|
||||
while (!pending.empty() && pending.front().commands->IsComplete()) {
|
||||
auto batch = std::move(pending.front());
|
||||
pending.erase(pending.begin());
|
||||
retire(std::move(batch));
|
||||
}
|
||||
}
|
||||
|
||||
void DrawQueue::WaitGpu() {
|
||||
Flush();
|
||||
for (auto& batch : pending) batch.commands->Wait();
|
||||
}
|
||||
|
||||
void DrawQueue::Wait() {
|
||||
if (pending.empty() && !recording.commands) return;
|
||||
PerformanceTimer timing("Graphics.DrawQueue.Wait");
|
||||
Flush();
|
||||
timing.Mark("submit");
|
||||
while (!pending.empty()) {
|
||||
pending.front().commands->Wait();
|
||||
timing.Mark("fence_wait");
|
||||
auto batch = std::move(pending.front());
|
||||
pending.erase(pending.begin());
|
||||
retire(std::move(batch));
|
||||
timing.Mark("retire");
|
||||
}
|
||||
}
|
||||
|
||||
void DrawQueue::RecordMemoryBarrier(const Context& context) {
|
||||
const auto commands = Begin(context);
|
||||
VkMemoryBarrier barrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr);
|
||||
recording.hasBarrier = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ DrawQueue::~DrawQueue() {
|
|||
}
|
||||
|
||||
VkCommandBuffer DrawQueue::Begin(const Context& context) {
|
||||
Collect();
|
||||
if (drawCount >= 64) Wait();
|
||||
if (!recording.commands) {
|
||||
if (available.empty()) recording.commands = std::make_unique<CommandBatch>(context);
|
||||
|
|
@ -32,7 +33,7 @@ void DrawQueue::Enqueue(std::shared_ptr<ShaderResources> resources, std::shared_
|
|||
|
||||
void DrawQueue::Flush() {
|
||||
if (!recording.commands) return;
|
||||
Require(!recording.entries.empty(), "cannot submit an incomplete draw batch");
|
||||
Require(!recording.entries.empty() || recording.hasBarrier, "cannot submit an incomplete draw batch");
|
||||
pending.push_back(std::move(recording));
|
||||
recording = Batch{};
|
||||
pending.back().commands->Submit();
|
||||
|
|
@ -43,23 +44,5 @@ void DrawQueue::Resolve(std::uint64_t address, std::size_t bytes) {
|
|||
if (std::any_of(recording.entries.begin(), recording.entries.end(), overlaps) || std::any_of(pending.begin(), pending.end(), [&](const auto& batch) { return std::any_of(batch.entries.begin(), batch.entries.end(), overlaps); })) Wait();
|
||||
}
|
||||
|
||||
void DrawQueue::Wait() {
|
||||
if (pending.empty() && !recording.commands) return;
|
||||
PerformanceTimer timing("Graphics.DrawQueue.Wait");
|
||||
const GuestMemory::MemoryAccessScope suspended(nullptr, nullptr);
|
||||
Flush();
|
||||
timing.Mark("submit");
|
||||
for (auto& batch : pending) {
|
||||
batch.commands->Wait();
|
||||
timing.Mark("fence_wait");
|
||||
for (auto& entry : batch.entries) entry.resources->WriteBack();
|
||||
timing.Mark("resources_writeback");
|
||||
available.push_back(std::move(batch.commands));
|
||||
batch.entries.clear();
|
||||
timing.Mark("resources_release");
|
||||
}
|
||||
pending.clear();
|
||||
drawCount = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,16 +149,6 @@ void GpuColorTransfer::Tile(VkCommandBuffer commands) {
|
|||
barrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &after, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
void GpuColorTransfer::WriteBack(std::uint64_t address) {
|
||||
PerformanceTimer timing("ColorTransfer.WriteBack");
|
||||
Require(tiled != nullptr, "color transfer is not prepared for writeback");
|
||||
const ColorTargetLayout layout(width, height, mode);
|
||||
timing.Mark("validate");
|
||||
readback->Invalidate();
|
||||
GuestMemory::Write(address, readback->Bytes(), layout.Alignment());
|
||||
timing.Mark("guest_write", layout.Bytes());
|
||||
}
|
||||
|
||||
VkBuffer GpuColorTransfer::LinearBuffer() const {
|
||||
Require(linear != nullptr, "color transfer is not prepared");
|
||||
return linear->Handle();
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
ResidentColor::ResidentColor(const Context& context, const ColorTarget& color) : context(context), color(color), transfer(context) {}
|
||||
|
||||
void ResidentColor::Transition(VkCommandBuffer commands, VkImageLayout next) {
|
||||
VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||
barrier.srcAccessMask = layout == VK_IMAGE_LAYOUT_UNDEFINED ? 0u : VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
|
|
@ -26,7 +24,9 @@ void ResidentColor::Begin(VkCommandBuffer commands) {
|
|||
PerformanceTimer timing("Graphics.ResidentColor.Begin");
|
||||
Require(generation != std::numeric_limits<std::uint64_t>::max(), "resident color generation overflow");
|
||||
++generation;
|
||||
if (!valid && !MatchesGuest()) {
|
||||
Require(memoryWatch != nullptr, "render target memory ownership was released");
|
||||
if (!valid) {
|
||||
memoryWatch->Protect(GuestMemoryTracking::Protection::Read);
|
||||
const GuestMemory::MemoryAccessScope suspended(nullptr, nullptr);
|
||||
transfer.Upload(color.address, color.extent.width, color.extent.height, color.tileMode);
|
||||
transfer.Detile(commands);
|
||||
|
|
@ -42,7 +42,7 @@ void ResidentColor::Begin(VkCommandBuffer commands) {
|
|||
Transition(commands, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
valid = true;
|
||||
dirty = true;
|
||||
hasSnapshot = false;
|
||||
memoryWatch->Protect(GuestMemoryTracking::Protection::None);
|
||||
}
|
||||
|
||||
void ResidentColor::Download(VkCommandBuffer commands) {
|
||||
|
|
@ -62,14 +62,9 @@ void ResidentColor::Download(VkCommandBuffer commands) {
|
|||
void ResidentColor::Commit() {
|
||||
if (!dirty) return;
|
||||
const GuestMemory::MemoryAccessScope suspended(nullptr, nullptr);
|
||||
transfer.WriteBack(color.address);
|
||||
transfer.WriteBackTracked(color.address);
|
||||
dirty = false;
|
||||
hasSnapshot = true;
|
||||
}
|
||||
|
||||
bool ResidentColor::MatchesGuest() {
|
||||
const GuestMemory::MemoryAccessScope suspended(nullptr, nullptr);
|
||||
return hasSnapshot && transfer.MatchesGuest(color.address);
|
||||
memoryWatch->Protect(GuestMemoryTracking::Protection::Read);
|
||||
}
|
||||
|
||||
std::shared_ptr<ResidentColor> RenderCache::Get(const ColorTarget& color, bool blending) {
|
||||
|
|
@ -82,16 +77,18 @@ std::shared_ptr<ResidentColor> RenderCache::Get(const ColorTarget& color, bool b
|
|||
}
|
||||
for (auto it = entries.begin(); it != entries.end();) {
|
||||
const auto& previous = it->second->Description();
|
||||
if (color.address >= previous.address + previous.bytes || previous.address >= color.address + color.bytes) {
|
||||
if (!it->second->SharesPages(color)) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
if (previous.address == color.address && previous.bytes == color.bytes && previous.extent.width == color.extent.width && previous.extent.height == color.extent.height && previous.format == color.format && previous.tileMode == color.tileMode) return it->second;
|
||||
Resolve(previous.address, previous.bytes, true);
|
||||
it->second->ReleaseMemory();
|
||||
it = entries.erase(it);
|
||||
}
|
||||
if (entries.size() >= 64) {
|
||||
Flush();
|
||||
for (const auto& [address, resident] : entries) resident->ReleaseMemory();
|
||||
entries.clear();
|
||||
}
|
||||
auto entry = std::make_shared<ResidentColor>(context, color);
|
||||
|
|
@ -102,7 +99,7 @@ std::shared_ptr<ResidentColor> RenderCache::Get(const ColorTarget& color, bool b
|
|||
std::shared_ptr<ResidentColor> RenderCache::Find(std::uint64_t address) const {
|
||||
const auto it = entries.find(address);
|
||||
if (it != entries.end() && context.drawQueue) context.drawQueue->Resolve(address, it->second->Description().bytes);
|
||||
return it != entries.end() && (it->second->Valid() || it->second->MatchesGuest()) ? it->second : nullptr;
|
||||
return it != entries.end() && it->second->Valid() ? it->second : nullptr;
|
||||
}
|
||||
|
||||
void RenderCache::Resolve(std::uint64_t address, std::size_t bytes, bool writable) {
|
||||
|
|
@ -131,10 +128,4 @@ void RenderCache::Flush() {
|
|||
Resolve(0, std::numeric_limits<std::size_t>::max(), true);
|
||||
}
|
||||
|
||||
void RenderCache::InvalidateClean() {
|
||||
for (const auto& [address, entry] : entries) {
|
||||
if (!entry->Dirty()) entry->Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
#include "prx/libSceAgcDriver/Graphics/include/RenderCache.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DrawQueue.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include <limits>
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
ResidentColor::ResidentColor(const Context& context, const ColorTarget& color) : context(context), color(color), transfer(context) {
|
||||
memoryWatch = std::make_unique<GuestMemoryTracking::Watch>(color.address, color.bytes, this, [](void* owner, GuestMemoryTracking::Access access) {
|
||||
static_cast<ResidentColor*>(owner)->resolveCpuAccess(access);
|
||||
});
|
||||
}
|
||||
|
||||
ResidentColor::~ResidentColor() = default;
|
||||
|
||||
void ResidentColor::Invalidate() {
|
||||
Require(!dirty, "cannot discard GPU-owned render target contents");
|
||||
if (memoryWatch) memoryWatch->Protect(GuestMemoryTracking::Protection::ReadWrite);
|
||||
valid = false;
|
||||
}
|
||||
|
||||
void ResidentColor::ReleaseMemory() {
|
||||
Require(!dirty, "cannot release GPU-owned render target memory");
|
||||
Invalidate();
|
||||
memoryWatch.reset();
|
||||
}
|
||||
|
||||
bool ResidentColor::SharesPages(const ColorTarget& other) const {
|
||||
const auto pageSize = GuestMemoryTracking::GuestMemoryTrackingPageSize_nid_postfix();
|
||||
Require(color.bytes != 0 && other.bytes != 0, "empty render target page range");
|
||||
Require(color.bytes <= std::numeric_limits<std::uint64_t>::max() - color.address && other.bytes <= std::numeric_limits<std::uint64_t>::max() - other.address, "render target page range overflow");
|
||||
const auto first = color.address / pageSize;
|
||||
const auto last = (color.address + color.bytes - 1) / pageSize;
|
||||
const auto otherFirst = other.address / pageSize;
|
||||
const auto otherLast = (other.address + other.bytes - 1) / pageSize;
|
||||
return first <= otherLast && otherFirst <= last;
|
||||
}
|
||||
|
||||
void ResidentColor::resolveCpuAccess(GuestMemoryTracking::Access access) {
|
||||
PerformanceTimer timing("Graphics.RenderMemory.CpuAccess");
|
||||
Require(memoryWatch != nullptr && context.drawQueue != nullptr, "render target memory resolver is unavailable");
|
||||
if (dirty || access == GuestMemoryTracking::Access::Invalidate) {
|
||||
context.drawQueue->WaitGpu();
|
||||
timing.Mark("draw_wait");
|
||||
}
|
||||
if (dirty) {
|
||||
CommandBatch batch(context);
|
||||
Download(batch.Handle());
|
||||
batch.SubmitAndWait();
|
||||
timing.Mark("download_wait");
|
||||
Commit();
|
||||
timing.Mark("guest_writeback");
|
||||
}
|
||||
if (access != GuestMemoryTracking::Access::Read) Invalidate();
|
||||
if (access == GuestMemoryTracking::Access::Invalidate) context.drawQueue->Wait();
|
||||
}
|
||||
|
||||
RenderCache::~RenderCache() {
|
||||
for (const auto& [address, entry] : entries) entry->ReleaseMemory();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -208,7 +208,7 @@ State DecodeState(const QueueState& queue) {
|
|||
Require((high & ~0xffu) == 0, "invalid color address extension");
|
||||
result.color.address = (static_cast<std::uint64_t>(high) << 40u) | (static_cast<std::uint64_t>(read(cx, 0x318)) << 8u);
|
||||
result.color.bytes = colorLayout.Bytes();
|
||||
GuestMemory::CheckRange(reinterpret_cast<const void*>(result.color.address), result.color.bytes, colorLayout.Alignment(), true);
|
||||
GuestMemory::CheckGpuRange(reinterpret_cast<const void*>(result.color.address), result.color.bytes, colorLayout.Alignment(), true);
|
||||
result.color.format = swap == 0 ? (number == 0 ? VK_FORMAT_R8G8B8A8_UNORM : VK_FORMAT_R8G8B8A8_SRGB) : (number == 0 ? VK_FORMAT_B8G8R8A8_UNORM : VK_FORMAT_B8G8R8A8_SRGB);
|
||||
result.color.componentMapping = 0xe4u;
|
||||
result.renderExtent = result.color.extent;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,13 @@ add_custom_command(
|
|||
|
||||
foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests agc_driver_bda_device_tests)
|
||||
if(TARGET ${agcTarget})
|
||||
target_sources(${agcTarget} PRIVATE Graphics/src/BufferPool.cpp)
|
||||
target_sources(${agcTarget} PRIVATE Graphics/src/BufferPool.cpp Graphics/src/CommandCompletion.cpp)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests agc_driver_bda_device_tests)
|
||||
if(TARGET ${agcTarget})
|
||||
target_sources(${agcTarget} PRIVATE Execution/src/GuestGpuRange.cpp)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
|
|
@ -24,7 +30,7 @@ endforeach()
|
|||
|
||||
foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests agc_driver_bda_device_tests)
|
||||
if(TARGET ${agcTarget})
|
||||
target_sources(${agcTarget} PRIVATE Graphics/src/GpuColorTransfer.cpp ${agcColorTransferHeader})
|
||||
target_sources(${agcTarget} PRIVATE Graphics/src/GpuColorTransfer.cpp Graphics/src/ColorMemoryWriteback.cpp ${agcColorTransferHeader})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
|
|
@ -37,12 +43,12 @@ endif()
|
|||
|
||||
foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests)
|
||||
if(TARGET ${agcTarget})
|
||||
target_sources(${agcTarget} PRIVATE Graphics/src/RenderCache.cpp Graphics/src/DrawQueue.cpp Graphics/src/RenderTexture.cpp Graphics/src/DescriptorCache.cpp)
|
||||
target_sources(${agcTarget} PRIVATE Graphics/src/RenderCache.cpp Graphics/src/RenderMemoryOwnership.cpp Graphics/src/DrawQueue.cpp Graphics/src/DrawCompletion.cpp Graphics/src/RenderTexture.cpp Graphics/src/DescriptorCache.cpp)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test)
|
||||
if(TARGET ${agcTarget})
|
||||
target_sources(${agcTarget} PRIVATE Execution/src/PresentationImage.cpp Graphics/src/GraphicsPipelineCache.cpp)
|
||||
target_sources(${agcTarget} PRIVATE Execution/src/PresentationImage.cpp Execution/src/DeviceSynchronization.cpp Graphics/src/GraphicsPipelineCache.cpp)
|
||||
endif()
|
||||
endforeach()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ target_sources(libc PRIVATE
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/Time.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/FileAndHeap.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/GuestAllocations.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/GuestMemoryTracking.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/GuestMemoryBacking.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/GuestHeapStorage.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/GuestHeap.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationHeap.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationProcessParameters.cpp
|
||||
|
|
@ -20,6 +23,14 @@ target_sources(libc PRIVATE
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/exception/Exports.cpp
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
target_sources(libc PRIVATE src/MemoryTrackingWindows.cpp src/MemoryBackingWindows.cpp)
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
|
||||
target_sources(libc PRIVATE src/MemoryTrackingLinux.cpp src/MemoryBackingLinux.cpp)
|
||||
else()
|
||||
message(FATAL_ERROR "Guest GPU memory tracking requires Windows or x86-64 Linux")
|
||||
endif()
|
||||
|
||||
target_include_directories(libc PRIVATE
|
||||
${LIBS_INCLUDE_DIR}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_GUESTHEAPSTORAGE_HPP
|
||||
#define CORE_LIBS_PRX_LIBC_INCLUDE_GUESTHEAPSTORAGE_HPP
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace GuestHeapStorage {
|
||||
|
||||
void* Allocate(std::size_t alignment, std::size_t bytes);
|
||||
void Free(void* pointer);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_GUESTMEMORYBACKING_HPP
|
||||
#define CORE_LIBS_PRX_LIBC_INCLUDE_GUESTMEMORYBACKING_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace GuestMemoryBacking {
|
||||
|
||||
extern "C" {
|
||||
void* GuestMemoryBackingMap_nid_postfix(void* address, std::size_t bytes, std::size_t alignment, int protection);
|
||||
void GuestMemoryBackingUnmap_nid_postfix(void* address, std::size_t bytes);
|
||||
void GuestMemoryBackingRequire_nid_postfix(std::uint64_t address, std::size_t bytes);
|
||||
void GuestMemoryBackingWrite_nid_postfix(std::uint64_t address, const void* source, std::size_t bytes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_GUESTMEMORYTRACKING_HPP
|
||||
#define CORE_LIBS_PRX_LIBC_INCLUDE_GUESTMEMORYTRACKING_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <functional>
|
||||
|
||||
namespace GuestMemoryTracking {
|
||||
|
||||
enum class Access { Read, Write, Invalidate };
|
||||
enum class Protection { None, Read, ReadWrite };
|
||||
using Resolver = void (*)(void*, Access);
|
||||
|
||||
extern "C" {
|
||||
std::recursive_mutex& GuestMemoryTrackingMutex_nid_postfix();
|
||||
std::size_t GuestMemoryTrackingPageSize_nid_postfix();
|
||||
void* GuestMemoryTrackingCreate_nid_postfix(std::uint64_t address, std::size_t bytes, void* context, Resolver resolver);
|
||||
void GuestMemoryTrackingDestroy_nid_postfix(void* handle) noexcept;
|
||||
void GuestMemoryTrackingProtect_nid_postfix(void* handle, Protection protection);
|
||||
void GuestMemoryTrackingResolve_nid_postfix(std::uint64_t address, std::size_t bytes, bool writable);
|
||||
void GuestMemoryTrackingInvalidate_nid_postfix(std::uint64_t address, std::size_t bytes);
|
||||
void GuestMemoryTrackingValidate_nid_postfix(std::uint64_t address, std::size_t bytes, const std::function<void(std::uint64_t, std::size_t)>& validate);
|
||||
}
|
||||
|
||||
class Watch {
|
||||
public:
|
||||
Watch(std::uint64_t address, std::size_t bytes, void* context, Resolver resolver) : handle(GuestMemoryTrackingCreate_nid_postfix(address, bytes, context, resolver)) {}
|
||||
~Watch() { GuestMemoryTrackingDestroy_nid_postfix(handle); }
|
||||
Watch(const Watch&) = delete;
|
||||
Watch& operator=(const Watch&) = delete;
|
||||
void Protect(Protection protection) { GuestMemoryTrackingProtect_nid_postfix(handle, protection); }
|
||||
|
||||
private:
|
||||
void* handle;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_MEMORYBACKINGPLATFORM_HPP
|
||||
#define CORE_LIBS_PRX_LIBC_INCLUDE_MEMORYBACKINGPLATFORM_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace GuestMemoryBacking::Platform {
|
||||
|
||||
struct Mapping {
|
||||
std::uint64_t address;
|
||||
std::size_t bytes;
|
||||
void* alias;
|
||||
std::uintptr_t handle;
|
||||
};
|
||||
|
||||
Mapping Map(void* address, std::size_t bytes, std::size_t alignment, int protection);
|
||||
void Unmap(const Mapping& mapping);
|
||||
void Deactivate(std::uint64_t address, std::size_t bytes);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_MEMORYTRACKINGPLATFORM_HPP
|
||||
#define CORE_LIBS_PRX_LIBC_INCLUDE_MEMORYTRACKINGPLATFORM_HPP
|
||||
|
||||
#include "prx/libc/include/GuestMemoryTracking.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace GuestMemoryTracking::Platform {
|
||||
|
||||
struct Region {
|
||||
std::uint64_t address;
|
||||
std::size_t bytes;
|
||||
std::uint64_t protection;
|
||||
};
|
||||
|
||||
using FaultHandler = bool (*)(std::uint64_t, bool);
|
||||
std::size_t PageSize();
|
||||
void Install(FaultHandler handler);
|
||||
std::vector<Region> Query(std::uint64_t address, std::size_t bytes);
|
||||
void Protect(std::uint64_t address, std::size_t bytes, Protection protection);
|
||||
void Restore(const std::vector<Region>& regions);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "prx/libc/include/GuestMemoryTracking.hpp"
|
||||
#include <limits>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
|
|
@ -14,7 +15,7 @@ namespace GuestAllocations {
|
|||
namespace {
|
||||
|
||||
struct Registry {
|
||||
std::mutex mutex;
|
||||
std::recursive_mutex& mutex = GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix();
|
||||
std::map<std::uint64_t, std::shared_ptr<const Range>> ranges;
|
||||
bool mainImageRegistered = false;
|
||||
};
|
||||
|
|
@ -31,11 +32,11 @@ void require(bool condition, const char* reason) {
|
|||
}
|
||||
|
||||
void* GuestAllocationsBegin_nid_postfix() {
|
||||
return new std::unique_lock<std::mutex>(registry().mutex);
|
||||
return new std::unique_lock<std::recursive_mutex>(registry().mutex);
|
||||
}
|
||||
|
||||
void GuestAllocationsEnd_nid_postfix(void* mutation) noexcept {
|
||||
delete static_cast<std::unique_lock<std::mutex>*>(mutation);
|
||||
delete static_cast<std::unique_lock<std::recursive_mutex>*>(mutation);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
|
@ -93,6 +94,7 @@ void GuestAllocationsAdd_nid_postfix(void*, void* pointer, std::size_t bytes, bo
|
|||
void GuestAllocationsRequireUnpinned_nid_postfix(void*, const void* pointer, std::size_t bytes) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(pointer);
|
||||
require(bytes <= std::numeric_limits<std::uint64_t>::max() - address, "guest allocation range overflow");
|
||||
GuestMemoryTracking::GuestMemoryTrackingInvalidate_nid_postfix(address, bytes);
|
||||
const auto end = address + bytes;
|
||||
for (const auto& [base, range] : registry().ranges) {
|
||||
if (base >= end && base != address) break;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "prx/libc/include/GuestHeap.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "prx/libc/include/GuestHeapStorage.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
|
@ -13,18 +14,11 @@ namespace {
|
|||
void* allocate(GuestAllocations::Mutation& mutation, std::size_t alignment, std::size_t bytes) {
|
||||
if (alignment == 0 || (alignment & (alignment - 1)) != 0) throw std::invalid_argument("invalid guest heap alignment");
|
||||
alignment = std::max(alignment, alignof(void*));
|
||||
constexpr auto headerBytes = 2 * sizeof(void*);
|
||||
if (alignment - 1 > std::numeric_limits<std::size_t>::max() - headerBytes || bytes > std::numeric_limits<std::size_t>::max() - headerBytes - (alignment - 1)) throw std::length_error("guest heap allocation overflow");
|
||||
void* raw = std::malloc(bytes + headerBytes + alignment - 1);
|
||||
if (raw == nullptr) throw std::bad_alloc();
|
||||
const auto address = (reinterpret_cast<std::uintptr_t>(raw) + headerBytes + alignment - 1) & ~(static_cast<std::uintptr_t>(alignment) - 1);
|
||||
auto* pointer = reinterpret_cast<void*>(address);
|
||||
reinterpret_cast<void**>(pointer)[-2] = raw;
|
||||
reinterpret_cast<std::uintptr_t*>(pointer)[-1] = 1;
|
||||
void* pointer = GuestHeapStorage::Allocate(alignment, bytes);
|
||||
try {
|
||||
mutation.Add(pointer, bytes, true, true);
|
||||
} catch (...) {
|
||||
std::free(raw);
|
||||
GuestHeapStorage::Free(pointer);
|
||||
throw;
|
||||
}
|
||||
return pointer;
|
||||
|
|
@ -32,7 +26,7 @@ void* allocate(GuestAllocations::Mutation& mutation, std::size_t alignment, std:
|
|||
|
||||
void free(GuestAllocations::Mutation& mutation, void* pointer) {
|
||||
mutation.Remove(pointer);
|
||||
std::free(reinterpret_cast<void**>(pointer)[-2]);
|
||||
GuestHeapStorage::Free(pointer);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
#include "prx/libc/include/GuestHeapStorage.hpp"
|
||||
#include "prx/libc/include/GuestMemoryBacking.hpp"
|
||||
#include "prx/libc/include/GuestMemoryTracking.hpp"
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace GuestHeapStorage {
|
||||
namespace {
|
||||
|
||||
struct Chunk {
|
||||
void* address;
|
||||
std::size_t bytes;
|
||||
std::map<std::uintptr_t, std::size_t> free;
|
||||
};
|
||||
|
||||
struct Allocation {
|
||||
Chunk* chunk;
|
||||
std::size_t bytes;
|
||||
};
|
||||
|
||||
struct Arena {
|
||||
std::vector<std::unique_ptr<Chunk>> chunks;
|
||||
std::map<std::uintptr_t, Allocation> allocations;
|
||||
};
|
||||
|
||||
Arena& arena() {
|
||||
static auto* value = new Arena;
|
||||
return *value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void* Allocate(std::size_t alignment, std::size_t bytes) {
|
||||
if (alignment == 0 || (alignment & (alignment - 1)) != 0) throw std::invalid_argument("invalid shared heap alignment");
|
||||
bytes = std::max(bytes, std::size_t{1});
|
||||
if (bytes > std::numeric_limits<std::size_t>::max() - alignment) throw std::length_error("shared heap allocation overflow");
|
||||
std::lock_guard lock(GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix());
|
||||
auto& state = arena();
|
||||
for (std::size_t index = 0;; ++index) {
|
||||
if (index == state.chunks.size()) {
|
||||
constexpr std::size_t chunkSize = 64 * 1024 * 1024;
|
||||
const auto granularity = std::max(alignment, std::size_t{65536});
|
||||
const auto requested = std::max(bytes, chunkSize);
|
||||
if (requested > std::numeric_limits<std::size_t>::max() - (granularity - 1)) throw std::length_error("shared heap chunk overflow");
|
||||
const auto size = (requested + granularity - 1) & ~(granularity - 1);
|
||||
void* memory = GuestMemoryBacking::GuestMemoryBackingMap_nid_postfix(nullptr, size, granularity, 3);
|
||||
try {
|
||||
auto chunk = std::make_unique<Chunk>();
|
||||
chunk->address = memory;
|
||||
chunk->bytes = size;
|
||||
chunk->free.emplace(reinterpret_cast<std::uintptr_t>(memory), size);
|
||||
state.chunks.push_back(std::move(chunk));
|
||||
} catch (...) {
|
||||
GuestMemoryBacking::GuestMemoryBackingUnmap_nid_postfix(memory, size);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
auto& chunk = *state.chunks[index];
|
||||
for (auto it = chunk.free.begin(); it != chunk.free.end(); ++it) {
|
||||
const auto first = it->first;
|
||||
const auto available = it->second;
|
||||
if (first > std::numeric_limits<std::uintptr_t>::max() - (alignment - 1)) throw std::overflow_error("shared heap address overflow");
|
||||
const auto address = (first + alignment - 1) & ~(static_cast<std::uintptr_t>(alignment) - 1);
|
||||
const auto prefix = address - first;
|
||||
if (prefix > available || bytes > available - prefix) continue;
|
||||
if (!state.allocations.emplace(address, Allocation{&chunk, bytes}).second) throw std::runtime_error("duplicate shared heap allocation");
|
||||
const auto suffix = available - prefix - bytes;
|
||||
try {
|
||||
if (suffix != 0) chunk.free.emplace(address + bytes, suffix);
|
||||
} catch (...) {
|
||||
state.allocations.erase(address);
|
||||
throw;
|
||||
}
|
||||
if (prefix != 0) it->second = prefix;
|
||||
else chunk.free.erase(it);
|
||||
return reinterpret_cast<void*>(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Free(void* pointer) {
|
||||
std::lock_guard lock(GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix());
|
||||
auto& allocations = arena().allocations;
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(pointer);
|
||||
const auto found = allocations.find(address);
|
||||
if (found == allocations.end()) throw std::invalid_argument("unknown shared heap allocation");
|
||||
auto* chunk = found->second.chunk;
|
||||
auto& free = chunk->free;
|
||||
auto [entry, inserted] = free.emplace(address, found->second.bytes);
|
||||
if (!inserted) throw std::runtime_error("shared heap allocation was already freed");
|
||||
allocations.erase(found);
|
||||
if (entry != free.begin()) {
|
||||
auto previous = std::prev(entry);
|
||||
if (previous->first + previous->second == entry->first) {
|
||||
previous->second += entry->second;
|
||||
free.erase(entry);
|
||||
entry = previous;
|
||||
}
|
||||
}
|
||||
const auto next = std::next(entry);
|
||||
if (next != free.end() && entry->first + entry->second == next->first) {
|
||||
entry->second += next->second;
|
||||
free.erase(next);
|
||||
}
|
||||
if (free.size() == 1 && entry->first == reinterpret_cast<std::uintptr_t>(chunk->address) && entry->second == chunk->bytes) {
|
||||
GuestMemoryBacking::GuestMemoryBackingUnmap_nid_postfix(chunk->address, chunk->bytes);
|
||||
std::erase_if(arena().chunks, [chunk](const auto& value) { return value.get() == chunk; });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
#include "prx/libc/include/GuestMemoryBacking.hpp"
|
||||
#include "prx/libc/include/MemoryBackingPlatform.hpp"
|
||||
#include "prx/libc/include/GuestMemoryTracking.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace GuestMemoryBacking {
|
||||
namespace {
|
||||
|
||||
struct Allocation {
|
||||
Platform::Mapping mapping;
|
||||
std::map<std::uint64_t, std::uint64_t> ranges;
|
||||
};
|
||||
|
||||
std::map<std::uint64_t, Allocation>& allocations() {
|
||||
static auto* value = new std::map<std::uint64_t, Allocation>;
|
||||
return *value;
|
||||
}
|
||||
|
||||
Allocation& find(std::uint64_t address, std::size_t bytes) {
|
||||
if (address == 0 || bytes == 0 || bytes > std::numeric_limits<std::uint64_t>::max() - address) throw std::invalid_argument("invalid guest backing range");
|
||||
auto found = allocations().upper_bound(address);
|
||||
if (found == allocations().begin()) throw std::runtime_error("guest memory has no shared backing");
|
||||
auto& allocation = std::prev(found)->second;
|
||||
auto range = allocation.ranges.upper_bound(address);
|
||||
if (range == allocation.ranges.begin() || address + bytes > std::prev(range)->second) throw std::runtime_error("guest memory backing range is unmapped");
|
||||
return allocation;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void* GuestMemoryBackingMap_nid_postfix(void* address, std::size_t bytes, std::size_t alignment, int protection) {
|
||||
const auto pageSize = GuestMemoryTracking::GuestMemoryTrackingPageSize_nid_postfix();
|
||||
if (bytes == 0 || bytes % pageSize != 0 || alignment < pageSize || (alignment & (alignment - 1)) != 0 || (protection & ~7) != 0) throw std::invalid_argument("invalid shared guest memory mapping");
|
||||
if (address != nullptr && reinterpret_cast<std::uintptr_t>(address) % alignment != 0) throw std::invalid_argument("misaligned fixed guest memory mapping");
|
||||
std::lock_guard lock(GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix());
|
||||
const auto mapping = Platform::Map(address, bytes, alignment, protection);
|
||||
try {
|
||||
if (mapping.bytes > std::numeric_limits<std::uint64_t>::max() - mapping.address) throw std::overflow_error("guest backing mapping overflow");
|
||||
const auto next = allocations().lower_bound(mapping.address);
|
||||
if (next != allocations().end() && next->first < mapping.address + bytes) throw std::runtime_error("overlapping guest backing mappings");
|
||||
if (next != allocations().begin()) {
|
||||
const auto& previous = std::prev(next)->second.mapping;
|
||||
if (previous.address + previous.bytes > mapping.address) throw std::runtime_error("guest mapping overlaps a retained backing reservation");
|
||||
}
|
||||
Allocation allocation{mapping, {{mapping.address, mapping.address + bytes}}};
|
||||
if (!allocations().emplace(mapping.address, std::move(allocation)).second) throw std::runtime_error("duplicate guest backing mapping");
|
||||
} catch (...) {
|
||||
Platform::Unmap(mapping);
|
||||
throw;
|
||||
}
|
||||
return reinterpret_cast<void*>(mapping.address);
|
||||
}
|
||||
|
||||
void GuestMemoryBackingUnmap_nid_postfix(void* pointer, std::size_t bytes) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(pointer);
|
||||
const auto pageSize = GuestMemoryTracking::GuestMemoryTrackingPageSize_nid_postfix();
|
||||
if (address % pageSize != 0 || bytes % pageSize != 0) throw std::invalid_argument("misaligned guest backing unmap");
|
||||
std::lock_guard lock(GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix());
|
||||
auto& allocation = find(address, bytes);
|
||||
auto replacement = allocation.ranges;
|
||||
const auto found = std::prev(replacement.upper_bound(address));
|
||||
const auto first = found->first;
|
||||
const auto last = found->second;
|
||||
replacement.erase(found);
|
||||
if (first < address) replacement.emplace(first, address);
|
||||
if (address + bytes < last) replacement.emplace(address + bytes, last);
|
||||
GuestMemoryTracking::GuestMemoryTrackingInvalidate_nid_postfix(address, bytes);
|
||||
if (replacement.empty()) {
|
||||
const auto base = allocation.mapping.address;
|
||||
Platform::Unmap(allocation.mapping);
|
||||
allocations().erase(base);
|
||||
} else {
|
||||
Platform::Deactivate(address, bytes);
|
||||
allocation.ranges.swap(replacement);
|
||||
}
|
||||
}
|
||||
|
||||
void GuestMemoryBackingRequire_nid_postfix(std::uint64_t address, std::size_t bytes) {
|
||||
std::lock_guard lock(GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix());
|
||||
static_cast<void>(find(address, bytes));
|
||||
}
|
||||
|
||||
void GuestMemoryBackingWrite_nid_postfix(std::uint64_t address, const void* source, std::size_t bytes) {
|
||||
if (source == nullptr) throw std::invalid_argument("missing guest backing write source");
|
||||
std::lock_guard lock(GuestMemoryTracking::GuestMemoryTrackingMutex_nid_postfix());
|
||||
auto& allocation = find(address, bytes);
|
||||
auto* destination = static_cast<std::byte*>(allocation.mapping.alias) + (address - allocation.mapping.address);
|
||||
std::memcpy(destination, source, bytes);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
#include "prx/libc/include/GuestMemoryTracking.hpp"
|
||||
#include "prx/libc/include/MemoryTrackingPlatform.hpp"
|
||||
#include "prx/libc/include/GuestMemoryBacking.hpp"
|
||||
#include <algorithm>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace GuestMemoryTracking {
|
||||
namespace {
|
||||
|
||||
struct Entry {
|
||||
std::uint64_t address;
|
||||
std::size_t bytes;
|
||||
void* context;
|
||||
Resolver resolver;
|
||||
Protection protection = Protection::ReadWrite;
|
||||
std::vector<Platform::Region> original;
|
||||
bool resolving = false;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
struct Registry {
|
||||
std::recursive_mutex mutex;
|
||||
std::map<std::uint64_t, std::shared_ptr<Entry>> entries;
|
||||
bool installed = false;
|
||||
};
|
||||
|
||||
Registry& registry() {
|
||||
static auto* value = new Registry;
|
||||
return *value;
|
||||
}
|
||||
|
||||
std::uint64_t checkedEnd(std::uint64_t address, std::size_t bytes) {
|
||||
if (address == 0 || bytes == 0 || bytes > std::numeric_limits<std::uint64_t>::max() - address) throw std::invalid_argument("invalid tracked guest memory range");
|
||||
return address + bytes;
|
||||
}
|
||||
|
||||
void resolve(const std::shared_ptr<Entry>& entry, Access access) {
|
||||
if (entry->resolving) throw std::runtime_error("recursive guest memory ownership resolution");
|
||||
entry->resolving = true;
|
||||
try {
|
||||
entry->resolver(entry->context, access);
|
||||
if (entry->protection == Protection::None || (access != Access::Read && entry->protection != Protection::ReadWrite)) throw std::runtime_error("guest memory resolver did not release CPU access");
|
||||
entry->resolving = false;
|
||||
} catch (...) {
|
||||
entry->resolving = false;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Entry>> overlapping(std::uint64_t address, std::size_t bytes) {
|
||||
const auto end = checkedEnd(address, bytes);
|
||||
auto& entries = registry().entries;
|
||||
auto first = entries.upper_bound(address);
|
||||
if (first != entries.begin()) --first;
|
||||
std::vector<std::shared_ptr<Entry>> result;
|
||||
for (auto it = first; it != entries.end() && it->first < end; ++it) {
|
||||
if (it->first + it->second->bytes > address) result.push_back(it->second);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool fault(std::uint64_t address, bool writable) {
|
||||
std::lock_guard lock(registry().mutex);
|
||||
const auto entries = overlapping(address, 1);
|
||||
if (entries.empty()) return false;
|
||||
bool handled = false;
|
||||
for (const auto& entry : entries) {
|
||||
if (!entry->active) continue;
|
||||
if (entry->protection == Protection::None || (writable && entry->protection == Protection::Read)) resolve(entry, writable ? Access::Write : Access::Read);
|
||||
handled = true;
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::recursive_mutex& GuestMemoryTrackingMutex_nid_postfix() {
|
||||
return registry().mutex;
|
||||
}
|
||||
|
||||
std::size_t GuestMemoryTrackingPageSize_nid_postfix() {
|
||||
return Platform::PageSize();
|
||||
}
|
||||
|
||||
void* GuestMemoryTrackingCreate_nid_postfix(std::uint64_t address, std::size_t bytes, void* context, Resolver resolver) {
|
||||
const auto end = checkedEnd(address, bytes);
|
||||
const auto pageSize = Platform::PageSize();
|
||||
if (context == nullptr || resolver == nullptr) throw std::invalid_argument("missing guest memory ownership resolver");
|
||||
if (end > std::numeric_limits<std::uint64_t>::max() - (pageSize - 1)) throw std::overflow_error("tracked guest memory page range overflow");
|
||||
const auto first = address - address % pageSize;
|
||||
const auto last = (end + pageSize - 1) / pageSize * pageSize;
|
||||
std::lock_guard lock(registry().mutex);
|
||||
GuestMemoryBacking::GuestMemoryBackingRequire_nid_postfix(first, static_cast<std::size_t>(last - first));
|
||||
if (!overlapping(first, static_cast<std::size_t>(last - first)).empty()) throw std::runtime_error("overlapping guest memory ownership pages");
|
||||
static_cast<void>(Platform::Query(first, static_cast<std::size_t>(last - first)));
|
||||
if (!registry().installed) {
|
||||
Platform::Install(fault);
|
||||
registry().installed = true;
|
||||
}
|
||||
auto entry = std::make_shared<Entry>();
|
||||
entry->address = first;
|
||||
entry->bytes = static_cast<std::size_t>(last - first);
|
||||
entry->context = context;
|
||||
entry->resolver = resolver;
|
||||
auto handle = std::make_unique<std::shared_ptr<Entry>>(entry);
|
||||
registry().entries.emplace(first, std::move(entry));
|
||||
return handle.release();
|
||||
}
|
||||
|
||||
void GuestMemoryTrackingDestroy_nid_postfix(void* handle) noexcept {
|
||||
if (handle == nullptr) std::terminate();
|
||||
std::lock_guard lock(registry().mutex);
|
||||
std::unique_ptr<std::shared_ptr<Entry>> owner(static_cast<std::shared_ptr<Entry>*>(handle));
|
||||
const auto& entry = **owner;
|
||||
Platform::Restore(entry.original);
|
||||
registry().entries.erase(entry.address);
|
||||
}
|
||||
|
||||
void GuestMemoryTrackingProtect_nid_postfix(void* handle, Protection protection) {
|
||||
if (handle == nullptr) throw std::invalid_argument("missing guest memory watch");
|
||||
std::lock_guard lock(registry().mutex);
|
||||
auto& entry = **static_cast<std::shared_ptr<Entry>*>(handle);
|
||||
if (entry.protection == protection) return;
|
||||
if (protection == Protection::ReadWrite) {
|
||||
Platform::Restore(entry.original);
|
||||
entry.original.clear();
|
||||
} else {
|
||||
if (entry.original.empty()) entry.original = Platform::Query(entry.address, entry.bytes);
|
||||
Platform::Protect(entry.address, entry.bytes, protection);
|
||||
entry.active = true;
|
||||
}
|
||||
entry.protection = protection;
|
||||
}
|
||||
|
||||
void GuestMemoryTrackingResolve_nid_postfix(std::uint64_t address, std::size_t bytes, bool writable) {
|
||||
if (bytes == 0) return;
|
||||
std::lock_guard lock(registry().mutex);
|
||||
for (const auto& entry : overlapping(address, bytes)) {
|
||||
if (entry->protection == Protection::None || (writable && entry->protection == Protection::Read)) resolve(entry, writable ? Access::Write : Access::Read);
|
||||
}
|
||||
}
|
||||
|
||||
void GuestMemoryTrackingInvalidate_nid_postfix(std::uint64_t address, std::size_t bytes) {
|
||||
if (bytes == 0) return;
|
||||
std::lock_guard lock(registry().mutex);
|
||||
for (const auto& entry : overlapping(address, bytes)) {
|
||||
resolve(entry, Access::Invalidate);
|
||||
entry->active = false;
|
||||
}
|
||||
}
|
||||
|
||||
void GuestMemoryTrackingValidate_nid_postfix(std::uint64_t address, std::size_t bytes, const std::function<void(std::uint64_t, std::size_t)>& validate) {
|
||||
if (bytes == 0) return;
|
||||
if (!validate) throw std::invalid_argument("missing native memory range validator");
|
||||
const auto end = checkedEnd(address, bytes);
|
||||
std::lock_guard lock(registry().mutex);
|
||||
for (const auto& entry : overlapping(address, bytes)) {
|
||||
if (address < entry->address) validate(address, static_cast<std::size_t>(entry->address - address));
|
||||
const auto first = std::max(address, entry->address);
|
||||
const auto last = std::min(end, entry->address + entry->bytes);
|
||||
if (entry->protection == Protection::ReadWrite) validate(first, static_cast<std::size_t>(last - first));
|
||||
else if (entry->original.empty()) throw std::runtime_error("protected guest memory has no native permission record");
|
||||
address = last;
|
||||
}
|
||||
if (address < end) validate(address, static_cast<std::size_t>(end - address));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
#include "prx/libc/include/MemoryBackingPlatform.hpp"
|
||||
#include <cerrno>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace GuestMemoryBacking::Platform {
|
||||
namespace {
|
||||
|
||||
void check(bool success, const char* operation) {
|
||||
if (!success) throw std::system_error(errno, std::generic_category(), operation);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Mapping Map(void* address, std::size_t bytes, std::size_t alignment, int protection) {
|
||||
if (bytes > static_cast<std::size_t>(std::numeric_limits<off_t>::max()) || bytes > std::numeric_limits<std::size_t>::max() - alignment) throw std::overflow_error("guest backing size overflow");
|
||||
int descriptor = memfd_create("AnyPS5 guest memory", MFD_CLOEXEC);
|
||||
check(descriptor >= 0, "memfd_create guest backing");
|
||||
void* alias = MAP_FAILED;
|
||||
void* guest = MAP_FAILED;
|
||||
void* reservation = MAP_FAILED;
|
||||
std::size_t reservationBytes = 0;
|
||||
try {
|
||||
check(ftruncate(descriptor, static_cast<off_t>(bytes)) == 0, "ftruncate guest backing");
|
||||
alias = mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, descriptor, 0);
|
||||
check(alias != MAP_FAILED, "mmap guest backing alias");
|
||||
if (address == nullptr) {
|
||||
reservationBytes = bytes + alignment;
|
||||
reservation = mmap(nullptr, reservationBytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
} else {
|
||||
reservationBytes = bytes;
|
||||
reservation = mmap(address, reservationBytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
|
||||
}
|
||||
check(reservation != MAP_FAILED, "mmap guest backing reservation");
|
||||
if (address != nullptr && reservation != address) throw std::runtime_error("fixed guest backing reservation address mismatch");
|
||||
const auto first = reinterpret_cast<std::uintptr_t>(reservation);
|
||||
const auto aligned = (first + alignment - 1) & ~(static_cast<std::uintptr_t>(alignment) - 1);
|
||||
guest = mmap(reinterpret_cast<void*>(aligned), bytes, protection, MAP_SHARED | MAP_FIXED, descriptor, 0);
|
||||
check(guest != MAP_FAILED, "mmap guest backing view");
|
||||
const auto prefix = aligned - first;
|
||||
const auto suffix = reservationBytes - prefix - bytes;
|
||||
if (prefix != 0) check(munmap(reservation, prefix) == 0, "munmap guest backing prefix");
|
||||
if (suffix != 0) check(munmap(reinterpret_cast<void*>(aligned + bytes), suffix) == 0, "munmap guest backing suffix");
|
||||
reservation = MAP_FAILED;
|
||||
const auto closeResult = close(descriptor);
|
||||
descriptor = -1;
|
||||
check(closeResult == 0, "close guest backing descriptor");
|
||||
return {aligned, bytes, alias, 0};
|
||||
} catch (...) {
|
||||
if (reservation != MAP_FAILED) check(munmap(reservation, reservationBytes) == 0, "munmap failed guest reservation");
|
||||
else if (guest != MAP_FAILED) check(munmap(guest, bytes) == 0, "munmap failed guest view");
|
||||
if (alias != MAP_FAILED) check(munmap(alias, bytes) == 0, "munmap failed guest alias");
|
||||
if (descriptor >= 0) check(close(descriptor) == 0, "close failed guest backing");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void Unmap(const Mapping& mapping) {
|
||||
check(munmap(reinterpret_cast<void*>(mapping.address), mapping.bytes) == 0, "munmap guest view");
|
||||
check(munmap(mapping.alias, mapping.bytes) == 0, "munmap guest alias");
|
||||
}
|
||||
|
||||
void Deactivate(std::uint64_t address, std::size_t bytes) {
|
||||
check(mprotect(reinterpret_cast<void*>(address), bytes, PROT_NONE) == 0, "mprotect guest backing unmap");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
#include "prx/libc/include/MemoryBackingPlatform.hpp"
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
namespace GuestMemoryBacking::Platform {
|
||||
namespace {
|
||||
|
||||
DWORD nativeProtection(int protection) {
|
||||
if ((protection & 4) != 0) return (protection & 2) != 0 ? PAGE_EXECUTE_READWRITE : (protection & 1) != 0 ? PAGE_EXECUTE_READ : PAGE_EXECUTE;
|
||||
if ((protection & 2) != 0) return PAGE_READWRITE;
|
||||
return (protection & 1) != 0 ? PAGE_READONLY : PAGE_NOACCESS;
|
||||
}
|
||||
|
||||
void check(bool success, const char* operation) {
|
||||
if (!success) throw std::system_error(static_cast<int>(GetLastError()), std::system_category(), operation);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Mapping Map(void* address, std::size_t bytes, std::size_t alignment, int protection) {
|
||||
SYSTEM_INFO info{};
|
||||
GetSystemInfo(&info);
|
||||
alignment = std::max(alignment, static_cast<std::size_t>(info.dwAllocationGranularity));
|
||||
if (address != nullptr && reinterpret_cast<std::uintptr_t>(address) % info.dwAllocationGranularity != 0) throw std::invalid_argument("fixed guest view is not aligned to native allocation granularity");
|
||||
const auto size = static_cast<std::uint64_t>(bytes);
|
||||
HANDLE section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE, static_cast<DWORD>(size >> 32u), static_cast<DWORD>(size), nullptr);
|
||||
check(section != nullptr, "CreateFileMapping guest backing");
|
||||
void* alias = nullptr;
|
||||
void* guest = nullptr;
|
||||
try {
|
||||
alias = MapViewOfFile(section, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, bytes);
|
||||
check(alias != nullptr, "MapViewOfFile guest backing alias");
|
||||
if (address == nullptr && alignment > info.dwAllocationGranularity) {
|
||||
if (bytes > std::numeric_limits<std::size_t>::max() - alignment) throw std::overflow_error("aligned guest backing reservation overflow");
|
||||
void* reservation = VirtualAlloc(nullptr, bytes + alignment, MEM_RESERVE, PAGE_NOACCESS);
|
||||
check(reservation != nullptr, "VirtualAlloc guest backing reservation");
|
||||
const auto first = reinterpret_cast<std::uintptr_t>(reservation);
|
||||
address = reinterpret_cast<void*>((first + alignment - 1) & ~(static_cast<std::uintptr_t>(alignment) - 1));
|
||||
check(VirtualFree(reservation, 0, MEM_RELEASE) != FALSE, "VirtualFree guest backing reservation");
|
||||
}
|
||||
guest = MapViewOfFileEx(section, FILE_MAP_READ | FILE_MAP_WRITE | FILE_MAP_EXECUTE, 0, 0, bytes, address);
|
||||
check(guest != nullptr, "MapViewOfFileEx guest memory");
|
||||
if (reinterpret_cast<std::uintptr_t>(guest) % alignment != 0 || (address != nullptr && guest != address)) throw std::runtime_error("guest backing view address mismatch");
|
||||
DWORD previous = 0;
|
||||
check(VirtualProtect(guest, bytes, nativeProtection(protection), &previous) != FALSE, "VirtualProtect guest backing view");
|
||||
return {reinterpret_cast<std::uintptr_t>(guest), bytes, alias, reinterpret_cast<std::uintptr_t>(section)};
|
||||
} catch (...) {
|
||||
if (guest != nullptr) check(UnmapViewOfFile(guest) != FALSE, "UnmapViewOfFile failed guest view");
|
||||
if (alias != nullptr) check(UnmapViewOfFile(alias) != FALSE, "UnmapViewOfFile failed guest alias");
|
||||
check(CloseHandle(section) != FALSE, "CloseHandle failed guest backing");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void Unmap(const Mapping& mapping) {
|
||||
check(UnmapViewOfFile(reinterpret_cast<void*>(mapping.address)) != FALSE, "UnmapViewOfFile guest view");
|
||||
check(UnmapViewOfFile(mapping.alias) != FALSE, "UnmapViewOfFile guest alias");
|
||||
check(CloseHandle(reinterpret_cast<HANDLE>(mapping.handle)) != FALSE, "CloseHandle guest backing");
|
||||
}
|
||||
|
||||
void Deactivate(std::uint64_t address, std::size_t bytes) {
|
||||
DWORD previous = 0;
|
||||
check(VirtualProtect(reinterpret_cast<void*>(address), bytes, PAGE_NOACCESS, &previous) != FALSE, "VirtualProtect guest backing unmap");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
#include "prx/libc/include/MemoryTrackingPlatform.hpp"
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <csignal>
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
#include <sys/mman.h>
|
||||
#include <ucontext.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace GuestMemoryTracking::Platform {
|
||||
namespace {
|
||||
|
||||
FaultHandler faultHandler = nullptr;
|
||||
struct sigaction previousAction{};
|
||||
|
||||
void handleFault(int signal, siginfo_t* info, void* context) {
|
||||
const auto* native = static_cast<const ucontext_t*>(context);
|
||||
const auto error = native->uc_mcontext.gregs[REG_ERR];
|
||||
if (info->si_code == SEGV_ACCERR && (error & 16) == 0) {
|
||||
try {
|
||||
if (faultHandler(reinterpret_cast<std::uintptr_t>(info->si_addr), (error & 2) != 0)) return;
|
||||
} catch (...) {
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
if (previousAction.sa_handler == SIG_DFL || previousAction.sa_handler == SIG_IGN) {
|
||||
if (sigaction(signal, &previousAction, nullptr) != 0) std::terminate();
|
||||
if (raise(signal) != 0) std::terminate();
|
||||
return;
|
||||
}
|
||||
if ((previousAction.sa_flags & SA_SIGINFO) != 0) previousAction.sa_sigaction(signal, info, context);
|
||||
else previousAction.sa_handler(signal);
|
||||
}
|
||||
|
||||
void protect(std::uint64_t address, std::size_t bytes, int protection) {
|
||||
if (mprotect(reinterpret_cast<void*>(address), bytes, protection) != 0) throw std::system_error(errno, std::generic_category(), "guest memory tracking mprotect failed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::size_t PageSize() {
|
||||
static const auto size = [] {
|
||||
const auto result = sysconf(_SC_PAGESIZE);
|
||||
if (result <= 0) throw std::runtime_error("invalid native page size");
|
||||
return static_cast<std::size_t>(result);
|
||||
}();
|
||||
return size;
|
||||
}
|
||||
|
||||
void Install(FaultHandler handler) {
|
||||
if (handler == nullptr || faultHandler != nullptr) throw std::runtime_error("invalid guest memory fault handler installation");
|
||||
faultHandler = handler;
|
||||
struct sigaction action{};
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
action.sa_sigaction = handleFault;
|
||||
if (sigemptyset(&action.sa_mask) != 0 || sigaction(SIGSEGV, &action, &previousAction) != 0) {
|
||||
const auto error = errno;
|
||||
faultHandler = nullptr;
|
||||
throw std::system_error(error, std::generic_category(), "guest memory fault handler installation failed");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Region> Query(std::uint64_t address, std::size_t bytes) {
|
||||
std::ifstream maps("/proc/self/maps");
|
||||
if (!maps) throw std::runtime_error("cannot query tracked guest memory mappings");
|
||||
std::vector<Region> regions;
|
||||
const auto end = address + bytes;
|
||||
std::string line;
|
||||
while (address < end && std::getline(maps, line)) {
|
||||
std::istringstream input(line);
|
||||
std::uint64_t first = 0;
|
||||
std::uint64_t last = 0;
|
||||
char separator = 0;
|
||||
std::string permissions;
|
||||
if (!(input >> std::hex >> first >> separator >> last >> permissions) || separator != '-' || permissions.size() < 3 || first >= last) throw std::runtime_error("invalid tracked guest memory mapping");
|
||||
if (last <= address) continue;
|
||||
if (first > address || permissions[0] != 'r' || permissions[1] != 'w' || permissions[2] != '-') throw std::runtime_error("tracked render memory must be mapped, writable and non-executable");
|
||||
const auto next = std::min(end, last);
|
||||
regions.push_back({address, static_cast<std::size_t>(next - address), static_cast<std::uint64_t>(PROT_READ | PROT_WRITE | (permissions[2] == 'x' ? PROT_EXEC : 0))});
|
||||
address = next;
|
||||
}
|
||||
if (address != end) throw std::runtime_error("tracked guest memory range is not mapped");
|
||||
return regions;
|
||||
}
|
||||
|
||||
void Protect(std::uint64_t address, std::size_t bytes, Protection protection) {
|
||||
if (protection != Protection::None && protection != Protection::Read) throw std::invalid_argument("invalid tracked page protection");
|
||||
protect(address, bytes, protection == Protection::None ? PROT_NONE : PROT_READ);
|
||||
}
|
||||
|
||||
void Restore(const std::vector<Region>& regions) {
|
||||
for (const auto& region : regions) protect(region.address, region.bytes, static_cast<int>(region.protection));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
#include "prx/libc/include/MemoryTrackingPlatform.hpp"
|
||||
#include <algorithm>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
namespace GuestMemoryTracking::Platform {
|
||||
namespace {
|
||||
|
||||
FaultHandler faultHandler = nullptr;
|
||||
|
||||
LONG CALLBACK handleException(EXCEPTION_POINTERS* exception) {
|
||||
const auto* record = exception->ExceptionRecord;
|
||||
if (record->ExceptionCode != EXCEPTION_ACCESS_VIOLATION || record->NumberParameters < 2 || record->ExceptionInformation[0] > 1) return EXCEPTION_CONTINUE_SEARCH;
|
||||
try {
|
||||
if (faultHandler(record->ExceptionInformation[1], record->ExceptionInformation[0] == 1)) return EXCEPTION_CONTINUE_EXECUTION;
|
||||
} catch (...) {
|
||||
std::terminate();
|
||||
}
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
void protect(std::uint64_t address, std::size_t bytes, DWORD protection) {
|
||||
DWORD previous = 0;
|
||||
if (!VirtualProtect(reinterpret_cast<void*>(address), bytes, protection, &previous)) throw std::system_error(static_cast<int>(GetLastError()), std::system_category(), "guest memory tracking VirtualProtect failed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::size_t PageSize() {
|
||||
static const auto size = [] {
|
||||
SYSTEM_INFO info{};
|
||||
GetSystemInfo(&info);
|
||||
if (info.dwPageSize == 0) throw std::runtime_error("invalid native page size");
|
||||
return static_cast<std::size_t>(info.dwPageSize);
|
||||
}();
|
||||
return size;
|
||||
}
|
||||
|
||||
void Install(FaultHandler handler) {
|
||||
if (handler == nullptr || faultHandler != nullptr) throw std::runtime_error("invalid guest memory fault handler installation");
|
||||
faultHandler = handler;
|
||||
if (AddVectoredExceptionHandler(1, handleException) == nullptr) {
|
||||
const auto error = GetLastError();
|
||||
faultHandler = nullptr;
|
||||
throw std::system_error(static_cast<int>(error), std::system_category(), "guest memory fault handler installation failed");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Region> Query(std::uint64_t address, std::size_t bytes) {
|
||||
std::vector<Region> regions;
|
||||
const auto end = address + bytes;
|
||||
while (address < end) {
|
||||
MEMORY_BASIC_INFORMATION info{};
|
||||
if (VirtualQuery(reinterpret_cast<const void*>(address), &info, sizeof(info)) != sizeof(info)) throw std::system_error(static_cast<int>(GetLastError()), std::system_category(), "tracked guest memory query failed");
|
||||
if (info.State != MEM_COMMIT || info.Protect != PAGE_READWRITE) throw std::runtime_error("tracked render memory must be committed, writable and non-executable");
|
||||
const auto next = std::min(end, reinterpret_cast<std::uintptr_t>(info.BaseAddress) + info.RegionSize);
|
||||
if (next <= address) throw std::runtime_error("invalid tracked guest memory mapping");
|
||||
regions.push_back({address, static_cast<std::size_t>(next - address), info.Protect});
|
||||
address = next;
|
||||
}
|
||||
return regions;
|
||||
}
|
||||
|
||||
void Protect(std::uint64_t address, std::size_t bytes, Protection protection) {
|
||||
if (protection != Protection::None && protection != Protection::Read) throw std::invalid_argument("invalid tracked page protection");
|
||||
protect(address, bytes, protection == Protection::None ? PAGE_NOACCESS : PAGE_READONLY);
|
||||
}
|
||||
|
||||
void Restore(const std::vector<Region>& regions) {
|
||||
for (const auto& region : regions) protect(region.address, region.bytes, static_cast<DWORD>(region.protection));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
#include "DirectMemory.hpp"
|
||||
#include "prx/libkernel/DirectMemory/DirectMemory.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "prx/libc/include/GuestMemoryBacking.hpp"
|
||||
#include <cerrno>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
|
@ -14,10 +15,6 @@ static constexpr int PROT_NONE = 0;
|
|||
static constexpr int PROT_READ = 1;
|
||||
static constexpr int PROT_WRITE = 2;
|
||||
static constexpr int PROT_EXEC = 4;
|
||||
static constexpr int MAP_PRIVATE = 0x02;
|
||||
static constexpr int MAP_ANONYMOUS = 0x20;
|
||||
static constexpr int MAP_FIXED = 0x10;
|
||||
static void* const MAP_FAILED = reinterpret_cast<void*>(-1);
|
||||
|
||||
static DWORD WinProtFromPosix(int prot) {
|
||||
if (prot == PROT_NONE) return PAGE_NOACCESS;
|
||||
|
|
@ -28,30 +25,6 @@ static DWORD WinProtFromPosix(int prot) {
|
|||
return PAGE_READONLY;
|
||||
}
|
||||
|
||||
static void* mmap(void* addr, size_t len, int prot, int flags, int, int) {
|
||||
DWORD winProt = WinProtFromPosix(prot);
|
||||
if (flags & MAP_FIXED) {
|
||||
void* result = VirtualAlloc(addr, len, MEM_RESERVE | MEM_COMMIT, winProt);
|
||||
if (!result) throw std::system_error(static_cast<int>(GetLastError()), std::system_category(), "VirtualAlloc fixed failed");
|
||||
return result;
|
||||
}
|
||||
void* result = VirtualAlloc(nullptr, len, MEM_RESERVE | MEM_COMMIT, winProt);
|
||||
if (!result) throw std::system_error(static_cast<int>(GetLastError()), std::system_category(), "VirtualAlloc failed");
|
||||
return result;
|
||||
}
|
||||
|
||||
static int munmap(void* addr, size_t len) {
|
||||
if (!VirtualFree(addr, len, MEM_DECOMMIT))
|
||||
throw std::system_error(static_cast<int>(GetLastError()), std::system_category(), "VirtualFree decommit failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int munmap_release(void* addr) {
|
||||
if (!VirtualFree(addr, 0, MEM_RELEASE))
|
||||
throw std::system_error(static_cast<int>(GetLastError()), std::system_category(), "VirtualFree release failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mprotect(void* addr, size_t len, int prot) {
|
||||
DWORD old;
|
||||
if (!VirtualProtect(addr, len, WinProtFromPosix(prot), &old))
|
||||
|
|
@ -100,60 +73,18 @@ int LinuxProtFromSce(int prot) {
|
|||
}
|
||||
|
||||
void Unmap(void* addr, size_t len) {
|
||||
#if defined(__linux__)
|
||||
if (munmap(addr, len) != 0) throw std::system_error(errno, std::generic_category(), "munmap failed");
|
||||
#else
|
||||
munmap_release(addr);
|
||||
#endif
|
||||
GuestMemoryBacking::GuestMemoryBackingUnmap_nid_postfix(addr, len);
|
||||
}
|
||||
|
||||
void* MapAligned(void* addr, size_t len, int prot, int flags, size_t alignment) {
|
||||
ValidateLength(len);
|
||||
alignment = ValidateAlignment(alignment);
|
||||
constexpr int GuestMapFixed = 0x10;
|
||||
constexpr int GuestMapNoCoalesce = 0x400000;
|
||||
if ((flags & ~(GuestMapFixed | GuestMapNoCoalesce)) != 0) {
|
||||
// return SCE_KERNEL_ERROR_EINVAL;
|
||||
throw std::invalid_argument("Unsupported memory mapping flags");
|
||||
}
|
||||
if ((flags & GuestMapFixed) != 0) {
|
||||
ValidateRange(addr, len, alignment);
|
||||
void* result = mmap(addr, len, prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
|
||||
if (result == MAP_FAILED) {
|
||||
// return SCE_KERNEL_ERROR_ENOMEM;
|
||||
throw std::system_error(errno, std::generic_category(), "Fixed mmap failed");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (addr) {
|
||||
// return SCE_KERNEL_ERROR_EINVAL;
|
||||
throw std::invalid_argument("Non-fixed mapping address hints are not implemented");
|
||||
}
|
||||
if (len > std::numeric_limits<size_t>::max() - alignment) {
|
||||
// return SCE_KERNEL_ERROR_EINVAL;
|
||||
throw std::overflow_error("Aligned mapping size overflow");
|
||||
}
|
||||
const size_t allocLen = len + alignment;
|
||||
void* result = mmap(nullptr, allocLen, prot, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (result == MAP_FAILED) {
|
||||
// return SCE_KERNEL_ERROR_ENOMEM;
|
||||
throw std::system_error(errno, std::generic_category(), "Aligned mmap failed");
|
||||
}
|
||||
const auto raw = reinterpret_cast<std::uintptr_t>(result);
|
||||
const size_t prefix = (alignment - (raw & (alignment - 1))) & (alignment - 1);
|
||||
void* aligned = reinterpret_cast<void*>(raw + prefix);
|
||||
const size_t suffix = allocLen - prefix - len;
|
||||
if (prefix != 0 && munmap(result, prefix) != 0) {
|
||||
const int error = errno;
|
||||
Unmap(result, allocLen);
|
||||
throw std::system_error(error, std::generic_category(), "Mapping prefix munmap failed");
|
||||
}
|
||||
if (suffix != 0 && munmap(reinterpret_cast<void*>(raw + prefix + len), suffix) != 0) {
|
||||
const int error = errno;
|
||||
Unmap(aligned, allocLen - prefix);
|
||||
throw std::system_error(error, std::generic_category(), "Mapping suffix munmap failed");
|
||||
}
|
||||
return aligned;
|
||||
constexpr int guestMapFixed = 0x10;
|
||||
constexpr int guestMapNoCoalesce = 0x400000;
|
||||
if ((flags & ~(guestMapFixed | guestMapNoCoalesce)) != 0) throw std::invalid_argument("Unsupported memory mapping flags");
|
||||
if ((flags & guestMapFixed) != 0) ValidateRange(addr, len, alignment);
|
||||
else if (addr != nullptr) throw std::invalid_argument("Non-fixed mapping address hints are not implemented");
|
||||
return GuestMemoryBacking::GuestMemoryBackingMap_nid_postfix(addr, len, alignment, prot);
|
||||
}
|
||||
|
||||
void ValidateOutput(void** addr) {
|
||||
|
|
@ -228,13 +159,8 @@ int DoMprotect(const void* addr, size_t len, int prot) {
|
|||
int DoMunmap(void* addr, size_t len) {
|
||||
if (len == 0 || (len & (PS5_PAGE_SIZE - 1)) != 0 || !addr) return SCE_KERNEL_ERROR_EINVAL;
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Unmap(addr, len, [&](const void* allocation, bool last) {
|
||||
#if defined(__linux__)
|
||||
mutation.Unmap(addr, len, [&](const void*, bool) {
|
||||
Unmap(addr, len);
|
||||
#else
|
||||
if (last) munmap_release(const_cast<void*>(allocation));
|
||||
else munmap(addr, len);
|
||||
#endif
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue