Repository navigation
drm/asahi: G17 stability fixes from linux-aurora PR #151 (7.3 port) - #390
Merged
Merged
Conversation
Rust Devres drops remove an exact node, but devres_node_remove scans the entire device list to find it. Repeated removal of older GPU SG-table resources therefore does quadratic work while holding devres_lock. After large concurrent GPU workloads this keeps exiting clients CPU-bound for roughly a minute. Track the device-list owner in each node and remove a matching node directly under the device lock. Clear membership on every detach path, including replacement, ordinary and action removal, group removal, and moves to the private release-all/group callback list. A concurrent drop must leave nodes already owned by that release path alone. Preserve callback ordering and lifetime handling. Use single-copy membership accesses so a simultaneous comparison using a different device does not race through an ordinary pointer access. The membership field adds one pointer per resource node. Source-derived ASan/UBSan controls cover the removal APIs, replacement, open/closed/nested groups, private-list ownership and 1000 concurrent removal/detachment/different-device lookup races. Retaining stale private list ownership makes the negative control fail. The combined series passes Neo G17P concurrent GPU stress, expiry cycles and retained-VM context churn. Four clients performing 30 seconds of checked compute work finish and exit in 31.46 seconds total, versus 86.97 seconds with the same compact target layout before this correction. Signed-off-by: DjDeveloperr <hey@djdev.me>
The target-record stride already accounts for its computed layout and UAT alignment, but its 256 KiB minimum wastes backing on smaller targets. Use one 32 KiB TVB page as the minimum instead, keeping the computed used size, consumer bounds, growth handling and all sixteen generations unchanged. Queue capacities, batching and synchronization policy are unchanged. Source-derived controls validate 388800 layouts against the original capacity and consumer bounds; 114426 need less backing. At forty held GPU clients with 124 target objects on Neo, allocation extents fall from 272236544 to 44695552 bytes, saving 217 MiB for that workload. The compact layout passes changing-pixel and layered readbacks, target growth, concurrent render/compute/mixed stress, Vulkan multisample checks, compute pool expiry and 2000 retained-VM contexts. Brilliant Diamond also reaches its language menu around 30 FPS in a bounded emulator run; broader gameplay and application performance validation remain separate. Signed-off-by: DjDeveloperr <hey@djdev.me>
When a logical queue closes, its scheduler release is published once its work is idle. The firmware consumes the release against the queues that scheduler last ran. After the host has seen it consumed, the teardown parks the scheduler's render pair, and a later owner reinstalls a parked pair with fresh queue records. A successor on the same VM can find such a pair still installed after the release was published but before its consumption was seen. The pair is then reusable() and not foreign, so build_render_pair() rebinds it in place, rewriting the job-list head, pid and scheduler fields of queue records the firmware ran for the released scheduler, and publishes its first command, which carries the owner change. A GL client that recreates its context every few milliseconds next to three Vulkan clients slows the release enough to open this window. On the 7.2.10 line its successor published the first commands inside it and the GPU firmware crashed about 3 ms later without writing a crash log. At the crash the release had been consumed, the pairs had been rebound in place and their first commands for the new owner were still in flight. The 7.3 rebind path keeps the same reusable() test and has no release check. Order owner changes and scheduler releases: - A render pair whose context has an armed or unconsumed scheduler release is not rebound. The new owner retries until the teardown has parked the pair, then installs it with fresh records. The check is made when the rebind is chosen and again when it is committed. - A scheduler release waits while a render pair or compute queue that changed owner away from that context still has the new owner's first command in flight. A pair records at publication which scheduler its queues last ran for. A borrowed pair keeps a snapshot of that state. - The first command after an owner change waits while the scheduler the pair last ran for is being released. - The same holds for compute queues: a queue whose bound scheduler has an armed or unconsumed release is not rebound or re-armed for another owner, and the first command after a compute owner change (including a queue an idle live owner yielded through share_idle()) waits while the scheduler the queue was bound to is being released. - A pair parked without a consumed release keeps the scheduler it last ran for, so its next owner change still waits for that release. A park after the consumed release of the pair's scheduler clears it. - A publication refused because its pair is busy kicks the event worker on every poll, since the event worker observes the consumed release and parks the pair, and gives up after the job timeout instead of after 5000 polls. The normal path is unchanged. A pair whose owner does not change compares one word at publication. A release is delayed only while an owner change is in flight. After a context churn, a same-VM successor's first render waits until the previous context's release has been consumed and its pair parked, which successors on another VM already did. Measured on Neo on the 7.2.10 line with diagnostic switches that apply the first two rules: - four-client churn: 3 of 3 runs crashed within 10 to 120 s without them, 2 runs of 90 s passed with them - GLES: 15160 vs 15175 iterations, 5054 vs 5059 contexts per 90 s - Vulkan 4x MSAA render: 212643 vs 214050 iterations tools/asahi/g17-owner-release-order-tests.py compiles the release table, pair switch tracking and release readiness predicates against host stubs and checks the guard placement in the rebind and publication paths. Signed-off-by: DjDeveloperr <hey@djdev.me>
A pair installs the client alias of each kick ring into its VM when the pair is installed (append_driver_mappings), and the VM keeps that alias until it closes: a client that gets the pair back later reuses it. Under identity pressure, reclaim_unused_render_spare() takes an installed pair whose graph never started, for compute or for another render owner's queue IDs, and drops it together with its rings. That frees the rings' shared lower addresses while the VM that installed the pair, which may outlive the closed queue (one DRM file with several queues), still aliases the old pages there. A later ring placed at the same address and handed to that VM cannot be aliased: prepare_client_alias() finds the VM's mapping naming other pages and fails with EFAULT, and the client's render is reported as a lost device. On the 7.2.10 line held GLES clients hit this every few seconds while fresh clients came and went. Keep the storage of a dropped ring and reuse it for later rings. An address then keeps naming the same pages, so every alias a client keeps stays valid; a reused ring is zeroed and keeps an existing alias. Storage of a ring that ever wrote an entry is retained and never reused. A new ring is counted only once its storage exists, reserving room to return it then, so a dropped ring never allocates and a failed creation leaves no reservation behind. Signed-off-by: DjDeveloperr <hey@djdev.me>
A render packet that waits in publication for graph capacity already holds its admission lease, and the entry of a pair under construction has no installed pair. Admission, the capacity waits and the busy-pair polls each have their own budget, so a publication can outlive the two-second scheduler watchdog. timed_out() then counts the expiry towards the timeout storm limit, and timeout_render() looks the pair up for an unpublished packet, fails with EIO, and the handler loses the device for every client. Eight such expiries within 30 seconds, which a burst of new clients reaches, lose the device permanently even without the lookup. On the 7.2.10 line a burst of new clients whose render free lists did not fit the shared lower GPU VA window stopped the runtime two seconds after the first wait began. Each retry of such a build also populates the full free-list backing, tens of MiB with ray-query scratch reservations, before the window is searched, and frees it again after the refusal. With the 1 ms capacity poll, sixteen concurrent builders reached several GiB of transient kernel allocations. - A watchdog expiry for a packet that is not published returns NoHang without counting towards the storm limit and keeps the packet's place in the backlog: its publisher owns the bounded wait and the result. - If the timeout collector cannot be allocated, the expiry is retried at the next watchdog period instead of stopping the runtime. - A refused free-list placement is not attempted again for the same or a larger size until free-list backing is freed, apart from one probe per 100 ms across all callers, so waiting builders allocate nothing. - While the window refuses free-list backing, idle retained compute pools are given back regardless of the warm cap. The host resource tests check the placement gate and the watchdog guard placement. Signed-off-by: DjDeveloperr <hey@djdev.me>
Every logical queue leases one of the 58 application contexts of the TTBAT (5..62) when it is created. A closed queue keeps that lease until its scheduler release has been consumed and the reclaim worker returns it; retained compute queues and render pairs keep only storage, not the context. After heavy device churn, contexts of queues closed moments earlier can therefore still be leased while fewer than 58 queues are open, and QUEUE_CREATE fails with ENOSPC from new_execution_context() although the logical-queue count admitted the queue. Userspace creates its kernel queue at the first submission and reports that as a lost device. When the context allocation is refused and some leased contexts belong to closed queues, kick the event worker and retry for a bounded time with neither the device mutex nor the preparation lease held. Only open queues then count against the limit. Nothing of the new queue is visible while it waits, so a creator with a pending signal returns ERESTARTSYS. A refusal that remains is counted and named as an exhausted execution context, next to the existing logical-queue refusal, in the capacity diagnostics. Signed-off-by: DjDeveloperr <hey@djdev.me>
Dependency-free renders are published from the submit ioctl, which holds the logical queue's mutex. try_render_early() only takes an admission lease that is free at once, but render_with_lease() can then still sleep for contention: render identities and capacity (up to a job timeout), a borrowed or settling pair (up to a job timeout), work states (up to 1000 polls), recovery settling, and an uninterruptible wait on a closed preparation gate. A client killed while its ioctl waited stalled for that long, and on the 7.2.10 line, where the budgets were longer, for about 30 s. The submit ioctl no longer sleeps for contention. An ioctl publication that would wait returns its admission lease and its publication claim before anything became firmware-visible, and leaves the command to the scheduler like a command with dependencies; later commands of the queue then wait behind it in the backlog. The scheduler's worker keeps the existing bounded waits. Uncontended ioctl publication is unchanged. Compute publication also failed the packet with EBUSY when the context had no free work state, although work states free as the context's earlier commands retire. Wait for one under the packet's admission deadline instead, as render publication does. tools/asahi/g17-compute-deadline-tests.py runs the production compute publication with a context that has no free work state and checks that it waits until the admission deadline. Signed-off-by: DjDeveloperr <hey@djdev.me>
…ir is borrowed Event passes observe only render pairs that are installed in their entry. A pair that preparation, growth or an observation has borrowed is skipped, and nothing requested another pass when it came back: the completion of work published on that pair waited for an unrelated later firmware event, or for the one-millisecond rescan when no further event arrived. Record a completion event that arrives for a borrowed pair and have the borrower request an event pass when it returns the pair. Growth needs no request of its own; its work item always queues one when it finishes. Signed-off-by: DjDeveloperr <hey@djdev.me>
Every compute command got a fresh page for its firmware start/end words: a GEM object, its write-combined kernel mapping and a GPU mapping were set up in the submit ioctl, and torn down again when the command's completion was dropped. On Neo a lone dispatch spends ~95 us from its fence signal to the next command's ioctl, against ~17 us for a render, which has no such per-command allocation. Keep up to eight retired pages per context, mapped in its VM, and hand them out again with both words cleared. A page returns only when its completion is dropped, which follows exact retirement or a kill witness, so the firmware no longer writes it. Commands beyond the retained count still allocate and free their page. Signed-off-by: DjDeveloperr <hey@djdev.me>
Contributor
Author
|
Neo (A18 Pro) hardware qualification on 7.3.0-1-73fix-ARCH (asahi.ko 6a9882cca5f8): Real-application mix (Chromium WebGL on GL and Vulkan, MSAA render, GL context churn and Vulkan compute device churn, 180 s): No EFAULT, EBUSY, ETIMEDOUT, WARN, BUG, coprocessor crash or runtime stop in the stress boot's kernel log, and no compositor crash. render50 and qid48 are limited only by the named ENOSPC capacity ceilings. The accounting step reads the 7.2.10 asahi-g17 memory debugfs, which 7.3 doesn't have. |
DjDeveloperr
marked this pull request as ready for review
October 23, 2026 01:20
This was referenced Oct 23, 2026
Merged
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This ports the G17 stability fixes from omacom/linux-aurora#151 (qualified on Neo on 7.2.10) to integration/aurora (Linux 7.3).
I triaged each commit by code. integration/aurora was still exposed to two bugs reproduced on hardware, so those ports go first:
reusable()check, with no scheduler-release check.Also ported:
Not ported:
Tests:
Neo hardware qualification (7.3.0-1-73fix-ARCH): in progress. It boots clean, with no WARN, BUG, coprocessor crash or runtime stop. The full stress battery and the Chromium real-application mix are running; results will follow here, and the PR stays a draft until they're in.