Skip to content

drm/asahi: G17 leak fix, render notifications and hop fixes from aurora (stacked on #229) - #244

Merged
maralcbr merged 21 commits into
integration/aurorafrom
neo/g17-aurora-merged-20261027
Oct 29, 2026
Merged

maralcbr merged 21 commits into
integration/aurorafrom
neo/g17-aurora-merged-20261027

Conversation

@DjDeveloperr

Copy link
Copy Markdown
Collaborator

Stacked on #229. This mirrors the G17 (Neo, A18 Pro) driver changes merged into aurora-silicon/linux integration/aurora on 2026-10-27. Each commit is a cherry-pick of the aurora commit (-x), and all of them applied cleanly on #229's head. After this series the G17 driver matches aurora's except for tree-specific items: aurora's firmware TEXT identification and its THIS_MODULE spelling.

commit aurora PR what it does
retire faulted G17 compute commands on the firmware's fault record aurora-silicon/linux#496 A faulted compute command retires on the firmware's fault record (event 7) instead of leaking its context.
let a G17 render queue keep 32 commands in flight aurora-silicon/linux#497 RENDER_DEPTH 16 → 32: render.light d32/d64 about −3 to −4.5%.
keep G17 long jobs alive beside other long jobs aurora-silicon/linux#499 The watchdog shares the budget between long jobs. Two concurrent ray-query geometry-shader loops: 24/24 pass vs 18/28.
retire a G17 render pass on its fragment notification; set the G17 tiling first-kick word on a pair's first kick only aurora-silicon/linux#504 Halves firmware notifications per render (GPU IRQs 2.18 → 1.10): render.light d8 −9.9%, d32/d64 −5.5%. The first-kick word change applies to the A18 Pro firmware only.
kill faulted G17 contexts before freeing their render work aurora-silicon/linux#505 Leak fix part 2: kill first, free after the firmware's acknowledgement, a natural-window wait with a memory-pressure exception, and killed pairs reused last.
kill G17 contexts left with only executed render work aurora-silicon/linux#508 Leak fix part 3: render-only kills for contexts with no compute queue left. 0 teardowns pending after the stress runs, vs 4 before.
gate G17 KUnit helpers on built-in KUnit aurora-silicon/linux#492 (Ryan) WERROR fix: KUnit-only helpers compile only with CONFIG_KUNIT=y.
keep withholding G17 compute doorbells while a stamp is awaited; restart a withheld G17 compute doorbell another doorbell covered aurora-silicon/linux#468 Cuts the cross-queue hop tail: doorbells −38%, hop −2.4 to −2.9%.
skip G17 event-pass housekeeping for completion records alone aurora-silicon/linux#494 Lighter event passes.
observe and retire a G17 render pair in one device-mutex hold aurora-silicon/linux#502 render.light d8 −7.6%.

Testing

On aurora, every change was qualified on Neo by its own PR: gate PERFORMANCE-REGRESSION-PASS and targeted CTS with 0 failures, plus the measurements above. The whole set was then qualified together, as all twelve aurora heads merged in one module:

  • five-lane stress for 10 minutes (render, render2, long ALU, churn and global-priority CTS, plus two ray-query geometry-shader loops): 0 GPU recoveries, 0 firmware crashes, desktop alive;
  • regression gate PASS;
  • targeted CTS: 4687 pass, 681 not supported, 0 fail;
  • ray-query and ray-tracing-pipeline CTS (57190 cases): 47099 pass, 10088 not supported, 0 kernel errors. The 2 crashes are the known host-built 1M-primitive cases killed for memory at 6 workers. The 1 fail is a host-built 1M case under memory pressure, also seen before this series and under investigation.
  • The merged aurora tree's drivers/gpu/drm/asahi is byte-identical to that qualified module's source.

On this branch: omacom KUnit, 1713 tests: 1710 pass, 3 skip, kunit.py rc=0, verdict check rc=0.

DjDeveloperr and others added 12 commits October 26, 2026 08:58
…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.

(cherry picked from commit 9a76756)
Signed-off-by: DjDeveloperr <hey@djdev.me>
A logical render queue admitted 16 commands at a time, although its pair
has command records (TA and fragment descriptors, configuration and
timestamp slots) for 32. At depth the firmware runs the renders already
announced when it starts a batch, then writes their completions one after
another before it starts the next, so the window bounded every batch: at
render depth 32 the median render ran in a batch of 8, at most 14, and the
fixed cost of each batch edge fell on few renders.

Admit as many commands as a pair has records; the scheduler's credits
follow. At depth 32 and 64 the median render now runs in a batch of 11
and 15, at most 32. Each pair keeps a Generation per command in flight,
16 more of them, about 1.75 MiB more per render queue. A build-time check
keeps the depth within the records.

Measured on Neo in one boot, 16 against 32 commands, alternating, three
rounds (us per submission, medians):

  render.light tri, depth 32       30.41 -> 29.50
  render.light tri, depth 64       30.34 -> 29.38

Shallower depths, several queues or processes and the GPU time of each
render are unchanged within run-to-run spread.

(cherry picked from commit 347831e)
Signed-off-by: DjDeveloperr <hey@djdev.me>
A published job's deadline is renewed, up to thirty seconds, while some
command retired within the last five seconds: the GPU is then shared,
not hung. Two ray traversal dispatches side by side retire nothing until
they end, though both progress. Alone each took about 6.3 s; side by
side they took 10 to 17 s, past the ten seconds a long job gets without
that renewal, so the watchdog failed most of them with ETIMEDOUT and
their clients lost the device.

Also count the GPU as shared while a long job of another queue runs
beside a long job. The thirty-second bound still ends a pair that never
finishes.

(cherry picked from commit 3db24da)
Signed-off-by: DjDeveloperr <hey@djdev.me>
An event pass considered a render pair only once both of its queues had
posted a stamp notification, the tiling queue's and the fragment
queue's. The tiling notification adds nothing a retirement needs: the
fragment kick starts only after its fragment barrier saw the tiling
stamp, and the observation still requires both stamps in memory to cover
the pass. Recovery attribution reads the stamps, and parameter-buffer
growth arrives as its own record.

Select a pair, and decide that it needs a rescan, on the fragment
queue's notification alone, through one helper with KUnit coverage. A
pass whose only news is a tiling stamp no longer borrows the pair for an
observation that cannot retire.

(cherry picked from commit de0b22a)
Signed-off-by: DjDeveloperr <hey@djdev.me>
…only

The tiling descriptor has a word at the position of the fragment
descriptor's first-kick word, which the fragment stage sets only on a
queue pair's first kick. The tiling setup set it on every kick. With it
set, the firmware also notifies the host of the tiling queue's
completion on every pass, so each render cost two firmware
notifications, two mailbox interrupts and two event passes, while
retirement needs only the fragment queue's notification (previous
commit).

Name the word first_kick and set it for ordinal 0 only, as for the
fragment stage, on the A18 Pro firmware (26A428). The M4 Pro firmware
(25G83) keeps the old value on every kick until it is measured there.

Measured on Neo in one boot through a diagnostic switch that toggled
only this word (3 rounds each, medians, us per submit):

  GPU mailbox IRQs per submit (render.light d32)    2.18 -> 1.10
  render.light tri queue d1                        105.26 -> 104.12
  render.light tri queue d8                         30.98 -> 29.30
  render.light tri queue d32                        29.67 -> 27.80
  render.light tri queue d64                        29.67 -> 27.78
  render.light tri procs d2                         52.12 -> 49.73
  render.light tri queues d2                        52.16 -> 49.65
  compute.light queue d1                           103.39 -> 104.08
  sync.signal queues d2 timeline / binary   22.79 / 22.37 -> 22.61 / 22.61

(cherry picked from commit 5073eac)
Signed-off-by: DjDeveloperr <hey@djdev.me>
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.

(cherry picked from commit 078d744)
Signed-off-by: DjDeveloperr <hey@djdev.me>
A closed context whose failed compute command was retired on the
firmware's fault record has a clean compute side, yet the render work
it queued behind the failure ran without storing its stamp and stays
linked in the context's work list. Such a pair is released only after
a kill of its context, and that kill was only ever published through
one of the context's compute queues, of which none is left to carry
it. The context, its pairs and its memory therefore stayed until the
GPU firmware stopped: 4 contexts after ten minutes of ray query stress
beside heavy geometry.

Publish the kill for such a context on its render pairs' behalf. A
context qualifies once it has been closed for 250 ms, every pair of it
is idle or has executed all of its commands, at least one has, and no
compute queue still runs it, switches away from it or has published
its kill. The kill names one of the context's own render queues that
its scheduler page lists and is refused if the page names a queue of
another context; the pairs it holds must have a single admission owner.

The rules of a failed context's kill apply unchanged: the pairs are
held for the kill, their memory is released only once the scheduler
page reports it, the kill waits for a moment without other contexts'
compute unless GPU memory is pressed, and the held pairs become killed
pairs, reused last, once the context's scheduler release is consumed.
The teardown entry keeps the kill's cookie, so only that exact cookie
acknowledges it, and its doorbell is retried without duplicating the
record. debugfs reports render-only kills, their acknowledgements, the
contexts refused by first reason and the kills waiting.

On Neo, ten minutes of two ray query stress loops beside heavy geometry
had 53 faults, 53 kills through compute queues and 4 render-only
kills, all acknowledged, with 0 GPU recoveries and nothing left pending
afterwards (4 teardowns before). Ten minutes of the five-lane stress
load had 48 faults, 8 kills through compute queues and 2 render-only
kills, all acknowledged, with 0 GPU recoveries and nothing left pending.
No render-only kill was refused in either run.

The asahi_g17_teardown tracepoint records render-only kills as kind 7
and their acknowledgements with queue 0x100.

(cherry picked from commit 72771fb)
Signed-off-by: DjDeveloperr <hey@djdev.me>
The kunit_tests macro compiles its test modules only when CONFIG_KUNIT is
y. With CONFIG_KUNIT=m the four test-only G17 helpers remain compiled and
produce dead-code warnings, breaking a CONFIG_WERROR build.

Use the same built-in KUnit condition for the helpers as for their callers.

Fixes: a36e352 ("drm/asahi: size the G17 compute QID budget by open logical queues")
Fixes: 014605a ("drm/asahi: add G17 resource wait domains and use them for queue creation")
Fixes: 64f717d ("drm/asahi: wait for G17 compute capacity on the release that frees it")
Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
(cherry picked from commit 91c4792)
Signed-off-by: DjDeveloperr <hey@djdev.me>
…aited

A queue whose retirement stops at a command with a completion record but
no covering stamp yet sets retire_pending, which counted as leaving
ordinary retirement: the next publication rang its doorbell at once, and
a withheld doorbell was released whatever its predecessors. That happens
after most completions. Each doorbell makes the firmware read the work
ring, and a hop between queues whose producer ends 30-80 us after one is
slowed by 12-24 us.

The withheld doorbell already counts only predecessors without a
completion record, which covers a front waiting for its stamp. Count
retire_pending as settling only when the front has no record, the case
of a command the firmware finished without one.

In a two-queue semaphore ping-pong (gpubench sync.signal, 0.6 s traced),
doorbells rung at publication fall from 2006 to 96 and releases forced
by settling from 1280 to 23; all doorbells fall from 8035 to 6707.

(cherry picked from commit 5de99fc)
Signed-off-by: DjDeveloperr <hey@djdev.me>
…overed

A doorbell announces every work slot written on its ring, so a withheld
doorbell of one queue is often covered by a doorbell another queue rings.
Publications of the first queue after that kept the withheld doorbell's
first publication, whose predecessors had long retired: the doorbell was
due at once and rang for one or two new slots. In a two-queue semaphore
ping-pong almost half of all releases announced a single slot.

When a publication finds that another doorbell announced every slot the
withheld one covered, it starts the withheld doorbell over from itself,
so the doorbell waits for this command's own predecessors.

Measured on Neo with this and the previous change against the base in
the same boot, alternating, three rounds (us, medians):

  sync.signal ping-pong doorbells     7409 -> 4625
  hops slowed by more than 7 us      19.6% -> 13.9%
  sync.signal timeline, per hop       23.40 -> 22.83
  sync.signal binary, per hop         23.35 -> 22.67
  compute.light queue d64             13.89 -> 13.11

The median hop is unchanged; compute.light queue d1/d2/d3/d4, queues
d1/d2/d4 and render.light d1/d32 stay within 1%.

(cherry picked from commit 83ff3a9)
Signed-off-by: DjDeveloperr <hey@djdev.me>
Every completion notification queues an event pass, and every pass ran
the full housekeeping after retiring the records: render linger expiry,
drained-pair reclaim, compute exit settlement, compute pool recycling and
vacating, evicted render pool vacating, heap shrinking and teardown. Each
of these scans every compute queue, render entry or pool, so with around
40000 passes a second in light rows the scans took a large part of the
event worker's time on the IRQ CPU, while retiring a completion and
signalling its fence needs none of them.

Run that housekeeping only when a pass is requested through
queue_events(), on recovery, while a teardown is pending, while anything
waits for channels, progress, queue slots or render capacity, when no work
remains in flight, and at the latest on the first pass 1 ms after the last
full one. The firmware's event notification, records that arrive during a
pass and render events missed while a pair was on loan queue a pass for
the records alone. Every other caller keeps getting a full pass.

Measured on Neo in the same boot against every pass running the
housekeeping, alternating, four rounds (medians):

  event pass, sync.signal + render.light procs   8.2 -> 6.2 us
  event worker share of CPU0, all rows           24.9% -> 20.8%
  compute.light procs d2                         36.80 -> 34.81 us
  compute.light queues d2                        36.76 -> 34.96 us
  submit.empty queue d2                          1.78 -> 1.69 us

compute.light queue d1/d2/d64, queues d4, render.light d1/d32 and queues
d4/d8, render.light procs d2, sync.signal binary/timeline, concurrent.cr
and the cross-queue pair stay within 2%.

(cherry picked from commit f769da0)
Signed-off-by: DjDeveloperr <hey@djdev.me>
The event worker read a render pair's completion memory outside the
device mutex. It took the pair out of its entry on loan under one hold,
read the front's stamps and graph cursors unlocked, then took the mutex
again to return the pair and retire what it had seen. The read is a
short memory snapshot, so the loan bought no useful concurrency, and
every observed pair cost two acquisitions of the mutex that submission
also takes, with publishers able to slip in between.

Select the pair, read its front and retire it in one hold. The pair
stays in its entry, so observations no longer need the loan bookkeeping
(the preparing marker and the borrowed dependency and switch snapshots)
or the return path and its invariant-failure handling. Preparers keep
using the loan. The read takes the context's work mutex for the shared
list head, which now nests inside the device mutex; that mutex is a
leaf, held only around accesses to the context's work storage.

Measured on Neo across boots, with this change, without it, and with it
again (3 rounds each, medians, us per submit):

  render.light tri queue d8         31.29 / 33.73 / 31.04  (-7.6%)
  render.light tri queue d1        105.49 / 105.75 / 105.53
  render.light tri queue d32/d64    30.06 / 30.01 / 29.68
  render.light tri procs d2         52.18 / 52.24 / 52.29
  render.light tri queues d2        51.98 / 52.12 / 52.12
  compute.light queue d1           103.61 / 103.73 / 104.34
  sync.signal queues d2 timeline    22.74 / 22.67 / 22.69
  sync.signal queues d2 binary      22.53 / 22.27 / 22.53

(cherry picked from commit cf38d3a)
Signed-off-by: DjDeveloperr <hey@djdev.me>

@maralcbr maralcbr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @DjDeveloperr, this is a careful port. The kill-before-free ordering is right, and the commit messages spell out each trade-off and its known limits, which made the series much easier to check. I read all twelve against neo/g17-neo-perf-20261025 at 578dd953: they apply cleanly and every commit keeps its -x line and sign-off, Ryan's included.

One thing before merge:

  • No Kernel CI checks are reported for this head. kernel-ci.yml and kernel-ci-label.yml only trigger for pull requests into integration/aurora (.github/workflows/kernel-ci.yml:20-22, kernel-ci-label.yml:9-11), so a PR stacked on #229's branch gets nothing, and the KUnit run in the body is the only evidence so far. It needs a manual Kernel CI run on neo/g17-aurora-merged-20261027 for the package, KUnit and boot jobs. The commit, per-commit compile and device-tree checks only run on a pull request or in the merge queue (kernel-ci-jobs.yml:106,132,185), so those have to come from a new #229 run that contains these commits; its earlier green run doesn't cover them.

Separately, and not a code issue: merging this folds twelve more commits into #229 while it's still under review. Whether they ride in #229 or land as their own PR after it is a maintainer call, and I'll settle it on #229.

Smaller things:

  • The render-only kill takes its admission hold before its cookie. In runtime/compute_exit.rs:378-385, if next_cookie() fails with EOVERFLOW, the hold taken at :380 is never released. The compute path takes the cookie first (:269); please do the same here.
  • Two comments no longer match the code. runtime/render/observe.rs:11-12 still says the pair needs both queues' notifications, and queue/admission.rs:943 still says a published hold lasts until both processors stop, but release_killed_slots now ends it at detach.
  • A failed reserve_kept borrows another object's reservation. object.rs:692-701 returns without reserving but retain_until_stop still sets keep, so the matching keep_object (:705) decrements a reservation that belongs to someone else. It only ever ends on the leak side, never an unmap, so this is minor. Having reserve_kept return whether it reserved, and keeping that on the object, would fix it.
  • Nit: 9d942b3's message calls the context work mutex a leaf, but retain_linked_work takes the object-pool mutex inside it (context.rs:293). I found no inversion; it's just worth correcting on the aurora side.

Follow-ups, not blocking:

  • Firmware-linked retention has no bound. Linked work objects (32 KiB each, context.rs:289-296) and kept render graphs (runtime/render/teardown.rs:144-148) stay until the firmware stops, which in practice means a crash or an unbind (mod.rs:645-653). A client that faults compute in a loop grows that by roughly 110 MiB an hour at one kill a second. The known limits in 95308a6 say this honestly, and it's far better than the base, which kept whole contexts and their pools. Could the kept graphs get a debugfs count beside firmware_linked_objects, and a bound that keeps firmware ownership intact be tracked as a follow-up?
  • Please name the Neo board, both firmware versions and the module build the stress and CTS numbers came from. The body cites the aurora-side qualification only.

For release (not the merge): a Neo run of this tree, since the numbers come from aurora's module rather than this branch. The kill and retention changes also apply under the 25G83 firmware, which has no evidence yet.

refuse_render_kill(&mut self.queues.teardown, index, RENDER_KILL_HOLD);
return Ok(());
}
let cookie = exit::next_cookie()?;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If this fails, the admission hold taken at :380 stays installed. Please take the cookie before the hold, as publish_compute_exit does at :269.


impl crate::g17::Firmware {
fn take_render_observation(&mut self, start: usize) -> Result<Option<(usize, KBox<Pair>)>> {
/// The first slot from `start` whose pair has news to observe: published work in flight, both

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: selection now needs only the fragment queue's notification (completion_noted), not both.

@@ -887,18 +941,38 @@ impl State {
impl Pool {
/// Hold the departed owner's idle pairs before publishing its exit record.
/// Successful publication retains these bits until both processors stop.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: the hold now ends at detach through release_killed_slots, not only at processor stop.

Comment thread drivers/gpu/drm/asahi/g17/object.rs Outdated

/// Room for one more kept object, taken by the matching `keep_object`. A failed
/// allocation is reported by that drop, which then leaks the object instead.
fn reserve_kept(&self) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When the reserve fails, keep is still set, so the matching keep_object decrements another object's reservation. Returning whether this reserved, and keeping that on the object, would fix it.

if linked {
// Guard (kill before free): the firmware may still walk this context's work
// list into the graph, so it is not freed or recycled before the firmware stops.
kernel::pr_warn_once!("G17: keeping render graphs of an unkilled context that still links work\n");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These kept graphs have no debugfs count and no bound, only this warn-once. Worth a count beside firmware_linked_objects in a follow-up.

…hold

A render-only kill took its admission hold on the context's render pairs
and only then its cookie. If the cookie counter was exhausted, the
EOVERFLOW returned with the hold still installed, so those pairs stayed
held for a kill that was never published. Take the cookie first, as a
failed context's kill already does.

(cherry picked from commit d6d0c93)
Signed-off-by: DjDeveloperr <hey@djdev.me>
retain_until_stop reserved a list entry for an object kept until firmware
stop, and its drop took that entry in keep_object. When the reservation
failed, keep was still set, so the drop decremented a reservation that
belonged to another kept object and could take its entry.

reserve_kept now reports whether it reserved, and the kept object carries
that. An object kept without a reservation is leaked at drop, as the
failed reservation already documented, and leaves the other objects'
reservations alone.

(cherry picked from commit 4e96b0a)
Signed-off-by: DjDeveloperr <hey@djdev.me>
Render graphs of a detached context that still links work are kept until
the firmware stops, like firmware-linked work objects, but had no counter
beside firmware_linked_objects. The capacity view now reports them as
firmware_linked_render_graphs, with the number leaked because the list
could not grow.

Two comments described the code before recent changes: render
observation needs only the fragment queue's notification now, and a kill
hold on render pairs ends when the killed context's scheduler release is
consumed, not only when the processors stop.

(cherry picked from commit 34cf645)
Signed-off-by: DjDeveloperr <hey@djdev.me>
@DjDeveloperr

DjDeveloperr commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator Author

Thanks for the careful read. I've pushed three commits for the code points. They are also an aurora-silicon/linux PR (aurora-silicon/linux#509, merged there), so both trees stay the same.

  • Render-only kill cookie order: fixed in 835865b. The cookie is taken before the admission hold, as the compute path does, so EOVERFLOW leaves no hold behind.
  • Stale comments: fixed in 2ef0352. runtime/render/observe.rs now says only the fragment queue's notification is needed (completion_noted). queue/admission.rs now says the hold ends when the killed context's scheduler release is consumed (release_killed_slots at detach), or at processor stop.
  • reserve_kept: fixed in be5bd19. It returns whether it reserved, and the kept object carries that flag. A kept object without a reservation is leaked at drop, as the failed reservation already documented, and no longer touches another object's entry.
  • Kept render graphs: 2ef0352 also adds firmware_linked_render_graphs kept= leaked= to the capacity view, beside firmware_linked_objects. A bound that keeps firmware ownership intact is a follow-up; I agree it's needed for a client that faults in a loop.
  • 9d942b3 "leaf" wording: right. retain_linked_work takes the object-pool mutex inside the context work mutex, so the work mutex isn't a leaf. The order is fixed, with no inversion. The merged aurora commit can't be reworded; I'll correct it in the next commit that touches that path.
  • CI: I started a manual Kernel CI run on neo/g17-aurora-merged-20261027: https://github.com/omacom/linux-aurora/actions/runs/40461018609. The per-commit compile, device-tree and commit checks need a drm/asahi: G17 (Neo, T8140) driver update for 7.2.11 #229 run that contains these commits, so they depend on how you settle drm/asahi: G17 (Neo, T8140) driver update for 7.2.11 #229.

Where the numbers came from:

@DjDeveloperr

Copy link
Copy Markdown
Collaborator Author

The manual Kernel CI run on neo/g17-aurora-merged-20261027 (2ef0352) passed: https://github.com/omacom/linux-aurora/actions/runs/40461018609. That covers plan, CI self-test, KUnit, package, both host test suites and the QEMU boot that loads every module. The per-commit compile, device-tree and commit checks don't run on a dispatch, so they still depend on a #229 run that contains these commits.

A closed context qualifies for a render-only kill while one of its
pairs holds executed but unstamped commands and no compute queue still
runs it, switches away from it or killed it. A context killed through
its compute queue normally leaves that state at once, since the
acknowledged kill releases the pair in the same service pass, but only
that timing kept the render-only path from killing it again: the
compute queue's own record of the kill ends when the queue is rebound.

A second kill of a killed context is harmful. In a diagnostic run that
held the pair's release back for 100 ms after the kill, 31 contexts
were killed twice, 0.1 to 2.5 ms apart, and twice the firmware
recovered the GPU 0.3 ms after acknowledging the second kill, on a page
fault in the killed pair's own tiling queue. The 35 contexts killed
once in that run caused no recovery.

Record on the context that a kill was published, by either path, and
never publish a render-only kill for a context that has one.

(cherry picked from commit 7a9ebf0)
Signed-off-by: DjDeveloperr <hey@djdev.me>
@DjDeveloperr

Copy link
Copy Markdown
Collaborator Author

Added f17489a "drm/asahi: kill a G17 context at most once" (aurora-silicon/linux AsahiLinux#513, merged as 53205cc).

Under forced faults, the compute-exit path and the render-only path could both kill the same context, a few ms apart. The second kill's acknowledgement then raced the firmware still touching the context's tiling queue. That accounted for the only recoveries in the forced-kill legs. The fix sets a per-context flag at the first published kill, and both paths check it.

  • Neo qualification on the aurora tree: gate PASS, targeted CTS 4687 pass / 681 not supported / 0 fail, KUnit 125/125.
  • Pairs leg (10 min, forced faults): 62 kills, 0 double kills, 0 GPU recoveries.
  • omacom KUnit with this commit on the branch: 1714 tests, 1711 pass, 3 skip, check rc=0.

Kernel CI was dispatched manually on the new head.

@DjDeveloperr

Copy link
Copy Markdown
Collaborator Author

Kernel CI (manual dispatch) on f17489a: success (run 40480119193: plan, self-test, KUnit, package, host tests). The compile, commits and device-tree jobs are skipped on workflow_dispatch, as before.

…0261027

Pick up #229's merge of integration/aurora at 30a6518, so this
branch builds and tests against the current base. No conflicts.

Signed-off-by: DjDeveloperr <hey@djdev.me>
@DjDeveloperr

Copy link
Copy Markdown
Collaborator Author

#229's branch now has integration/aurora (30a6518) merged in (691c687; one conflict in mmu.rs, described on #229). I merged that branch into this one as 58b3cf1, with no conflicts, so this head builds and tests against the current base.

  • omacom KUnit, run locally: 1718 tests, 1715 pass, 3 skip; verdict check rc=0. The asahi driver also builds with G15 and M3_T8122 enabled.
  • Kernel CI (manual dispatch) on 58b3cf1 passed: https://github.com/omacom/linux-aurora/actions/runs/40681399826 (plan, self-test, KUnit, package, boot, host tests). As before, compile, commits and device trees are skipped on a dispatch.

maralcbr
maralcbr previously approved these changes Oct 28, 2026

@maralcbr maralcbr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @DjDeveloperr. Re-checked at 58b3cf14. Everything from my earlier review is fixed, and f17489a is a good catch.

  • Render-only kill cookie: fixed. next_cookie() now runs before the admission hold (runtime/compute_exit.rs:392), and a failed publication still releases the hold.
  • Stale comments: fixed (runtime/render/observe.rs:11-12, queue/admission.rs:943-944).
  • reserve_kept: fixed. The kept object carries whether it reserved, and keep_object decrements only its own reservation and leaks an unreserved object (object.rs:524-526,708-729).
  • Kept render graphs: firmware_linked_render_graphs kept= leaked= is now in the capacity view (runtime/render.rs:1148). The bound stays a follow-up, as agreed.
  • Provenance: thanks for naming the board, firmware and module builds.
  • "Leaf" wording: fine to correct in the next aurora commit that touches it.

f17489a looks right to me. Eligibility and publication run under the device mutex, both publication sites set the flag before the doorbell, and a new context starts with it clear, so a reused queue doesn't carry a stale flag.

Small things, none blocking:

  • runtime/render/teardown.rs:197-200: LEAKED_GRAPHS sits between keep_graph's doc comment and the function, so rustdoc gives that description to the static.
  • runtime/compute_exit.rs:408-409 and :292-309: note_kill_published() runs after a fallible step that follows the irrevocable publication (record_render_kill(...)?, and .ok_or(EIO)? on the compute side). Neither can fail today, since the candidate checks rule those cases out under the same mutex. Still, setting the flag right after publish_control succeeds would keep the at-most-once rule from depending on that.
  • Taking the cookie first means a refused hold now uses one up. That matches the compute path, and exhaustion fails closed, so it's fine.

CI: the manual Kernel CI runs on 2ef0352, f17489a and now 58b3cf14 (run 40681399826) all passed. A dispatch skips the commit, per-commit compile and device-tree jobs, so those still need to come from #229's run once this is in it. The merge of #229's branch at 58b3cf14 leaves g17/ unchanged.

Merging this into neo/g17-neo-perf-20261025 folds the series into #229, so #229 is where it gets settled.

For release, not the merge: a Neo run of this exact tree (the numbers are from aurora's allpr2 module), and an M4 Pro run under 25G83 for the kill and retention changes.

/// Keeps `graph` until the firmware stops; if even that cannot be recorded, it is leaked
/// rather than freed.
/// Graphs that could not be recorded in `kept_graphs` and were leaked instead, for debugfs.
pub(super) static LEAKED_GRAPHS: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: this static now sits between keep_graph's doc comment and the function, so rustdoc attaches "Keeps graph until the firmware stops…" to LEAKED_GRAPHS. Moving the static above that comment fixes it.

// Publication is irrevocable. The held pairs stay held until this context's
// scheduler release is consumed (`release_killed_slots` at detach).
self.queues.teardown.record_render_kill(index, cookie, now)?;
context.note_kill_published();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Optional: setting the flag right after publish_control succeeds, before record_render_kill(...)?, keeps the at-most-once rule from depending on that call never failing. Today the candidate checks make it unreachable, so this is hardening only. Same shape at :309 after .ok_or(EIO)?.

LEAKED_GRAPHS was added between keep_graph()'s doc comment and the
function, so rustdoc attached "Keeps `graph` until the firmware
stops..." to the static. Move the static above that comment.

Signed-off-by: DjDeveloperr <hey@djdev.me>
Both kill paths set the context's kill-published flag only after a
fallible step that follows the irrevocable publication:
record_render_kill(...)? on the render-only path, and the compute queue
lookup's .ok_or(EIO)? on the compute path. Neither can fail today,
because the candidate checks rule those cases out under the same
device mutex, but the at-most-once rule should not depend on that.

Set the flag as soon as publish_control() succeeds.

Signed-off-by: DjDeveloperr <hey@djdev.me>
…0261027

Pick up #229's review fixes (the M3 GET_PARAMS minimum and the G17-only
memory limits). No conflicts.

Signed-off-by: DjDeveloperr <hey@djdev.me>
@DjDeveloperr

Copy link
Copy Markdown
Collaborator Author

Thanks. Both small things are done:

  • 5389bde moves LEAKED_GRAPHS above keep_graph()'s doc comment.
  • 8a7ef5d sets the kill-published flag as soon as publish_control() succeeds, before record_render_kill(...)? and before the compute queue lookup's .ok_or(EIO)?.

10fe909 merges #229's review fixes into this branch, with no conflicts.

The same two commits are aurora-silicon/linux#550.

…0261027

Pick up #229's restored DRM module owner (6c66b41). No conflicts.

Signed-off-by: DjDeveloperr <hey@djdev.me>
DjDeveloperr added a commit that referenced this pull request Oct 29, 2026
…mp-fallback-20261028

Pick up #229's restored DRM module owner (6c66b41) through #244. No conflicts.

Signed-off-by: DjDeveloperr <hey@djdev.me>
DjDeveloperr added a commit that referenced this pull request Oct 29, 2026
…ity-20261028

Pick up #229's restored DRM module owner (6c66b41) through #244. No conflicts.

Signed-off-by: DjDeveloperr <hey@djdev.me>
DjDeveloperr added a commit that referenced this pull request Oct 29, 2026
…se-light-20261028

Pick up #229's restored DRM module owner (6c66b41) through #244 and #270. No conflicts.

Signed-off-by: DjDeveloperr <hey@djdev.me>
chriskearney
chriskearney previously approved these changes Oct 29, 2026

@chriskearney chriskearney left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed current successor 071f1581 for the shared-code/M3 regression scope. Its tree f56154aff85f782215d8a38eff3db399d17700fc matches the clean merge of #244's frozen changes with #229's module-owner fix 6c66b41a; driver.rs retains the repaired owner override and assertion.

The earlier frozen #235+#229+#244 combination passed M3/G15 boot, native GL/Vulkan correctness, unchanged OMT status maps and the bounded A/B/A suite with exact restoration. Detailed timings and scope. The source-only owner repair was checked separately; this is not an updated-head or Neo/G17 hardware result. Single-submit CPU timing remains variable and fresh CI/normal merge requirements still apply.

@maralcbr maralcbr closed this Oct 29, 2026
@maralcbr maralcbr reopened this Oct 29, 2026
@maralcbr
maralcbr changed the base branch from neo/g17-neo-perf-20261025 to integration/aurora October 29, 2026 02:26
@maralcbr
maralcbr dismissed stale reviews from chriskearney and themself October 29, 2026 02:26

The base branch was changed.

@maralcbr maralcbr closed this Oct 29, 2026
@maralcbr maralcbr reopened this Oct 29, 2026

@maralcbr maralcbr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-approving at 071f158. Retargeting this to integration/aurora after #229 merged dismissed the earlier approvals. Since my last check at 10fe909 the only change is the restored MODULE owner from #229, and PR CI passed on this head.

@maralcbr
maralcbr added this pull request to the merge queue Oct 29, 2026
Merged via the queue into integration/aurora with commit 7e4fc9f Oct 29, 2026
25 checks passed
DjDeveloperr added a commit that referenced this pull request Oct 29, 2026
…20261028

Bring the branch up to date with integration/aurora at 7e4fc9f, now that its
base, #244, has merged. No conflicts.

Signed-off-by: DjDeveloperr <hey@djdev.me>
DjDeveloperr added a commit that referenced this pull request Oct 29, 2026
Bring the branch up to date with integration/aurora at 7e4fc9f, now that its
base, #244, has merged. No conflicts.

Signed-off-by: DjDeveloperr <hey@djdev.me>
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.

4 participants