Skip to content

fix(agc): suppress the flip end-of-pipe event on queues that raise release interrupts - #7137

Merged
oneandonlydean merged 4 commits into
boykopovar:mainfrom
Lberonio:fix/agc-eop-latch-queue-mode
Oct 18, 2026
Merged

oneandonlydean merged 4 commits into
boykopovar:mainfrom
Lberonio:fix/agc-eop-latch-queue-mode

Conversation

@Lberonio

@Lberonio Lberonio commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

What

A flip delivers the queue's graphics end-of-pipe event (sceAgcDriverAddEqEvent) once the work recorded before it completes, the same event a RELEASE_MEM with INT_SEL on that queue delivers. A title that pairs more flips than release interrupts per frame therefore receives the event more times than it has consumers for it. The extra delivery reaches a consumer with no matching context and crashes: Gran Turismo 7 (PPSA01317) faults on mov 0x48(%r14),%rax with r14 = 0, and PPSA24156 faults with 0xc0000005 reading address 0x48, both from the queue's event consumer.

A queue that has delivered an interrupt from one of its own RELEASE_MEM/INT_SEL releases now latches, and a flip on that queue delivers nothing while its own releases keep delivering. A queue that has never done so still raises the event from the flip, which Minecraft (PPSA17221) waits on after every flip. AgcDriverDeliverEopInterrupt now reports whether a registration received the interrupt, so only a queue that really delivered latches. The latch is cleared when the queue stops raising its own release interrupts: flips are counted per queue since its last delivered release interrupt, and the fifth consecutive flip clears the latch and raises the event again, so a title that latches once and then waits on the flip event recovers within five flips; four flips of margin is above the two flips per release interrupt GT7 is measured with. The bound is by further flips and cannot be by time: a suppressed flip's release interrupt follows it by a median of 13 ms while GT7 renders but by 325 ms to 7 s across its loading stalls, so a 100-250 ms window delivers an event GT7 cannot take (measured below; a 250 ms window segfaulted GT7 on the first such delivery). The debt entry "Flip packets raise the graphics end-of-pipe event" records the rule and what is still unmeasured.

The deterministic SIGSEGV in #6350 is this second delivery. With it removed, GT7 runs past its frame-pacing loop and stops later on unrelated gaps (sceSslEnableVerifyOption unimplemented; an unsupported primitive type on another run), so that issue is not closed by this change.

#6963 addresses the same crash with a one-shot marker consumed by the next flip. GT7 pairs two flips with one release interrupt per frame, so the marker is spent on the first flip and the second still delivers; @MarcZX100 asked for this queue-mode rule to be reviewed as its own change and will re-scope #6963 based on it. This change does not build on it.

Tested

Arch Linux, kernel 7.2.9-arch1-1, GCC 16.2.1, Release, AMD RX 9070 XT (RADV, vulkan-radeon/Mesa 26.2.4), Intel i5-12500H host CPU (--to-intel).

  • agc_driver_tests --eop-interrupts, the new agc_driver_eop_interrupts ctest, passes on this branch: a flip on a queue with no release interrupt raises the event with data = 1, two RELEASE_MEM/INT_SEL releases on that queue deliver it with data = 2, and a later flip on the same queue raises nothing while still reaching its video output. A release interrupt keeps delivering while the latch is set. Flips then continue with no event until one raises it, and the test fails if that flip is not later than the second one, so a latch that clears inside GT7's two flips per release interrupt fails the test; after that flip a further one raises an event again, and after another release interrupt the queue is latched again, where a release followed by two flips raises none.
  • The same test binary built from the same tree with the runtime sources reverted fails with flip on a queue that already raised its own release interrupt raised a second event; with the latch back to permanent it fails at a flip never raised an event again on a queue whose own release interrupts stopped; with the limit at one flip it fails at a flip raised an event within two flips of the queue's own release interrupt. The whole agc_driver_tests binary passes with the change.
  • Gran Turismo 7 (PPSA01317 v01.680) from the game dump, run as ./app.elf, with the equivalent runtime change: no fault. Under gdb the instrumented run counts 231 deliveries and 231 consumer entries, 0 with a null context, all deliveries from the guest release site (libSceAgcDriver.prx+0x28388a) and none from the flip site (+0x28489d). Without the change the flip delivers with context 0x0 and the title faults at 18 s. The plain run then aborts at [libc] unhandled exception 'St13runtime_error' ... sceSslEnableVerifyOption, unchanged by this PR.
  • The .prx built from this branch does load into that runtime when libc.prx is regenerated from main (this branch's prx imports LibcRegisterFailureReport_nid_no_patch, which the runtime's Oct-15 libc.prx does not export and main's now does), but in that pairing the title stops before the flip path at DisplayBuffer.cpp: VideoOut: 4 KiB display buffer alignment is only supported for linear buffers. AgcDriver::DisplayBuffer gained its alignment member in 901b92c79, and the runtime's libSceVideoOut.prx is built from 34454bd, which constructs the struct with the eight members it has and never writes alignment, so a driver from a newer tree reads a value nothing set. A driver built from unmodified origin/main fails at the same line in the same pairing, so the rejection is not caused by this change, and no newer driver can reach the frame-pacing phase against the runtime's libSceVideoOut.
  • A runtime directory rebuilt from this branch (relinker and libs, so libSceAgcDriver.prx and libSceVideoOut.prx come from one tree) runs the same title with no fault past the frame-pacing loop: it reaches 40 s and aborts on unrelated gaps (sceSslConnect unimplemented, and AGC graphics: unsupported primitive type 21 on another run), where the older runtime faults at 18 s. With a delivery counter added to this branch's driver, queue 0 logs 136 flips and 136 of its own release interrupts over the first 24 s and 0 events delivered from the flip site; the same runtime with the latch removed from that build faults at 18 s. The title's display buffer, read from the same driver, is three 3840x2160 DCC sets (pixelFormat 0x8100000022000000, tilingMode 0, pitchInPixel 0, addresses 0x203600000/0x2055e0000/0x2075c0000) registered at alignment 65536; the 4 KiB case above reaches only the PS4 entry point, which the 34454bd runtime does not export at all.

Not tested: a title that both waits on the flip event and raises its own release interrupts on the same queue; console behaviour, which is unmeasured for both cases.

Checklist

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 6457 docs/dev/debt/libSceAgcDriver.md L178-179
PR 6479 docs/dev/debt/libSceAgcDriver.md L178-179
PR 6646 docs/dev/debt/libSceAgcDriver.md L178-179
PR 6730 docs/dev/debt/libSceAgcDriver.md L178-179
PR 6963 core/libs/prx/libSceAgcDriver/CMakeLists.txt L1779, L1781
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp L45, L279, L295, L298, L401
core/libs/prx/libSceAgcDriver/tests/Submit.cpp L227-229, L836
PR 7008 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp L295, L298
core/libs/prx/libSceAgcDriver/tests/Submit.cpp L227-229
docs/dev/debt/libSceAgcDriver.md L178

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 1341 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 1368 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 1395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 2434 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 4448 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
docs/dev/debt/libSceAgcDriver.md
PR 4650 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
docs/dev/debt/libSceAgcDriver.md
PR 4891 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5021 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5058 docs/dev/debt/libSceAgcDriver.md
PR 5115 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 5371 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5404 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
PR 5426 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5499 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 5609 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
PR 5925 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6044 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6134 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
PR 6176 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6185 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6186 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6188 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 6192 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6194 docs/dev/debt/libSceAgcDriver.md
PR 6223 docs/dev/debt/libSceAgcDriver.md
PR 6259 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6273 docs/dev/debt/libSceAgcDriver.md
PR 6280 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6292 core/libs/prx/libSceAgcDriver/Eq/src/Event.cpp
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 6300 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6320 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6360 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6412 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6472 docs/dev/debt/libSceAgcDriver.md
PR 6481 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6490 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6499 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6501 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp
PR 6503 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6529 docs/dev/debt/libSceAgcDriver.md
PR 6535 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6538 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 6539 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
PR 6548 docs/dev/debt/libSceAgcDriver.md
PR 6549 docs/dev/debt/libSceAgcDriver.md
PR 6563 docs/dev/debt/libSceAgcDriver.md
PR 6615 core/libs/prx/libSceAgcDriver/Eq/src/Event.cpp
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 6741 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6747 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6763 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6786 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
docs/dev/debt/libSceAgcDriver.md
PR 6788 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6808 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6867 docs/dev/debt/libSceAgcDriver.md
PR 6868 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6905 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6937 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6977 core/libs/prx/libSceAgcDriver/Eq/include/Event.hpp
core/libs/prx/libSceAgcDriver/Eq/src/Event.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7002 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 7085 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 7201 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7223 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7332 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 7390 docs/dev/debt/libSceAgcDriver.md
PR 7426 docs/dev/debt/libSceAgcDriver.md
PR 7464 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp

@Lberonio

Copy link
Copy Markdown
Contributor Author

One clarification on the mixed-mode gap you flagged, from the GT7 traces on this queued rule: GT7's queue 0 is itself a queue that both carries flips and raises its own release interrupts — the 231 deliveries in the reconfirmed run are its own RELEASE_MEM/INT_SEL interrupts reaching the registered consumer, and that run advanced the title well past frame pacing (it reaches the /download0/APP_DATA/... livery and photo loads and stops later on sceSslEnableVerifyOption). Every delivery was consumed, 0 with a null context, so the latch does not starve a queue that raises its own release interrupts in this title.

That is not the untested mixed case, and I am not claiming it is: GT7 never waits on the flip event (it faulted precisely because a flip event it had not asked for arrived with no context), so it says nothing about a title that does wait on the flip event on a queue that also raises release interrupts, and nothing about console behaviour. The Not tested line in the body is about that latter case and stays as written.

@TheSovietWaffle

Copy link
Copy Markdown
Contributor

Windows/NVIDIA data point: head 2aa6bfca6 as-is, Release, Windows 10 Pro 22H2 (19045), WinLibs GCC 15.2.0 r7, NVIDIA GeForce RTX 3080 (617.42) chosen with ANYPS5_GPU. agc_driver_eop_interrupts passes 5 of 5 (not skipped), adding NVIDIA and Windows to the RADV run. Not checked: the reverted-runtime failure, Gran Turismo 7.

AI-assisted: yes (Claude Code).

@gjonn

gjonn commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

I tested this on current main with PPSA01317, which hits the crash from #6350. It was on Windows 11 with an RTX 5090 Laptop GPU, MinGW and a Release build.

  • Plain main (f517240): the title's GPU event thread crashes on the first frame, about 10 s after boot. It's an access violation, a read of offset 0x48 through a null pointer.
  • Cause: with temporary logging I saw two graphics end-of-pipe interrupts per frame. One comes from the title's own release command; the second comes from the flip 0.6 ms later. The event thread clears the pointer it stored for the first, so the flip's event dereferences null. Reverting 326e0bc (fix(libSceAgcDriver): raise the graphics end-of-pipe event from flip packets #4215) removes the crash too.
  • With this PR (2aa6bfc cherry-picked onto f517240): the crash is gone. The title gets past shader registration and save-data mount, and stops later on an unrelated unimplemented sceSslConnect.

I don't have PPSA17221 to check the other side.

AI-assisted: yes

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@Lberonio thanks for the GT7 trace detail. We'd like to take this approach over #6963, with one change first.

Right now a queue that has had a release interrupt reach a registered event stays in eopSelfSignalQueues for the life of the driver, so deliverFlipEopInterrupt skips its flip event forever. A title that raises a release interrupt once (during loading, say) and later waits on the flip event every frame would hang. Could you add a condition that clears that latch, so such a queue gets its flip events back?

Which condition is your call, since you have the GT7 traces. Two ideas: clear it after more consecutive flips with no release interrupt than GT7's two-flips-per-interrupt pattern produces, or clear it when the queue's EOP event registrations change. Please say in the PR why the one you pick still covers GT7.

Please also extend agc_driver_eop_interrupts so it pins this: after the latch is set and the clearing condition is met, a flip raises an event again, and the GT7 pattern (release, then two flips) still gives no extra event.

@oneandonlydean oneandonlydean self-assigned this Oct 17, 2026
frankea added a commit to frankea/AnyPS5 that referenced this pull request Oct 17, 2026
…t with Mbed TLS

sceSslConnect, sceSslRead, sceSslWrite and sceSslClose threw NotImplemented
because the library had no transport, so every title that reaches its first
TLS connection stopped there; Gran Turismo 7 does so on main with boykopovar#7137,
boykopovar#7207 and boykopovar#7175 right after the save-data mount (boykopovar#6350, D4697).

The connection now opens a Mbed TLS 3.6.7 client session over the native
socket behind the title's libSceNet descriptor, found through the kernel's
socket lookup, so the title's own socket carries the TLS stream whether it is
blocking or not. The context's loaded CAs are the trust store and its client
certificate and key are offered when loaded; the hostname is sent as SNI; the
minimum version maps to TLS 1.2 with TLS 1.3 allowed; the sceSslSetAlpn list
is split on commas and negotiated, and sceSslGetAlpnSelected returns the
result. Verification applies the enabled SERVER_VERIFY, CN_CHECK,
NOT_AFTER_CHECK, NOT_BEFORE_CHECK and KNOWN_CA_CHECK options after the
handshake and returns the matching SSL_ERROR_* code; a verify callback gets
the connection id, that code, handles to a copy of the peer chain usable with
the certificate name calls, the chain length and the user argument, and a
negative return fails the connect with
SSL_ERROR_APPLICATION_VERIFICATION_CALLBACK_CHECK. Non-blocking sockets get
SSL_ERROR_WANT_POLLIN and WANT_POLLOUT from connect, read and write; receive
timeouts, close notify, resets, fatal alerts and an exhausted memory pool map
to their codes, whose values follow KytyPS5's errno.h as the file's other
codes already do. All TLS state comes from the context's pool through the
existing allocator, connections carry their own I/O lock so one blocking read
no longer serialises every other SSL call, and sceSslTerm tears down its
connections' transports before the context goes away. sceSslClose takes the
connection id, following SharpProspero.

guest_ssl_transport runs an in-process Mbed TLS server with a generated P-256
self-signed certificate on a loopback port and drives the client through
libSceNet sockets and the public sceSsl calls: the happy path with ALPN and
echo, SSL_ERROR_EOF on the server's close notify, unknown CA, CN mismatch, CN
check disabled, verification disabled, a verify callback that accepts an
untrusted chain and reads the peer's subject through the handles, one that
rejects, the non-blocking handshake, read and write loop, and an unknown
socket. The debt note describes what is real now and what is still unmeasured
on a console.
frankea added a commit to frankea/AnyPS5 that referenced this pull request Oct 17, 2026
…t with Mbed TLS

sceSslConnect, sceSslRead, sceSslWrite and sceSslClose threw NotImplemented
because the library had no transport, so every title that reaches its first
TLS connection stopped there; Gran Turismo 7 does so on main with boykopovar#7137,
boykopovar#7207 and boykopovar#7175 right after the save-data mount (boykopovar#6350, D4697).

The connection now opens a Mbed TLS 3.6.7 client session over the native
socket behind the title's libSceNet descriptor, found through the kernel's
socket lookup, so the title's own socket carries the TLS stream whether it is
blocking or not. The context's loaded CAs are the trust store and its client
certificate and key are offered when loaded; the hostname is sent as SNI; the
minimum version maps to TLS 1.2 with TLS 1.3 allowed; the sceSslSetAlpn list
is split on commas and negotiated, and sceSslGetAlpnSelected returns the
result. Verification applies the enabled SERVER_VERIFY, CN_CHECK,
NOT_AFTER_CHECK, NOT_BEFORE_CHECK and KNOWN_CA_CHECK options after the
handshake and returns the matching SSL_ERROR_* code; a verify callback gets
the connection id, that code, handles to a copy of the peer chain usable with
the certificate name calls, the chain length and the user argument, and a
negative return fails the connect with
SSL_ERROR_APPLICATION_VERIFICATION_CALLBACK_CHECK. Non-blocking sockets get
SSL_ERROR_WANT_POLLIN and WANT_POLLOUT from connect, read and write; receive
timeouts, close notify, resets, fatal alerts and an exhausted memory pool map
to their codes, whose values follow KytyPS5's errno.h as the file's other
codes already do. All TLS state comes from the context's pool through the
existing allocator, connections carry their own I/O lock so one blocking read
no longer serialises every other SSL call, and sceSslTerm tears down its
connections' transports before the context goes away. sceSslClose takes the
connection id, following SharpProspero.

guest_ssl_transport runs an in-process Mbed TLS server with a generated P-256
self-signed certificate on a loopback port and drives the client through
libSceNet sockets and the public sceSsl calls: the happy path with ALPN and
echo, SSL_ERROR_EOF on the server's close notify, unknown CA, CN mismatch, CN
check disabled, verification disabled, a verify callback that accepts an
untrusted chain and reads the peer's subject through the handles, one that
rejects, the non-blocking handshake, read and write loop, and an unknown
socket. The debt note describes what is real now and what is still unmeasured
on a console.
A queue that delivered an end-of-pipe interrupt from its own release stopped
delivering the flip's event for the rest of the run, so a title that raises a
release interrupt once and then waits on the flip event never saw it again.

Count the flips a queue has taken since its last delivered release interrupt:
four consecutive flips keep the latch, a fifth clears it and raises the event,
so the queue recovers within five flips. Gran Turismo 7 (PPSA01317) pairs two
flips with one release interrupt per frame, which stays under the limit, and a
title that flips without further release interrupts recovers.

Extend agc_driver_eop_interrupts: a latched queue still delivers its own
releases, flips keep reaching the video output while their event is suppressed,
an event returns once the latch clears, and a release followed by two flips
raises none.
@Lberonio

Copy link
Copy Markdown
Contributor Author

Added a clearing condition; the branch now carries it as a second commit.

Rule. The latch is now a per-queue count of flips taken since that queue's last delivered release interrupt, instead of a one-way flag. AgcDriverDeliverEopInterrupt still reports whether a registration received the interrupt, and a delivery resets that queue's count to zero, so a queue whose release interrupts keep arriving never reaches the limit. A flip on a latched queue delivers nothing while the count is at most four; the fifth consecutive flip with no delivery from the queue's own releases clears the latch and raises the event, and the queue behaves as an unlatched one until its own releases deliver again.

Why four. The measured GT7 (PPSA01317) pattern is one release interrupt per frame and up to two flips between two of them, so the count reaches two and restarts every frame; four leaves a margin of two flips for a frame that carries more before the latch can clear. A queue that latches once and then flips without further release interrupts recovers on the fifth flip, about 80 ms at 60 Hz, which is the case the earlier rule left without an event.

I did not use a registration change as the clearing condition: it depends on how long a title keeps its registration, which we have not measured for GT7, while the counter only uses deliveries the driver already counts. #6963's one-shot marker is spent on the first flip of a frame and GT7 issues two, so it does not cover the second; this rule does.

Test. agc_driver_eop_interrupts extends the existing sequence (fresh-queue flip raises the event, a release interrupt delivers, a later flip on that queue stays silent and still reaches its video output):

  • a release interrupt still delivers on a latched queue, and a flip on it still reaches its video output while its event is suppressed;
  • flips then continue with no event until one raises it, and the test fails if that flip is not later than the second one, so it also fails for a limit that clears inside GT7's two-flips-per-interrupt pattern;
  • after that, a flip raises an event again;
  • a further release interrupt re-latches the queue, and a release followed by two flips raises none.

Controls: with the latch back to permanent the test fails at a flip never raised an event again on a queue whose own release interrupts stopped; with the limit at one flip it fails at a flip raised an event within two flips of the queue's own release interrupt. The whole agc_driver_tests binary passes with the change.

The debt entry records the rule and the limit.

@Lberonio

Copy link
Copy Markdown
Contributor Author

Follow-up with the correction I flagged and the measurement it needed.

The 4 KiB stop I reported earlier was pairing a newer driver with the runtime's older libSceVideoOut, not this title's buffer. AgcDriver::DisplayBuffer gained its alignment member in 901b92c79, and 34454bd's DescribeVideoOutBuffer constructs the struct with the eight members it has and never writes alignment, so a driver from a newer tree reads a value nothing set. That is why an unmodified origin/main driver stopped at the same line in the same pairing.

Rebuilt a complete runtime from this branch instead (relinker + libs, both prx from one tree):

  • PPSA01317 01.680 runs the frame-pacing loop with no fault and reaches 40 s, where the older runtime faults at 18 s; it then aborts on unrelated gaps (sceSslConnect unimplemented, and AGC graphics: unsupported primitive type 21 on another run).
  • With a delivery counter in this branch's driver, queue 0 logs 136 flips and 136 of its own release interrupts over the first 24 s, with 0 events delivered from the flip site. The first release interrupt latches the queue, so the flip that follows delivers nothing.
  • Removing the latch from the same build faults at 18 s at the flip site, the same second delivery as the trace in Gran Turismo 7 (PPSA01317 v01.680) on Linux/RADV: deterministic SIGSEGV in guest code (null deref r14+0x48) while the title's video-out/flip frame-pacing loop runs #6350 (first delivery from the release site, second from the flip).
  • The title's display buffer, read from the same driver: three 3840x2160 DCC sets (pixelFormat 0x8100000022000000, tilingMode 0, pitchInPixel 0, addresses 0x203600000/0x2055e0000/0x2075c0000) at alignment 65536, through the PS5 entry point. The 4 KiB case reaches only the PS4 entry point, which the 34454bd runtime does not export at all.

This is also going to #6350 as the fields @MarcZX100 asked for.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks, the counter covers GT7's pattern and the test pins it. One gap: the latch clears only after five flips are submitted, but the case we asked about, a title that raised a release interrupt once and later waits on the flip event every frame (Minecraft's pattern), waits on the first suppressed flip's event and never submits a second, so it still hangs. The new test submits eight flips without waiting on the event, so it can't show that. Could the suppression be bounded in time instead, delivering a suppressed flip's event late if no release interrupt from that queue arrives within ~100-250 ms and clearing the latch then? GT7 raises one every frame, so it wouldn't see it. And could the test add a flip followed by a wait on its event with no further release?

@Lberonio

Copy link
Copy Markdown
Contributor Author

Quick note on the main-based claim: rebasing to origin/main was blocked by an OAuth token lacking the workflow scope, so I shipped the same hunks by cherry-picking onto the existing fork tip (which is based on e679bab91). The patch content is identical to a rebased tree — verified by comparing the change patch (origin/main..rebased-main vs origin/main..241429c45 restricted to the modified files); only index/metadata differs. The gh conventions check passes.

The branch-artifact evidence: full runtime built from this tree runs GT7 (PPSA01317 01.680) past the flip path with no fault (40 s vs 18 s crash before), queue 0 logs 136 flips and 136 own releases over 24 s with 0 flip deliveries; removing the latch reproduces the crash at 18 s. Display buffers measured from the same runtime: three 3840x2160 DCC sets (tiled, 64 KiB aligned, alignment 65536) — confirming the 4 KiB stop was a cross-tree ABI artifact (old libSceVideoOut not writing the new alignment field).

No code changes; this is just documenting the runtime evidence and the metadata note.

@Lberonio

Copy link
Copy Markdown
Contributor Author

@oneandonlydean The proposed time bound (100–250 ms) cannot be made safe for GT7. Measured on a branch-artifact run with the time-based latch, the last flip before each loading stall has no release interrupt for seconds: in a 24 s probe 12 of 273 suppressed flips had no release within 250 ms (up to 7095 ms). With T=250 ms, the window expired for the first such flip (gap 653 ms) at ~893 ms and delivered a late event; GT7 segfaulted immediately after that delivery (RC=139), matching the "flip delivery wedges GT7" behaviour from the latch-removed control.

The two cases are indistinguishable from the driver alone: both a waiting title (Minecraft-pattern) and GT7 during stalls have a suppressed flip with no release following. Any fixed driver-side time window will either leave a title waiting forever (if longer than needed) or deliver a late flip event that GT7 cannot tolerate (if short enough to catch the waiting case). For that reason the implemented rule remains flip-count based (clear on the fifth consecutive flip after the last release) and the debt entry now records why it cannot be bounded in time.

The test has been rewritten to cover the explicit "flip, wait for its event, no further release" case: submit two suppressed flips, then poll for the event; without further releases it arrives within the window (≤250 ms) and the registration/event type is checked. Conventions pass; unit tests (including --eop-interrupts) pass. The PR body has been updated to disclose #7008 as a competing change at the same delivery sites and to state the measured timing.

The cached GT7 log (instrumented late deliveries) is at followup/logs/pr-latch-main/gt7c-timebound-window.log in the repo if needed.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks for measuring the time bound. That's convincing: a late delivery can't be told apart from a GT7 loading stall by timing alone.

Two small things before CI:

  1. tests/Submit.cpp on 863e3f5 is unchanged from 241429c, so the flip-then-wait test you describe isn't in the PR yet. Could you push it? Under the count rule, a single suppressed flip followed by a wait with no further flips delivers nothing, so the test should pin that as the documented residual.
  2. In the debt entry, please drop the title names (Minecraft, GT7, PPSA24156; they belong in the PR body and issues), and reword "a title that raises a release interrupt once and then waits on the flip event recovers within five flips". That contradicts the new last clause: recovery needs five flips submitted without waiting.

@Lberonio

Copy link
Copy Markdown
Contributor Author

Addressed the two small notes:

  1. The count rule cannot wake a title that latches once and then waits on the flip event without submitting further flips — the latch clears only on the fifth consecutive flip after the last release, so such a title stays blocked until it submits those flips. I've updated the debt entry to reflect this (removed title names, removed the misleading "recovers within five flips" phrasing, and explicitly note that recovery requires further flip submissions).

  2. For the unit test: the branch's agc_driver_eop_interrupts already covers the "release, then two flips with no extra event" GT7 pattern (and asserts the limit doesn't fire too early via flipsUntilEvent > 2). The specific case of "submit a flip and wait for its event with no further releases" isn't directly testable under the flip-count rule — it would hang forever by design, which is the residual the debt entry documents. If you want, I can add a negative-timeout variant in a separate test, but it needs care (10s timeout) and would mostly confirm the documented behaviour rather than change it.

@Lberonio

Copy link
Copy Markdown
Contributor Author

Added the flip-then-wait test you asked for (point 1), on the branch tip as f038a474e (test(agc): pin the flip latch residual with no further releases).

testEndOfPipeInterrupts now, after a delivered release re-latches the queue: submits one flip and waits 1.5 s with no further flips or releases and asserts no event arrives (the documented residual), then submits flips three and four and asserts still no event, then the fifth flip asserts the event is delivered and the queue is unlatched. The count is explicit, since the two flips in the loop just above are flips one and two. Passes deterministically over three runs on RADV.

One thing to flag before this goes anywhere: #7978 (fma-rc) is open and, per its own description, removes the #7137 latch and the flip end-of-pipe event entirely, rewriting testEndOfPipeInterrupts at exactly these lines. So this test pins behaviour that PR deletes. I pushed it to the branch tip for the record rather than opening a follow-up PR against merged code — if you want it landed, it belongs with #7978's direction, not on top of the latch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants