Three fixes to the safe Rust KMS layer, shaken out while bringing up the
out-of-tree evdi virtual-display driver (they are equally correct for any
KmsDriver, including vino):

  * kms: store MODE_CONFIG_OPS as a 'static reference. probe_kms() installed
    mode_config.funcs / .helper_private from a by-value ModeConfigOps local,
    leaving those pointers dangling once probe_kms() returned. The DRM core
    later read a garbage mode_valid slot out of the reused stack and jumped to
    it (RIP=0x9). Promoting the vtable to a const-promoted &'static keeps it
    alive for the device's lifetime.

  * framebuffer: bracket the vmap in drm_gem_fb_begin/end_cpu_access. Reads
    through the CPU mapping could otherwise return stale cached pixels (an
    earlier compositor frame) while new frames were rendered elsewhere -- the
    scanout appeared frozen. end_cpu_access is balanced in the vmap guard's
    Drop.

  * driver: call drm_atomic_helper_shutdown() from Registration::drop() before
    drm_dev_unplug(), replacing the commented-out TODO. Without it the last
    committed atomic state kept framebuffers -- and the connectors they
    reference -- pinned, surfacing as drm_mode_config_cleanup() "leaked"
    warnings at teardown.

Signed-off-by: Mike Lothian <[email protected]>
Assisted-by: Claude:claude-opus-4-8 [Claude-Code]
---
 rust/kernel/drm/driver.rs          | 24 ++++++++++++------------
 rust/kernel/drm/kms.rs             | 12 +++++++++---
 rust/kernel/drm/kms/framebuffer.rs | 24 ++++++++++++++++++++++++
 3 files changed, 45 insertions(+), 15 deletions(-)

diff --git a/rust/kernel/drm/driver.rs b/rust/kernel/drm/driver.rs
index b7e80df6289e..1d9309bc41c2 100644
--- a/rust/kernel/drm/driver.rs
+++ b/rust/kernel/drm/driver.rs
@@ -287,6 +287,18 @@ unsafe impl<T: Driver> Send for Registration<T> where
 
 impl<T: Driver> Drop for Registration<T> {
     fn drop(&mut self) {
+        // Disable the display pipeline before unregistering. KMS drivers must 
shut the active
+        // configuration down at teardown; otherwise the committed atomic 
state keeps the
+        // framebuffers -- and the mode objects it references, such as the 
still-attached
+        // connectors -- alive, which surfaces later as 
`drm_mode_config_cleanup()` "leaked"
+        // warnings once the last userspace fd closes. This must run while the 
device is still
+        // registered (i.e. before `drm_dev_unplug()`).
+        if drm::Device::<T>::has_kms() {
+            // SAFETY: KMS is set up for this device (checked above) and it is 
still registered, so
+            // committing the "everything off" state is valid.
+            unsafe { bindings::drm_atomic_helper_shutdown(self.drm.as_raw()) };
+        }
+
         // Use `drm_dev_unplug` rather than `drm_dev_unregister` to ensure 
that existing
         // `drm_dev_enter()` critical sections complete before unregistration 
proceeds. This
         // is required for the safety of `UnbindGuard`, which relies on the 
SRCU barrier in
@@ -299,17 +311,5 @@ fn drop(&mut self) {
         // After drm_dev_unplug(), the SRCU barrier guarantees that all 
UnbindGuard critical
         // sections have completed, so no one holds a reference to reg_data 
anymore.
         // reg_data is dropped here automatically.
-
-        /* TODO: We need to figure out what to do about this, and whether or 
not we should just call
-         * this before dev unplug
-         */
-        /*
-        if drm::device::Device::<T>::has_kms() {
-            // SAFETY: We just checked above that KMS was setup for this 
device, so this is safe to
-            // call
-            unsafe { bindings::drm_atomic_helper_shutdown(self.0.as_raw()) }
-        }
-        */
-        //build_error!("You forgot to update the cleanup methods")
     }
 }
diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs
index 874a2ebc7887..f1472e913952 100644
--- a/rust/kernel/drm/kms.rs
+++ b/rust/kernel/drm/kms.rs
@@ -59,7 +59,13 @@ pub trait KmsImpl {
         type Driver: Driver;
 
         /// The optional KMS callback operations for this driver.
-        const MODE_CONFIG_OPS: Option<ModeConfigOps>;
+        ///
+        /// This is a `'static` reference so that the `drm_mode_config_funcs` 
and
+        /// `drm_mode_config_helper_funcs` pointers installed into 
`drm_device.mode_config` in
+        /// [`probe_kms`] outlive `probe_kms`'s stack frame. Storing 
`&ops.kms_vtable` from a
+        /// by-value local left `mode_config.funcs` dangling once `probe_kms` 
returned; the DRM core
+        /// then read a garbage `mode_valid` slot out of the reused stack and 
called it (RIP=0x9).
+        const MODE_CONFIG_OPS: Option<&'static ModeConfigOps>;
 
         /// The callback for using a Driver's callbacks to probe the KMS 
capabilities and object
         /// topology.
@@ -189,7 +195,7 @@ fn atomic_commit_tail<'a>(
 impl<T: KmsDriver> private::KmsImpl for T {
     type Driver = Self;
 
-    const MODE_CONFIG_OPS: Option<ModeConfigOps> = Some(ModeConfigOps {
+    const MODE_CONFIG_OPS: Option<&'static ModeConfigOps> = 
Some(&ModeConfigOps {
         kms_vtable: bindings::drm_mode_config_funcs {
             atomic_check: Some(bindings::drm_atomic_helper_check),
             fb_create: Some(bindings::drm_gem_fb_create),
@@ -269,7 +275,7 @@ impl<T: KmsDriver> KmsImpl for T {}
 impl<T: Driver> private::KmsImpl for PhantomData<T> {
     type Driver = T;
 
-    const MODE_CONFIG_OPS: Option<ModeConfigOps> = None;
+    const MODE_CONFIG_OPS: Option<&'static ModeConfigOps> = None;
 }
 
 impl<T: Driver> KmsImpl for PhantomData<T> {}
diff --git a/rust/kernel/drm/kms/framebuffer.rs 
b/rust/kernel/drm/kms/framebuffer.rs
index e12fa7a24e15..00696b733c7a 100644
--- a/rust/kernel/drm/kms/framebuffer.rs
+++ b/rust/kernel/drm/kms/framebuffer.rs
@@ -171,6 +171,21 @@ pub fn vmap(&self) -> Result<FramebufferVmap<'_, T>> {
             unsafe { bindings::drm_gem_fb_vunmap(self.as_raw(), 
map.as_mut_ptr()) };
             return Err(EINVAL);
         }
+        // Synchronise the CPU's view with any GPU (or dma-buf importer) 
writes before the caller
+        // reads the pixels. Without this, reads through the vmap can return 
stale *cached* data --
+        // e.g. a compositor's earlier frame -- so the scanout appears frozen 
while the GPU keeps
+        // rendering new frames elsewhere. `end_cpu_access` in `Drop` balances 
it.
+        // SAFETY: `self.as_raw()` is the valid GEM-backed framebuffer just 
vmapped above.
+        if let Err(e) = to_result(unsafe {
+            bindings::drm_gem_fb_begin_cpu_access(
+                self.as_raw(),
+                bindings::dma_data_direction_DMA_FROM_DEVICE,
+            )
+        }) {
+            // SAFETY: balances the vmap just done, with the same `map`.
+            unsafe { bindings::drm_gem_fb_vunmap(self.as_raw(), 
map.as_mut_ptr()) };
+            return Err(e);
+        }
         Ok(FramebufferVmap { fb: self, map, _p: PhantomData })
     }
 }
@@ -209,6 +224,15 @@ pub fn as_bytes(&self) -> &[u8] {
 
 impl<'a, T: KmsDriver> Drop for FramebufferVmap<'a, T> {
     fn drop(&mut self) {
+        // Balance the `begin_cpu_access` from `vmap` before unmapping.
+        // SAFETY: `self.fb.as_raw()` is the framebuffer passed to 
`drm_gem_fb_begin_cpu_access`
+        // in `vmap`, with the same direction.
+        unsafe {
+            bindings::drm_gem_fb_end_cpu_access(
+                self.fb.as_raw(),
+                bindings::dma_data_direction_DMA_FROM_DEVICE,
+            )
+        };
         // SAFETY: `self.fb.as_raw()`/`self.map` are exactly the pair passed to
         // `drm_gem_fb_vmap` in `Framebuffer::vmap`, and the mapping has not 
been released
         // since.
-- 
2.55.0

Reply via email to