From 699df8d5fa1cc301b9e9f1fbfcd9ed6bdabd453f Mon Sep 17 00:00:00 2001 From: boykopovar Date: Mon, 14 Sep 2026 11:43:13 +0300 Subject: [PATCH] feat(agcDriver): add 54 PM4 execution and indirect dispatch support --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 8 + .../Execution/include/GuestMemory.hpp | 16 + .../libSceAgcDriver/Execution/include/Pm4.hpp | 21 ++ .../Execution/include/Pm4Opcodes.hpp | 73 ++++ .../Execution/include/QueueState.hpp | 13 + .../libSceAgcDriver/Execution/src/Driver.cpp | 135 ++------ .../Execution/src/GuestMemory.cpp | 77 +++++ .../prx/libSceAgcDriver/Execution/src/Pm4.cpp | 316 ++++++++++++++++++ core/libs/prx/libSceAgcDriver/tests/Pm4.cpp | 225 +++++++++++++ .../prx/libSceAgcDriver/tests/Recompiler.cpp | 11 +- 10 files changed, 785 insertions(+), 110 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp create mode 100644 core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp create mode 100644 core/libs/prx/libSceAgcDriver/Execution/include/Pm4Opcodes.hpp create mode 100644 core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp create mode 100644 core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/Pm4.cpp diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 4c09bf371..54a236ce3 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1,5 +1,7 @@ add_library(libSceAgcDriver SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Driver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/GuestMemory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Pm4.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/VulkanDevice.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Eq/src/Event.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Eq/src/Query.cpp @@ -39,3 +41,9 @@ target_include_directories(agc_driver_flip_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(agc_driver_flip_tests PRIVATE libSceAgcDriver libc) set_target_properties(agc_driver_flip_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_driver_flip_tests) + +add_executable(agc_driver_pm4_tests EXCLUDE_FROM_ALL tests/Pm4.cpp) +target_include_directories(agc_driver_pm4_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_driver_pm4_tests PRIVATE libSceAgcDriver libc) +set_target_properties(agc_driver_pm4_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_driver_pm4_tests) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp new file mode 100644 index 000000000..cf958f695 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp @@ -0,0 +1,16 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_GUESTMEMORY_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_GUESTMEMORY_HPP + +#include +#include +#include + +namespace AgcDriver::GuestMemory { + +void CheckRange(const void* pointer, std::size_t bytes, std::size_t alignment, bool writable = false); +void Read(std::uint64_t address, std::span destination, std::size_t alignment = 1); +void Write(std::uint64_t address, std::span source, std::size_t alignment = 1); + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp new file mode 100644 index 000000000..7db11cbf7 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp @@ -0,0 +1,21 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PM4_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PM4_HPP + +#include "prx/libSceAgcDriver/Execution/include/QueueState.hpp" +#include "prx/libSceAgcDriver/Execution/include/Pm4Opcodes.hpp" +#include +#include +#include + +namespace AgcDriver::Pm4 { + +std::string Name(std::uint32_t header); +std::string_view UnsupportedReason(std::uint32_t header); +void Validate(std::span packet, std::uint32_t queue); +void Execute(std::span packet, QueueState& queue); +bool AccessesMemory(std::uint32_t header); +std::array ResolveDispatch(std::span packet, const QueueState& queue); + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4Opcodes.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4Opcodes.hpp new file mode 100644 index 000000000..b5522dda3 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4Opcodes.hpp @@ -0,0 +1,73 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PM4OPCODES_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PM4OPCODES_HPP + +#include +#include + +namespace AgcDriver::Pm4 { + +struct Opcode { + std::uint32_t value; + std::string_view name; +}; + +inline constexpr Opcode Opcodes[] = { + {0x10, "NOP"}, + {0x11, "SET_BASE"}, + {0x12, "CLEAR_STATE"}, + {0x13, "INDEX_BUFFER_SIZE"}, + {0x15, "DISPATCH_DIRECT"}, + {0x16, "DISPATCH_INDIRECT"}, + {0x20, "SET_PREDICATION"}, + {0x22, "COND_EXEC"}, + {0x24, "DRAW_INDIRECT"}, + {0x25, "DRAW_INDEX_INDIRECT"}, + {0x26, "INDEX_BASE"}, + {0x27, "DRAW_INDEX_2"}, + {0x28, "CONTEXT_CONTROL"}, + {0x2A, "INDEX_TYPE"}, + {0x2C, "DRAW_INDIRECT_MULTI"}, + {0x2D, "DRAW_INDEX_AUTO"}, + {0x2F, "NUM_INSTANCES"}, + {0x33, "INDIRECT_BUFFER_CNST"}, + {0x35, "DRAW_INDEX_OFFSET_2"}, + {0x37, "WRITE_DATA"}, + {0x39, "MEM_SEMAPHORE"}, + {0x38, "DRAW_INDEX_INDIRECT_MULTI"}, + {0x3A, "DISPATCH_DRAW_PREAMBLE"}, + {0x3C, "WAIT_REG_MEM"}, + {0x3F, "INDIRECT_BUFFER"}, + {0x40, "COPY_DATA"}, + {0x41, "CP_DMA"}, + {0x42, "PFP_SYNC_ME"}, + {0x43, "SURFACE_SYNC"}, + {0x46, "EVENT_WRITE"}, + {0x47, "EVENT_WRITE_EOP"}, + {0x48, "EVENT_WRITE_EOS"}, + {0x49, "RELEASE_MEM"}, + {0x50, "DMA_DATA"}, + {0x58, "ACQUIRE_MEM"}, + {0x59, "REWIND"}, + {0x63, "SET_SH_REG_INDIRECT"}, + {0x64, "SET_UCONFIG_REG_INDIRECT"}, + {0x68, "SET_CONFIG_REG"}, + {0x69, "SET_CONTEXT_REG"}, + {0x76, "SET_SH_REG"}, + {0x78, "SET_QUEUE_REG"}, + {0x79, "SET_UCONFIG_REG"}, + {0x7A, "SET_UCONFIG_REG_INDEX"}, + {0x81, "WRITE_CONST_RAM"}, + {0x83, "DUMP_CONST_RAM"}, + {0x84, "INCREMENT_CE_COUNTER"}, + {0x85, "INCREMENT_DE_COUNTER"}, + {0x86, "WAIT_ON_CE_COUNTER"}, + {0x88, "WAIT_ON_DE_COUNTER_DIFF"}, + {0x8D, "DISPATCH_DRAW"}, + {0x8E, "GET_LOD_STATS"}, + {0x93, "WAIT_REG_MEM_64"}, + {0x9F, "SET_CONTEXT_REG_INDIRECT"}, +}; + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp index 06ea96277..a19a03bb3 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp @@ -3,6 +3,10 @@ #include #include +#include +#include +#include +#include namespace AgcDriver { @@ -12,6 +16,15 @@ struct QueueState { Registers shader; Registers context; Registers userConfig; + std::optional savedContext; + std::array constantRam{}; + std::uint64_t indexBase = 0; + std::uint64_t drawIndirectBase = 0; + std::uint64_t dispatchIndirectBase = 0; + std::uint32_t indexBufferSize = 0; + std::uint32_t indexType = 0; + std::uint32_t instanceCount = 1; + std::vector markers; void ClearContext() { context.clear(); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp index eb0a4f509..ec0b40344 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp @@ -1,4 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include "prx/libSceAgcDriver/Execution/include/QueueState.hpp" #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" #include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" @@ -18,15 +20,6 @@ #include #include #include -#ifdef _WIN32 -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include -#else -#include -#include -#endif namespace AgcDriver { namespace { @@ -37,42 +30,6 @@ void require(bool condition, const char* reason) { } } -void checkRange(const void* pointer, std::size_t bytes, std::size_t alignment) { - const auto address = reinterpret_cast(pointer); - require(address != 0 && address % alignment == 0, "null or misaligned address"); - require(bytes <= std::numeric_limits::max() - address, "address range overflow"); - auto cursor = address; - const auto end = address + bytes; -#ifdef _WIN32 - while (cursor < end) { - MEMORY_BASIC_INFORMATION memory{}; - require(VirtualQuery(reinterpret_cast(cursor), &memory, sizeof(memory)) == sizeof(memory), "cannot query guest memory"); - require(memory.State == MEM_COMMIT && (memory.Protect & (PAGE_GUARD | PAGE_NOACCESS)) == 0, "guest memory is not readable"); - const auto protection = memory.Protect & 0xffu; - require(protection == PAGE_READONLY || protection == PAGE_READWRITE || protection == PAGE_WRITECOPY || protection == PAGE_EXECUTE_READ || protection == PAGE_EXECUTE_READWRITE || protection == PAGE_EXECUTE_WRITECOPY, "guest memory has no read permission"); - const auto base = reinterpret_cast(memory.BaseAddress); - require(memory.RegionSize <= std::numeric_limits::max() - base && base + memory.RegionSize > cursor, "invalid guest memory mapping"); - cursor = std::min(end, base + memory.RegionSize); - } -#else - std::ifstream maps("/proc/self/maps"); - require(maps.is_open(), "cannot query guest memory maps"); - std::string line; - while (cursor < end && std::getline(maps, line)) { - std::istringstream fields(line); - std::uintptr_t first = 0; - std::uintptr_t last = 0; - char separator = 0; - std::string permissions; - require(static_cast(fields >> std::hex >> first >> separator >> last >> permissions) && separator == '-' && first < last && !permissions.empty(), "invalid guest memory map entry"); - if (last <= cursor) continue; - require(first <= cursor && permissions[0] == 'r', "guest memory is not readable"); - cursor = std::min(end, last); - } - require(cursor == end, "guest address range is not mapped"); -#endif -} - struct ShaderSnapshot { std::uint64_t codeAddress; std::uint64_t headerAddress; @@ -138,14 +95,14 @@ public: void Submit(const Packet* packet, std::uint32_t queue) { CheckFailure(); require(queue == 0 || (queue >= 0x20 && queue < 0x58), "unsupported compute queue"); - checkRange(packet, sizeof(Packet), alignof(Packet)); + GuestMemory::CheckRange(packet, sizeof(Packet), alignof(Packet)); const auto descriptor = *packet; require(descriptor.flags == 0, "nonzero submission flags are not implemented"); Submission submission{}; submission.queue = queue; if (descriptor.dw_num != 0) { require(descriptor.dw_num <= std::numeric_limits::max() / sizeof(std::uint32_t), "command size overflow"); - checkRange(descriptor.addr, static_cast(descriptor.dw_num) * sizeof(std::uint32_t), alignof(std::uint32_t)); + GuestMemory::CheckRange(descriptor.addr, static_cast(descriptor.dw_num) * sizeof(std::uint32_t), alignof(std::uint32_t)); submission.commands.assign(descriptor.addr, descriptor.addr + descriptor.dw_num); } validate(submission.commands, queue); @@ -264,13 +221,13 @@ public: void RegisterShader(const Shader* shader) { CheckFailure(); - checkRange(shader, sizeof(Shader), alignof(Shader)); + GuestMemory::CheckRange(shader, sizeof(Shader), alignof(Shader)); require(shader->file_header == 0x34333231u && shader->version == 0x18u, "invalid shader header"); require(shader->header_size >= sizeof(Shader), "shader header is smaller than its fixed fields"); require(shader->shader_size != 0 && (shader->shader_size & 3u) == 0, "invalid shader size"); - checkRange(shader, shader->header_size, alignof(Shader)); + GuestMemory::CheckRange(shader, shader->header_size, alignof(Shader)); const auto* code = const_cast(shader->code); - checkRange(code, shader->shader_size, 256); + GuestMemory::CheckRange(code, shader->shader_size, 256); ShaderSnapshot snapshot{reinterpret_cast(code), reinterpret_cast(shader), shader->type, {}, {}}; snapshot.code.resize(shader->shader_size / sizeof(std::uint32_t)); std::memcpy(snapshot.code.data(), code, shader->shader_size); @@ -320,36 +277,10 @@ private: require((header & 0xc0000000u) == 0xc0000000u, "unsupported PM4 packet type"); const auto count = static_cast((header >> 16u) & 0x3fffu) + 2; require(count <= commands.size() - cursor, "truncated PM4 packet"); - if (header == FlipPacketHeader) { - require(count == FlipPacketWords && queue == 0, "invalid flip packet size or queue"); - cursor += count; - continue; - } - require((header & 0xffu) == 0, "PM4 header flags or internal extension are not implemented"); - const auto packet = commands.subspan(cursor, count); - const auto opcode = (header >> 8u) & 0xffu; - switch (opcode) { - case 0x12: - require(queue == 0, "CLEAR_STATE in compute queue"); - require(count == 2, "invalid CLEAR_STATE packet size"); - require((packet[1] & ~0xfu) == 0, "unsupported CLEAR_STATE payload bits"); - break; - case 0x10: - require((packet[1] & 0xffff0000u) != 0x68750000u, "marker NOP is not implemented"); - break; - case 0x69: - case 0x76: - case 0x79: - require(count >= 3, "register packet has no values"); - require(packet[1] <= 0xffffu && count - 2 <= 0x10000u - packet[1], "register range overflow or unsupported register index"); - require(queue == 0 || opcode == 0x76, "graphics register packet in compute queue"); - break; - case 0x15: - require(count == 5, "invalid dispatch packet size"); - require((packet[4] & ~0x8000u) == 0x41u, "dispatch modifiers are not implemented"); - break; - default: - throw std::runtime_error("AGC driver: unsupported PM4 opcode " + std::to_string(opcode) + " at DWORD " + std::to_string(cursor)); + try { + Pm4::Validate(commands.subspan(cursor, count), queue); + } catch (const std::exception& error) { + throw std::runtime_error("AGC driver: " + Pm4::Name(header) + " at DWORD " + std::to_string(cursor) + ": " + error.what()); } cursor += count; } @@ -404,35 +335,21 @@ private: const auto header = submission.commands[cursor]; const auto count = static_cast((header >> 16u) & 0x3fffu) + 2; const auto packet = std::span(submission.commands).subspan(cursor, count); - switch ((header >> 8u) & 0xffu) { - case 0x12: - queue.ClearContext(); - break; - case 0x10: - if (header == FlipPacketHeader) { - { - std::lock_guard gpuLock(gpuMutex); - if (device != nullptr) device->WaitIdle(); - } - CheckFailure(); - submission.flips.at(cursor)->GpuReady(); - } - break; - case 0x69: - case 0x76: - case 0x79: { - const auto opcode = (header >> 8u) & 0xffu; - auto& registers = opcode == 0x69 ? queue.context : opcode == 0x76 ? queue.shader : queue.userConfig; - for (std::size_t i = 2; i < count; ++i) { - registers.insert_or_assign(packet[1] + static_cast(i - 2), packet[i]); - } - break; - } - case 0x15: - dispatch(queue, packet, submission); - break; - default: - throw std::runtime_error("AGC driver: validated packet has no executor"); + const auto opcode = (header >> 8u) & 0xffu; + if (Pm4::AccessesMemory(header) || opcode == 0x42 || header == FlipPacketHeader) { + std::lock_guard gpuLock(gpuMutex); + if (device != nullptr) device->WaitIdle(); + } + if (header == FlipPacketHeader) { + CheckFailure(); + submission.flips.at(cursor)->GpuReady(); + } else if (opcode == 0x15) { + dispatch(queue, packet, submission); + } else if (opcode == 0x16) { + const auto direct = Pm4::ResolveDispatch(packet, queue); + dispatch(queue, direct, submission); + } else if (opcode != 0x42) { + Pm4::Execute(packet, queue); } cursor += count; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp new file mode 100644 index 000000000..1908f5508 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -0,0 +1,77 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include +#include +#include +#include +#include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#include +#endif + +namespace AgcDriver::GuestMemory { +namespace { +void require(bool condition, const char* reason) { + if (!condition) throw std::runtime_error(std::string("AGC driver: ") + reason); +} +} + +void CheckRange(const void* pointer, std::size_t bytes, std::size_t alignment, bool writable) { + require(alignment != 0, "zero guest memory alignment"); + const auto address = reinterpret_cast(pointer); + require(address != 0 && address % alignment == 0, "null or misaligned address"); + require(bytes <= std::numeric_limits::max() - address, "address range overflow"); + auto cursor = address; + const auto end = address + bytes; +#ifdef _WIN32 + while (cursor < end) { + MEMORY_BASIC_INFORMATION memory{}; + require(VirtualQuery(reinterpret_cast(cursor), &memory, sizeof(memory)) == sizeof(memory), "cannot query guest memory"); + require(memory.State == MEM_COMMIT && (memory.Protect & (PAGE_GUARD | PAGE_NOACCESS)) == 0, "guest memory is not readable"); + const auto protection = memory.Protect & 0xffu; + require(protection == PAGE_READONLY || protection == PAGE_READWRITE || protection == PAGE_WRITECOPY || protection == PAGE_EXECUTE_READ || protection == PAGE_EXECUTE_READWRITE || protection == PAGE_EXECUTE_WRITECOPY, "guest memory has no read permission"); + const auto base = reinterpret_cast(memory.BaseAddress); + require(memory.RegionSize <= std::numeric_limits::max() - base && base + memory.RegionSize > cursor, "invalid guest memory mapping"); + require(!writable || protection == PAGE_READWRITE || protection == PAGE_WRITECOPY || protection == PAGE_EXECUTE_READWRITE || protection == PAGE_EXECUTE_WRITECOPY, "guest memory has no write permission"); + cursor = std::min(end, base + memory.RegionSize); + } +#else + std::ifstream maps("/proc/self/maps"); + require(maps.is_open(), "cannot query guest memory maps"); + std::string line; + while (cursor < end && std::getline(maps, line)) { + std::istringstream fields(line); + std::uintptr_t first = 0; + std::uintptr_t last = 0; + char separator = 0; + std::string permissions; + require(static_cast(fields >> std::hex >> first >> separator >> last >> permissions) && separator == '-' && first < last && !permissions.empty(), "invalid guest memory map entry"); + if (last <= cursor) continue; + require(first <= cursor && permissions[0] == 'r', "guest memory is not readable"); + require(!writable || (permissions.size() > 1 && permissions[1] == 'w'), "guest memory has no write permission"); + cursor = std::min(end, last); + } + require(cursor == end, "guest address range is not mapped"); +#endif +} + +void Read(std::uint64_t address, std::span destination, std::size_t alignment) { + if (destination.empty()) return; + const auto* source = reinterpret_cast(address); + CheckRange(source, destination.size(), alignment); + std::memcpy(destination.data(), source, destination.size()); +} + +void Write(std::uint64_t address, std::span source, std::size_t alignment) { + if (source.empty()) return; + auto* destination = reinterpret_cast(address); + CheckRange(destination, source.size(), alignment, true); + std::memcpy(destination, source.data(), source.size()); +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp new file mode 100644 index 000000000..094a76254 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -0,0 +1,316 @@ +#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include +#include +#include +#include +#include + +namespace AgcDriver::Pm4 { +namespace { + +void require(bool condition, const char* reason) { + if (!condition) throw std::runtime_error(reason); +} + +std::uint64_t address(std::uint32_t low, std::uint32_t high) { + return low | (static_cast(high) << 32u); +} + +std::uint32_t registerOffset(std::uint32_t value) { + require(value != 0xffffffffu, "indirect register sentinel semantics are not implemented"); + const auto offset = value & ~0x70000000u; + require(offset <= 0xffffu, "extended register semantics are not implemented"); + return offset; +} + +Registers& registersFor(QueueState& queue, std::uint32_t opcode) { + if (opcode == 0x69 || opcode == 0x9f) return queue.context; + if (opcode == 0x76 || opcode == 0x63) return queue.shader; + return queue.userConfig; +} + +void writeRegister(QueueState& queue, std::uint32_t opcode, std::uint32_t offset, std::uint32_t value) { + registersFor(queue, opcode).insert_or_assign(offset, value); + if ((opcode == 0x64 || opcode == 0x79 || opcode == 0x7a) && offset == 0x243) queue.indexType = value & 3u; +} + +bool memorySelector(std::uint32_t selector) { + return selector == 0 || selector == 3; +} + +std::uint32_t dmaSource(std::span packet) { + return ((packet[1] >> 29u) & 3u) | ((packet[6] >> 24u) & 4u) | ((packet[6] >> 25u) & 8u); +} + +std::uint32_t dmaDestination(std::span packet) { + return ((packet[1] >> 20u) & 3u) | ((packet[6] >> 25u) & 4u) | ((packet[6] >> 26u) & 8u); +} + +void copyMemory(std::uint64_t source, std::uint64_t destination, std::size_t bytes, bool immediate) { + if (bytes == 0) return; + GuestMemory::CheckRange(reinterpret_cast(destination), bytes, 1, true); + std::vector data(bytes); + if (immediate) { + const auto value = static_cast(source); + for (std::size_t i = 0; i < bytes; ++i) data[i] = static_cast(value >> ((i % 4) * 8)); + } else { + GuestMemory::Read(source, data); + } + GuestMemory::Write(destination, data); +} + +} + +std::string Name(std::uint32_t header) { + const auto opcode = (header >> 8u) & 0xffu; + if (opcode == 0x10 && (header & 0xfcu) != 0) { + switch ((header >> 2u) & 0x3fu) { + case 0x05: return "DRAW_RESET"; + case 0x06: return "WAIT_FLIP_DONE"; + case 0x09: return "DISPATCH_RESET"; + case 0x0b: return "PUSH_MARKER"; + case 0x0c: return "POP_MARKER"; + case 0x14: return "ACQUIRE_MEM_CUSTOM"; + case 0x15: return "WRITE_DATA_CUSTOM"; + case 0x17: return "FLIP"; + case 0x18: return "RELEASE_MEM_CUSTOM"; + case 0x19: return "DMA_DATA_CUSTOM"; + case 0x1a: return "CONTEXT_STATE"; + default: return "UNKNOWN_CUSTOM"; + } + } + for (const auto& entry : Opcodes) if (entry.value == opcode) return std::string(entry.name); + char text[24]{}; + std::snprintf(text, sizeof(text), "UNKNOWN_0x%02x", opcode); + return text; +} + +std::string_view UnsupportedReason(std::uint32_t header) { + const auto opcode = (header >> 8u) & 0xffu; + if (opcode == 0x10) { + switch ((header >> 2u) & 0x3fu) { + case 0: case 0x09: case 0x0b: case 0x0c: case 0x17: case 0x1a: return {}; + case 0x06: return "VideoOut buffer-completion wait interface is not implemented"; + case 0x14: case 0x18: return "guest cache actions and GPU release events are not implemented"; + default: return "custom packet has no implemented contract in the reference dispatch table"; + } + } + switch (opcode) { + case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x26: + case 0x2a: case 0x2f: case 0x37: case 0x40: case 0x42: case 0x50: + case 0x63: case 0x64: case 0x69: case 0x76: case 0x79: case 0x7a: + case 0x81: case 0x83: case 0x9f: return {}; + case 0x24: case 0x25: case 0x27: case 0x2c: case 0x2d: case 0x35: case 0x38: case 0x3a: case 0x8d: + return "graphics draw, shader stages and guest render-target materialization are not implemented"; + case 0x20: return "GPU query predication is not implemented"; + case 0x22: return "conditional command execution and conditional flip reservation are not implemented"; + case 0x33: case 0x3f: return "nested command buffers, branching and nested flip reservation are not implemented"; + case 0x39: case 0x3c: case 0x59: case 0x93: + return "cooperative command-queue waits are not implemented"; + case 0x84: case 0x85: case 0x86: case 0x88: + return "separate CE/DE execution and counter synchronization are not implemented"; + case 0x43: case 0x46: case 0x47: case 0x48: case 0x49: case 0x58: + return "guest cache actions, GPU events and interrupt delivery are not implemented"; + case 0x8e: return "GPU LOD statistics are not implemented; synthetic results are forbidden"; + case 0x28: case 0x41: case 0x68: case 0x78: + return "opcode is named but has no handler in the reference dispatch table"; + default: return "opcode is not known in the reference"; + } +} + +void Validate(std::span packet, std::uint32_t queue) { + require(packet.size() >= 2, "truncated PM4 header or payload"); + const auto header = packet[0]; + require((header & 0xc0000000u) == 0xc0000000u, "unsupported PM4 packet type"); + require(packet.size() == ((header >> 16u) & 0x3fffu) + 2u, "invalid PM4 packet size"); + const auto opcode = (header >> 8u) & 0xffu; + const auto size = [&](std::size_t count) { require(packet.size() == count, "invalid packet size"); }; + const auto graphics = [&] { require(queue == 0, "graphics packet in compute queue"); }; + const auto reason = UnsupportedReason(header); + if (!reason.empty()) throw std::runtime_error(std::string(reason)); + if (opcode == 0x10) { + require((header & 3u) == 0, "unsupported NOP header flags"); + switch ((header >> 2u) & 0x3fu) { + case 0: + require((packet[1] & 0xffff0000u) != 0x68750000u, "typed user-data and legacy flip markers are not implemented"); + break; + case 0x09: size(2); break; + case 0x0b: { + const auto data = std::as_bytes(packet.subspan(1)); + require(std::find(data.begin(), data.end(), std::byte{}) != data.end(), "unterminated marker text"); + break; + } + case 0x0c: break; + case 0x17: graphics(); size(6); break; + case 0x1a: + graphics(); + require(packet.size() == 3 || packet.size() == 5, "invalid context-state packet size"); + require(packet[1] <= 3, "unknown context-state operation"); + require(std::all_of(packet.begin() + 2, packet.end(), [](auto value) { return value == 0; }), "context-state trailing fields are not implemented"); + break; + } + return; + } + require((header & 0xffu) == 0 || (opcode == 0x11 && (header & 0xffu) == 2), "PM4 header flags are not implemented"); + switch (opcode) { + case 0x11: + size(4); + require(packet[1] == 1 && (packet[2] & 7u) == 0 && packet[3] <= 0xffffu, "unsupported indirect base index, alignment or address bits"); + if ((header & 2u) == 0) graphics(); + break; + case 0x12: graphics(); size(2); require((packet[1] & ~0xfu) == 0, "unsupported CLEAR_STATE payload bits"); break; + case 0x13: case 0x2f: graphics(); size(2); break; + case 0x26: graphics(); size(3); break; + case 0x2a: graphics(); size(2); require(packet[1] <= 3, "unsupported index-type modifiers"); break; + case 0x15: size(5); require((packet[4] & ~0x8000u) == 0x41u, "dispatch modifiers are not implemented"); break; + case 0x16: + require(packet.size() == 3 || packet.size() == 4, "invalid indirect dispatch size"); + require((packet.back() & ~0x8000u) == 0x41u, "indirect dispatch modifiers are not implemented"); + break; + case 0x42: size(2); require(packet[1] == 0, "unsupported PFP_SYNC_ME payload"); break; + case 0x63: case 0x64: case 0x9f: + if (opcode != 0x63) graphics(); + size(5); + require((packet[1] & 3u) == 0 && packet[3] == 0x80000000u && packet[4] <= 0x3fffu, "unsupported indirect-register address or control fields"); + break; + case 0x69: case 0x76: case 0x79: case 0x7a: { + if (opcode != 0x76) graphics(); + require(packet.size() >= 3, "register packet has no values"); + if (opcode == 0x7a) require((packet[1] & 0xf0000000u) == 0 || (packet.size() == 3 && packet[1] == 0x20000243u), "indexed register bank selection is not implemented"); + const auto offset = registerOffset(packet[1]); + require(packet.size() - 2 <= 0x10000u - offset, "register range overflow"); + break; + } + case 0x81: + graphics(); + require(packet[1] <= 0xbffcu && (packet[1] & 3u) == 0 && packet.size() - 2 <= 0x3000u - packet[1] / 4u, "constant RAM write range overflow or misalignment"); + break; + case 0x83: + graphics(); size(5); + require(packet[1] <= 0xbffcu && (packet[1] & 3u) == 0 && packet[2] <= 0x3000u - packet[1] / 4u, "constant RAM dump range overflow or misalignment"); + break; + case 0x37: { + require(packet.size() >= 5, "WRITE_DATA has no data"); + require((packet[1] & ~0x00110f00u) == 0, "WRITE_DATA engine, cache or reserved fields are not implemented"); + const auto destination = (packet[1] >> 8u) & 0xfu; + require(destination == 1 || destination == 2 || (queue != 0 && destination == 5), "WRITE_DATA register or GDS destination is not implemented"); + require((packet[2] & 3u) == 0, "misaligned WRITE_DATA destination"); + break; + } + case 0x40: { + size(6); + require((packet[1] & ~0x40110f0fu) == 0, "COPY_DATA engine, cache or reserved fields are not implemented"); + const auto source = ((packet[1] & 0xfu) << 1u) | ((packet[1] >> 30u) & 1u); + const auto destination = ((packet[1] >> 8u) & 0xfu) << 1u; + require(destination == 2 || destination == 4, "COPY_DATA register or GDS destination is not implemented"); + require(source == 2 || source == 4 || source == 5 || source == 10 || source == 11, "COPY_DATA register, GDS or reference-clock source is not implemented"); + require(source < 10 || ((packet[1] & 0x10000u) == 0 && packet[3] == 0), "64-bit immediate COPY_DATA is not implemented"); + break; + } + case 0x50: + size(7); + require((packet[1] & ~0xe0300001u) == 0, "DMA_DATA cache or reserved fields are not implemented"); + require(memorySelector(dmaDestination(packet)), "DMA_DATA register, GDS or prefetch destination is not implemented"); + require(memorySelector(dmaSource(packet)) || dmaSource(packet) == 2, "DMA_DATA register or GDS source is not implemented"); + require(dmaSource(packet) != 2 || packet[3] == 0, "DMA_DATA immediate exceeds 32 bits"); + break; + default: throw std::runtime_error("known packet has no validator"); + } +} + +bool AccessesMemory(std::uint32_t header) { + switch ((header >> 8u) & 0xffu) { + case 0x16: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true; + default: return false; + } +} + +std::array ResolveDispatch(std::span packet, const QueueState& queue) { + std::uint64_t source = 0; + if (packet.size() == 4) source = address(packet[1], packet[2]); + else { + require(queue.dispatchIndirectBase != 0, "indirect dispatch base has not been set"); + require(packet[1] <= std::numeric_limits::max() - queue.dispatchIndirectBase, "indirect dispatch address overflow"); + source = queue.dispatchIndirectBase + packet[1]; + } + std::array result{0xc0031500u, 0, 0, 0, packet.back()}; + GuestMemory::Read(source, std::as_writable_bytes(std::span(result).subspan(1, 3)), 4); + return result; +} + +void Execute(std::span packet, QueueState& queue) { + const auto opcode = (packet[0] >> 8u) & 0xffu; + switch (opcode) { + case 0x10: + switch ((packet[0] >> 2u) & 0x3fu) { + case 0: return; + case 0x09: queue = QueueState{}; return; + case 0x0b: queue.markers.emplace_back(reinterpret_cast(packet.data() + 1)); return; + case 0x0c: + require(!queue.markers.empty(), "marker stack underflow"); + queue.markers.pop_back(); + return; + case 0x1a: + switch (packet[1]) { + case 0: queue.ClearContext(); break; + case 1: case 3: + require(!queue.savedContext.has_value(), "context state is already pushed"); + queue.savedContext = queue.context; + if (packet[1] == 3) queue.ClearContext(); + break; + case 2: + require(queue.savedContext.has_value(), "context state has not been pushed"); + queue.context = std::move(*queue.savedContext); + queue.savedContext.reset(); + break; + } + return; + default: throw std::runtime_error("custom packet requires driver execution"); + } + case 0x11: + ((packet[0] & 2u) == 0 ? queue.drawIndirectBase : queue.dispatchIndirectBase) = address(packet[2], packet[3]); + return; + case 0x12: queue.ClearContext(); return; + case 0x13: queue.indexBufferSize = packet[1]; return; + case 0x26: queue.indexBase = address(packet[1], packet[2]); return; + case 0x2a: queue.indexType = packet[1]; return; + case 0x2f: queue.instanceCount = packet[1]; return; + case 0x63: case 0x64: case 0x9f: { + std::vector pairs(static_cast(packet[4]) * 2); + GuestMemory::Read(address(packet[1], packet[2]), std::as_writable_bytes(std::span(pairs)), 4); + for (std::size_t i = 0; i < pairs.size(); i += 2) registerOffset(pairs[i]); + for (std::size_t i = 0; i < pairs.size(); i += 2) writeRegister(queue, opcode, registerOffset(pairs[i]), pairs[i + 1]); + return; + } + case 0x69: case 0x76: case 0x79: case 0x7a: { + const auto offset = registerOffset(packet[1]); + for (std::size_t i = 2; i < packet.size(); ++i) writeRegister(queue, opcode, offset + static_cast(i - 2), packet[i]); + return; + } + case 0x81: + std::copy(packet.begin() + 2, packet.end(), queue.constantRam.begin() + packet[1] / 4); + return; + case 0x83: + GuestMemory::Write(address(packet[3], packet[4]), std::as_bytes(std::span(queue.constantRam).subspan(packet[1] / 4, packet[2])), 4); + return; + case 0x37: { + const auto destination = address(packet[2], packet[3]); + if ((packet[1] & 0x10000u) != 0) { + for (const auto& value : packet.subspan(4)) GuestMemory::Write(destination, std::as_bytes(std::span(&value, 1)), 4); + } else GuestMemory::Write(destination, std::as_bytes(packet.subspan(4)), 4); + return; + } + case 0x40: { + const auto source = ((packet[1] & 0xfu) << 1u) | ((packet[1] >> 30u) & 1u); + copyMemory(address(packet[2], packet[3]), address(packet[4], packet[5]), (packet[1] & 0x10000u) != 0 ? 8 : 4, source >= 10); + return; + } + case 0x50: + copyMemory(address(packet[2], packet[3]), address(packet[4], packet[5]), packet[6] & 0x3ffffffu, dmaSource(packet) == 2); + return; + default: throw std::runtime_error("packet requires driver execution"); + } +} + +} diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp new file mode 100644 index 000000000..98b700c24 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -0,0 +1,225 @@ +#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libc/include/Shutdown.hpp" +#include +#include +#include +#include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif + +namespace { + +void check(bool condition, const char* reason) { + if (!condition) throw std::runtime_error(reason); +} + +template +void expectFailure(TAction action, const char* text) { + try { action(); } + catch (const std::runtime_error& error) { + check(std::string(error.what()).find(text) != std::string::npos, error.what()); + return; + } + throw std::runtime_error("expected PM4 rejection"); +} + +std::vector makePacket(std::uint32_t opcode, std::initializer_list payload, std::uint32_t flags = 0) { + std::vector result{0xc0000000u | (static_cast(payload.size() - 1) << 16u) | (opcode << 8u) | flags}; + result.insert(result.end(), payload); + return result; +} + +std::uint32_t low(const void* pointer) { return static_cast(reinterpret_cast(pointer)); } +std::uint32_t high(const void* pointer) { return static_cast(reinterpret_cast(pointer) >> 32u); } + +void execute(AgcDriver::QueueState& state, const std::vector& packet) { + AgcDriver::Pm4::Validate(packet, 0); + AgcDriver::Pm4::Execute(packet, state); +} + +void testCatalog() { + std::set values; + for (const auto& opcode : AgcDriver::Pm4::Opcodes) { + check(values.insert(opcode.value).second, "duplicate PM4 opcode"); + const auto packet = makePacket(opcode.value, {0}); + check(AgcDriver::Pm4::Name(packet[0]) == opcode.name, "opcode name mismatch"); + const auto reason = AgcDriver::Pm4::UnsupportedReason(packet[0]); + if (!reason.empty()) expectFailure([&] { AgcDriver::Pm4::Validate(packet, 0); }, std::string(reason).c_str()); + } + check(values.size() == 54, "reference opcode catalog is incomplete"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0xff, {0}), 0); }, "not known"); + const std::array, 11> custom{{ + {5, "DRAW_RESET"}, {6, "WAIT_FLIP_DONE"}, {9, "DISPATCH_RESET"}, {11, "PUSH_MARKER"}, + {12, "POP_MARKER"}, {20, "ACQUIRE_MEM_CUSTOM"}, {21, "WRITE_DATA_CUSTOM"}, {23, "FLIP"}, + {24, "RELEASE_MEM_CUSTOM"}, {25, "DMA_DATA_CUSTOM"}, {26, "CONTEXT_STATE"} + }}; + for (const auto& [id, name] : custom) check(AgcDriver::Pm4::Name(makePacket(0x10, {0}, id << 2)[0]) == name, "custom opcode name mismatch"); +} + +void testRegisters() { + AgcDriver::QueueState state; + const std::array indirectOpcodes{0x9f, 0x63, 0x64}; + for (auto opcode : indirectOpcodes) { + std::array pairs{0x10, 41, 0x11, 42, 0x10, 43}; + auto packet = makePacket(opcode, {low(pairs.data()), high(pairs.data()), 0x80000000, 3}); + execute(state, packet); + const auto& registers = opcode == 0x9f ? state.context : opcode == 0x63 ? state.shader : state.userConfig; + check(registers.at(0x10) == 43 && registers.at(0x11) == 42, "indirect register order or bank lost"); + pairs[0] = 0x12; + pairs[4] = 0xffffffffu; + expectFailure([&] { execute(state, packet); }, "sentinel"); + check(!registers.contains(0x12), "invalid indirect packet partially changed state"); + packet[1] = 0x1000; + packet[2] = 0; + expectFailure([&] { execute(state, packet); }, "guest"); + packet[3] = 0; + expectFailure([&] { AgcDriver::Pm4::Validate(packet, 0); }, "control"); + } + execute(state, makePacket(0x69, {0x11, 50, 51})); + check(state.context.at(0x11) == 50 && state.context.at(0x12) == 51, "direct registers not sequential"); + execute(state, makePacket(0x7a, {0x10, 60})); + check(state.userConfig.at(0x10) == 60, "uconfig index zero failed"); + execute(state, makePacket(0x7a, {0x20000243, 0x441})); + check(state.indexType == 1 && state.userConfig.at(0x243) == 0x441, "indexed VGT_INDEX_TYPE write lost state"); + expectFailure([&] { execute(state, makePacket(0x7a, {0x10000010, 1})); }, "bank selection"); + expectFailure([&] { execute(state, makePacket(0x69, {0xffff, 1, 2})); }, "overflow"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x9f, {0, 0, 0x80000000, 0}), 0x20); }, "compute"); +} + +void testContextAndBases() { + AgcDriver::QueueState state; + execute(state, makePacket(0x69, {0x10, 17})); + execute(state, makePacket(0x76, {0x20c, 2})); + execute(state, makePacket(0x10, {3, 0}, 0x68)); + check(state.context.empty() && state.shader.at(0x20c) == 2, "push-clear reset wrong state"); + expectFailure([&] { execute(state, makePacket(0x10, {1, 0}, 0x68)); }, "already pushed"); + execute(state, makePacket(0x69, {0x10, 19})); + execute(state, makePacket(0x10, {2, 0}, 0x68)); + check(state.context.at(0x10) == 17, "pop did not restore context"); + expectFailure([&] { execute(state, makePacket(0x10, {2, 0}, 0x68)); }, "not been pushed"); + alignas(8) std::array arguments{7, 8, 9, 0}; + execute(state, makePacket(0x11, {1, low(arguments.data()), high(arguments.data())}, 2)); + auto packet = makePacket(0x16, {0, 0x8041}); + AgcDriver::Pm4::Validate(packet, 0); + auto resolved = AgcDriver::Pm4::ResolveDispatch(packet, state); + check(resolved == std::array{0xc0031500, 7, 8, 9, 0x8041}, "base-relative dispatch arguments changed"); + packet = makePacket(0x16, {low(arguments.data()), high(arguments.data()), 0x41}); + AgcDriver::Pm4::Validate(packet, 0x20); + check(AgcDriver::Pm4::ResolveDispatch(packet, state)[3] == 9, "absolute indirect dispatch arguments changed"); + execute(state, makePacket(0x13, {32})); + execute(state, makePacket(0x26, {0x1000, 1})); + execute(state, makePacket(0x2a, {1})); + execute(state, makePacket(0x2f, {3})); + check(state.indexBufferSize == 32 && state.indexBase == 0x100001000ull && state.indexType == 1 && state.instanceCount == 3, "draw setup state lost"); + execute(state, makePacket(0x10, {0x00636261}, 0x2c)); + check(state.markers.back() == "abc", "marker text lost"); + execute(state, makePacket(0x10, {0}, 0x30)); + expectFailure([&] { execute(state, makePacket(0x10, {0}, 0x30)); }, "underflow"); + execute(state, makePacket(0x10, {0}, 0x24)); + check(state.shader.empty() && state.context.empty() && state.dispatchIndirectBase == 0 && state.indexBase == 0 && !state.savedContext, "dispatch reset retained state"); +} + +void testMemory() { + AgcDriver::QueueState state; + std::array data{0, 0, 0, 0}; + execute(state, makePacket(0x37, {0x100, low(data.data()), high(data.data()), 11, 12})); + check(data[0] == 11 && data[1] == 12, "WRITE_DATA increment failed"); + execute(state, makePacket(0x37, {0x10100, low(data.data()), high(data.data()), 21, 22})); + check(data[0] == 22 && data[1] == 12, "WRITE_DATA fixed destination failed"); + execute(state, makePacket(0x81, {4, 31, 32})); + execute(state, makePacket(0x83, {4, 2, low(data.data()), high(data.data())})); + check(data[0] == 31 && data[1] == 32, "constant RAM round trip failed"); + expectFailure([&] { execute(state, makePacket(0x81, {0xbffc, 1, 2})); }, "overflow"); + expectFailure([&] { execute(state, makePacket(0x40, {0x10105, 0, 0, low(data.data()), high(data.data())})); }, "64-bit immediate"); +} + +void testCopies() { + AgcDriver::QueueState state; + alignas(8) std::array source{11, 12, 13, 14}; + alignas(8) std::array destination{}; + execute(state, makePacket(0x40, {0x10101, low(source.data()), high(source.data()), low(destination.data()), high(destination.data())})); + check(destination[0] == 11 && destination[1] == 12 && destination[2] == 0, "64-bit COPY_DATA failed"); + execute(state, makePacket(0x40, {0x105, 0x12345678, 0, low(destination.data()), high(destination.data())})); + check(destination[0] == 0x12345678, "immediate COPY_DATA failed"); + execute(state, makePacket(0x50, {0x60000000, low(source.data()), high(source.data()), low(destination.data()), high(destination.data()), 16})); + check(source == destination, "DMA_DATA copy failed"); + execute(state, makePacket(0x50, {0x40000000, 0x44332211, 0, low(destination.data()), high(destination.data()), 6})); + check(destination[0] == 0x44332211 && destination[1] == 0x00002211, "DMA_DATA byte fill failed"); + expectFailure([&] { execute(state, makePacket(0x50, {0x60100000, low(source.data()), high(source.data()), 0, 0, 4})); }, "GDS"); + expectFailure([&] { execute(state, makePacket(0x37, {0x100, 0x1000, 0, 1})); }, "guest"); +#ifdef _WIN32 + auto* memory = VirtualAlloc(nullptr, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + check(memory != nullptr, "VirtualAlloc failed"); + DWORD previous = 0; + check(VirtualProtect(memory, 4096, PAGE_READONLY, &previous) != 0, "VirtualProtect failed"); + try { + expectFailure([&] { execute(state, makePacket(0x37, {0x100, low(memory), high(memory), 1})); }, "write permission"); + } catch (...) { VirtualFree(memory, 0, MEM_RELEASE); throw; } + check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed"); +#endif +} + +void testDriverSubmission() { + std::array source{0x10, 73}; + std::array destination{}; + std::vector commands; + for (const auto& packet : { + makePacket(0x9f, {low(source.data()), high(source.data()), 0x80000000, 1}), + makePacket(0x81, {0, 83}), + makePacket(0x42, {0}), + makePacket(0x83, {0, 1, low(destination.data()), high(destination.data())}) + }) commands.insert(commands.end(), packet.begin(), packet.end()); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + check(sceAgcDriverSubmitDcb(&packet) == 0, "PM4 submission failed"); + AgcDriverWaitIdle_nid_postfix(); + check(destination[0] == 83, "worker did not execute PM4 memory operations"); + destination[0] = 0; + const auto draw = makePacket(0x2d, {3, 2}); + commands.insert(commands.end(), draw.begin(), draw.end()); + packet = Packet{commands.data(), static_cast(commands.size()), 0, {}}; + expectFailure([&] { sceAgcDriverSubmitDcb(&packet); }, "DRAW_INDEX_AUTO at DWORD"); + AgcDriverWaitIdle_nid_postfix(); + check(destination[0] == 0, "rejected submission executed a prefix"); +} + +void testAsyncMemoryFailure() { + auto commands = makePacket(0x37, {0x100, 0x1000, 0, 1}); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + check(sceAgcDriverSubmitDcb(&packet) == 0, "memory packet was not submitted"); + expectFailure([] { AgcDriverWaitIdle_nid_postfix(); }, "guest"); + expectFailure([] { AgcDriverSuspendPoint_nid_postfix(); }, "guest"); + expectFailure([&] { sceAgcDriverSubmitDcb(&packet); }, "guest"); + expectFailure([] { LibcRunShutdown_nid_postfix(); }, "guest"); +} + +} + +int main(int argc, char** argv) { + try { + if (argc == 2 && std::string(argv[1]) == "failure") { + testAsyncMemoryFailure(); + std::puts("PM4 asynchronous memory failure propagated to idle, suspend, submit and shutdown"); + return 0; + } + testCatalog(); + testRegisters(); + testContextAndBases(); + testMemory(); + testCopies(); + testDriverSubmission(); + LibcRunShutdown_nid_postfix(); + std::puts("PM4 catalog, registers, state, memory and submission tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + try { LibcRunShutdown_nid_postfix(); } catch (...) {} + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/Recompiler.cpp b/core/libs/prx/libSceAgcDriver/tests/Recompiler.cpp index 7fa045b92..5ab13e9c3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recompiler.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recompiler.cpp @@ -6,7 +6,7 @@ #include #include -int main() { +int main(int argc, char** argv) { try { alignas(256) const std::array code{0xbf810000}; Shader shader{}; @@ -23,6 +23,15 @@ int main() { 0xc0031500, 1, 1, 1, 0x8041 }; Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + const std::array arguments{1, 1, 1}; + if (argc == 2 && std::string(argv[1]) == "indirect") { + const auto argumentAddress = reinterpret_cast(arguments.data()); + commands[7] = 0xc0021600; + commands[8] = static_cast(argumentAddress); + commands[9] = static_cast(argumentAddress >> 32u); + commands[10] = 0x8041; + packet.dw_num = 11; + } sceAgcDriverSubmitAcb(0x20, &packet); try { AgcDriverWaitIdle_nid_postfix();