Skip to content

drm/asahi: G15 pin module before retaining firmware ownership - #237

Merged
chriskearney merged 1 commit into
integration/aurorafrom
fix/g15-retain-module-pin-20261026
Oct 26, 2026
Merged

chriskearney merged 1 commit into
integration/aurorafrom
fix/g15-retain-module-pin-20261026

Conversation

@chriskearney

@chriskearney chriskearney commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

A first deferred or asynchronous G15 probe can race ordinary module removal. The old retain path set RETAINED and then called __module_get, which can increment the reference count after unload has already committed to removal. Firmware and RTKit callbacks could then outlive the module.

Use try_module_get before marking the session retained or touching firmware. A refused pin returns ENODEV with the session unretained; cached objects follow the existing failed-self-check retention policy and the PMP vote is released. Keep one permanent reference and drop only redundant temporary references. The post-start retain call propagates errors. Existing model, firmware, display and autoload admission rules are unchanged.

Validation at 095edba:

  • G15 host runner: 78 passed, 2 pre-existing ignored, on Rust 1.85.0, 1.93.1 and stable. Six independent ordering/reference mutations are rejected.
  • Source review covers the deferred/async first-probe path and module-removal ordering. This is not a hardware reproduction of the race.
  • Native M3-enabled kernel build passed with GCC 14.2.0 / Rust 1.93.1. The actual AArch64 module contains the new C helpers, whose disassembly calls try_module_get / module_put; the old unconditional G15 helper is absent. The resolved config differs from the frozen M3 input only in LOCALVERSION, and Image/module/J516S DTB identities agree.
  • M3 Pro/J516S booted this exact fix. Same-boot GL and Vulkan 3-second readbacks passed with zero wrong pixels on system Mesa 147a62f619, all three displays were active, and normal return/recovery completed. The original job retains an early display-topology assertion failure; the successful same-boot supplement is recorded separately. OMT reported zero new regressions (33 pass / 13 existing fail / 27 skip). See the detailed hardware comment below for scope and limitations.
  • Full production composition with drm/asahi: prepare native defaults for supported M3 Air and Pro #230 + drm/asahi: m3: one GPU firmware start per boot, and quarantine only published packets #236 compiled and passed artifact/config/linkage verification with clang/LLD 18.1.8 and Rust 1.85.0. This is build evidence, not a physical boot of that final package cohort. No Max or T8122 hardware qualification is claimed.

This is a separate G15 fix; #236 addresses the T8122 Air runtime. Both should precede enabling the corresponding runtime in #230. #229/#233 are not included.

A started G15 firmware session cannot be stopped, so boot::start pins
the module before its first firmware-visible write and never drops the
reference. The pin used __module_get(), which increments the count
unconditionally, even after an ordinary rmmod has passed its reference
check in try_stop_module().

The first start can run while the module is LIVE. Activation attaches
synchronously during module init, but a probe deferred for the PMP
supplier, or attached asynchronously with async_probe, runs later from
the deferred-probe or async worker. If an rmmod has passed its
reference check by then, module exit unregisters the driver, which
waits for the in-flight probe to finish starting the firmware, unbinds
the device and frees the module under the live session and its RTKit
callbacks.

Take the reference with try_module_get() before RETAINED is set and
before anything firmware-visible. Once removal has begun this fails
with ENODEV and leaves the firmware untouched, cleaned up as for a
failed self-check: the cached-class objects are forgotten, the PMP vote
is released and the session stays unretained. A caller that loses the
race to set RETAINED drops only its own reference.

The policy is in g15/retain.rs so the asahi-g15 host runner covers it:
a refused pin marks nothing, the first pin is kept and later calls take
nothing, a lost race drops only its own reference, and
barrier-synchronized concurrent pins leave exactly one reference.

Generated-by: LLM
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Chris Kearney <303316+chriskearney@users.noreply.github.com>
@chriskearney
chriskearney marked this pull request as ready for review October 26, 2026 03:25
@chriskearney

Copy link
Copy Markdown
Collaborator Author

M3 Pro/J516S check at 095edba is complete. The native kernel booted in 33 seconds, bound the T6030 G15 driver, exposed no bind/unbind attributes, and had a positive Asahi module reference count. With system Mesa 147a62f619, pinned GL and Vulkan readback probes each ran for 3 seconds with zero wrong pixels. All three displays were active on that same boot. No matched AOP/GPU/DART fault was found in the retained candidate logs.

The automated job retains TESTS_FAILED because its first display-topology assertion sampled before the third display appeared. The separately recorded same-boot follow-up saw all three displays and passed both pixel probes; it does not rewrite the original test. OMT comparison reported 0 new regressions, with 33 pass / 13 fail / 27 skipped against the recorded current-kernel baseline. Those existing failures are not a full release pass.

The ordinary return to the working kernel completed, the archived boot entry was restored, and protected boot/reference files and the ESP reserve were verified. No unload/rebind experiment, Max hardware test, CTS, suspend, or RC-Mesa-7c86130 hardware qualification is claimed.

The full production composition of #230 + #236 + this fix also built with clang/LLD 18.1.8 and Rust 1.85.0. Its actual module contains both corrected backend pin paths, and all 458 undefined symbols resolve against the shipped Module.symvers. This composition is separate build evidence; the physical boot above tested 095edba with the frozen M3 configuration.

@scottjones scottjones left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We went through this against the current integration/aurora tip (3e8ae2a, which includes #236). It merges cleanly and doesn't touch anything under m3/, so it doesn't interact with #236's claim_start / pin_module.

The ordering looks right to us. try_module_get runs after the self-check and before vote.keep(), Rtkit::new and the ASC RUN write. A refused pin returns ENODEV with RETAINED still clear, so the PMP vote and the power hold are released the same way as on a failed self-check. A caller that loses the race puts only its own reference, and the winner's reference is never put. Built-in (THIS_MODULE is NULL) and CONFIG_MODULE_UNLOAD=n both use the stock try_module_get / module_put stubs, so those are fine too.

What we ran:

  • W=1 builds of drivers/gpu/drm/asahi/ at the head and at its merge-base with G15 only, G15+M3_T8122 and M3_T8122 only. No errors in any of them, and the warnings are the same as the base in each config.
  • The G15 host suites (run.py plus the eight g15-*-tests.py with --mutations) and m3-batching-tests.rs. All pass.
  • Mutations of retain.rs: marking before get, no put on a lost race, putting the winner, dropping the fast path, and ignoring a refused get. The runner catches every one of them.

One gap, which we don't think needs to block this: nothing tests the call site in boot::start. We moved retain_module() back to after shared.started is set and ignored its error, and run.py, g15-retention, g15-probe-init and g15-maintained-handoff all still passed. The helper is well covered, but the ordering this PR is really about is only checked by reading the code. #236 is in the same position. A follow-up that pulls the order of start into something the host runner can check would be nice, but it isn't needed for this PR.

Looks good to us once the M3 Pro boot check is in.

@chriskearney
chriskearney added this pull request to the merge queue Oct 26, 2026
Merged via the queue into integration/aurora with commit b9221c3 Oct 26, 2026
23 of 26 checks passed
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