Skip to content

drm/asahi: g17: keep innocent clients alive across another client's recovery - #433

Closed
jgoyvaerts wants to merge 2 commits into
aurora-silicon:integration/aurorafrom
jgoyvaerts:m4pro/g17-innocent-clients-recovery
Closed

jgoyvaerts wants to merge 2 commits into
aurora-silicon:integration/aurorafrom
jgoyvaerts:m4pro/g17-innocent-clients-recovery

Conversation

@jgoyvaerts

Copy link
Copy Markdown
Contributor

When one client hangs the GPU and the firmware recovers, other clients lose their device too. On a J616s (T6040, 25G83), three reason-0 recoveries of a single dEQP client took down Hyprland (a frozen desktop), Chromium, an Electron app and a muvm GPU worker. A hang in headless vkmark took down another app the same way.

None of the victims had work on the GPU that failed. Each one failed before publication, and fail_unpublished() reported that error, which Honeykrisp treats as a lost device. There were two causes.

1. Waits shorter than a recovery

Innocent work waited only briefly for a recovery to settle:

  • render admission behind a spared, quarantined pair: 1 s;
  • render publication: 2 s;
  • render and compute while a recovery was pending: 200 polls of 1 ms;
  • a spared compute drain: 200 polls;
  • compute admission, render capacity and busy waits: 2 s. The busy wait returned ETIMEDOUT, which also marks the VM lost.

A spared pair drains only once the watchdog settles the dropped work, after one or more 2 s polls, so all of these lost the race.

Now, while a recovery is pending or the owner's pair is spared and draining, these waits last up to RECOVERY_WAIT_MS from the start of the wait. That is the longest the watchdog defers work behind another VM before it settles it, ray work included (POLL_MS * (1 + EXECUTION_RENEWALS + MAX_RENEWALS), 26 s).

  • The compute Admission deadline can now be extended for this.
  • Each render wait keeps its own clock (WaitClock), so its usual bound applies afresh once the recovery ends, instead of being used up during it.
  • Guilty work still fails as before: a guilty pair's publication fails after a second, and a guilty VM's sticky status fails its next submission at once.

2. Inherited cancellation

A command was cancelled (inherited ECANCELED) when its input fence, same-VM predecessor or compute producer had failed. So when the firmware dropped innocent work in a recovery, that work's consumer failed too, and so did a replay of such a command. An ordered output publication inherited its predecessor's error and reported it.

This follows drm_sched instead: a dependency's error doesn't fail the consumer. Only the command's own state decides, meaning its cancellation and its VM's status. A signalled producer, failed or not, orders nothing, and the producer's client sees its own error. The queue's per-engine failed-ordinal record existed only to cancel later commands, so it goes.

fail_unpublished(), the ordered output publication and fail_while_owned() for spared work no longer report ECANCELED, which matches what complete() already does. A cancellation isn't this client's device loss, and a guilty VM carries its own sticky error.

UAPI

No new interface, but the documented contract changes. The text for DRM_ASAHI_FEATURE_PER_COMMAND_INPUT_SYNCS and the VM-status query is updated:

  • An input that signalled with an error is now waited for, not inherited: the command runs, and the input's contents are undefined.
  • A job cancelled by another VM's recovery, by an input that didn't signal in time, or by its queue closing leaves the VM's error at 0. Its fence carries ECANCELED.

Trade-off: a command no longer waits for a failed same-VM predecessor that is still held on a quarantined pair. If that predecessor is replayed later, the two run out of order. The cost is wrong contents for one frame, not a lost device.

Tested: J616s, 7.3 daily tree (this commit on top of the integration tree). The client that kept hanging is dEQP-VK.memory.pipeline_barrier.graphics.all_device.1048576_vertex_buffer_stride_2. I ran it next to glmark2 and the desktop:

  • Before: the recoveries froze the desktop, as described above.
  • After: three reason-0 recoveries, each dropping an innocent render. glmark2 and Hyprland survived, with Hyprland's PID unchanged.
  • The case alone: one recovery and no victims.

KUnit (asahi, QEMU) 107/107, including new tests for the extendable admission deadline, spared_quarantined() and a failed predecessor. checkpatch: one line-length warning.

Not in this PR: the firmware crash we see when that same case triggers firmware-requested TVB growth under heavy device churn. That crash is inside the firmware (a NULL+0x210 read) and needs more work on our side first.

Base: integration/aurora (420a1c8f5).

🤖 Generated with Claude Code

jgoyvaerts and others added 2 commits October 24, 2026 18:34
…ecovery

When one client hangs the GPU and the firmware recovers, other clients
lost their device too. On a J616s (25G83) three reason-0 recoveries of
one dEQP client took down Hyprland (frozen desktop), Chromium, Electron
and a muvm GPU worker; a hang in headless vkmark took down T3 Code the
same way. Each failed before publication, and fail_unpublished()
reported the error, which Honeykrisp treats as a lost device:

- Innocent work waited only briefly for a recovery to settle: render
  admission behind a spared, quarantined pair for 1 s, the render
  publication for 2 s, render and compute for 200 polls of 1 ms while
  a recovery was pending, a spared compute drain for 200 polls, and
  the compute admission, render capacity and busy waits for 2 s (the
  last with ETIMEDOUT, which also marks the VM lost). A spared pair
  drains only when the watchdog settles the dropped work, after one or
  more 2 s polls: EBUSY.
- A command whose input fence, same-VM predecessor or compute producer
  had failed was cancelled (inherited ECANCELED), so the consumer of
  innocent work that the firmware dropped in the recovery failed too,
  and so did a replay of such a command; an ordered output publication
  inherited its predecessor's error and reported it.

Wait instead, while a recovery is pending or the owner's pair is
spared and draining, for up to RECOVERY_WAIT_MS from the start of the
wait: the longest the watchdog defers work behind another VM before it
settles it, ray work included (26 s). The compute admission deadline
becomes extendable for this; each render wait keeps its own clock, so
its usual bound applies afresh once the recovery ends instead of having
been spent during it. A guilty pair's publication still fails after a
second, and a guilty VM's sticky status fails its next submission at
once.

Do not fail a command because an input, a predecessor or a producer
failed. As in drm_sched, only its own state decides (cancellation and
its VM status); a signalled producer, failed or not, orders nothing.
The producer's client sees its own error. The queue's per-engine
failed-ordinal record, which existed only to cancel later commands,
goes.

Do not report ECANCELED from fail_unpublished(), the ordered output
publication, or fail_while_owned() for spared work, as complete()
already does: a cancellation is not this client's device loss, and a
guilty VM carries its own sticky error. The UAPI text for
PER_COMMAND_INPUT_SYNCS and VM_STATUS says so.

Trade-off: a command no longer waits for a failed same-VM predecessor
that is still retained on a quarantined pair; if that predecessor is
replayed later, the two run out of order (wrong contents for a frame,
not a lost device).

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
A failed fence is not proof that its firmware command has retired.
Quarantined render and compute work can still own memory, and an
abandoned render can replay. Dropping that dependency lets its consumer
execute before the retained producer, then lets the producer overwrite
the consumer's result after replay.

Keep the longer recovery waits and avoid reporting ECANCELED as device
loss, but retain cancellation of commands with failed inputs or internal
predecessors. Restore error propagation through ordered output publication
and replay admission as well. The cancellation reaches the fence without
poisoning an innocent VM, while no successful consumer can bypass a
retained failed producer. Keep the existing input-sync UAPI contract.

Add a regression case for failed predecessor admission. Independent work
can still proceed; making failed-input consumers execute needs a separate
retirement guarantee first.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>

@RyanTheTide RyanTheTide left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed Ryan fixup 986ec63. A failed fence does not prove that its firmware producer has retired. Dropping that dependency allowed a consumer to run before retained work that could later replay and overwrite its result.

The fix keeps the longer recovery waits and avoids recording ECANCELED as an innocent VM's device loss. It restores failed-input and predecessor cancellation, replay admission checks and ordered output error propagation. Consumers of failed work remain cancelled; allowing them to execute needs a separate retirement guarantee. The input-sync UAPI contract is preserved. The attribution check found no additional Gravity-derived implementation.

The failed-predecessor regression test passes in Asahi KUnit 86/86. The fixup also resolves the long-line warning and is checkpatch-clean. Apple-dcp passes 63/63. The first defconfig plus asahi.config WERROR build passed; the shipping and final fixup-tree builds are still running.

This is not merge-ready until the M1 Pro and M2 one-shot checks pass and the common config dependency in #438 is reviewed. I have not run a new G17 recovery campaign on a J616s.

@RyanTheTide

Copy link
Copy Markdown
Contributor

I re-read the PR body, reviews and both comment lists. There are currently no inline review threads or new replies here. The retained-producer ordering issue is resolved conservatively by 986ec63: longer recovery waits and cancellation handling remain, while failed dependencies still cancel their consumers. Running those consumers requires a separate retirement guarantee. My current verdict remains not merge-ready pending the final universal build, the required M1 Pro/M2 one-shot checks and the #438 config review. The new predecessor regression test passes in Asahi KUnit 86/86.

@RyanTheTide

Copy link
Copy Markdown
Contributor

