A driver that forwards a hardware cursor plane's contents to userspace (e.g.
DisplayLink's evdi, whose daemon composites the cursor itself) needs to hand the
client a GEM handle for the cursor bitmap on each update. 
drm_gem_handle_create()
may sleep, so the connected file must be reachable from sleepable context -- but
drm::event::EventChannel stored the receiver under the device event_lock (a
spinlock).

Rework EventChannel to guard the receiver with a Mutex instead: send() holds it
across the event_lock-protected reserve+queue (so delivery still cannot race a
close), and a new with_receiver() runs a closure with the connected File while
holding the mutex, allowing a sleepable GEM-handle creation without the file
closing underneath it (notify_close() takes the same mutex). new() now takes a
lock class so the class lives in the driver module. connect/disconnect/
notify_close/is_connected no longer need the device.

Add the accessors such a cursor consumer needs:
  - plane state crtc_x()/crtc_y() (cursor position) and hotspot_x()/hotspot_y();
  - Framebuffer::format() (FourCC) and Framebuffer::create_handle() (a GEM 
handle
    for plane-0 in a given file).

Signed-off-by: Mike Lothian <[email protected]>
Assisted-by: Claude:claude-opus-4-8 [Claude-Code]
---
 rust/kernel/drm/event.rs           | 179 +++++++++++++++--------------
 rust/kernel/drm/kms/framebuffer.rs |  28 +++++
 rust/kernel/drm/kms/plane.rs       |  20 ++++
 3 files changed, 138 insertions(+), 89 deletions(-)

diff --git a/rust/kernel/drm/event.rs b/rust/kernel/drm/event.rs
index 39d65d2a8fb2..dadb38c7df20 100644
--- a/rust/kernel/drm/event.rs
+++ b/rust/kernel/drm/event.rs
@@ -13,8 +13,8 @@
 //! target `struct drm_file` must be valid for that whole critical section. 
Reading the target file
 //! pointer outside the lock is a classic source of use-after-free/NULL-deref 
crashes (a driver's
 //! flush worker racing a client disconnect). [`EventChannel`] encodes that 
rule in its API: the
-//! receiver file is only ever touched while `event_lock` is held, so 
registration, teardown and
-//! delivery cannot race.
+//! receiver file lives behind a mutex and [`send`](EventChannel::send) holds 
it across the whole
+//! `event_lock`-protected reserve+queue, so registration, teardown and 
delivery cannot race.
 //!
 //! C header: [`include/drm/drm_file.h`](srctree/include/drm/drm_file.h)
 
@@ -29,8 +29,9 @@
     error::to_result,
     interrupt,
     prelude::*,
+    sync::{LockClassKey, Mutex},
 };
-use core::{cell::UnsafeCell, mem};
+use core::mem;
 
 /// A driver-defined DRM event payload.
 ///
@@ -63,94 +64,101 @@ struct EventStorage<T: EventPayload> {
 
 /// A per-device channel that delivers driver events to the [`File`] 
registered as the receiver.
 ///
-/// The receiver pointer is stored under the owning device's `event_lock` — 
the same lock the DRM
-/// core requires held while queuing events — so [`connect`](Self::connect),
-/// [`disconnect`](Self::disconnect), [`notify_close`](Self::notify_close) and 
[`send`](Self::send)
-/// are all serialized against each other and against the core's own event 
handling. This makes the
-/// "send to a file that just disconnected" race unrepresentable.
+/// The receiver `drm_file` is stored behind a [`Mutex`], not the device 
`event_lock`, so it can be
+/// touched from sleepable context — in particular so a driver can create a 
GEM handle for the
+/// connected client (`drm_gem_handle_create` may sleep) via 
[`with_receiver`](Self::with_receiver).
+/// [`send`](Self::send) still acquires the device's `event_lock` (as the DRM 
core requires) while
+/// holding the mutex, so the receiver pointer is valid for the whole 
reserve+queue and cannot race
+/// [`disconnect`](Self::disconnect)/[`notify_close`](Self::notify_close). 
This makes the classic
+/// "deliver to a file that just closed" use-after-free unrepresentable.
 ///
-/// A channel is bound to exactly one [`Device`]: always pass the same device 
to every method.
+/// A channel is bound to exactly one [`Device`]: always pass the same device 
to [`send`](Self::send).
 /// Drivers must call [`notify_close`](Self::notify_close) from their 
