Skip to content

drm/asahi: kill faulted G17 contexts before freeing their render work - #505

Merged
DjDeveloperr merged 1 commit into
integration/aurorafrom
fix/g17-render-quarantine-witness-20261026
Oct 27, 2026
Merged

DjDeveloperr merged 1 commit into
integration/aurorafrom
fix/g17-render-quarantine-witness-20261026

Conversation

@DjDeveloperr

Copy link
Copy Markdown
Contributor

Follow-up to #496 (stacked on it). This handles the case #496 left open: a closed context whose compute command failed, with render work queued behind the failure.

What the firmware does

When a compute command fails, the firmware holds the commands its context published after it and never runs them. A kill of the context is the only thing that returns them. The context's render work behind the same failure behaves differently:

  • It runs about a second later and consumes its ring entries.
  • It returns its free-list blocks.
  • Its completion stamp is never stored, and the job stays linked in the context's work list until the firmware processes a kill.

Before this change none of that work could retire. The context, its pools, its render pairs and its memory stayed allocated until the GPU firmware restarted, which on Neo means until reboot. On #496 a ten-minute fault storm left 12 such teardowns pending.

What this change does

  1. The kill can proceed. For a closed context whose compute failed:

    • Each quarantined render pair whose commands all ran counts as idle for the kill: both pass timestamps written, rings consumed, free list balanced.
    • Admission holds such pairs for the kill together with their unretired commands.
    • The kill does not wait for the context's render pools to be released, because that only happens after the kill.
    • A faulted compute queue with no commands behind the failure is a kill candidate too. Without this, single-command faulted queues were stranded.
  2. Memory is freed only after the firmware acknowledges the kill. The unstamped render work of such a pair is retired only once the context's scheduler page reports the kill. Until then its graph memory stays allocated and out of the reuse cache. This is enforced on the retire path itself, and again at the point where a detached context's graphs are handed to reclaim: a context whose work list still links a node, with no acknowledged kill, never has its graphs freed or recycled.

  3. The context's work storage is retained. The firmware leaves a killed context's last job linked in its work list even after the kill. The 32 KiB work object is therefore kept mapped and out of reuse until the firmware stops, instead of being handed to the next context. Kept objects are counted in debugfs (firmware_linked_objects), and a warning is printed past 256.

  4. Killed pairs are reused last. A killed context's render slots are released once its scheduler release is consumed, instead of at the next firmware stop. A new owner then takes, in this order:

    • a clean released pair, least recently used first;
    • an unowned slot, but only while the physical queue table can still hold both of the new pair's identities. The runtime updates admission whenever the table changes;
    • otherwise the killed pair released longest ago.

    Siblings and spares never take killed pairs, and a sibling doesn't claim an unowned slot once the table is full.

  5. Closed owners only. The executed-witness retirement applies only to pairs whose owner has closed. A live owner's quarantined pair stays quarantined as before.

  6. The kill waits for a moment without other clients' compute, within a memory bound.

    • A failed context's kill is published only when no other context's compute command is in flight; render work isn't waited for. A GPU that retired nothing for the liveness interval counts as stopped, and the kill then goes ahead.
    • When GPU memory (user committed plus kernel resident) comes within an eighth of the device limit, one waiting kill skips the wait, at most one per 250 ms. Each kill's memory is then freed before the next is considered.
    • Nothing is freed before the acknowledgement, so the waiting kill doesn't use recycled memory.
    • debugfs reports the waits in buckets, plus the memory-pressure kills and a live gauge (failed_kills waiting= longest_wait_ms=). A wait over 5 s is warned about once per kill.

Why the order matters

Earlier versions retired and freed the pair's memory first and published the kill a few milliseconds later, or minutes later with a gate. Both made the firmware use memory that had already been handed out again:

  • Freed first, killed a few ms later: GPU recoveries 4–7 ms after the kill, and occasional firmware crashes, in a five-lane stress harness.
  • Freed first, killed minutes later: a storm of compute watchdog timeouts while the kill waited.

Killing first and freeing after the acknowledgement removes both. Each of the other rules is there because a simpler version failed on the same harnesses:

  • Killed pairs first. A diagnostic run that handed killed pairs to new owners first stranded the teardowns of 24 contexts, and a render stall behind a failed context took the desktop down.
  • Killed pairs last, ignoring the queue table. The killed pairs kept their physical queue IDs installed, and the table ran out after about 50 kills: 34 publications failed with EBUSY. The queue-aware order reuses a killed pair once the table is full. In the pairs run below the table reached 127 of 128 with no refusals.
  • No wait. With kill-before-free alone, kills published beside other clients' running compute made the firmware recover the GPU twice in ten minutes of the five-lane load (67 faults). drm/asahi: retire faulted G17 compute commands on the firmware's fault record #496 never kills, and had 0 recoveries in 414 faults on the same harness.
  • Unbounded wait. Under sustained compute the dead clients' memory reached the device GPU memory limit (67% of RAM, 5082 MiB on Neo) in both legs. gnome-shell's next allocation failed with ENOMEM and it crashed. The memory exception in (6) prevents that.