Final software gates now pass on the combined tree containing these heads and the proposed common config correction in #438. Both defconfig plus asahi.config and the Fedora-base recipe build Image, modules and DTBs with WERROR enabled. The Fedora recipe also covers KUNIT=m. Every requested asahi.config setting survives resolution. Asahi KUnit passes 86/86 and apple-dcp host tests pass 63/63. The targeted J713/J616s binding and DTB checks have no diagnostics.

The conservative ordering fix and failed-predecessor regression test pass. This remains not merge-ready until the M1 Pro/M2 candidate boots pass and #438 is reviewed.

@RyanTheTide

Copy link
Copy Markdown
Contributor

M2 no-regression qualification now passes with this head in the combined source tree 031f7dec92a1bef6b72509b08c5c597f50474adb. The approved one-shot ran 7.3.0-aurora-t3028. A native Apple GPU compute/readback check verified all 4096 words, the checked kernel fault counters stayed at zero, and device counts, active services and SELinux enforcing matched the baseline. This uses the installed system Mesa to exercise the candidate kernel.

The machine is back on 73w, where the same check passes again. The test kernel, its GRUB entry and fallback units have been removed. The saved default, boot.bin and pinned DTB setting are unchanged.

The M1 Pro candidate check and the #438 common-config review are still outstanding, so this remains not merge-ready.

@RyanTheTide

Copy link
Copy Markdown
Contributor

Final qualification for 986ec63. The combined test source is 031f7dec92a1bef6b72509b08c5c597f50474adb, tree 36854d3a72e4efdf2c352166498ab436adaf3c48.

Both defconfig plus asahi.config and the Fedora-base shipping recipe pass the full Image/modules/DTB build with WERROR enabled. Every requested fragment value survives olddefconfig. Asahi KUnit passes 86/86, apple-dcp host tests pass 63/63, and targeted J713/J616s binding/DTB checks have no diagnostics. Strict source-style checks pass; commit sign-offs are checked separately. The attribution audit found no new material Gravity implementation blocks. Existing GPL-2.0-only Cody Ho and Niklas Sheth credit is preserved.

The same candidate image passed approved M1 Pro and M2 one-shot smoke checks against fresh normal baselines. Native GPU compute/readback verified all 4096 words on each device, kernel fault counters stayed at zero, and baseline devices, active services and SELinux enforcing were preserved. Both machines are back on 73w with the checks passing again. Test kernels, task GRUB entries and fallback units are removed. The saved defaults, stock boot.bin files and pinned DTB setting are unchanged.

Ryan fixup 986ec63 keeps longer recovery waits and avoids poisoning innocent VMs for cancellation, while preserving failed-dependency cancellation and ordered publication. The failed-predecessor regression test passes. Failed-input consumers remain cancelled; running them requires a separate physical-retirement guarantee. No code hold remains under that conservative scope. Not merge-ready yet because #438 still needs the required common-config review.

jgoyvaerts added a commit to jgoyvaerts/linux that referenced this pull request Nov 1, 2026
…ecovery

A recovery for one client's hang still killed the others: a CS2 reason-0
recovery cancelled 42 commands and Hyprland got ECANCELED from
fail_unpublished(), which Honeykrisp treats as a lost device, so the
desktop froze until a reboot.

- ECANCELED is not reported as this client's failure from
  fail_unpublished(), the ordered output publication, or
  fail_while_owned() for spared work. Work the firmware listed as
  interrupted keeps reporting its failure.
- Publication waits for a pending recovery, or for this owner's spared
  pair to drain, for up to RECOVERY_WAIT_MS (26 s) instead of the usual
  2 s: the compute admission deadline extends (Admission::
  extend_for_recovery) in ensure_compute, in the recovery waits of
  compute_packet including the input-source wait, and in
  Gate::enter_until, whose gate only a recovery closes; the render waits
  keep a WaitClock whose usual bound starts afresh once the recovery
  ends, and render admission extends its deadline while a recovery is
  pending. A spared quarantined pair waits until the caller's deadline;
  a guilty pair's publication still fails after a second.
- Commands with failed inputs or predecessors stay cancelled: a failed
  fence does not prove its producer retired.

The compute deadline host tests move to the longer recovery budget, and
a mutant now checks that removing the extension fails them.

Link: aurora-silicon#433
Co-developed-by: Ryan Murray <ryan@aurorasilicon.org>
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
@jgoyvaerts

Copy link
Copy Markdown
Contributor Author

Replaced by #803: the G17 rework replaced the submission path this changed, so it no longer applied, and #438 closed without merging. #803 is the same fix ported onto aurora-wip, with Ryan's 986ec63 ordering fixup included and credited.

@jgoyvaerts jgoyvaerts closed this Nov 1, 2026
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.

2 participants