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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,13 @@ bool memorySelector(std::uint32_t selector) {
}

std::uint32_t dmaSource(std::span<const std::uint32_t> packet) {
return ((packet[1] >> 29u) & 3u) | ((packet[6] >> 24u) & 4u) | ((packet[6] >> 25u) & 8u);
const auto selector = ((packet[1] >> 29u) & 3u) | ((packet[6] >> 24u) & 4u) | ((packet[6] >> 25u) & 8u);
return selector == (DmaSelectGds | 12u) ? DmaSelectGds : selector;
}

std::uint32_t dmaDestination(std::span<const std::uint32_t> packet) {
return ((packet[1] >> 20u) & 3u) | ((packet[6] >> 25u) & 4u) | ((packet[6] >> 26u) & 8u);
const auto selector = ((packet[1] >> 20u) & 3u) | ((packet[6] >> 25u) & 4u) | ((packet[6] >> 26u) & 8u);
return selector == (DmaSelectGds | 12u) ? DmaSelectGds : selector;
}

// The bytes a COPY_DATA or DMA_DATA stores: an immediate is a repeating 32-bit pattern, a memory
Expand Down
53 changes: 53 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/Pm4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,58 @@ void testMemory() {
expectFailure([&] { execute(state, makePacket(0x40, {0x10105, 0, 0, low(data.data()), high(data.data())})); }, "64-bit immediate");
}

void testDmaGdsAddressControls() {
using namespace AgcDriver::Pm4;
AgcDriver::QueueState state;
alignas(8) std::array<std::uint32_t, 4> source{0x12345678, 0xabcdef01, 0x87654321, 0x10203040};
alignas(8) std::array<std::uint32_t, 4> destination{};
const auto packet = [](std::uint32_t selectors, std::uint64_t sourceAddress, std::uint64_t destinationAddress, std::uint32_t controls) {
return makePacket(0x50, {selectors, static_cast<std::uint32_t>(sourceAddress), static_cast<std::uint32_t>(sourceAddress >> 32u), static_cast<std::uint32_t>(destinationAddress), static_cast<std::uint32_t>(destinationAddress >> 32u), controls | 16u});
};
const auto sourceAddress = reinterpret_cast<std::uintptr_t>(source.data());
const auto destinationAddress = reinterpret_cast<std::uintptr_t>(destination.data());
const auto compareStores = [&](const auto& encoded, const auto& plain) {
Validate(encoded, 0);
const auto resolved = ResolveStore(encoded, state, 64);
const auto baseline = ResolveStore(plain, state, 64);
check(resolved.has_value() && baseline.has_value() && resolved->address == baseline->address && resolved->Bytes().size() == 16 && baseline->Bytes().size() == 16 && std::memcmp(resolved->Bytes().data(), baseline->Bytes().data(), 16) == 0, "GDS address controls changed ResolveStore");
const auto decoded = DecodeStores(encoded);
const auto expected = DecodeStores(plain);
check(decoded.size() == expected.size(), "GDS address controls changed DecodeStores count");
if (!decoded.empty()) check(decoded[0].address == expected[0].address && decoded[0].bytes == expected[0].bytes && decoded[0].value == expected[0].value, "GDS address controls changed DecodeStores");
};
const auto toGds = packet(0x60100000, sourceAddress, 0x300, 0x28000000);
compareStores(toGds, packet(0x60100000, sourceAddress, 0x300, 0));
check(DecodeStores(toGds).empty(), "GDS destination decoded as a guest memory store");
execute(state, toGds);
const auto fromGds = packet(0x20000000, 0x300, destinationAddress, 0x14000000);
compareStores(fromGds, packet(0x20000000, 0x300, destinationAddress, 0));
execute(state, fromGds);
check(destination == source, "DMA_DATA controlled GDS round trip did not advance every word");
const auto betweenGds = packet(0x20100000, 0x300, 0xfff0, 0x3c000000);
compareStores(betweenGds, packet(0x20100000, 0x300, 0xfff0, 0));
execute(state, betweenGds);
destination = {};
execute(state, packet(0x20000000, 0xfff0, destinationAddress, 0x14000000));
check(destination == source, "DMA_DATA controlled GDS copy did not advance every word");
const auto fill = packet(0x40100000, 0x5eed1234, 0x400, 0x28000000);
compareStores(fill, packet(0x40100000, 0x5eed1234, 0x400, 0));
execute(state, fill);
execute(state, packet(0x20000000, 0x400, destinationAddress, 0x14000000));
check(std::all_of(destination.begin(), destination.end(), [](auto word) { return word == 0x5eed1234; }), "DMA_DATA controlled GDS immediate fill failed");
for (const auto offset : {0xfff4ull, 0x10001ull, 0x100000300ull}) {
expectFailure([&] { Validate(packet(0x60100000, sourceAddress, offset, 0x28000000), 0); }, "DMA_DATA GDS destination range exceeds the GDS");
expectFailure([&] { Validate(packet(0x20000000, offset, destinationAddress, 0x14000000), 0); }, "DMA_DATA GDS source range exceeds the GDS");
}
for (std::uint32_t selector = 0; selector < 4; ++selector) {
for (const auto controls : {4u, 8u, 12u}) {
if (selector == 1 && controls == 12) continue;
expectFailure([&] { Validate(packet(selector << 29u, 0x300, destinationAddress, ((controls & 4u) << 24u) | ((controls & 8u) << 25u)), 0); }, "DMA_DATA register source is not implemented");
expectFailure([&] { Validate(packet(0x60000000 | (selector << 20u), sourceAddress, 0x300, ((controls & 4u) << 25u) | ((controls & 8u) << 26u)), 0); }, "DMA_DATA register destination is not implemented");
}
}
}

void testCopies() {
AgcDriver::QueueState state;
alignas(8) std::array<std::uint32_t, 4> source{11, 12, 13, 14};
Expand Down Expand Up @@ -2224,6 +2276,7 @@ int main(int argc, char** argv) {
testTagMarkers();
testUserDataWriters();
testCopies();
testDmaGdsAddressControls();
testMemoryCopyDecode();
testMemorySynchronization();
testConditionalValidation();
Expand Down
1 change: 1 addition & 0 deletions docs/dev/debt/libSceAgcDriver.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
- [sceAgcDriverGetEqContextId](../../../core/libs/prx/libSceAgcDriver/Eq/src/Query.cpp) (libSceAgcDriver) - a graphics end-of-pipe event carries `queue | context << 16`, context being the raising `RELEASE_MEM`'s dword 7 masked to 28 bits, as the PS5 13.60 kernel builds the event; `sceAgcDriverGetEqContextId` returns `data >> 16` and `sceAgcDriverGetEqEventType` the low 32 bits, as the PS5 10.01 module reads them. The kernel also packs the pipe, ME, generation and VMID into the low bits; only the queue is filled here, and the other fields are not measured. A typed context (bit 27) is routed elsewhere by the kernel and throws. Other event filters throw
- [User data registers](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Diagnostics.cpp) (AGC driver) - a `SPI_SHADER_USER_DATA_*` or `COMPUTE_USER_DATA_*` register the title never wrote reads as 0 when a stage's RSRC2 count covers it, as Kyty (zeroed `UserSgprInfo`) and SharpEmu (missing registers read 0) do, and as AMD PAL and Mesa initialize them; the console's value is not confirmed
- [SET_PREDICATION](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (AGC driver) - boolean predication follows the AMD PM4 encoding [Mesa](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/common/sid.h) uses: operation 3 reads 64 bits and 4 reads 32 bits, and bit 8 runs predicated packets when the value is non-zero (clear: when it is zero); the bit 12 hint is ignored. The address must have its low 4 bits clear, as `sceAgcDcbSetPredication` writes it. Query predication (Z-pass, primitive count), a predicated COND_EXEC, predicated command buffer chains and flips in predicated command buffers throw
- [DMA_DATA GDS address controls](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (AGC driver) are not measured on PS5. GDS selector 1 accepts both clear controls and register address space with no increment (SAS/SAIC or DAS/DAIC both 1), transferring consecutive bytes as [Mesa 24.0.0 si_cp_dma.c lines 72-86](https://github.com/chaotic-cx/mesa-mirror/blob/03ecd8b0a517e528d790a3d252194f7b898d0e8f/src/gallium/drivers/radeonsi/si_cp_dma.c#L72-L86) specifies. Partial GDS controls and non-GDS register-space or no-increment endpoints still throw.
- [sceAgcDriverUserDataWritePacket, sceAgcDriverUserDataWritePushMarker, sceAgcDriverUserDataWriteSetMarker, sceAgcDriverUserDataWritePopMarker](../../../core/libs/prx/libSceAgcDriver/Submit/src/Dcb.cpp) (libSceAgcDriver) - the packets follow firmware 13.42's libSceAgcDriver, read from disassembly, not measured on a console: a SET_UCONFIG_REG of register 0x342 (and 0x343 with up to four data bytes), then a WRITE_DATA of the remaining bytes to register 0xC343. The second argument is named `asyncCompute` because firmware libSceAgc passes 1 from its `sceAgcAcb*` marker calls and 0 from `sceAgcDcbPopMarker`; 0 sets header bit 2 in both packets. The marker kinds (0xC0000000 set, 0xC1000000 push, 0xC2000000 pop) and the colour packing are copied without names
- [Barycentrics without mesh shaders](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp) (AGC driver) - a pixel shader that reads barycentrics on the mesh path is emulated only on a device with `VK_EXT_mesh_shader`; a device with neither it nor `VK_KHR_fragment_shader_barycentric` (reported on Linux RADV with a GCN 5 GPU) throws that the device has neither the barycentric extension nor mesh shaders, because a geometry stage cannot follow a mesh stage and no other route is implemented. `VK_AMD_shader_explicit_vertex_parameter`, which a GCN 5 device on RADV exposes, is an unexplored option.
- [NGG vertex programs on the mesh path](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a vertex program with `PRIMGEN_EN` that uses LDS or `s_barrier` runs on the geometry (mesh) path. Each input primitive is assumed to yield at most one output primitive, the subgroup is bounded by `GE_MAX_OUTPUT_PER_SUBGROUP` as a vertex budget, and the GS/ES VGPR count checks are skipped, because the merged inputs keep their positions. Taken from the code Mesa's ACO emits for NGG culling and from PPSA59322 and PPSA99010 draws, not measured on hardware.
Expand Down
Loading