Measured effect

Neo (A18 Pro), Mesa agx3, final version of this change.

Five-lane stress, 10 minutes (render, render2, long ALU, churn and global-priority CTS, plus two loops of dEQP-VK.ray_query.stress.geometry_shader.{triangles,aabbs}):

#496 with this change
faults (event 7) 44 52
failed contexts killed (acknowledged, pairs retired) 0 15 (2 under memory pressure)
GPU recoveries / firmware crashes 0 / 0 0 / 0
queue publications refused with EBUSY 101 0
scheduler teardowns pending after the run 12 0
render pairs still quarantined after the run 12 0
GPU memory peak (device limit 5.0 GiB) not sampled 4.3 GiB, no allocation refused
gnome-shell alive alive

Pairs harness, 10 minutes (two ray query stress loops beside heavy geometry):

Without the memory-pressure exception, this harness ended in a desktop death from ENOMEM while kills waited.

Known limits

  • The work storage of a killed context (32 KiB) stays allocated until the GPU firmware restarts.
  • While other clients keep compute running, a faulted client's kill waits, and its memory stays held until the GPU first has no foreign compute in flight, or until GPU memory nears the device limit. Under ordinary desktop load that moment comes almost at once. Under the stress harnesses kills waited up to 305 s, and GPU memory sat just under the pressure threshold.
  • A kill published through the memory exception runs beside foreign compute, which risks a GPU recovery. There were 5 such kills over both runs, with no recovery after any of them.
  • Render-only remainders: a closed context whose compute side is clean but whose render pair holds executed, unstamped work isn't killed yet. These are the 4 teardowns left pending in the pairs run. A kill transaction that doesn't need a compute exit candidate would close this. It is follow-up work.
  • A firmware-safe kill beside running compute would remove the wait. It is not understood yet.

Testing

  • Regression gate on the final build: PERFORMANCE-REGRESSION-PASS.
  • Targeted CTS on the final build (5368 cases): 4687 pass, 681 not supported, 0 fail.
  • Ray query and ray tracing pipeline CTS (57190 cases), on an earlier revision with the same kill path, before killed-last ordering and the memory exception: 47100 pass, 10088 not supported, 0 fail. Two host-built 1M-primitive acceleration-structure cases were killed for memory exhaustion, as on drm/asahi: retire faulted G17 compute commands on the firmware's fault record #496. Both pass when rerun serially on the final build (build.cpu.level_geometries.aabbs_1_1048576_1, build.cpuht_max.level_geometries.triangles_1_1048576_1).
  • KUnit (asahi_g17*, 117 cases), new tests:
    • executed-and-consumed snapshot;
    • kill hold ends at the consumed release, and the killed pair is reused last;
    • killed pairs are taken last, oldest first;
    • a full queue table reuses killed pairs before unowned slots, without a sibling;
    • linked unkilled work is retained.
  • An asahi_g17_teardown tracepoint (static key) records fault, retire, kill (marking memory-pressure kills), acknowledgement, detach and slot-owner events for diagnosing teardown on hardware. It is only called on teardown paths.

When a compute command fails, the firmware holds the commands its
context published after it and never runs them; only a kill of the
context returns them. The context's render work behind the same
failure behaves differently. It runs about a second later, consumes
its ring entries and returns its free-list blocks, but the firmware
never stores its completion stamp and leaves the job linked in the
context's work list until it processes a kill. None of that work could
retire, so a closed context whose compute failed kept its pools, its
render pairs, its memory and its scheduler teardown until the GPU
firmware stopped.

Let the kill proceed for such a context. A quarantined render pair
whose commands all ran (both pass timestamps written, rings consumed,
free list balanced) counts as idle for the kill and is held for it,
and the kill no longer waits for render pools that are only released
after it. A faulted compute queue with no commands behind the failure
is a kill candidate as well. This applies only once the owner has
closed; a live owner's quarantined pair stays quarantined.

Free only after the firmware acknowledges the kill. The pair's
unstamped work is retired once the context's scheduler page reports
the kill, and its graph memory stays allocated and out of the reuse
cache until then. The detach path enforces the same rule: a context
whose work list still links a node and has no acknowledged kill never
has its graphs freed or recycled. The firmware leaves a killed
context's last job linked even after the kill, so the context's 32 KiB
work object is kept out of reuse until the firmware stops; debugfs
counts these as firmware_linked_objects and a warning is printed past
256.

Reuse a killed context's render pairs last. The kill hold on them ends
when the context's scheduler release is consumed. A new owner then
takes a clean released pair first, then an unowned slot while the
physical queue table still has room for both of its identities, and
only then the killed pair released longest ago. Siblings and spares
never take killed pairs, and a sibling no longer claims an unowned
slot once the table is full. Admission is told whether the table has
room whenever the runtime changes it.

