Merge pull request #622 from DotDebian/feat/agc-dcb-prime-utcl2
feat(agc): implement sceAgcDcbPrimeUtcl2 and its size query
This commit is contained in:
commit
8c906ca985
|
|
@ -47,14 +47,7 @@ std::uint32_t APS5_VABI sceAgcAcbAtomicGdsGetSize() {
|
|||
}
|
||||
|
||||
std::uint32_t* APS5_VABI sceAgcAcbPrimeUtcl2(CommandBuffer* buf, const volatile void* address, std::uint32_t sizeInBytes) {
|
||||
const auto guestAddress = reinterpret_cast<std::uintptr_t>(address);
|
||||
auto* packet = Agc::Command::Allocate(buf, 5, __func__);
|
||||
packet[0] = Agc::Command::Header(0x10u, 5);
|
||||
packet[1] = 0;
|
||||
packet[2] = static_cast<std::uint32_t>(guestAddress);
|
||||
packet[3] = static_cast<std::uint32_t>(guestAddress >> 32u);
|
||||
packet[4] = sizeInBytes;
|
||||
return packet;
|
||||
return Agc::Command::WritePrimeUtcl2(buf, address, sizeInBytes, __func__);
|
||||
}
|
||||
|
||||
std::uint32_t APS5_VABI sceAgcAcbPrimeUtcl2GetSize() {
|
||||
|
|
|
|||
|
|
@ -151,3 +151,23 @@ if(BUILD_TESTING)
|
|||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_prime_utcl2_tests EXCLUDE_FROM_ALL
|
||||
tests/PrimeUtcl2.cpp
|
||||
Command/src/Packet.cpp
|
||||
Command/src/Memory.cpp
|
||||
Acb/src/Memory.cpp
|
||||
DcbFlow/src/Memory.cpp
|
||||
DcbState/src/Display.cpp
|
||||
)
|
||||
target_include_directories(agc_prime_utcl2_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(agc_prime_utcl2_tests PRIVATE libc)
|
||||
configure_windows_unwind(agc_prime_utcl2_tests)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_prime_utcl2_tests PROPERTIES EXCLUDE_FROM_ALL FALSE)
|
||||
add_test(NAME agc_prime_utcl2_tests COMMAND agc_prime_utcl2_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_prime_utcl2_tests PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ namespace Agc::Command {
|
|||
std::uint32_t* WriteDma(CommandBuffer* buffer, bool compute, std::uint8_t engine, std::uint8_t dst, std::uint8_t dstCachePolicy, std::uint64_t dstAddress, std::uint8_t src, std::uint8_t srcCachePolicy, std::uint64_t srcAddress, std::uint32_t numBytes, std::uint8_t waitForPrevious, std::uint8_t writeConfirm, std::uint8_t blockEngine, const char* function);
|
||||
std::uint32_t* WriteCopyData(CommandBuffer* buffer, bool compute, std::uint8_t dst, std::uint8_t dstCachePolicy, std::uint64_t dstAddress, std::uint8_t src, std::uint8_t srcCachePolicy, std::uint64_t srcAddressOrImmediate, std::uint8_t itemSize, std::uint8_t writeConfirm, const char* function);
|
||||
std::uint32_t* WriteAtomicMem(CommandBuffer* buffer, std::uint8_t atomicOp, std::uint8_t command, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t srcData, std::uint64_t compareData, std::uint16_t loopInterval, const char* function);
|
||||
std::uint32_t* WritePrimeUtcl2(CommandBuffer* buffer, const volatile void* address, std::uint32_t sizeInBytes, const char* function);
|
||||
std::uint32_t* WriteData(CommandBuffer* buffer, bool compute, std::uint8_t dst, std::uint8_t cachePolicy, std::uint64_t address, const void* data, std::uint32_t count, std::uint8_t increment, std::uint8_t writeConfirm, const char* function);
|
||||
std::uint32_t* WriteWait(CommandBuffer* buffer, std::uint8_t size, std::uint8_t compareFunction, std::uint8_t operation, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t reference, std::uint64_t mask, std::uint32_t pollCycles, const char* function);
|
||||
std::uint32_t* ValidateWait(std::uint32_t* packet, const char* function);
|
||||
|
|
|
|||
|
|
@ -49,6 +49,11 @@ std::uint32_t* WriteAtomicMem(CommandBuffer* buffer, std::uint8_t atomicOp, std:
|
|||
return Emit(buffer, 0x1eu, {atomicOp | (static_cast<std::uint32_t>(command) << 8u) | (static_cast<std::uint32_t>(cachePolicy) << 25u), static_cast<std::uint32_t>(guestAddress), static_cast<std::uint32_t>(guestAddress >> 32u), static_cast<std::uint32_t>(srcData), static_cast<std::uint32_t>(srcData >> 32u), static_cast<std::uint32_t>(compareData), static_cast<std::uint32_t>(compareData >> 32u), loopInterval}, function);
|
||||
}
|
||||
|
||||
std::uint32_t* WritePrimeUtcl2(CommandBuffer* buffer, const volatile void* address, std::uint32_t sizeInBytes, const char* function) {
|
||||
const auto guestAddress = reinterpret_cast<std::uintptr_t>(address);
|
||||
return Emit(buffer, 0x10u, {0, static_cast<std::uint32_t>(guestAddress), static_cast<std::uint32_t>(guestAddress >> 32u), sizeInBytes}, function);
|
||||
}
|
||||
|
||||
std::uint32_t* WriteData(CommandBuffer* buffer, bool compute, std::uint8_t dst, std::uint8_t cachePolicy, std::uint64_t address, const void* data, std::uint32_t count, std::uint8_t increment, std::uint8_t writeConfirm, const char* function) {
|
||||
Require(data != nullptr && count != 0 && count <= 0x3ffdu, function, "invalid write data payload");
|
||||
CheckBits(dst, compute ? 0xfu : 0x1fu, function);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "prx/libSceAgc/DcbState/include/Display.hpp"
|
||||
|
||||
#include "prx/libSceAgc/Command/include/Memory.hpp"
|
||||
#include "prx/libSceAgc/Command/include/Packet.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp"
|
||||
#include <cstdint>
|
||||
|
|
@ -21,16 +22,11 @@ uint32_t* APS5_VABI sceAgcDcbSetFlip(CommandBuffer* buf, uint32_t video_out_hand
|
|||
}
|
||||
|
||||
uint32_t* APS5_VABI sceAgcDcbPrimeUtcl2(CommandBuffer* buf, const volatile void* address, uint32_t size_in_bytes) {
|
||||
(void)buf;
|
||||
(void)address;
|
||||
(void)size_in_bytes;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return nullptr;
|
||||
return Agc::Command::WritePrimeUtcl2(buf, address, size_in_bytes, __func__);
|
||||
}
|
||||
|
||||
std::uint32_t APS5_VABI sceAgcDcbPrimeUtcl2GetSize() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return 20;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
#include "prx/libSceAgc/Command/include/Packet.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
|
||||
extern "C" {
|
||||
std::uint32_t* APS5_VABI sceAgcAcbPrimeUtcl2(CommandBuffer*, const volatile void*, std::uint32_t);
|
||||
std::uint32_t APS5_VABI sceAgcAcbPrimeUtcl2GetSize();
|
||||
std::uint32_t* APS5_VABI sceAgcDcbPrimeUtcl2(CommandBuffer*, const volatile void*, std::uint32_t);
|
||||
std::uint32_t APS5_VABI sceAgcDcbPrimeUtcl2GetSize();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::uint32_t Sentinel = 0xabcdef01u;
|
||||
constexpr std::uint64_t Address = 0x0000112233440000ull;
|
||||
constexpr std::uint32_t SizeInBytes = 0x00345000u;
|
||||
|
||||
void check(bool condition, const char* message) {
|
||||
if (!condition) throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
template <typename TAction>
|
||||
void expectFailure(TAction action) {
|
||||
try {
|
||||
action();
|
||||
} catch (const std::runtime_error&) {
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error("expected invalid input to fail");
|
||||
}
|
||||
|
||||
struct Storage {
|
||||
std::array<std::uint32_t, 16> words{};
|
||||
CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0};
|
||||
|
||||
Storage() {
|
||||
words.fill(Sentinel);
|
||||
}
|
||||
|
||||
void limitTo(std::uint32_t count) {
|
||||
buffer.cursor_down = buffer.cursor_up + count;
|
||||
}
|
||||
};
|
||||
|
||||
std::uint32_t* writeCompute(CommandBuffer* buffer) {
|
||||
return sceAgcAcbPrimeUtcl2(buffer, reinterpret_cast<const volatile void*>(Address), SizeInBytes);
|
||||
}
|
||||
|
||||
std::uint32_t* writeDraw(CommandBuffer* buffer) {
|
||||
return sceAgcDcbPrimeUtcl2(buffer, reinterpret_cast<const volatile void*>(Address), SizeInBytes);
|
||||
}
|
||||
|
||||
template <typename TWriter, typename TSize>
|
||||
void testCommand(TWriter writer, TSize size) {
|
||||
const std::array expected{0xc0031000u, 0u, 0x33440000u, 0x1122u, SizeInBytes};
|
||||
const auto count = static_cast<std::uint32_t>(expected.size());
|
||||
check(size() == count * sizeof(std::uint32_t), "size query does not match the prime packet");
|
||||
|
||||
Storage storage;
|
||||
auto* packet = writer(&storage.buffer);
|
||||
check(packet == storage.words.data() && std::equal(expected.begin(), expected.end(), packet), "incorrect prime packet");
|
||||
check(storage.buffer.cursor_up == packet + size() / sizeof(std::uint32_t), "cursor advance does not match the size query");
|
||||
check(std::all_of(storage.words.begin() + count, storage.words.end(), [](std::uint32_t word) { return word == Sentinel; }), "prime command overwrote following words");
|
||||
|
||||
auto* second = writer(&storage.buffer);
|
||||
check(second == packet + count && std::equal(expected.begin(), expected.end(), second), "second prime packet does not follow the first");
|
||||
|
||||
Storage exact;
|
||||
exact.limitTo(size() / sizeof(std::uint32_t));
|
||||
check(writer(&exact.buffer) == exact.words.data() && exact.buffer.cursor_up == exact.buffer.cursor_down, "prime packet does not fill the queried size");
|
||||
|
||||
Storage shortBuffer;
|
||||
shortBuffer.limitTo(size() / sizeof(std::uint32_t) - 1u);
|
||||
const auto before = shortBuffer.words;
|
||||
expectFailure([&] { writer(&shortBuffer.buffer); });
|
||||
check(shortBuffer.words == before && shortBuffer.buffer.cursor_up == shortBuffer.words.data(), "failed prime write modified the buffer");
|
||||
|
||||
expectFailure([&] { writer(nullptr); });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
testCommand(writeCompute, sceAgcAcbPrimeUtcl2GetSize);
|
||||
testCommand(writeDraw, sceAgcDcbPrimeUtcl2GetSize);
|
||||
check(sceAgcAcbPrimeUtcl2GetSize() == sceAgcDcbPrimeUtcl2GetSize(), "compute and draw prime sizes differ");
|
||||
std::puts("AGC prime UTCL2 tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "%s\n", error.what());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [dolOmWH+huQ](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name
|
||||
- [V++UgBtQhn0](../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - unknown name
|
||||
- [gQkqkLttcpw](../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - unknown name, signature
|
||||
- [sceAgcDcbPrimeUtcl2](../../core/libs/prx/libSceAgc/DcbState/src/Display.cpp) (libSceAgc) - assumed to write the packet `sceAgcAcbPrimeUtcl2` writes: PRIME_UTCL2 has the same 5 dwords on the graphics and compute rings. Not confirmed on a title. Both write a NOP of that size, as there is no TLB to prime
|
||||
- [sceKernelInternalMemoryGetModuleSegmentInfo](../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature
|
||||
- [sceKernelSyncOnAddressWait](../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - the only known caller passes a null timeout and a name string as the fourth argument; the timeout is assumed to point to microseconds like the other kernel waits, and the name is ignored
|
||||
- [sceKernelMapNamedFlexibleMemoryInternal](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - flag 0x8000 unknown; only the sceKernelMapNamedFlexibleMemory flags are accepted
|
||||
|
|
|
|||
Loading…
Reference in New Issue