`postclose` hook (and/or
 /// [`disconnect`](Self::disconnect) when tearing the logical connection down) 
so the receiver
 /// pointer never outlives the file it refers to.
+#[pin_data]
 pub struct EventChannel {
-    /// The receiver `drm_file`, or null when no client is connected.
+    /// The receiver `drm_file` address, or `0` when no client is connected.
     ///
-    /// Only ever accessed while the owning device's `event_lock` is held.
-    receiver: UnsafeCell<*mut bindings::drm_file>,
+    /// A `usize` (not a raw pointer) so the `Mutex` is `Send`/`Sync` on its 
own; the value is only
+    /// ever turned back into a `*mut drm_file` while the mutex is held.
+    #[pin]
+    receiver: Mutex<usize>,
 }
 
-// SAFETY: `receiver` is only accessed while holding the owning device's 
`event_lock`, which
-// serializes all access across threads.
-unsafe impl Send for EventChannel {}
-// SAFETY: See `Send`.
-unsafe impl Sync for EventChannel {}
-
 impl EventChannel {
     /// Create a new, disconnected channel.
-    pub const fn new() -> Self {
-        Self {
-            receiver: UnsafeCell::new(core::ptr::null_mut()),
-        }
+    ///
+    /// `key` is the lock class for the receiver mutex; pass
+    /// [`static_lock_class!()`](crate::sync::static_lock_class) from the 
driver so the class lives
+    /// in the driver module.
+    #[inline]
+    pub fn new(key: Pin<&'static LockClassKey>) -> impl PinInit<Self> {
+        pin_init!(Self {
+            receiver <- Mutex::new(0, c"EventChannel::receiver", key),
+        })
     }
 
     /// Register `file` as the receiver for events sent through this channel.
     ///
-    /// Any previously registered file is replaced. Runs under `dev`'s 
`event_lock`.
-    pub fn connect<D: drm::Driver, C: DeviceContext, F: DriverFile>(
-        &self,
-        dev: &Device<D, C>,
-        file: &File<F>,
-    ) {
-        let irq = interrupt::local_interrupt_disable();
-        let _guard = dev.event_lock().lock_with(&irq);
-        // SAFETY: `receiver` is only accessed under `event_lock`, which is 
held here.
-        unsafe { *self.receiver.get() = file.as_raw() };
+    /// Any previously registered file is replaced.
+    pub fn connect<F: DriverFile>(&self, file: &File<F>) {
+        *self.receiver.lock() = file.as_raw() as usize;
     }
 
     /// Clear the receiver, dropping any future events on the floor until the 
next
-    /// [`connect`](Self::connect). Runs under `dev`'s `event_lock`.
-    pub fn disconnect<D: drm::Driver, C: DeviceContext>(&self, dev: &Device<D, 
C>) {
-        let irq = interrupt::local_interrupt_disable();
-        let _guard = dev.event_lock().lock_with(&irq);
-        // SAFETY: `receiver` is only accessed under `event_lock`, which is 
held here.
-        unsafe { *self.receiver.get() = core::ptr::null_mut() };
+    /// [`connect`](Self::connect).
+    #[inline]
+    pub fn disconnect(&self) {
+        *self.receiver.lock() = 0;
     }
 
     /// Clear the receiver if it is `file`.
     ///
     /// Intended to be called from the driver's `postclose` hook so the stored 
pointer never
-    /// outlives the file. Runs under `dev`'s `event_lock`.
-    pub fn notify_close<D: drm::Driver, C: DeviceContext, F: DriverFile>(
-        &self,
-        dev: &Device<D, C>,
-        file: &File<F>,
-    ) {
-        let irq = interrupt::local_interrupt_disable();
-        let _guard = dev.event_lock().lock_with(&irq);
-        // SAFETY: `receiver` is only accessed under `event_lock`, which is 
held here.
-        unsafe {
-            if *self.receiver.get() == file.as_raw() {
-                *self.receiver.get() = core::ptr::null_mut();
-            }
+    /// outlives the file.
+    pub fn notify_close<F: DriverFile>(&self, file: &File<F>) {
+        let mut recv = self.receiver.lock();
+        if *recv == file.as_raw() as usize {
+            *recv = 0;
         }
     }
 
-    /// Whether a receiver is currently connected. Runs under `dev`'s 
`event_lock`.
-    pub fn is_connected<D: drm::Driver, C: DeviceContext>(&self, dev: 
&Device<D, C>) -> bool {
-        let irq = interrupt::local_interrupt_disable();
-        let _guard = dev.event_lock().lock_with(&irq);
-        // SAFETY: `receiver` is only accessed under `event_lock`, which is 
held here.
-        !unsafe { *self.receiver.get() }.is_null()
+    /// Whether a receiver is currently connected.
+    #[inline]
+    pub fn is_connected(&self) -> bool {
+        *self.receiver.lock() != 0
+    }
+
+    /// Run `f` with the connected receiver [`File`], if any, returning its 
result.
+    ///
+    /// The receiver mutex is held for the duration, so `f` may sleep (e.g. to 
create a GEM handle
+    /// in the client's file with 
[`Framebuffer::create_handle`](crate::drm::kms::framebuffer::Framebuffer::create_handle))
+    /// without the file being closed underneath it: 
[`notify_close`](Self::notify_close) takes the
+    /// same mutex and so blocks until `f` returns. Returns [`None`] if no 
client is connected.
+    ///
+    /// # Type parameter
+    ///
+    /// `F` must be the same [`DriverFile`] type the receiver was 
[`connect`](Self::connect)ed with.
+    pub fn with_receiver<F: DriverFile, R>(&self, f: impl FnOnce(&File<F>) -> 
R) -> Option<R> {
+        let recv = self.receiver.lock();
+        let raw = *recv as *mut bindings::drm_file;
+        if raw.is_null() {
+            return None;
+        }
+        // SAFETY: `raw` was stored from a live `File<F>` by `connect`; it 
stays valid because
+        // `notify_close`/`disconnect` (which clear it) take this same mutex, 
held here.
+        let file = unsafe { File::<F>::from_raw(raw) };
+        Some(f(file))
     }
 
     /// Deliver `payload` to the connected receiver.
     ///
     /// Allocates the backing event, fills its `drm_event` header, and hands 
it to the DRM core
-    /// under `event_lock`. If no receiver is connected the event is silently 
dropped and `Ok(())`
-    /// is returned (mirroring the C drivers, which cannot deliver to a closed 
client). If the
-    /// client has no space left in its event queue this returns `Err(ENOMEM)` 
and the event is
-    /// dropped; the caller may retry later.
+    /// under `event_lock` (acquired while the receiver mutex is held). If no 
receiver is connected
+    /// the event is silently dropped and `Ok(())` is returned (mirroring the 
C drivers, which
+    /// cannot deliver to a closed client). If the client has no space left in 
its event queue this
+    /// returns `Err(ENOMEM)` and the event is dropped; the caller may retry 
later.
     pub fn send<D: drm::Driver, C: DeviceContext, T: EventPayload>(
         &self,
         dev: &Device<D, C>,
@@ -175,41 +183,34 @@ pub fn send<D: drm::Driver, C: DeviceContext, T: 
EventPayload>(
         storage.pending.event = ev_ptr;
         let pending: *mut bindings::drm_pending_event = &raw mut 
storage.pending;
 
-        let irq = interrupt::local_interrupt_disable();
-        let guard = dev.event_lock().lock_with(&irq);
-
-        // SAFETY: `receiver` is only accessed under `event_lock`, which is 
held here.
-        let receiver = unsafe { *self.receiver.get() };
+        // Hold the receiver mutex across the whole reserve+queue so the file 
can't close underneath
+        // us, then take `event_lock` as the C API requires.
+        let recv = self.receiver.lock();
+        let receiver = *recv as *mut bindings::drm_file;
         if receiver.is_null() {
-            drop(guard);
-            // `storage` is dropped here, freeing the allocation.
+            // No client; `storage` is dropped here, freeing the allocation.
             return Ok(());
         }
 
         let dev_raw = dev.as_raw();
-        // SAFETY: `dev_raw` and `receiver` are valid; `pending`/`ev_ptr` 
point into a live
-        // allocation; `event_lock` is held as required by the C API.
-        let ret = unsafe {
-            bindings::drm_event_reserve_init_locked(dev_raw, receiver, 
pending, ev_ptr)
-        };
-        if ret != 0 {
-            drop(guard);
-            // Reservation failed, so `pending` was never queued: `storage` 
frees the allocation.
-            return to_result(ret);
+        let irq = interrupt::local_interrupt_disable();
+        {
+            let _ev = dev.event_lock().lock_with(&irq);
+            // SAFETY: `dev_raw`/`receiver` are valid; `pending`/`ev_ptr` 
point into a live
+            // allocation; `event_lock` is held as required by the C API.
+            let ret = unsafe {
+                bindings::drm_event_reserve_init_locked(dev_raw, receiver, 
pending, ev_ptr)
+            };
+            if ret != 0 {
+                // Reservation failed, so `pending` was never queued: 
`storage` frees it.
+                return to_result(ret);
+            }
+            // SAFETY: The event is reserved against `receiver`; hand 
ownership of the allocation to
+            // the DRM core, which frees it (via `kfree` of the 
`drm_pending_event` at offset 0)
+            // once delivered or the file closes. `event_lock` is held as 
required.
+            unsafe { bindings::drm_send_event_locked(dev_raw, pending) };
         }
-
-        // SAFETY: The event is reserved against `receiver`; hand ownership of 
the allocation to the
-        // DRM core, which frees it (via `kfree` of the `drm_pending_event` at 
offset 0) once the
-        // event is delivered or the file is closed. `event_lock` is held as 
required.
-        unsafe { bindings::drm_send_event_locked(dev_raw, pending) };
         let _ = KBox::into_raw(storage);
-        drop(guard);
         Ok(())
     }
 }
-
-impl Default for EventChannel {
-    fn default() -> Self {
-        Self::new()
-    }
-}
diff --git a/rust/kernel/drm/kms/framebuffer.rs 
b/rust/kernel/drm/kms/framebuffer.rs
index c7089b12d0d1..e12fa7a24e15 100644
--- a/rust/kernel/drm/kms/framebuffer.rs
+++ b/rust/kernel/drm/kms/framebuffer.rs
@@ -121,6 +121,34 @@ pub fn pitch(&self, index: usize) -> u32 {
         unsafe { (*self.as_raw()).pitches[index & 3] }
     }
 
+    /// The framebuffer's pixel format as a FourCC code (`DRM_FORMAT_*`).
+    #[inline]
+    pub fn format(&self) -> u32 {
+        // SAFETY: a valid framebuffer always has a non-null, immutable 
`format` descriptor.
+        unsafe { (*(*self.as_raw()).format).format }
+    }
+
+    /// Create a GEM handle for this framebuffer's plane-0 backing object in 
`file`'s handle space,
+    /// returning it.
+    ///
+    /// This lets a driver hand a userspace client (e.g. via an event) a 
handle to a buffer the
+    /// client did not create itself -- for instance a cursor bitmap set by a 
compositor that a
+    /// separate control daemon needs to read. May sleep.
+    pub fn create_handle<F: crate::drm::file::DriverFile>(
+        &self,
+        file: &crate::drm::File<F>,
+    ) -> Result<u32> {
+        // SAFETY: `as_raw()` is a valid framebuffer; `obj[0]` is its plane-0 
GEM object.
+        let obj = unsafe { (*self.as_raw()).obj[0] };
+        if obj.is_null() {
+            return Err(EINVAL);
+        }
+        let mut handle = 0u32;
+        // SAFETY: `file` and `obj` are valid; `drm_gem_handle_create` 
initializes `handle`.
+        to_result(unsafe { 
bindings::drm_gem_handle_create(file.as_raw().cast(), obj, &mut handle) })?;
+        Ok(handle)
+    }
+
     /// Map this framebuffer's plane-0 backing pages into the kernel address 
space for CPU
     /// access, for the duration of the returned guard.
     ///
diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs
index 3863dfe1b84c..fcaa8ba550fe 100644
--- a/rust/kernel/drm/kms/plane.rs
+++ b/rust/kernel/drm/kms/plane.rs
@@ -605,6 +605,26 @@ fn crtc_h(&self) -> u32 {
         self.as_raw().crtc_h
     }
 
+    /// The X position of this plane's destination rectangle within the CRTC 
(`crtc_x`).
+    fn crtc_x(&self) -> i32 {
+        self.as_raw().crtc_x
+    }
+
+    /// The Y position of this plane's destination rectangle within the CRTC 
(`crtc_y`).
+    fn crtc_y(&self) -> i32 {
+        self.as_raw().crtc_y
+    }
+
+    /// The cursor hotspot X offset (`hotspot_x`), for a virtualized cursor 
plane.
+    fn hotspot_x(&self) -> i32 {
+        self.as_raw().hotspot_x
+    }
+
+    /// The cursor hotspot Y offset (`hotspot_y`), for a virtualized cursor 
plane.
+    fn hotspot_y(&self) -> i32 {
+        self.as_raw().hotspot_y
+    }
+
     /// Return the current [`OpaqueCrtc`] assigned to this plane, if there is 
one.
     fn crtc<'a, 'b: 'a, D>(&'a self) -> Option<&'b OpaqueCrtc<D>>
     where
-- 
2.55.0

Reply via email to