Publish a failed context's kill only while no other context has a
compute command in flight, or once the GPU has retired nothing for the
liveness interval. When GPU memory comes within an eighth of the
device limit, at most one waiting kill per 250 ms skips that wait.
debugfs reports the waits and the pressure kills, and a wait over 5 s
is warned about once per kill.

The order matters. Earlier versions retired and freed the pair first
and killed afterwards, and the firmware then used memory that had been
handed out again: GPU recoveries 4-7 ms after the kill and occasional
firmware crashes when the kill followed within milliseconds, and a
storm of compute watchdog timeouts when it followed minutes later.
A run that handed killed pairs to new owners first stranded the
teardowns of 24 contexts, and a render stall behind a failed context
took the desktop down. Reusing them last without regard to the queue
table ran out of physical queues after about 50 kills. A kill
published beside other clients' running compute made the firmware
recover the GPU twice in ten minutes of a five-lane stress load, which
the wait avoids. Waiting without a bound let the dead clients' memory
reach the device limit under sustained compute, and gnome-shell then
crashed on ENOMEM, which the memory escape avoids.

On Neo, ten minutes of a five-lane stress load (render, render2, long
ALU, churn and global-priority CTS beside two ray query geometry-shader
stress loops) had 52 faults and 15 kills (2 under memory pressure), all
acknowledged and their pairs retired. There were 0 GPU recoveries and
nothing was left pending afterwards, against 12 teardowns left pending
without this change. Ten minutes of two ray query stress loops beside
heavy geometry had 51 faults and 51 kills (3 under memory pressure),
with 0 recoveries, the physical queue table full at 127 of 128 with no
refusals, and 4 render-only teardowns left pending. In both, GPU
memory peaked at 4.3 GiB of the 5.0 GiB device limit, no allocation
was refused and gnome-shell stayed up.

Known limits:
- A killed context's 32 KiB work object stays allocated until the GPU
  firmware restarts.
- While other clients keep compute running, a faulted client's memory
  stays held until its kill publishes: at the next moment without
  foreign compute, or through the memory escape near the device limit.
- A closed context whose compute side is clean but whose render pair
  holds executed, unstamped work is not killed, since no compute queue
  is left to carry the kill. A kill that doesn't need a compute exit
  candidate would close this.
- A kill that is safe beside running compute would remove the wait.
  It is not understood yet.

An asahi_g17_teardown tracepoint records fault, unstamped retire,
kill, acknowledgement, detach and slot-owner events.
@DjDeveloperr
DjDeveloperr changed the base branch from fix/g17-quarantine-leak-20261026 to integration/aurora October 27, 2026 05:48
@DjDeveloperr
DjDeveloperr merged commit 918d919 into integration/aurora Oct 27, 2026
RyanTheTide pushed a commit that referenced this pull request Oct 27, 2026
…t record

When a compute command fails on the GPU the firmware writes a
zero-duration completion record for it, which quarantines its queue,
and reports the failure with a firmware event of type 7 carrying the
queue and the stamp the command would have stored. It never stores
that stamp. The driver ignored the event, so the quarantine waited for
a retirement witness that could not arrive, and the queue kept its
pool, its traversal scratch and its share of the lower GPU VA window
for as long as the module stayed loaded.

Under ray query stress that fails this way every few seconds (for
example ray_query.stress.geometry_shader beside other clients) the
retained queues filled the lower window within minutes. Every later
allocation there failed: publications returned EBUSY, and gnome-shell
crashed on ENOMEM creating a 16K texture after about 7 minutes.

Decode the event as ComputeFault and accept its stamp as the faulted
command's completion stamp for the quarantine's retirement witness,
while it is newer than the stored one. The witness still requires the
queue's ring to be fully consumed, so commands published after the
failed one keep waiting for their own stamp.

Port prerequisite for #505. Keep type 7 unknown on 25G83: the public
retirement witness is qualified on 26A428 only, and a matching tag and
aligned integers cannot establish T6040 DMA visibility. KUnit checks
that identical words cannot become a T6040 release witness.

Link: 9a76756
Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
RyanTheTide added a commit that referenced this pull request Oct 27, 2026
Pin the public #496/#504/#505 ports and distinguish shared retention fixes
from firmware-qualified fault witnesses and descriptor changes. Document
raw observability and the first-fault/idle-floor evidence needed before
claiming achieved-clock, CTS or remaining performance parity.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
RyanTheTide added a commit that referenced this pull request Oct 27, 2026
…rfaces

Keep host resource/ownership tests compiling the production methods after
#496/#505. Include the actual fault-stamp comparison, model the new queue
field, and supply inert tracing/capacity notifications. The owner-ordering
fixture models no executed unstamped pair; that predicate and its lifetime
are covered in kernel KUnit. Preserve each original negative control.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant