Skip to contentSkip to conversation

rust: drm: Higher-Ranked Lifetime private data

PATCH Applied Danilo Krummrich 5 participants 53 messages Jun 28 – Jul 12

Subsystems (9)
lore
Share & tools
lore

Danilo Krummrich

author maintainer
#0 lore raw

v5 cover rust: drm: Higher-Ranked Lifetime private data

DRM ioctls run in process context without any guarantee that the parent bus device is still bound. This series solves the problem by introducing RegistrationGuard -- a guard representing a drm_dev_enter/exit SRCU critical section that proves the parent bus device is bound for the lifetime of the guard.

As initial plumbing for this, the DRM DeviceContext typestates are
reworked: Uninit is renamed to Normal, defaults are adjusted, AlwaysRefCounted is restricted to Normal, and a Deref chain from Device<T, Registered> to Device<T, Normal> is established. This gives Device<T, Registered> the semantic that the device is currently registered and the parent bus device is bound, which makes the RegistrationGuard and ioctl dispatch much cleaner. An Ioctl context restricts registration_guard() to ioctl dispatch, where the DRM core guarantees prior registration.

On top of that, add RegistrationData as a GAT (Generic Associated Type) on drm::Driver, allowing drivers to store data whose lifetime is tied to the parent bus device binding scope. The data is allocated in Registration::new(), lifetime-erased to 'static for storage, and made accessible through Device<T, Registered>::registration_data_with(). The closure's HRTB ties the lifetime to the closure scope; internally the 'static pointer is cast back to the closure-scoped lifetime. The reference is valid for the duration of the drm_dev_enter/exit critical section held by RegistrationGuard.

Also update the ioctl dispatch macro to wrap every handler in a RegistrationGuard, returning ENODEV if the device has been unplugged, and pass the registration data to handlers.

A branch with all patches can be found in [1].

[1] https://git.kernel.org/pub/scm/linux/kernel/git/dakr/linux.git/log/?h=drm-lifetime

Changes in v5:
- Replace __call_ioctl() helper with a simple for<'a> fn(...) coercion
- Rebase onto latest drm-rust-next, which requires a faux::Device type with AsBusDevice impl and motivates the removal of the DeviceContext generic from shmem::Object entirely
- Replace ForLt with a GAT type RegistrationData<'a>
- Fix drm_dev_register() error path race on the C side
- Add a few missing #[inline] annotations

Changes in v4:
- Fix pre-existing unbounded lifetimes in ioctl handler arguments.
- Fix registration_guard() being callable on unregistered devices by introducing an Ioctl device context typestate; registration_guard() is now only available on Device<T, Ioctl>, which is exclusively constructed in ioctl dispatch context where the DRM core guarantees prior registration.
- Fix type inference allowing handlers to obtain Device<Registered> before RegistrationGuard is acquired.
- Make RegistrationGuard !Send via NotThreadSafe to prevent potential lockdep splats from cross-thread SRCU unlock.
- Store &Device<T, Registered> directly in RegistrationGuard instead of calling assume_ctx() in Deref.

Changes in v3:
- Rename UnbindGuard to RegistrationGuard
- RegistrationGuard no longer dereferences to &Device<Bound>; it dereferences to &drm::Device<T, Registered> instead
- Drop Registration::new() and rename Registration::new_with_lt() to Registration::new()
- Rework DeviceContext typestates: rename Uninit to Normal, restrict AlwaysRefCounted to Normal, establish Deref chain from Registered to Normal
- Add AsRef<ParentDevice<Bound>> on Device<T, Registered> for parent device access
- Move registration_data_with() from RegistrationGuard to drm::Device<T, Registered>
- Ioctl handlers no longer receive &Device<Bound>, only registration data and drm::Device<T, Registered>
- Use Device<Registered>::as_ref() to access parent device in nova-drm

Changes in v2:
- Replace unsafe direct registration data access in ioctl dispatch with safe UnbindGuard::registration_data_with() closure
- Eliminate unbind_guard() free function; use type-inference anchor to enable direct dev.unbind_guard() method call in the ioctl macro
- UnbindGuard::registration_data_with() provides both parent device and registration data to the closure
- Add nova-drm conversion patch demonstrating lifetime-aware registration data with &'bound auxiliary::Device<Bound>
- Various safety comment and documentation improvements

Danilo Krummrich (19):
rust: drm: ioctl: fix unbounded lifetimes in ioctl handler arguments
rust: drm: rename Uninit DeviceContext to Normal
rust: faux: add Device type with AsBusDevice support
rust: drm: Add Driver::ParentDevice associated type
rust: drm: change default DeviceContext to Normal
rust: drm: restrict AlwaysRefCounted to Normal Device context
rust: drm: restrict AlwaysRefCounted to Normal GEM Object context
rust: drm/gem: remove DeviceContext from shmem::Object
rust: drm: split Deref for Device context typestates
rust: drm: pin ioctl Device reference to Normal context
rust: drm: add Ioctl device context typestate
rust: drm: Add RegistrationGuard for drm_dev_enter/exit critical
sections
rust: drm: Wrap ioctl dispatch in RegistrationGuard
rust: drm: return ParentDevice from Device AsRef
rust: drm: add AsRef<ParentDevice<Bound>> for Device<Registered>
drm: fix race between partial drm_dev_register() failure and ioctl
rust: drm: Add RegistrationData to drm::Driver
rust: drm: Pass registration data to ioctl handlers
drm: nova: Use drm::Device<Registered> to access the parent bus device

 drivers/gpu/drm/drm_drv.c        |  34 +++-
 drivers/gpu/drm/nova/driver.rs   |  38 ++--
 drivers/gpu/drm/nova/file.rs     |  22 ++-
 drivers/gpu/drm/nova/gem.rs      |  18 +-
 drivers/gpu/drm/tyr/driver.rs    |  25 ++-
 drivers/gpu/drm/tyr/file.rs      |   8 +-
 drivers/gpu/drm/tyr/gem.rs       |  11 +-
 rust/kernel/drm/device.rs        | 293 +++++++++++++++++++++++--------
 rust/kernel/drm/driver.rs        | 111 +++++++-----
 rust/kernel/drm/gem/mod.rs       |  98 +++++------
 rust/kernel/drm/gem/shmem.rs     | 257 +++++++++++++--------------
 rust/kernel/drm/ioctl.rs         |  56 +++++-
 rust/kernel/drm/mod.rs           |   4 +-
 rust/kernel/faux.rs              |  69 ++++++--
 samples/rust/rust_driver_faux.rs |   3 +-
 15 files changed, 660 insertions(+), 387 deletions(-)

base-commit: fa8cc4e3067f958ea2057f37a8a6f9c6b10a9c03

Signature · 1 line
2.54.0
author maintainer
#1 lore raw

PATCH v5 1/19 rust: drm: ioctl: fix unbounded lifetimes in ioctl handler arguments

References to dev, data, and file in the declare_drm_ioctls! macro are created via unsafe pointer dereferences, producing unbounded lifetimes. If an ioctl handler explicitly annotates its parameters with 'static, the compiler accepts this, allowing the handler to stash references that outlive the ioctl call.

Fix this by adding a higher-ranked function pointer coercion that enforces the handler accepts universally quantified lifetimes:

  let _: for<'a> fn(&'a _, &'a mut _, &'a _) -> _ = $func;

Since the handler must be coercible to a function pointer accepting any lifetime 'a, it can no longer demand 'static on any parameter.

Fixes 9a69570682b1 ("rust: drm: ioctl: Add DRM ioctl abstraction")
Reported-by [email protected]
---
 rust/kernel/drm/ioctl.rs | 6 ++++++
 1 file changed, 6 insertions(+)
rust/kernel/drm/ioctl.rs +6 -0
index cf328101dde4..ccf4150d83b6 100644
@@ -135,6 +135,12 @@ macro_rules! declare_drm_ioctls {
                             // dev/file match the current driver these ioctls are being declared
                             // for, and it's not clear how to enforce this within the type system.
                             let dev = $crate::drm::device::Device::from_raw(raw_dev);
+
+                            // Enforce that the handler accepts higher-ranked
+                            // lifetimes, preventing it from requiring 'static
+                            // references that could escape this scope.
+                            let _: for<'a> fn(&'a _, &'a mut _, &'a _) -> _ = $func;
+
                             // SAFETY: The ioctl argument has size `_IOC_SIZE(cmd)`, which we
                             // asserted above matches the size of this type, and all bit patterns of
                             // UAPI structs must be valid.
Signature · 1 line
2.54.0
#2 lore raw

Re: [PATCH v5 01/19] rust: drm: ioctl: fix unbounded lifetimes in ioctl handler arguments

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] The declare_drm_ioctls! macro is unsound because it allows type inference to dictate the generic Driver type for Device and File, enabling potential cross-type downcasts (type confusion). --

commit 541a4b9cfa288156f9ba5c6b0436d72c3dc130fe
Author: Danilo Krummrich <[email protected]>

rust: drm: ioctl: fix unbounded lifetimes in ioctl handler arguments

This commit fixes unbounded lifetimes created by unsafe pointer dereferences in the declare_drm_ioctls! macro. It introduces a higher-ranked function pointer coercion to ensure that handlers accept universally quantified lifetimes, preventing them from improperly demanding static references.

rust/kernel/drm/ioctl.rs macro_rules! declare_drm_ioctls { · line 137

Quoted 8 lines
rust/kernel/drm/ioctl.rs +0 -0
index cf328101dde4e..ccf4150d83b69 100644
@@ -135,6 +135,12 @@ macro_rules! declare_drm_ioctls {
                             // dev/file match the current driver these ioctls are being declared
                             // for, and it's not clear how to enforce this within the type system.
                             let dev = $crate::drm::device::Device::from_raw(raw_dev);

[Severity: High]
This is a pre-existing issue, but does this instantiation of Device::from_raw() leave the generic driver type parameter unbounded?

Since type inference can dictate the generic driver type for the device based on the handler's signature, is it possible for a handler expecting &Device<DriverB> to be passed for an IOCTL declared in DriverA?

If that happens, type inference might instantiate Device::from_raw<DriverB>. The DRM core would later pass a DriverA device, but the safe handler might access it assuming DriverB's layout, potentially causing type confusion.

Is there a way to strictly bind the driver type in the macro so we prevent potential cross-type downcasts?

Quoted 9 lines
rust/kernel/drm/ioctl.rs +6 -0
+
+                            // Enforce that the handler accepts higher-ranked
+                            // lifetimes, preventing it from requiring 'static
+                            // references that could escape this scope.
+                            let _: for<'a> fn(&'a _, &'a mut _, &'a _) -> _ = $func;
+
                             // SAFETY: The ioctl argument has size `_IOC_SIZE(cmd)`, which we
                             // asserted above matches the size of this type, and all bit patterns of
                             // UAPI structs must be valid.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Reviewed-by on v5 1/19
#3 lore raw

Re: [PATCH v5 01/19] rust: drm: ioctl: fix unbounded lifetimes in ioctl handler arguments

In reply to Danilo Krummrich

this macro is wild.

Quoted 55 lines · Danilo Krummrich

References to dev, data, and file in the declare_drm_ioctls! macro are
created via unsafe pointer dereferences, producing unbounded lifetimes.
If an ioctl handler explicitly annotates its parameters with 'static, the compiler accepts this, allowing the handler to stash references that
outlive the ioctl call.

Fix this by adding a higher-ranked function pointer coercion that enforces the handler accepts universally quantified lifetimes:

  let _: for<'a> fn(&'a _, &'a mut _, &'a _) -> _ = $func;

Since the handler must be coercible to a function pointer accepting any
lifetime 'a, it can no longer demand 'static on any parameter.

Fixes 9a69570682b1 ("rust: drm: ioctl: Add DRM ioctl abstraction")
Reported-by [email protected]

Closes:
https://lore.kernel.org/all/[email protected]/

---
 rust/kernel/drm/ioctl.rs | 6 ++++++

 1 file changed, 6 insertions(+)
rust/kernel/drm/ioctl.rs +0 -0
index cf328101dde4..ccf4150d83b6 100644
@@ -135,6 +135,12 @@ macro_rules! declare_drm_ioctls {
                              // dev/file match the current driver

these ioctls are being declared
                             // for, and it's not clear how to enforce this within the type system.
                             let dev = $crate::drm::device::Device::from_raw(raw_dev);
+
+                            // Enforce that the handler accepts higher-ranked
+                            // lifetimes, preventing it from requiring 'static
+                            // references that could escape this scope.
+                            let _: for<'a> fn(&'a _, &'a mut _, &'a _) -> _ = $func;
+
                             // SAFETY: The ioctl argument has size _IOC_SIZE(cmd), which we
                             // asserted above matches the size of this type, and all bit patterns of
                             // UAPI structs must be valid.

maintainer Reviewed-by on v5 1/19
#4 lore raw

Re: [PATCH v5 01/19] rust: drm: ioctl: fix unbounded lifetimes in ioctl handler arguments

In reply to Danilo Krummrich
Quoted 14 lines · Danilo Krummrich

References to dev, data, and file in the declare_drm_ioctls! macro are created via unsafe pointer dereferences, producing unbounded lifetimes. If an ioctl handler explicitly annotates its parameters with 'static, the compiler accepts this, allowing the handler to stash references that outlive the ioctl call.

Fix this by adding a higher-ranked function pointer coercion that enforces the handler accepts universally quantified lifetimes:

  let _: for<'a> fn(&'a _, &'a mut _, &'a _) -> _ = $func;

Since the handler must be coercible to a function pointer accepting any lifetime 'a, it can no longer demand 'static on any parameter.

Quoted 6 lines
Fixes 9a69570682b1 ("rust: drm: ioctl: Add DRM ioctl abstraction")
Reported-by [email protected]
author maintainer
#5 lore raw

PATCH v5 2/19 rust: drm: rename Uninit DeviceContext to Normal

In reply to Danilo Krummrich

Rename the Uninit DeviceContext to Normal to better reflect its purpose as the general-purpose, reference-counted device context. The Uninit name was a leftover from when DRM device private data initialization was planned to split across UnregisteredDevice::new() and Registration::new(); with the subsequent introduction of RegistrationData, this distinction is no longer needed.

This also simplifies the DeviceContext documentation, trimming the multi-stage initialization description that no longer applies. Subsequent patches will refine the semantics of the Registered context accordingly.

No functional change.

---
 rust/kernel/drm/device.rs | 92 ++++++++++++---------------------------
 rust/kernel/drm/mod.rs    |  2 +-
 2 files changed, 28 insertions(+), 66 deletions(-)
rust/kernel/drm/device.rs +27 -65
index 477cf771fb10..5e91474e6dbb 100644
@@ -74,36 +74,22 @@ macro_rules! drm_legacy_fields {
 
 /// A trait implemented by all possible contexts a [`Device`] can be used in.
 ///
-/// Setting up a new [`Device`] is a multi-stage process. Each step of the process that a user
-/// interacts with in Rust has a respective [`DeviceContext`] typestate. For example,
-/// `Device<T, Registered>` would be a [`Device`] that reached the [`Registered`] [`DeviceContext`].
+/// A [`Device`] can be in one of two contexts:
 ///
-/// Each stage of this process is described below:
-///
-/// ```text
-///        1                     2                         3
-/// +--------------+   +------------------+   +-----------------------+
-/// |Device created| → |Device initialized| → |Registered w/ userspace|
-/// +--------------+   +------------------+   +-----------------------+
-///    (Uninit)                                      (Registered)
-/// ```
-///
-/// 1. The [`Device`] is in the [`Uninit`] context and is not guaranteed to be initialized or
-///    registered with userspace. Only a limited subset of DRM core functionality is available.
-/// 2. The [`Device`] is guaranteed to be fully initialized, but is not guaranteed to be registered
-///    with userspace. All DRM core functionality which doesn't interact with userspace is
-///    available. We currently don't have a context for representing this.
-/// 3. The [`Device`] is guaranteed to be fully initialized, and is guaranteed to have been
-///    registered with userspace at some point - thus putting it in the [`Registered`] context.
-///
-/// An important caveat of [`DeviceContext`] which must be kept in mind: when used as a typestate
-/// for a reference type, it can only guarantee that a [`Device`] reached a particular stage in the
-/// initialization process _at the time the reference was taken_. No guarantee is made in regards to
-/// what stage of the process the [`Device`] is currently in. This means for instance that a
-/// `&Device<T, Uninit>` may actually be registered with userspace, it just wasn't known to be
-/// registered at the time the reference was taken.
+/// - [`Normal`]: The general-purpose, reference-counted context. A [`Device`] in this context may
+///   or may not be registered with userspace.
+/// - [`Registered`]: The device has been registered with userspace at some point.
 pub trait DeviceContext: Sealed + Send + Sync {}
 
+/// The general-purpose, reference-counted [`DeviceContext`].
+///
+/// A [`Device`] in this context may or may not be registered with userspace. This context is used
+/// for reference-counted device handles and during device setup via [`UnregisteredDevice`].
+pub struct Normal;
+
+impl Sealed for Normal {}
+impl DeviceContext for Normal {}
+
 /// The [`DeviceContext`] of a [`Device`] that was registered with userspace at some point.
 ///
 /// This represents a [`Device`] which is guaranteed to have been registered with userspace at
@@ -121,20 +107,6 @@ pub trait DeviceContext: Sealed + Send + Sync {}
 impl Sealed for Registered {}
 impl DeviceContext for Registered {}
 
-/// The [`DeviceContext`] of a [`Device`] that may be unregistered and partly uninitialized.
-///
-/// A [`Device`] in this context is only guaranteed to be partly initialized, and may or may not
-/// be registered with userspace. Thus operations which depend on the [`Device`] being fully
-/// initialized, or which depend on the [`Device`] being registered with userspace are not
-/// available through this [`DeviceContext`].
-///
-/// A [`Device`] in this context can be used to create a
-/// [`Registration`](drm::driver::Registration).
-pub struct Uninit;
-
-impl Sealed for Uninit {}
-impl DeviceContext for Uninit {}
-
 /// A [`Device`] which is known at compile-time to be unregistered with userspace.
 ///
 /// This type allows performing operations which are only safe to do before userspace registration,
@@ -147,10 +119,10 @@ impl DeviceContext for Uninit {}
 ///
 /// The device in `self.0` is guaranteed to be a newly created [`Device`] that has not yet been
 /// registered with userspace until this type is dropped.
-pub struct UnregisteredDevice<T: drm::Driver>(ARef<Device<T, Uninit>>, NotThreadSafe);
+pub struct UnregisteredDevice<T: drm::Driver>(ARef<Device<T, Normal>>, NotThreadSafe);
 
 impl<T: drm::Driver> Deref for UnregisteredDevice<T> {
-    type Target = Device<T, Uninit>;
+    type Target = Device<T, Normal>;
 
     fn deref(&self) -> &Self::Target {
         &self.0
@@ -178,15 +150,13 @@ const fn compute_features() -> u32 {
         master_drop: None,
         debugfs_init: None,
 
-        // Ignore the Uninit DeviceContext below. It is only provided because it is required by the
-        // compiler, and it is not actually used by these functions.
-        gem_create_object: T::Object::<Uninit>::ALLOC_OPS.gem_create_object,
-        prime_handle_to_fd: T::Object::<Uninit>::ALLOC_OPS.prime_handle_to_fd,
-        prime_fd_to_handle: T::Object::<Uninit>::ALLOC_OPS.prime_fd_to_handle,
-        gem_prime_import: T::Object::<Uninit>::ALLOC_OPS.gem_prime_import,
-        gem_prime_import_sg_table: T::Object::<Uninit>::ALLOC_OPS.gem_prime_import_sg_table,
-        dumb_create: T::Object::<Uninit>::ALLOC_OPS.dumb_create,
-        dumb_map_offset: T::Object::<Uninit>::ALLOC_OPS.dumb_map_offset,
+        gem_create_object: T::Object::<Normal>::ALLOC_OPS.gem_create_object,
+        prime_handle_to_fd: T::Object::<Normal>::ALLOC_OPS.prime_handle_to_fd,
+        prime_fd_to_handle: T::Object::<Normal>::ALLOC_OPS.prime_fd_to_handle,
+        gem_prime_import: T::Object::<Normal>::ALLOC_OPS.gem_prime_import,
+        gem_prime_import_sg_table: T::Object::<Normal>::ALLOC_OPS.gem_prime_import_sg_table,
+        dumb_create: T::Object::<Normal>::ALLOC_OPS.dumb_create,
+        dumb_map_offset: T::Object::<Normal>::ALLOC_OPS.dumb_map_offset,
 
         show_fdinfo: None,
         fbdev_probe: None,
@@ -211,7 +181,7 @@ const fn compute_features() -> u32 {
     pub fn new(dev: &device::Device, data: impl PinInit<T::Data, Error>) -> Result<Self> {
         // `__drm_dev_alloc` uses `kmalloc()` to allocate memory, hence ensure a `kmalloc()`
         // compatible `Layout`.
-        let layout = Kmalloc::aligned_layout(Layout::new::<Device<T, Uninit>>());
+        let layout = Kmalloc::aligned_layout(Layout::new::<Device<T, Normal>>());
 
         // Use a temporary vtable without a `release` callback until `data` is initialized, so
         // init failure can release the DRM device without dropping uninitialized fields.
@@ -223,12 +193,12 @@ pub fn new(dev: &device::Device, data: impl PinInit<T::Data, Error>) -> Result<S
         // SAFETY:
         // - `alloc_vtable` reference remains valid until no longer used,
         // - `dev` is valid by its type invarants,
-        let raw_drm: *mut Device<T, Uninit> = unsafe {
+        let raw_drm: *mut Device<T, Normal> = unsafe {
             bindings::__drm_dev_alloc(
                 dev.as_raw(),
                 &alloc_vtable,
                 layout.size(),
-                mem::offset_of!(Device<T, Uninit>, dev),
+                mem::offset_of!(Device<T, Normal>, dev),
             )
         }
         .cast();
@@ -264,16 +234,8 @@ pub fn new(dev: &device::Device, data: impl PinInit<T::Data, Error>) -> Result<S
 
 /// A typed DRM device with a specific [`drm::Driver`] implementation and [`DeviceContext`].
 ///
-/// Since DRM devices can be used before being fully initialized and registered with userspace, `C`
-/// represents the furthest [`DeviceContext`] we can guarantee that this [`Device`] has reached.
-///
-/// Keep in mind: this means that an unregistered device can still have the registration state
-/// [`Registered`] as long as it was registered with userspace once in the past, and that the
-/// behavior of such a device is still well-defined. Additionally, a device with the registration
-/// state [`Uninit`] simply does not have a guaranteed registration state at compile time, and could
-/// be either registered or unregistered. Since there is no way to guarantee a long-lived reference
-/// to an unregistered device would remain unregistered, we do not provide a [`DeviceContext`] for
-/// this.
+/// A device in the [`Registered`] context is guaranteed to have been registered with userspace
+/// at some point. The [`Normal`] context is the general-purpose, reference-counted context.
 ///
 /// # Invariants
 ///
rust/kernel/drm/mod.rs +1 -1
index a66e7166f66b..e5bfaf130342 100644
@@ -11,8 +11,8 @@
 
 pub use self::device::Device;
 pub use self::device::DeviceContext;
+pub use self::device::Normal;
 pub use self::device::Registered;
-pub use self::device::Uninit;
 pub use self::device::UnregisteredDevice;
 pub use self::driver::Driver;
 pub use self::driver::DriverInfo;
Signature · 1 line
2.54.0
maintainer Reviewed-by on v5 2/19
#6 lore raw

Re: [PATCH v5 02/19] rust: drm: rename Uninit DeviceContext to Normal

Quoted 12 lines · Danilo Krummrich

Rename the Uninit DeviceContext to Normal to better reflect its purpose as the general-purpose, reference-counted device context. The Uninit name was a leftover from when DRM device private data initialization was planned to split across UnregisteredDevice::new() and Registration::new(); with the subsequent introduction of RegistrationData, this distinction is no longer needed.

This also simplifies the DeviceContext documentation, trimming the multi-stage initialization description that no longer applies. Subsequent patches will refine the semantics of the Registered context accordingly.

Quoted 4 lines

No functional change.

author maintainer
#7 lore raw

PATCH v5 3/19 rust: faux: add Device type with AsBusDevice support

In reply to Danilo Krummrich

Add a faux::Device type that wraps struct faux_device and implements AsBusDevice, enabling faux devices to be used as parent devices for subsystems that require a bus device, such as DRM.

Update Registration to return &faux::Device<Bound> via AsRef.

---
 rust/kernel/drm/gem/shmem.rs     | 11 +++--
 rust/kernel/faux.rs              | 69 +++++++++++++++++++++++++++-----
 samples/rust/rust_driver_faux.rs |  3 +-
 3 files changed, 68 insertions(+), 15 deletions(-)
rust/kernel/drm/gem/shmem.rs +7 -4
index 3ee19ef6264e..52de59b14dad 100644
@@ -692,10 +692,12 @@ impl drm::Driver for KunitDriver {
     fn create_drm_dev() -> Result<(faux::Registration, UnregisteredDevice<KunitDriver>)> {
         // Create a faux DRM device so we can test gem object creation.
         let data = try_pin_init!(KunitData {});
-        let dev = faux::Registration::new(c"Kunit", None)?;
-        let drm = UnregisteredDevice::new(dev.as_ref(), data)?;
+        let reg = faux::Registration::new(c"Kunit", None)?;
+        let fdev = reg.as_ref();
+        let dev = fdev.as_ref();
+        let drm = UnregisteredDevice::new(dev, data)?;
 
-        Ok((dev, drm))
+        Ok((reg, drm))
     }
 
     #[test]
@@ -755,7 +757,8 @@ fn vmap_io() -> Result {
     #[test]
     fn fail_sg_table_on_wrong_dev() -> Result {
         let (_dev, drm) = create_drm_dev()?;
-        let wrong_dev = faux::Registration::new(c"EvilKunit", None)?;
+        let reg = faux::Registration::new(c"EvilKunit", None)?;
+        let wrong_dev = reg.as_ref();
 
         let obj = Object::<KunitObject, _>::new(&drm, PAGE_SIZE, ObjectConfig::default(), ())?;
 
rust/kernel/faux.rs +59 -10

91 lines not on this page · raw message

samples/rust/rust_driver_faux.rs +2 -1
index 99876c8e3743..27b6d3e2bb44 100644
@@ -25,8 +25,9 @@ fn init(_module: &'static ThisModule) -> Result<Self> {
         pr_info!("Initialising Rust Faux Device Sample\n");
 
         let reg = faux::Registration::new(c"rust-faux-sample-device", None)?;
+        let fdev = reg.as_ref();
 
-        dev_info!(reg, "Hello from faux device!\n");
+        dev_info!(fdev, "Hello from faux device!\n");
 
         Ok(Self { _reg: reg })
     }
Signature · 1 line
2.54.0
#8 lore raw

Re: [PATCH v5 03/19] rust: faux: add Device type with AsBusDevice support

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] Missing standard bus device infrastructure for `faux::Device` (`AlwaysRefCounted`, `Send`, `Sync`…

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Missing standard bus device infrastructure for faux::Device (AlwaysRefCounted, Send, Sync, impl_device_context_deref!, impl_device_context_into_aref!). --

commit e85bef2071c929034918bb94467777aee21de70f
Author: Danilo Krummrich <[email protected]>

rust: faux: add Device type with AsBusDevice support

This commit introduces a faux::Device type wrapping struct faux_device and implements AsBusDevice for it. This allows faux devices to be used as parent devices for subsystems that require a bus device like DRM.

Quoted 4 lines
rust/kernel/faux.rs +0 -0
index 36c92ae2943c0..cd0724af446be 100644

[ ... ]

rust/kernel/faux.rs line 36

Quoted 13 lines
rust/kernel/faux.rs +13 -0
+/// A faux device.
+///
+/// A faux device is a virtual device backed by the faux bus, primarily used for scenarios where a
+/// real hardware device is not available or for testing.
+///
+/// # Invariants
+///
+/// The underlying `struct faux_device` is valid and the embedded `struct device` is initialized.
+#[repr(transparent)]
+pub struct Device<Ctx: device::DeviceContext = device::Normal>(
+    Opaque<bindings::faux_device>,
+    PhantomData<Ctx>,
+);

[Severity: Low]
Is faux::Device missing the standard bus device infrastructure here?

Since it wraps Opaque (which is neither Send nor Sync by default), should this type explicitly implement Send and Sync so that it can be safely shared across threads?

Additionally, to fully participate in the standard device context and reference counting hierarchy, should this also implement AlwaysRefCounted and use the standard device macros (impl_device_context_deref! and impl_device_context_into_aref!)?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
maintainer Reviewed-by on v5 3/19
#9 lore raw

Re: [PATCH v5 03/19] rust: faux: add Device type with AsBusDevice support

In reply to Danilo Krummrich
Quoted 7 lines · Danilo Krummrich

Add a faux::Device type that wraps struct faux_device and implements AsBusDevice, enabling faux devices to be used as parent devices for subsystems that require a bus device, such as DRM.

Update Registration to return &faux::Device<Bound> via AsRef.

(one small comment below)

Quoted 75 lines
---
 rust/kernel/drm/gem/shmem.rs     | 11 +++--
 rust/kernel/faux.rs              | 69 +++++++++++++++++++++++++++-----
 samples/rust/rust_driver_faux.rs |  3 +-
 3 files changed, 68 insertions(+), 15 deletions(-)
rust/kernel/drm/gem/shmem.rs +7 -4
index 3ee19ef6264e..52de59b14dad 100644
@@ -692,10 +692,12 @@ impl drm::Driver for KunitDriver {
     fn create_drm_dev() -> Result<(faux::Registration, UnregisteredDevice<KunitDriver>)> {
         // Create a faux DRM device so we can test gem object creation.
         let data = try_pin_init!(KunitData {});
-        let dev = faux::Registration::new(c"Kunit", None)?;
-        let drm = UnregisteredDevice::new(dev.as_ref(), data)?;
+        let reg = faux::Registration::new(c"Kunit", None)?;
+        let fdev = reg.as_ref();
+        let dev = fdev.as_ref();
+        let drm = UnregisteredDevice::new(dev, data)?;
 
-        Ok((dev, drm))
+        Ok((reg, drm))
     }
 
     #[test]
@@ -755,7 +757,8 @@ fn vmap_io() -> Result {
     #[test]
     fn fail_sg_table_on_wrong_dev() -> Result {
         let (_dev, drm) = create_drm_dev()?;
-        let wrong_dev = faux::Registration::new(c"EvilKunit", None)?;
+        let reg = faux::Registration::new(c"EvilKunit", None)?;
+        let wrong_dev = reg.as_ref();
 
         let obj = Object::<KunitObject, _>::new(&drm, PAGE_SIZE, ObjectConfig::default(), ())?;
 
rust/kernel/faux.rs +22 -6
index 36c92ae2943c..cd0724af446b 100644
@@ -9,15 +9,63 @@
 use crate::{
     bindings,
     device,
-    prelude::*, //
+    prelude::*,
+    types::Opaque, //
 };
-use core::ptr::{
-    addr_of_mut,
-    null,
-    null_mut,
-    NonNull, //
+use core::{
+    marker::PhantomData,
+    ptr::{
+        null,
+        null_mut,
+        NonNull, //
+    },
 };
 
+/// A faux device.
+///
+/// A faux device is a virtual device backed by the faux bus, primarily used for scenarios where a
+/// real hardware device is not available or for testing.
+///
+/// # Invariants
+///
+/// The underlying `struct faux_device` is valid and the embedded `struct device` is initialized.
+#[repr(transparent)]
+pub struct Device<Ctx: device::DeviceContext = device::Normal>(
+    Opaque<bindings::faux_device>,
+    PhantomData<Ctx>,
+);

rust/kernel/faux.rs line 46

Quoted 10 lines
rust/kernel/faux.rs +10 -0
+
+impl<Ctx: device::DeviceContext> Device<Ctx> {
+    #[inline]
+    fn as_raw(&self) -> *mut bindings::faux_device {
+        self.0.get()
+    }
+
+    /// # Safety
+    ///
+    /// `ptr` must be a valid pointer to a `struct faux_device`.

The invariant for Device also says: "... and the embedded struct device is valid". IIUC this requirement should also be mentioned here, unless we consider that a valid struct faux_device also implies that its embedded struct device is initialized, in which case that bit can be removed from the invariant section of Device.

author maintainer
#10 lore raw

PATCH v5 4/19 rust: drm: Add Driver::ParentDevice associated type

In reply to Danilo Krummrich

Add a ParentDevice associated type to the Driver trait, allowing each DRM driver to declare its parent bus device type (e.g. auxiliary::Device, platform::Device).

Change UnregisteredDevice::new() to take &T::ParentDevice<Bound>, ensuring at the type level that the DRM device's parent matches the declared bus device type.

---
 drivers/gpu/drm/nova/driver.rs | 8 ++++++--
 drivers/gpu/drm/tyr/driver.rs  | 6 ++++--
 rust/kernel/drm/device.rs      | 7 +++++--
 rust/kernel/drm/driver.rs      | 3 +++
 rust/kernel/drm/gem/shmem.rs   | 4 ++--
 5 files changed, 20 insertions(+), 8 deletions(-)
drivers/gpu/drm/nova/driver.rs +6 -2
index 48933d86ddda..c5b0313006bd 100644
@@ -2,7 +2,10 @@
 
 use kernel::{
     auxiliary,
-    device::Core,
+    device::{
+        Core,
+        DeviceContext, //
+    },
     drm::{
         self,
         gem,
@@ -62,7 +65,7 @@ fn probe<'bound>(
     ) -> impl PinInit<Self::Data<'bound>, Error> + 'bound {
         let data = try_pin_init!(NovaData { adev: adev.into() });
 
-        let drm = drm::UnregisteredDevice::<Self>::new(adev.as_ref(), data)?;
+        let drm = drm::UnregisteredDevice::<Self>::new(adev, data)?;
         let drm = drm::Registration::new_foreign_owned(drm, adev.as_ref(), 0)?;
 
         Ok(Nova { drm: drm.into() })
@@ -74,6 +77,7 @@ impl drm::Driver for NovaDriver {
     type Data = NovaData;
     type File = File;
     type Object<Ctx: drm::DeviceContext> = gem::Object<NovaObject, Ctx>;
+    type ParentDevice<Ctx: DeviceContext> = auxiliary::Device<Ctx>;
 
     const INFO: drm::DriverInfo = INFO;
 
drivers/gpu/drm/tyr/driver.rs +4 -2
index d063bc664cc1..338c25ccc151 100644
@@ -7,7 +7,8 @@
     },
     device::{
         Core,
-        Device, //
+        Device,
+        DeviceContext, //
     },
     dma::{
         Device as DmaDevice,
@@ -148,7 +149,7 @@ fn probe<'bound>(
                 gpu_info,
         });
 
-        let tdev = drm::UnregisteredDevice::<TyrDrmDriver>::new(pdev.as_ref(), data)?;
+        let tdev = drm::UnregisteredDevice::<TyrDrmDriver>::new(pdev, data)?;
         let tdev = drm::driver::Registration::new_foreign_owned(tdev, pdev.as_ref(), 0)?;
 
         let driver = TyrPlatformDriverData {
@@ -182,6 +183,7 @@ impl drm::Driver for TyrDrmDriver {
     type Data = TyrDrmDeviceData;
     type File = TyrDrmFileData;
     type Object<R: drm::DeviceContext> = drm::gem::shmem::Object<BoData, R>;
+    type ParentDevice<Ctx: DeviceContext> = platform::Device<Ctx>;
 
     const INFO: drm::DriverInfo = INFO;
     const FEAT_RENDER: bool = true;
rust/kernel/drm/device.rs +5 -2
index 5e91474e6dbb..23eb4c0a65ef 100644
@@ -178,7 +178,10 @@ const fn compute_features() -> u32 {
     /// Create a new `UnregisteredDevice` for a `drm::Driver`.
     ///
     /// This can be used to create a [`Registration`](kernel::drm::Registration).
-    pub fn new(dev: &device::Device, data: impl PinInit<T::Data, Error>) -> Result<Self> {
+    pub fn new(
+        dev: &T::ParentDevice<device::Bound>,
+        data: impl PinInit<T::Data, Error>,
+    ) -> Result<Self> {
         // `__drm_dev_alloc` uses `kmalloc()` to allocate memory, hence ensure a `kmalloc()`
         // compatible `Layout`.
         let layout = Kmalloc::aligned_layout(Layout::new::<Device<T, Normal>>());
@@ -195,7 +198,7 @@ pub fn new(dev: &device::Device, data: impl PinInit<T::Data, Error>) -> Result<S
         // - `dev` is valid by its type invarants,
         let raw_drm: *mut Device<T, Normal> = unsafe {
             bindings::__drm_dev_alloc(
-                dev.as_raw(),
+                dev.as_ref().as_raw(),
                 &alloc_vtable,
                 layout.size(),
                 mem::offset_of!(Device<T, Normal>, dev),
rust/kernel/drm/driver.rs +3 -0
index 25f7e233884d..802e7fc13e30 100644
@@ -116,6 +116,9 @@ pub trait Driver {
     /// The type used to represent a DRM File (client)
     type File: drm::file::DriverFile;
 
+    /// The bus device type of the parent device that the DRM device is associated with.
+    type ParentDevice<Ctx: device::DeviceContext>: device::AsBusDevice<Ctx>;
+
     /// Driver metadata
     const INFO: DriverInfo;
 
rust/kernel/drm/gem/shmem.rs +2 -2
index 52de59b14dad..cbcfc7e4edb6 100644
@@ -684,6 +684,7 @@ impl drm::Driver for KunitDriver {
         type Data = KunitData;
         type File = KunitFile;
         type Object<Ctx: DeviceContext> = Object<KunitObject, Ctx>;
+        type ParentDevice<Ctx: device::DeviceContext> = faux::Device<Ctx>;
 
         const INFO: drm::DriverInfo = INFO;
         const IOCTLS: &'static [drm::ioctl::DrmIoctlDescriptor] = &[];
@@ -694,8 +695,7 @@ fn create_drm_dev() -> Result<(faux::Registration, UnregisteredDevice<KunitDrive
         let data = try_pin_init!(KunitData {});
         let reg = faux::Registration::new(c"Kunit", None)?;
         let fdev = reg.as_ref();
-        let dev = fdev.as_ref();
-        let drm = UnregisteredDevice::new(dev, data)?;
+        let drm = UnregisteredDevice::new(fdev, data)?;
 
         Ok((reg, drm))
     }
Signature · 1 line
2.54.0
maintainer Reviewed-by on v5 4/19
#11 lore raw

Re: [PATCH v5 04/19] rust: drm: Add Driver::ParentDevice associated type

Quoted 10 lines · Danilo Krummrich

Add a ParentDevice associated type to the Driver trait, allowing each DRM driver to declare its parent bus device type (e.g. auxiliary::Device, platform::Device).

Change UnregisteredDevice::new() to take &T::ParentDevice<Bound>, ensuring at the type level that the DRM device's parent matches the declared bus device type.

author maintainer
#12 lore raw

PATCH v5 5/19 rust: drm: change default DeviceContext to Normal

In reply to Danilo Krummrich

Change the default DeviceContext from Registered to Normal for drm::Device, gem::Object, gem::shmem::Object and gem::shmem::ObjectConfig.

Normal is the general-purpose, reference-counted context suitable for most uses; Registered represents a device that was registered with userspace and will become a non-owning context obtained through a RegistrationGuard.

Update the create_handle/lookup_handle bounds from Object<Registered> to Object<Normal> to match the new default context of GEM objects, and update the driver device type aliases (NovaDevice, TyrDrmDevice) to default to Normal.

---
 drivers/gpu/drm/nova/driver.rs |  2 +-
 drivers/gpu/drm/tyr/driver.rs  |  2 +-
 rust/kernel/drm/device.rs      |  2 +-
 rust/kernel/drm/gem/mod.rs     |  7 ++++---
 rust/kernel/drm/gem/shmem.rs   | 10 +++++-----
 5 files changed, 12 insertions(+), 11 deletions(-)
drivers/gpu/drm/nova/driver.rs +1 -1
index c5b0313006bd..8ddb81fd0c87 100644
@@ -26,7 +26,7 @@ pub(crate) struct Nova {
 }
 
 /// Convienence type alias for the DRM device type for this driver
-pub(crate) type NovaDevice<Ctx = drm::Registered> = drm::Device<NovaDriver, Ctx>;
+pub(crate) type NovaDevice<Ctx = drm::Normal> = drm::Device<NovaDriver, Ctx>;
 
 #[pin_data]
 pub(crate) struct NovaData {
drivers/gpu/drm/tyr/driver.rs +1 -1
index 338c25ccc151..180631daff02 100644
@@ -47,7 +47,7 @@
 pub(crate) struct TyrDrmDriver;
 
 /// Convenience type alias for the DRM device type for this driver.
-pub(crate) type TyrDrmDevice<Ctx = drm::Registered> = drm::Device<TyrDrmDriver, Ctx>;
+pub(crate) type TyrDrmDevice<Ctx = drm::Normal> = drm::Device<TyrDrmDriver, Ctx>;
 
 pub(crate) struct TyrPlatformDriver;
 
rust/kernel/drm/device.rs +1 -1
index 23eb4c0a65ef..d712387707d2 100644
@@ -246,7 +246,7 @@ pub fn new(
 /// * The data layout of `Self` remains the same across all implementations of `C`.
 /// * Any invariants for `C` also apply.
 #[repr(C)]
-pub struct Device<T: drm::Driver, C: DeviceContext = Registered> {
+pub struct Device<T: drm::Driver, C: DeviceContext = Normal> {
     dev: Opaque<bindings::drm_device>,
     data: T::Data,
     _ctx: PhantomData<C>,
rust/kernel/drm/gem/mod.rs +4 -3
index c8b66d816871..1023ddccd785 100644
@@ -10,6 +10,7 @@
         self,
         device::{
             DeviceContext,
+            Normal,
             Registered, //
         },
         driver::{
@@ -183,7 +184,7 @@ fn size(&self) -> usize {
     fn create_handle<D, F>(&self, file: &drm::File<F>) -> Result<u32>
     where
         Self: AllocImpl<Driver = D>,
-        D: drm::Driver<Object<Registered> = Self, File = F>,
+        D: drm::Driver<Object<Normal> = Self, File = F>,
         F: drm::file::DriverFile<Driver = D>,
     {
         let mut handle: u32 = 0;
@@ -198,7 +199,7 @@ fn create_handle<D, F>(&self, file: &drm::File<F>) -> Result<u32>
     fn lookup_handle<D, F>(file: &drm::File<F>, handle: u32) -> Result<ARef<Self>>
     where
         Self: AllocImpl<Driver = D>,
-        D: drm::Driver<Object<Registered> = Self, File = F>,
+        D: drm::Driver<Object<Normal> = Self, File = F>,
         F: drm::file::DriverFile<Driver = D>,
     {
         // SAFETY: The arguments are all valid per the type invariants.
@@ -254,7 +255,7 @@ impl<T: IntoGEMObject> BaseObjectPrivate for T {}
 /// * Any type invariants of `Ctx` apply to the parent DRM device for this GEM object.
 #[repr(C)]
 #[pin_data]
-pub struct Object<T: DriverObject + Send + Sync, Ctx: DeviceContext = Registered> {
+pub struct Object<T: DriverObject + Send + Sync, Ctx: DeviceContext = Normal> {
     obj: Opaque<bindings::drm_gem_object>,
     #[pin]
     data: T,
rust/kernel/drm/gem/shmem.rs +5 -5
index cbcfc7e4edb6..5ffa1355ecf2 100644
@@ -22,7 +22,7 @@
         private::Sealed,
         Device,
         DeviceContext,
-        Registered, //
+        Normal, //
     },
     error::{
         from_err_ptr,
@@ -73,7 +73,7 @@
 ///
 /// This is used with [`Object::new()`] to control various properties that can only be set when
 /// initially creating a shmem-backed GEM object.
-pub struct ObjectConfig<'a, T: DriverObject, C: DeviceContext = Registered> {
+pub struct ObjectConfig<'a, T: DriverObject, C: DeviceContext = Normal> {
     /// Whether to set the write-combine map flag.
     pub map_wc: bool,
 
@@ -102,7 +102,7 @@ fn default() -> Self {
 /// - Any type invariants of `C` apply to the parent DRM device for this GEM object.
 #[repr(C)]
 #[pin_data]
-pub struct Object<T: DriverObject, C: DeviceContext = Registered> {
+pub struct Object<T: DriverObject, C: DeviceContext = Normal> {
     #[pin]
     obj: Opaque<bindings::drm_gem_shmem_object>,
     /// Parent object that owns this object's DMA reservation object.
@@ -409,7 +409,7 @@ impl<T: DriverObject, C: DeviceContext> driver::AllocImpl for Object<T, C> {
 /// When this is dropped, the `dma_resv` lock is dropped as well.
 ///
 // TODO: This should be replace with a WwMutex equivalent once we have such bindings in the kernel.
-struct DmaResvGuard<'a, T: DriverObject, C: DeviceContext = Registered>(
+struct DmaResvGuard<'a, T: DriverObject, C: DeviceContext = Normal>(
     &'a Object<T, C>,
     NotThreadSafe,
 );
@@ -438,7 +438,7 @@ fn drop(&mut self) {
 ///
 /// - The size of `owner` is >= SIZE.
 /// - The memory pointed to by `addr` remains valid at least until this object is dropped.
-pub struct VMap<D, R, C = Registered, const SIZE: usize = 0>
+pub struct VMap<D, R, C = Normal, const SIZE: usize = 0>
 where
     D: DriverObject,
     C: DeviceContext,
Signature · 1 line
2.54.0
maintainer Reviewed-by on v5 5/19
#13 lore raw

Re: [PATCH v5 05/19] rust: drm: change default DeviceContext to Normal

Quoted 9 lines · Danilo Krummrich

Change the default DeviceContext from Registered to Normal for drm::Device, gem::Object, gem::shmem::Object and gem::shmem::ObjectConfig.

Normal is the general-purpose, reference-counted context suitable for most uses; Registered represents a device that was registered with userspace and will become a non-owning context obtained through a RegistrationGuard.

Quoted 7 lines

Update the create_handle/lookup_handle bounds from Object<Registered> to Object<Normal> to match the new default context of GEM objects, and update the driver device type aliases (NovaDevice, TyrDrmDevice) to default to Normal.

author maintainer
#14 lore raw

PATCH v5 6/19 rust: drm: restrict AlwaysRefCounted to Normal Device context

In reply to Danilo Krummrich

Restrict the AlwaysRefCounted implementation for drm::Device to the Normal context. Registered devices represent a non-owning view of a device within a RegistrationGuard scope and must not be independently reference-counted.

---
 rust/kernel/drm/device.rs | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)
rust/kernel/drm/device.rs +5 -4
index d712387707d2..9825d52832af 100644
@@ -85,6 +85,9 @@ pub trait DeviceContext: Sealed + Send + Sync {}
 ///
 /// A [`Device`] in this context may or may not be registered with userspace. This context is used
 /// for reference-counted device handles and during device setup via [`UnregisteredDevice`].
+///
+/// [`AlwaysRefCounted`] is only implemented for `Device<T, Normal>`, making this the required
+/// context for [`ARef`]-based device handles.
 pub struct Normal;
 
 impl Sealed for Normal {}
@@ -327,7 +330,7 @@ fn deref(&self) -> &Self::Target {
 
 // SAFETY: DRM device objects are always reference counted and the get/put functions
 // satisfy the requirements.
-unsafe impl<T: drm::Driver, C: DeviceContext> AlwaysRefCounted for Device<T, C> {
+unsafe impl<T: drm::Driver> AlwaysRefCounted for Device<T> {
     fn inc_ref(&self) {
         // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero.
         unsafe { bindings::drm_dev_get(self.as_raw()) };
@@ -357,12 +360,10 @@ unsafe impl<T: drm::Driver, C: DeviceContext> Send for Device<T, C> {}
 // by the synchronization in `struct drm_device`.
 unsafe impl<T: drm::Driver, C: DeviceContext> Sync for Device<T, C> {}
 
-impl<T, C, const ID: u64> WorkItem<ID> for Device<T, C>
+impl<T: drm::Driver, const ID: u64> WorkItem<ID> for Device<T>
 where
-    T: drm::Driver,
     T::Data: WorkItem<ID, Pointer = ARef<Self>>,
     T::Data: HasWork<Self, ID>,
-    C: DeviceContext,
 {
     type Pointer = ARef<Self>;
 
Signature · 1 line
2.54.0
maintainer Reviewed-by on v5 6/19
#15 lore raw

Re: [PATCH v5 06/19] rust: drm: restrict AlwaysRefCounted to Normal Device context

Quoted 7 lines · Danilo Krummrich

Restrict the AlwaysRefCounted implementation for drm::Device to the Normal context. Registered devices represent a non-owning view of a device within a RegistrationGuard scope and must not be independently reference-counted.

author maintainer
#16 lore raw

PATCH v5 7/19 rust: drm: restrict AlwaysRefCounted to Normal GEM Object context

In reply to Danilo Krummrich

Restrict AlwaysRefCounted for gem::Object and gem::shmem::Object to the Normal context, since only Normal objects should be independently reference-counted.

To avoid cascading through IntoGEMObject (which had AlwaysRefCounted as a supertrait), remove AlwaysRefCounted from IntoGEMObject's supertraits and instead add it as an explicit bound on lookup_handle(), which is the only BaseObject method that returns an ARef.

Since Object::new() and shmem::Object::new() return ARef<Self>, move them to Normal-only impl blocks. Similarly, simplify ObjectConfig and shmem's parent_resv_obj field to the Normal context.

Remove the DeviceContext generic from DriverObject::new() and Driver::Object, since GEM objects can only be constructed in the Normal context. Simplify DriverAllocImpl accordingly.

---
 drivers/gpu/drm/nova/driver.rs |   2 +-
 drivers/gpu/drm/nova/gem.rs    |  18 ++---
 drivers/gpu/drm/tyr/driver.rs  |   2 +-
 drivers/gpu/drm/tyr/gem.rs     |  11 +--
 rust/kernel/drm/device.rs      |  14 ++--
 rust/kernel/drm/driver.rs      |   2 +-
 rust/kernel/drm/gem/mod.rs     |  97 ++++++++++++-------------
 rust/kernel/drm/gem/shmem.rs   | 129 +++++++++++++++++----------------
 8 files changed, 130 insertions(+), 145 deletions(-)
drivers/gpu/drm/nova/driver.rs +1 -1
index 8ddb81fd0c87..e3c54303d70e 100644
@@ -76,7 +76,7 @@ fn probe<'bound>(
 impl drm::Driver for NovaDriver {
     type Data = NovaData;
     type File = File;
-    type Object<Ctx: drm::DeviceContext> = gem::Object<NovaObject, Ctx>;
+    type Object = gem::Object<NovaObject>;
     type ParentDevice<Ctx: DeviceContext> = auxiliary::Device<Ctx>;
 
     const INFO: drm::DriverInfo = INFO;
drivers/gpu/drm/nova/gem.rs +7 -11
index 9d8ff7de2c0f..2b6fe9dc0bfa 100644
@@ -2,7 +2,10 @@
 
 use kernel::{
     drm,
-    drm::{gem, gem::BaseObject, DeviceContext},
+    drm::{
+        gem,
+        gem::BaseObject, //
+    },
     page,
     prelude::*,
     sync::aref::ARef,
@@ -21,27 +24,20 @@ impl gem::DriverObject for NovaObject {
     type Driver = NovaDriver;
     type Args = ();
 
-    fn new<Ctx: DeviceContext>(
-        _dev: &NovaDevice<Ctx>,
-        _size: usize,
-        _args: Self::Args,
-    ) -> impl PinInit<Self, Error> {
+    fn new(_dev: &NovaDevice, _size: usize, _args: Self::Args) -> impl PinInit<Self, Error> {
         try_pin_init!(NovaObject {})
     }
 }
 
 impl NovaObject {
     /// Create a new DRM GEM object.
-    pub(crate) fn new<Ctx: DeviceContext>(
-        dev: &NovaDevice<Ctx>,
-        size: usize,
-    ) -> Result<ARef<gem::Object<Self, Ctx>>> {
+    pub(crate) fn new(dev: &NovaDevice, size: usize) -> Result<ARef<gem::Object<Self>>> {
         if size == 0 {
             return Err(EINVAL);
         }
         let aligned_size = page::page_align(size).ok_or(EINVAL)?;
 
-        gem::Object::<Self, Ctx>::new(dev, aligned_size, ())
+        gem::Object::<Self>::new(dev, aligned_size, ())
     }
 
     /// Look up a GEM object handle for a `File` and return an `ObjectRef` for it.
drivers/gpu/drm/tyr/driver.rs +1 -1
index 180631daff02..7f082de6d6dc 100644
@@ -182,7 +182,7 @@ fn drop(self: Pin<&mut Self>) {}
 impl drm::Driver for TyrDrmDriver {
     type Data = TyrDrmDeviceData;
     type File = TyrDrmFileData;
-    type Object<R: drm::DeviceContext> = drm::gem::shmem::Object<BoData, R>;
+    type Object = drm::gem::shmem::Object<BoData>;
     type ParentDevice<Ctx: DeviceContext> = platform::Device<Ctx>;
 
     const INFO: drm::DriverInfo = INFO;
drivers/gpu/drm/tyr/gem.rs +2 -9
index c6d4d6f9bae3..1640a161754b 100644
@@ -5,10 +5,7 @@
 //! DRM's GEM subsystem with shmem backing.
 
 use kernel::{
-    drm::{
-        gem,
-        DeviceContext, //
-    },
+    drm::gem,
     prelude::*, //
 };
 
@@ -33,11 +30,7 @@ impl gem::DriverObject for BoData {
     type Driver = TyrDrmDriver;
     type Args = BoCreateArgs;
 
-    fn new<Ctx: DeviceContext>(
-        _dev: &TyrDrmDevice<Ctx>,
-        _size: usize,
-        args: BoCreateArgs,
-    ) -> impl PinInit<Self, Error> {
+    fn new(_dev: &TyrDrmDevice, _size: usize, args: BoCreateArgs) -> impl PinInit<Self, Error> {
         try_pin_init!(Self { flags: args.flags })
     }
 }
rust/kernel/drm/device.rs +7 -7
index 9825d52832af..6f3af46ff647 100644
@@ -153,13 +153,13 @@ const fn compute_features() -> u32 {
         master_drop: None,
         debugfs_init: None,
 
-        gem_create_object: T::Object::<Normal>::ALLOC_OPS.gem_create_object,
-        prime_handle_to_fd: T::Object::<Normal>::ALLOC_OPS.prime_handle_to_fd,
-        prime_fd_to_handle: T::Object::<Normal>::ALLOC_OPS.prime_fd_to_handle,
-        gem_prime_import: T::Object::<Normal>::ALLOC_OPS.gem_prime_import,
-        gem_prime_import_sg_table: T::Object::<Normal>::ALLOC_OPS.gem_prime_import_sg_table,
-        dumb_create: T::Object::<Normal>::ALLOC_OPS.dumb_create,
-        dumb_map_offset: T::Object::<Normal>::ALLOC_OPS.dumb_map_offset,
+        gem_create_object: T::Object::ALLOC_OPS.gem_create_object,
+        prime_handle_to_fd: T::Object::ALLOC_OPS.prime_handle_to_fd,
+        prime_fd_to_handle: T::Object::ALLOC_OPS.prime_fd_to_handle,
+        gem_prime_import: T::Object::ALLOC_OPS.gem_prime_import,
+        gem_prime_import_sg_table: T::Object::ALLOC_OPS.gem_prime_import_sg_table,
+        dumb_create: T::Object::ALLOC_OPS.dumb_create,
+        dumb_map_offset: T::Object::ALLOC_OPS.dumb_map_offset,
 
         show_fdinfo: None,
         fbdev_probe: None,
rust/kernel/drm/driver.rs +1 -1
index 802e7fc13e30..5152a18a8312 100644
@@ -111,7 +111,7 @@ pub trait Driver {
     type Data: Sync + Send;
 
     /// The type used to manage memory for this driver.
-    type Object<Ctx: drm::DeviceContext>: AllocImpl;
+    type Object: AllocImpl;
 
     /// The type used to represent a DRM File (client)
     type File: drm::file::DriverFile;
rust/kernel/drm/gem/mod.rs +46 -51

150 lines not on this page · raw message

rust/kernel/drm/gem/shmem.rs +65 -64

186 lines not on this page · raw message

Signature · 1 line
2.54.0
#17 lore raw

Re: [PATCH v5 07/19] rust: drm: restrict AlwaysRefCounted to Normal GEM Object context

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Type confusion leading to out-of-bounds memory access due to C-allocated GEM objects being unsafely cast to Rust Object<T>. --

commit c0b1cc771ed0a5a11fb0a16d94befa79e5c52a6d
Author: Danilo Krummrich <[email protected]>

rust: drm: restrict AlwaysRefCounted to Normal GEM Object context

This commit restricts AlwaysRefCounted for gem::Object and gem::shmem::Object to the Normal context. It updates Object::new methods to Normal-only blocks and simplifies DeviceContext usage.

Quoted 4 lines
rust/kernel/drm/gem/mod.rs +0 -0
index 1023ddccd7857..d56cbe2663e29 100644

[ ... ]

rust/kernel/drm/gem/mod.rs fn create_handle<D, F>(&self, file: &drm::File<F>) -> Result<u32> · line 203

Quoted 11 lines
rust/kernel/drm/gem/mod.rs +2 -2
@@ -198,8 +196,8 @@ fn create_handle<D, F>(&self, file: &drm::File<F>) -> Result<u32>
     /// Looks up an object by its handle for a given File.
     fn lookup_handle<D, F>(file: &drm::File<F>, handle: u32) -> Result<ARef<Self>>
     where
-        Self: AllocImpl<Driver = D>,
-        D: drm::Driver<Object<Normal> = Self, File = F>,
+        Self: AllocImpl<Driver = D> + AlwaysRefCounted,
+        D: drm::Driver<Object = Self, File = F>,
         F: drm::file::DriverFile<Driver = D>,
     {
         // SAFETY: The arguments are all valid per the type invariants.

[Severity: High]
This is a pre-existing issue, but can this lead to an out-of-bounds memory access during type casting?

In rust/kernel/drm/gem/shmem.rs, ALLOC_OPS leaves gem_create_object as None. When userspace creates a dumb buffer or imports a PRIME buffer, the C helpers drm_gem_shmem_dumb_create() and drm_gem_shmem_prime_import_sg_table() fall back to allocating a bare C struct drm_gem_shmem_object via kzalloc().

When the driver later calls lookup_handle() (defined here in rust/kernel/drm/gem/mod.rs), the underlying code unsafely casts the raw C object pointer to the Rust wrapper:

    let obj = unsafe { Self::from_raw(ptr) };

Since the C-allocated object lacks the trailing fields of the Rust Object wrapper (such as inner and sgt_lock), would accessing those fields read or write out of bounds? Is there a need to provide a gem_create_object callback so the full Rust wrapper is always allocated?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
maintainer Reviewed-by on v5 7/19
#18 lore raw

Re: [PATCH v5 07/19] rust: drm: restrict AlwaysRefCounted to Normal GEM Object context

In reply to Danilo Krummrich
Quoted 9 lines · Danilo Krummrich

Restrict AlwaysRefCounted for gem::Object and gem::shmem::Object to the Normal context, since only Normal objects should be independently reference-counted.

To avoid cascading through IntoGEMObject (which had AlwaysRefCounted as a supertrait), remove AlwaysRefCounted from IntoGEMObject's supertraits and instead add it as an explicit bound on lookup_handle(), which is the only BaseObject method that returns an ARef.

Quoted 7 lines

Since Object::new() and shmem::Object::new() return ARef<Self>, move them to Normal-only impl blocks. Similarly, simplify ObjectConfig and shmem's parent_resv_obj field to the Normal context.

Remove the DeviceContext generic from DriverObject::new() and Driver::Object, since GEM objects can only be constructed in the Normal context. Simplify DriverAllocImpl accordingly.

I think this last item could be its own patch - it wouldn't introduce any extra churn, and would separate the concepts (restricting GEM references to Normal context, and updating the API) nicely, making the first patch a bit more focused (~+20/-35 LoCs extracted from it).

But even without this,

author maintainer
#19 lore raw

PATCH v5 8/19 rust: drm/gem: remove DeviceContext from shmem::Object

In reply to Danilo Krummrich

Now that AlwaysRefCounted is restricted to the Normal GEM Object context, there is no use for instantiating Object<T, C> with a non-Normal context. Remove the DeviceContext generic parameter from shmem::Object and all associated types (VMap, VMapRef, VMapOwned, DmaResvGuard, SGTableMap), simplifying the API.

---
 rust/kernel/drm/gem/shmem.rs | 121 +++++++++++++++--------------------
 1 file changed, 51 insertions(+), 70 deletions(-)
rust/kernel/drm/gem/shmem.rs +51 -70
index cf8410e0f228..e0ef47352e88 100644
@@ -20,9 +20,7 @@
         driver,
         gem,
         private::Sealed,
-        Device,
-        DeviceContext,
-        Normal, //
+        Device, //
     },
     error::{
         from_err_ptr,
@@ -48,7 +46,6 @@
 };
 use core::{
     ffi::c_void,
-    marker::PhantomData,
     mem::{
         ManuallyDrop,
         MaybeUninit, //
@@ -99,22 +96,20 @@ fn default() -> Self {
 ///
 /// - `obj` contains a valid initialized `struct drm_gem_shmem_object` for the lifetime of this
 ///   object.
-/// - Any type invariants of `C` apply to the parent DRM device for this GEM object.
 #[repr(C)]
 #[pin_data]
-pub struct Object<T: DriverObject, C: DeviceContext = Normal> {
+pub struct Object<T: DriverObject> {
     #[pin]
     obj: Opaque<bindings::drm_gem_shmem_object>,
     /// Parent object that owns this object's DMA reservation object.
     parent_resv_obj: Option<ARef<Object<T>>>,
     /// Devres object for unmapping any SGTable on driver-unbind.
-    sgt_res: ManuallyDrop<SetOnce<Devres<SGTableMap<T, C>>>>,
+    sgt_res: ManuallyDrop<SetOnce<Devres<SGTableMap<T>>>>,
     #[pin]
     /// Lock for protecting initialization of `sgt_res`.
     sgt_lock: Mutex<()>,
     #[pin]
     inner: T,
-    _ctx: PhantomData<C>,
 }
 
 super::impl_aref_for_gem_obj! {
@@ -124,12 +119,12 @@ impl<T> for Object<T>
 }
 
 // SAFETY: All GEM objects are thread-safe.
-unsafe impl<T: DriverObject, C: DeviceContext> Send for Object<T, C> {}
+unsafe impl<T: DriverObject> Send for Object<T> {}
 
 // SAFETY: All GEM objects are thread-safe.
-unsafe impl<T: DriverObject, C: DeviceContext> Sync for Object<T, C> {}
+unsafe impl<T: DriverObject> Sync for Object<T> {}
 
-impl<T: DriverObject, C: DeviceContext> Object<T, C> {
+impl<T: DriverObject> Object<T> {
     /// `drm_gem_object_funcs` vtable suitable for GEM shmem objects.
     const VTABLE: bindings::drm_gem_object_funcs = bindings::drm_gem_object_funcs {
         free: Some(Self::free_callback),
@@ -157,7 +152,7 @@ fn as_raw_shmem(&self) -> *mut bindings::drm_gem_shmem_object {
     }
 
     /// Returns the `Device` that owns this GEM object.
-    pub fn dev(&self) -> &Device<T::Driver, C> {
+    pub fn dev(&self) -> &Device<T::Driver> {
         // SAFETY: `dev` will have been initialized in `Self::new()` by `drm_gem_shmem_init()`.
         unsafe { Device::from_raw((*self.as_raw()).dev) }
     }
@@ -171,8 +166,8 @@ extern "C" fn free_callback(obj: *mut bindings::drm_gem_object) {
 
         // SAFETY:
         // - We verified above that `obj` is valid, which makes `this` valid
-        // - This function is set in AllocOps, so we know that `this` is contained within a
-        //   `Object<T, C>`
+        // - This function is set in AllocOps, so we know that `this` is contained within an
+        //   `Object<T>`
         let this = unsafe { container_of!(Opaque::cast_from(base), Self, obj) }.cast_mut();
 
         // We need to drop `sgt_res` first, since doing so requires that the GEM object is still
@@ -193,7 +188,7 @@ extern "C" fn free_callback(obj: *mut bindings::drm_gem_object) {
     }
 
     /// Attempt to create a vmap from the gem object, and confirm the size of said vmap.
-    fn make_vmap<'a, R, const SIZE: usize>(&'a self) -> Result<VMap<T, R, C, SIZE>>
+    fn make_vmap<'a, R, const SIZE: usize>(&'a self) -> Result<VMap<T, R, SIZE>>
     where
         R: Deref<Target = Self> + From<&'a Self>,
     {
@@ -255,7 +250,7 @@ unsafe fn raw_vunmap(&self, mut map: bindings::iosys_map) {
 
     /// Creates and returns a virtual kernel memory mapping for this object.
     #[inline]
-    pub fn vmap<const SIZE: usize>(&self) -> Result<VMapRef<'_, T, C, SIZE>> {
+    pub fn vmap<const SIZE: usize>(&self) -> Result<VMapRef<'_, T, SIZE>> {
         self.make_vmap()
     }
 
@@ -298,9 +293,7 @@ pub fn sg_table<'a>(
 
         Ok(sgt_res.access(dev)?)
     }
-}
 
-impl<T: DriverObject> Object<T> {
     /// Create a new shmem-backed DRM object of the given size.
     ///
     /// Additional config options can be specified using `config`.
@@ -317,7 +310,6 @@ pub fn new(
                 sgt_res: ManuallyDrop::new(SetOnce::new()),
                 sgt_lock <- new_mutex!(()),
                 inner <- T::new(dev, size, args),
-                _ctx: PhantomData,
             }),
             GFP_KERNEL,
         )?;
@@ -351,12 +343,12 @@ pub fn new(
 
     /// Creates and returns an owned reference to a virtual kernel memory mapping for this object.
     #[inline]
-    pub fn owned_vmap<const SIZE: usize>(&self) -> Result<VMapOwned<T, Normal, SIZE>> {
+    pub fn owned_vmap<const SIZE: usize>(&self) -> Result<VMapOwned<T, SIZE>> {
         self.make_vmap()
     }
 }
 
-impl<T: DriverObject, C: DeviceContext> Deref for Object<T, C> {
+impl<T: DriverObject> Deref for Object<T> {
     type Target = T;
 
     fn deref(&self) -> &Self::Target {
@@ -364,15 +356,15 @@ fn deref(&self) -> &Self::Target {
     }
 }
 
-impl<T: DriverObject, C: DeviceContext> DerefMut for Object<T, C> {
+impl<T: DriverObject> DerefMut for Object<T> {
     fn deref_mut(&mut self) -> &mut Self::Target {
         &mut self.inner
     }
 }
 
-impl<T: DriverObject, C: DeviceContext> Sealed for Object<T, C> {}
+impl<T: DriverObject> Sealed for Object<T> {}
 
-impl<T: DriverObject, C: DeviceContext> gem::IntoGEMObject for Object<T, C> {
+impl<T: DriverObject> gem::IntoGEMObject for Object<T> {
     fn as_raw(&self) -> *mut bindings::drm_gem_object {
         // SAFETY:
         // - Our immutable reference is proof that this is safe to dereference.
@@ -391,7 +383,7 @@ unsafe fn from_raw<'a>(obj: *mut bindings::drm_gem_object) -> &'a Self {
     }
 }
 
-impl<T: DriverObject, C: DeviceContext> driver::AllocImpl for Object<T, C> {
+impl<T: DriverObject> driver::AllocImpl for Object<T> {
     type Driver = T::Driver;
 
     const ALLOC_OPS: driver::AllocOps = driver::AllocOps {
@@ -410,14 +402,11 @@ impl<T: DriverObject, C: DeviceContext> driver::AllocImpl for Object<T, C> {
 /// When this is dropped, the `dma_resv` lock is dropped as well.
 ///
 // TODO: This should be replace with a WwMutex equivalent once we have such bindings in the kernel.
-struct DmaResvGuard<'a, T: DriverObject, C: DeviceContext = Normal>(
-    &'a Object<T, C>,
-    NotThreadSafe,
-);
+struct DmaResvGuard<'a, T: DriverObject>(&'a Object<T>, NotThreadSafe);
 
-impl<'a, T: DriverObject, C: DeviceContext> DmaResvGuard<'a, T, C> {
+impl<'a, T: DriverObject> DmaResvGuard<'a, T> {
     #[inline]
-    fn new(obj: &'a Object<T, C>) -> Self {
+    fn new(obj: &'a Object<T>) -> Self {
         // SAFETY: This lock is initialized throughout the lifetime of `object`.
         unsafe { bindings::dma_resv_lock(obj.raw_dma_resv(), ptr::null_mut()) };
 
@@ -425,7 +414,7 @@ fn new(obj: &'a Object<T, C>) -> Self {
     }
 }
 
-impl<'a, T: DriverObject, C: DeviceContext> Drop for DmaResvGuard<'a, T, C> {
+impl<'a, T: DriverObject> Drop for DmaResvGuard<'a, T> {
     #[inline]
     fn drop(&mut self) {
         // SAFETY: We are releasing the lock grabbed during the creation of this object.
@@ -439,40 +428,37 @@ fn drop(&mut self) {
 ///
 /// - The size of `owner` is >= SIZE.
 /// - The memory pointed to by `addr` remains valid at least until this object is dropped.
-pub struct VMap<D, R, C = Normal, const SIZE: usize = 0>
+pub struct VMap<D, R, const SIZE: usize = 0>
 where
     D: DriverObject,
-    C: DeviceContext,
-    R: Deref<Target = Object<D, C>>,
+    R: Deref<Target = Object<D>>,
 {
     addr: *mut c_void,
     owner: R,
 }
 
 /// An alias type for a reference to a shmem-based GEM object's VMap.
-pub type VMapRef<'a, D, C, const SIZE: usize = 0> = VMap<D, &'a Object<D, C>, C, SIZE>;
+pub type VMapRef<'a, D, const SIZE: usize = 0> = VMap<D, &'a Object<D>, SIZE>;
 
 /// An alias type for an owned reference to a shmem-based GEM object's VMap.
-pub type VMapOwned<D, C, const SIZE: usize = 0> = VMap<D, ARef<Object<D, C>>, C, SIZE>;
+pub type VMapOwned<D, const SIZE: usize = 0> = VMap<D, ARef<Object<D>>, SIZE>;
 
-impl<D, R, C, const SIZE: usize> VMap<D, R, C, SIZE>
+impl<D, R, const SIZE: usize> VMap<D, R, SIZE>
 where
     D: DriverObject,
-    C: DeviceContext,
-    R: Deref<Target = Object<D, C>>,
+    R: Deref<Target = Object<D>>,
 {
     /// Borrows a reference to the object that owns this virtual mapping.
     #[inline]
-    pub fn owner(&self) -> &Object<D, C> {
+    pub fn owner(&self) -> &Object<D> {
         &self.owner
     }
 }
 
-impl<D, R, C, const SIZE: usize> Drop for VMap<D, R, C, SIZE>
+impl<D, R, const SIZE: usize> Drop for VMap<D, R, SIZE>
 where
     D: DriverObject,
-    C: DeviceContext,
-    R: Deref<Target = Object<D, C>>,
+    R: Deref<Target = Object<D>>,
 {
     #[inline]
     fn drop(&mut self) {
@@ -491,29 +477,26 @@ fn drop(&mut self) {
 
 // SAFETY: `addr` points to a valid memory address for as long as `owner` exists, meaning that so
 // long as `owner` is `Send` so is `VMap`.
-unsafe impl<D, R, C, const SIZE: usize> Send for VMap<D, R, C, SIZE>
+unsafe impl<D, R, const SIZE: usize> Send for VMap<D, R, SIZE>
 where
     D: DriverObject,
-    C: DeviceContext,
-    R: Deref<Target = Object<D, C>> + Send,
+    R: Deref<Target = Object<D>> + Send,
 {
 }
 
 // SAFETY: `addr` points to a valid memory address for as long as `owner` exists, meaning that so
 // long as `owner` is `Sync` so is `VMap`.
-unsafe impl<D, R, C, const SIZE: usize> Sync for VMap<D, R, C, SIZE>
+unsafe impl<D, R, const SIZE: usize> Sync for VMap<D, R, SIZE>
 where
     D: DriverObject,
-    C: DeviceContext,
-    R: Deref<Target = Object<D, C>> + Sync,
+    R: Deref<Target = Object<D>> + Sync,
 {
 }
 
-impl<D, R, C, const SIZE: usize> Io for VMap<D, R, C, SIZE>
+impl<D, R, const SIZE: usize> Io for VMap<D, R, SIZE>
 where
     D: DriverObject,
-    C: DeviceContext,
-    R: Deref<Target = Object<D, C>>,
+    R: Deref<Target = Object<D>>,
 {
     #[inline]
     fn addr(&self) -> usize {
@@ -526,22 +509,20 @@ fn maxsize(&self) -> usize {
     }
 }
 
-impl<D, R, C, const SIZE: usize> IoKnownSize for VMap<D, R, C, SIZE>
+impl<D, R, const SIZE: usize> IoKnownSize for VMap<D, R, SIZE>
 where
     D: DriverObject,
-    C: DeviceContext,
-    R: Deref<Target = Object<D, C>>,
+    R: Deref<Target = Object<D>>,
 {
     const MIN_SIZE: usize = SIZE;
 }
 
 macro_rules! impl_vmap_io_capable {
     ($ty:ty) => {
-        impl<D, R, C, const SIZE: usize> IoCapable<$ty> for VMap<D, R, C, SIZE>
+        impl<D, R, const SIZE: usize> IoCapable<$ty> for VMap<D, R, SIZE>
         where
             D: DriverObject,
-            C: DeviceContext,
-            R: Deref<Target = Object<D, C>>,
+            R: Deref<Target = Object<D>>,
         {
             #[inline]
             unsafe fn io_read(&self, address: usize) -> $ty {
@@ -584,11 +565,11 @@ unsafe fn io_write(&self, value: $ty, address: usize) {
 ///   [`SGTable`].
 ///
 /// [`SGTable`]: scatterlist::SGTable
-pub struct SGTableMap<T: DriverObject, C: DeviceContext> {
-    obj: NonNull<Object<T, C>>,
+pub struct SGTableMap<T: DriverObject> {
+    obj: NonNull<Object<T>>,
 }
 
-impl<T: DriverObject, C: DeviceContext> Deref for SGTableMap<T, C> {
+impl<T: DriverObject> Deref for SGTableMap<T> {
     type Target = scatterlist::SGTable;
 
     fn deref(&self) -> &Self::Target {
@@ -599,7 +580,7 @@ fn deref(&self) -> &Self::Target {
     }
 }
 
-impl<T: DriverObject, C: DeviceContext> Drop for SGTableMap<T, C> {
+impl<T: DriverObject> Drop for SGTableMap<T> {
     fn drop(&mut self) {
         // SAFETY: `obj` is always valid via our type invariants
         let obj = unsafe { self.obj.as_ref() };
@@ -610,8 +591,8 @@ fn drop(&mut self) {
     }
 }
 
-impl<T: DriverObject, C: DeviceContext> SGTableMap<T, C> {
-    fn new(obj: &Object<T, C>) -> impl Init<Self, Error> {
+impl<T: DriverObject> SGTableMap<T> {
+    fn new(obj: &Object<T>) -> impl Init<Self, Error> {
         // INVARIANT:
         // - We call drm_gem_shmem_get_pages_sgt below and check whether or not it succeeds,
         //   fulfilling the invariant of SGTableMap that the object's `sgt` field is initialized.
@@ -625,10 +606,10 @@ fn new(obj: &Object<T, C>) -> impl Init<Self, Error> {
 
 // SAFETY: The NonNull in SGTableMap is guaranteed valid by our type invariants, and the GEM object
 // it points to is guaranteed to be thread-safe.
-unsafe impl<T: DriverObject, C: DeviceContext> Send for SGTableMap<T, C> {}
+unsafe impl<T: DriverObject> Send for SGTableMap<T> {}
 // SAFETY: The NonNull in SGTableMap is guaranteed valid by our type invariants, and the GEM object
 // it points to is guaranteed to be thread-safe.
-unsafe impl<T: DriverObject, C: DeviceContext> Sync for SGTableMap<T, C> {}
+unsafe impl<T: DriverObject> Sync for SGTableMap<T> {}
 
 #[kunit_tests(rust_drm_gem_shmem)]
 mod tests {
@@ -705,7 +686,7 @@ fn create_drm_dev() -> Result<(faux::Registration, UnregisteredDevice<KunitDrive
     fn compile_time_vmap_sizes() -> Result {
         let (_dev, drm) = create_drm_dev()?;
 
-        let obj = Object::<KunitObject, _>::new(&drm, PAGE_SIZE, ObjectConfig::default(), ())?;
+        let obj = Object::<KunitObject>::new(&drm, PAGE_SIZE, ObjectConfig::default(), ())?;
 
         // Try creating a normal vmap
         obj.vmap::<PAGE_SIZE>()?;
@@ -729,7 +710,7 @@ fn compile_time_vmap_sizes() -> Result {
     fn vmap_io() -> Result {
         let (_dev, drm) = create_drm_dev()?;
 
-        let obj = Object::<KunitObject, _>::new(&drm, PAGE_SIZE, ObjectConfig::default(), ())?;
+        let obj = Object::<KunitObject>::new(&drm, PAGE_SIZE, ObjectConfig::default(), ())?;
 
         let vmap = obj.vmap::<PAGE_SIZE>()?;
 
@@ -761,7 +742,7 @@ fn fail_sg_table_on_wrong_dev() -> Result {
         let reg = faux::Registration::new(c"EvilKunit", None)?;
         let wrong_dev = reg.as_ref();
 
-        let obj = Object::<KunitObject, _>::new(&drm, PAGE_SIZE, ObjectConfig::default(), ())?;
+        let obj = Object::<KunitObject>::new(&drm, PAGE_SIZE, ObjectConfig::default(), ())?;
 
         assert_eq!(obj.sg_table(wrong_dev.as_ref()).err().unwrap(), EINVAL);
 
Signature · 1 line
2.54.0
Reviewed-by on v5 8/19
#20 lore raw

Re: [PATCH v5 08/19] rust: drm/gem: remove DeviceContext from shmem::Object

Quoted 456 lines · Danilo Krummrich

Now that AlwaysRefCounted is restricted to the Normal GEM Object context, there is no use for instantiating Object<T, C> with a non-Normal context. Remove the DeviceContext generic parameter from shmem::Object and all associated types (VMap, VMapRef, VMapOwned, DmaResvGuard, SGTableMap), simplifying the API.

---
 rust/kernel/drm/gem/shmem.rs | 121 +++++++++++++++------------------ --

 1 file changed, 51 insertions(+), 70 deletions(-)
rust/kernel/drm/gem/shmem.rs +1 -4
index cf8410e0f228..e0ef47352e88 100644
@@ -20,9 +20,7 @@
          driver,
          gem,
          private::Sealed,
-        Device,
-        DeviceContext,
-        Normal, //
+        Device, //
      },
      error::{
          from_err_ptr,
@@ -48,7 +46,6 @@
  };
  use core::{
      ffi::c_void,
-    marker::PhantomData,
      mem::{
          ManuallyDrop,

         MaybeUninit, //

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -99,22 +96,20 @@ fn default() -> Self {
  ///
  /// - `obj` contains a valid initialized `struct

drm_gem_shmem_object` for the lifetime of this
 ///   object.
-/// - Any type invariants of C apply to the parent DRM device for this GEM object.
 #[repr(C)]
 #[pin_data]

rust/kernel/drm/gem/shmem.rs +1 -1
-pub struct Object<T: DriverObject, C: DeviceContext = Normal> {
+pub struct Object<T: DriverObject> {

     #[pin]
     obj: Opaque<bindings::drm_gem_shmem_object>,
     /// Parent object that owns this object's DMA reservation object.
     parent_resv_obj: Option<ARef<Object<T>>>,
     /// Devres object for unmapping any SGTable on driver-unbind. -    sgt_res: ManuallyDrop<SetOnce<Devres<SGTableMap<T, C>>>>, +    sgt_res: ManuallyDrop<SetOnce<Devres<SGTableMap<T>>>>, #[pin]
     /// Lock for protecting initialization of sgt_res.
     sgt_lock: Mutex<()>,
     #[pin]
     inner: T,
-    _ctx: PhantomData<C>,
 }

 super::impl_aref_for_gem_obj! {

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -124,12 +119,12 @@ impl<T> for Object<T>
  }


 // SAFETY: All GEM objects are thread-safe.
-unsafe impl<T: DriverObject, C: DeviceContext> Send for Object<T, C> {}
+unsafe impl<T: DriverObject> Send for Object<T> {}

 // SAFETY: All GEM objects are thread-safe.
-unsafe impl<T: DriverObject, C: DeviceContext> Sync for Object<T, C> {}
+unsafe impl<T: DriverObject> Sync for Object<T> {}

rust/kernel/drm/gem/shmem.rs +1 -1
-impl<T: DriverObject, C: DeviceContext> Object<T, C> {
+impl<T: DriverObject> Object<T> {

     /// drm_gem_object_funcs vtable suitable for GEM shmem objects.
     const VTABLE: bindings::drm_gem_object_funcs = bindings::drm_gem_object_funcs {
         free: Some(Self::free_callback),

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -157,7 +152,7 @@ fn as_raw_shmem(&self) -> *mut
 bindings::drm_gem_shmem_object {
      }


     /// Returns the Device that owns this GEM object.
-    pub fn dev(&self) -> &Device<T::Driver, C> {
+    pub fn dev(&self) -> &Device<T::Driver> {
         // SAFETY: dev will have been initialized in Self::new() by drm_gem_shmem_init().
         unsafe { Device::from_raw((*self.as_raw()).dev) } }

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -171,8 +166,8 @@ extern "C" fn free_callback(obj: *mut
 bindings::drm_gem_object) {


         // SAFETY:
         // - We verified above that obj is valid, which makes this valid
-        // - This function is set in AllocOps, so we know that this is contained within a
-        //   Object<T, C>
+        // - This function is set in AllocOps, so we know that this is contained within an
+        //   Object<T>
         let this = unsafe { container_of!(Opaque::cast_from(base), Self, obj) }.cast_mut();

         // We need to drop sgt_res first, since doing so requires that the GEM object is still

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -193,7 +188,7 @@ extern "C" fn free_callback(obj: *mut
 bindings::drm_gem_object) {
      }


     /// Attempt to create a vmap from the gem object, and confirm the size of said vmap.
-    fn make_vmap<'a, R, const SIZE: usize>(&'a self) -> Result<VMap<T, R, C, SIZE>>
+    fn make_vmap<'a, R, const SIZE: usize>(&'a self) -> Result<VMap<T, R, SIZE>>
     where
         R: Deref<Target = Self> + From<&'a Self>,
     {

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -255,7 +250,7 @@ unsafe fn raw_vunmap(&self, mut map:
 bindings::iosys_map) {


     /// Creates and returns a virtual kernel memory mapping for this object.
     #[inline]
-    pub fn vmap<const SIZE: usize>(&self) -> Result<VMapRef<'_, T, C, SIZE>> {
+    pub fn vmap<const SIZE: usize>(&self) -> Result<VMapRef<'_, T, SIZE>> {
         self.make_vmap()
     }

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -298,9 +293,7 @@ pub fn sg_table<'a>(


         Ok(sgt_res.access(dev)?)
     }
-}

-impl<T: DriverObject> Object<T> {
     /// Create a new shmem-backed DRM object of the given size. ///
     /// Additional config options can be specified using config.

rust/kernel/drm/gem/shmem.rs +0 -1
@@ -317,7 +310,6 @@ pub fn new(
                  sgt_res: ManuallyDrop::new(SetOnce::new()),
                  sgt_lock <- new_mutex!(()),
                  inner <- T::new(dev, size, args),
-                _ctx: PhantomData,
              }),
              GFP_KERNEL,
          )?;
@@ -351,12 +343,12 @@ pub fn new(


     /// Creates and returns an owned reference to a virtual kernel memory mapping for this object.
     #[inline]
-    pub fn owned_vmap<const SIZE: usize>(&self) -> Result<VMapOwned<T, Normal, SIZE>> {
+    pub fn owned_vmap<const SIZE: usize>(&self) -> Result<VMapOwned<T, SIZE>> {
         self.make_vmap()
     }
 }

rust/kernel/drm/gem/shmem.rs +1 -1
-impl<T: DriverObject, C: DeviceContext> Deref for Object<T, C> {
+impl<T: DriverObject> Deref for Object<T> {

     type Target = T;

     fn deref(&self) -> &Self::Target {

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -364,15 +356,15 @@ fn deref(&self) -> &Self::Target {
      }
  }

rust/kernel/drm/gem/shmem.rs +1 -1
-impl<T: DriverObject, C: DeviceContext> DerefMut for Object<T, C> {
+impl<T: DriverObject> DerefMut for Object<T> {

     fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner
     }
 }

rust/kernel/drm/gem/shmem.rs +1 -1
-impl<T: DriverObject, C: DeviceContext> Sealed for Object<T, C> {}
+impl<T: DriverObject> Sealed for Object<T> {}


-impl<T: DriverObject, C: DeviceContext> gem::IntoGEMObject for Object<T, C> {
+impl<T: DriverObject> gem::IntoGEMObject for Object<T> { fn as_raw(&self) -> *mut bindings::drm_gem_object { // SAFETY:
         // - Our immutable reference is proof that this is safe to dereference.

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -391,7 +383,7 @@ unsafe fn from_raw<'a>(obj: *mut
 bindings::drm_gem_object) -> &'a Self {
      }
  }


-impl<T: DriverObject, C: DeviceContext> driver::AllocImpl for Object<T, C> {
+impl<T: DriverObject> driver::AllocImpl for Object<T> { type Driver = T::Driver;

     const ALLOC_OPS: driver::AllocOps = driver::AllocOps {

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -410,14 +402,11 @@ impl<T: DriverObject, C: DeviceContext>
 driver::AllocImpl for Object<T, C> {
  /// When this is dropped, the `dma_resv` lock is dropped as well.
  ///
  // TODO: This should be replace with a WwMutex equivalent once we

have such bindings in the kernel.

rust/kernel/drm/gem/shmem.rs +1 -4
-struct DmaResvGuard<'a, T: DriverObject, C: DeviceContext = Normal>(
-    &'a Object<T, C>,
-    NotThreadSafe,
-);
+struct DmaResvGuard<'a, T: DriverObject>(&'a Object<T>,

NotThreadSafe);

rust/kernel/drm/gem/shmem.rs +1 -1
-impl<'a, T: DriverObject, C: DeviceContext> DmaResvGuard<'a, T, C> {
+impl<'a, T: DriverObject> DmaResvGuard<'a, T> {

     #[inline]
-    fn new(obj: &'a Object<T, C>) -> Self {
+    fn new(obj: &'a Object<T>) -> Self {
         // SAFETY: This lock is initialized throughout the lifetime of object.
         unsafe { bindings::dma_resv_lock(obj.raw_dma_resv(), ptr::null_mut()) };

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -425,7 +414,7 @@ fn new(obj: &'a Object<T, C>) -> Self {
      }
  }


-impl<'a, T: DriverObject, C: DeviceContext> Drop for DmaResvGuard<'a, T, C> {
+impl<'a, T: DriverObject> Drop for DmaResvGuard<'a, T> { #[inline]
     fn drop(&mut self) {
         // SAFETY: We are releasing the lock grabbed during the creation of this object.

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -439,40 +428,37 @@ fn drop(&mut self) {
  ///
  /// - The size of `owner` is >= SIZE.
  /// - The memory pointed to by `addr` remains valid at least until

this object is dropped.

rust/kernel/drm/gem/shmem.rs +1 -1
-pub struct VMap<D, R, C = Normal, const SIZE: usize = 0>
+pub struct VMap<D, R, const SIZE: usize = 0>

 where
     D: DriverObject,
-    C: DeviceContext,
-    R: Deref<Target = Object<D, C>>,
+    R: Deref<Target = Object<D>>,
 {
     addr: *mut c_void,
     owner: R,
 }

 /// An alias type for a reference to a shmem-based GEM object's VMap.
-pub type VMapRef<'a, D, C, const SIZE: usize = 0> = VMap<D, &'a Object<D, C>, C, SIZE>;
+pub type VMapRef<'a, D, const SIZE: usize = 0> = VMap<D, &'a Object<D>, SIZE>;

 /// An alias type for an owned reference to a shmem-based GEM object's VMap.
-pub type VMapOwned<D, C, const SIZE: usize = 0> = VMap<D, ARef<Object<D, C>>, C, SIZE>;
+pub type VMapOwned<D, const SIZE: usize = 0> = VMap<D, ARef<Object<D>>, SIZE>;

rust/kernel/drm/gem/shmem.rs +1 -1
-impl<D, R, C, const SIZE: usize> VMap<D, R, C, SIZE>
+impl<D, R, const SIZE: usize> VMap<D, R, SIZE>

 where
     D: DriverObject,
-    C: DeviceContext,
-    R: Deref<Target = Object<D, C>>,
+    R: Deref<Target = Object<D>>,
 {
     /// Borrows a reference to the object that owns this virtual mapping.
     #[inline]
-    pub fn owner(&self) -> &Object<D, C> {
+    pub fn owner(&self) -> &Object<D> {
         &self.owner
     }
 }

rust/kernel/drm/gem/shmem.rs +1 -1
-impl<D, R, C, const SIZE: usize> Drop for VMap<D, R, C, SIZE>
+impl<D, R, const SIZE: usize> Drop for VMap<D, R, SIZE>

 where
     D: DriverObject,
-    C: DeviceContext,
-    R: Deref<Target = Object<D, C>>,
+    R: Deref<Target = Object<D>>,
 {
     #[inline]
     fn drop(&mut self) {

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -491,29 +477,26 @@ fn drop(&mut self) {


 // SAFETY: addr points to a valid memory address for as long as owner exists, meaning that so
 // long as owner is Send so is VMap.

rust/kernel/drm/gem/shmem.rs +1 -1
-unsafe impl<D, R, C, const SIZE: usize> Send for VMap<D, R, C, SIZE>
+unsafe impl<D, R, const SIZE: usize> Send for VMap<D, R, SIZE>

 where
     D: DriverObject,
-    C: DeviceContext,
-    R: Deref<Target = Object<D, C>> + Send,
+    R: Deref<Target = Object<D>> + Send,
 {
 }

 // SAFETY: addr points to a valid memory address for as long as owner exists, meaning that so
 // long as owner is Sync so is VMap.

rust/kernel/drm/gem/shmem.rs +1 -1
-unsafe impl<D, R, C, const SIZE: usize> Sync for VMap<D, R, C, SIZE>
+unsafe impl<D, R, const SIZE: usize> Sync for VMap<D, R, SIZE>

 where
     D: DriverObject,
-    C: DeviceContext,
-    R: Deref<Target = Object<D, C>> + Sync,
+    R: Deref<Target = Object<D>> + Sync,
 {
 }

rust/kernel/drm/gem/shmem.rs +1 -1
-impl<D, R, C, const SIZE: usize> Io for VMap<D, R, C, SIZE>
+impl<D, R, const SIZE: usize> Io for VMap<D, R, SIZE>

 where
     D: DriverObject,
-    C: DeviceContext,
-    R: Deref<Target = Object<D, C>>,
+    R: Deref<Target = Object<D>>,
 {
     #[inline]
     fn addr(&self) -> usize {

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -526,22 +509,20 @@ fn maxsize(&self) -> usize {
      }
  }

rust/kernel/drm/gem/shmem.rs +1 -1
-impl<D, R, C, const SIZE: usize> IoKnownSize for VMap<D, R, C, SIZE>
+impl<D, R, const SIZE: usize> IoKnownSize for VMap<D, R, SIZE>

 where
     D: DriverObject,
-    C: DeviceContext,
-    R: Deref<Target = Object<D, C>>,
+    R: Deref<Target = Object<D>>,
 {
     const MIN_SIZE: usize = SIZE;
 }

 macro_rules! impl_vmap_io_capable {
     ($ty:ty) => {
-        impl<D, R, C, const SIZE: usize> IoCapable<$ty> for VMap<D, R, C, SIZE>
+        impl<D, R, const SIZE: usize> IoCapable<$ty> for VMap<D, R, SIZE>
         where
             D: DriverObject,
-            C: DeviceContext,
-            R: Deref<Target = Object<D, C>>,
+            R: Deref<Target = Object<D>>,
         {
             #[inline]
             unsafe fn io_read(&self, address: usize) -> $ty {

rust/kernel/drm/gem/shmem.rs +2 -2
@@ -584,11 +565,11 @@ unsafe fn io_write(&self, value: $ty, address:
 usize) {
  ///   [`SGTable`].
  ///
  /// [`SGTable`]: scatterlist::SGTable
-pub struct SGTableMap<T: DriverObject, C: DeviceContext> {
-    obj: NonNull<Object<T, C>>,
+pub struct SGTableMap<T: DriverObject> {
+    obj: NonNull<Object<T>>,
  }

rust/kernel/drm/gem/shmem.rs +1 -1
-impl<T: DriverObject, C: DeviceContext> Deref for SGTableMap<T, C> {
+impl<T: DriverObject> Deref for SGTableMap<T> {

     type Target = scatterlist::SGTable;

     fn deref(&self) -> &Self::Target {

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -599,7 +580,7 @@ fn deref(&self) -> &Self::Target {
      }
  }

rust/kernel/drm/gem/shmem.rs +1 -1
-impl<T: DriverObject, C: DeviceContext> Drop for SGTableMap<T, C> {
+impl<T: DriverObject> Drop for SGTableMap<T> {

     fn drop(&mut self) {
         // SAFETY: obj is always valid via our type invariants let obj = unsafe { self.obj.as_ref() };

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -610,8 +591,8 @@ fn drop(&mut self) {
      }
  }

rust/kernel/drm/gem/shmem.rs +2 -2
-impl<T: DriverObject, C: DeviceContext> SGTableMap<T, C> {
-    fn new(obj: &Object<T, C>) -> impl Init<Self, Error> {
+impl<T: DriverObject> SGTableMap<T> {
+    fn new(obj: &Object<T>) -> impl Init<Self, Error> {

         // INVARIANT:
         // - We call drm_gem_shmem_get_pages_sgt below and check whether or not it succeeds,
         //   fulfilling the invariant of SGTableMap that the object's sgt field is initialized.

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -625,10 +606,10 @@ fn new(obj: &Object<T, C>) -> impl Init<Self,
 Error> {


 // SAFETY: The NonNull in SGTableMap is guaranteed valid by our type invariants, and the GEM object
 // it points to is guaranteed to be thread-safe.
-unsafe impl<T: DriverObject, C: DeviceContext> Send for SGTableMap<T, C> {}
+unsafe impl<T: DriverObject> Send for SGTableMap<T> {}
 // SAFETY: The NonNull in SGTableMap is guaranteed valid by our type invariants, and the GEM object
 // it points to is guaranteed to be thread-safe.
-unsafe impl<T: DriverObject, C: DeviceContext> Sync for SGTableMap<T, C> {}
+unsafe impl<T: DriverObject> Sync for SGTableMap<T> {}

 #[kunit_tests(rust_drm_gem_shmem)]
 mod tests {

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -705,7 +686,7 @@ fn create_drm_dev() ->

Result<(faux::Registration, UnregisteredDevice<KunitDrive fn compile_time_vmap_sizes() -> Result {
         let (_dev, drm) = create_drm_dev()?;

-        let obj = Object::<KunitObject, _>::new(&drm, PAGE_SIZE, ObjectConfig::default(), ())?;
+        let obj = Object::<KunitObject>::new(&drm, PAGE_SIZE, ObjectConfig::default(), ())?;

         // Try creating a normal vmap obj.vmap::<PAGE_SIZE>()?;

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -729,7 +710,7 @@ fn compile_time_vmap_sizes() -> Result {
      fn vmap_io() -> Result {
          let (_dev, drm) = create_drm_dev()?;


-        let obj = Object::<KunitObject, _>::new(&drm, PAGE_SIZE, ObjectConfig::default(), ())?;
+        let obj = Object::<KunitObject>::new(&drm, PAGE_SIZE, ObjectConfig::default(), ())?;

         let vmap = obj.vmap::<PAGE_SIZE>()?;

rust/kernel/drm/gem/shmem.rs +0 -0
@@ -761,7 +742,7 @@ fn fail_sg_table_on_wrong_dev() -> Result {
          let reg = faux::Registration::new(c"EvilKunit", None)?;
          let wrong_dev = reg.as_ref();


-        let obj = Object::<KunitObject, _>::new(&drm, PAGE_SIZE, ObjectConfig::default(), ())?;
+        let obj = Object::<KunitObject>::new(&drm, PAGE_SIZE, ObjectConfig::default(), ())?;
assert_eq!(obj.sg_table(wrong_dev.as_ref()).err().unwrap(), EINVAL);

maintainer Reviewed-by on v5 8/19
#21 lore raw

Re: [PATCH v5 08/19] rust: drm/gem: remove DeviceContext from shmem::Object

In reply to Danilo Krummrich
Quoted 7 lines · Danilo Krummrich

Now that AlwaysRefCounted is restricted to the Normal GEM Object context, there is no use for instantiating Object<T, C> with a non-Normal context. Remove the DeviceContext generic parameter from shmem::Object and all associated types (VMap, VMapRef, VMapOwned, DmaResvGuard, SGTableMap), simplifying the API.

author maintainer
#22 lore raw

PATCH v5 9/19 rust: drm: split Deref for Device context typestates

In reply to Danilo Krummrich

Split the Deref implementation for drm::Device by context:

- Device<T> (Normal) dereferences to T::Data.
- Device<T, Registered> dereferences to Device<T> (Normal).

---
 rust/kernel/drm/device.rs | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)
rust/kernel/drm/device.rs +15 -1
index 6f3af46ff647..86a7fca1d33f 100644
@@ -79,6 +79,9 @@ macro_rules! drm_legacy_fields {
 /// - [`Normal`]: The general-purpose, reference-counted context. A [`Device`] in this context may
 ///   or may not be registered with userspace.
 /// - [`Registered`]: The device has been registered with userspace at some point.
+///
+/// `Device<T, Registered>` dereferences to `Device<T>` ([`Normal`]), so any method available on a
+/// [`Normal`] device is also available on a [`Registered`] one.
 pub trait DeviceContext: Sealed + Send + Sync {}
 
 /// The general-purpose, reference-counted [`DeviceContext`].
@@ -320,7 +323,7 @@ pub(crate) unsafe fn assume_ctx<NewCtx: DeviceContext>(&self) -> &Device<T, NewC
     }
 }
 
-impl<T: drm::Driver, C: DeviceContext> Deref for Device<T, C> {
+impl<T: drm::Driver> Deref for Device<T> {
     type Target = T::Data;
 
     fn deref(&self) -> &Self::Target {
@@ -328,6 +331,17 @@ fn deref(&self) -> &Self::Target {
     }
 }
 
+impl<T: drm::Driver> Deref for Device<T, Registered> {
+    type Target = Device<T>;
+
+    #[inline]
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: The caller holds a `Device<T, Registered>`, which guarantees all invariants
+        // of the weaker `Normal` context.
+        unsafe { self.assume_ctx() }
+    }
+}
+
 // SAFETY: DRM device objects are always reference counted and the get/put functions
 // satisfy the requirements.
 unsafe impl<T: drm::Driver> AlwaysRefCounted for Device<T> {
Signature · 1 line
2.54.0
maintainer Reviewed-by on v5 9/19
#23 lore raw

Re: [PATCH v5 09/19] rust: drm: split Deref for Device context typestates

Quoted 7 lines · Danilo Krummrich

Split the Deref implementation for drm::Device by context:

- Device<T> (Normal) dereferences to T::Data.
- Device<T, Registered> dereferences to Device<T> (Normal).

author maintainer
#24 lore raw

PATCH v5 10/19 rust: drm: pin ioctl Device reference to Normal context

In reply to Danilo Krummrich

Explicitly annotate the Device reference produced by from_raw() in the ioctl dispatch macro as Device<_, Normal>. Without this annotation, the context is inferred from the handler's first parameter type, which would allow a handler declaring &Device<T, Registered> to obtain a Registered reference without runtime proof via RegistrationGuard.

---
 rust/kernel/drm/ioctl.rs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
rust/kernel/drm/ioctl.rs +2 -1
index ccf4150d83b6..6f5a9877bdae 100644
@@ -134,7 +134,8 @@ macro_rules! declare_drm_ioctls {
                             // FIXME: Currently there is nothing enforcing that the types of the
                             // dev/file match the current driver these ioctls are being declared
                             // for, and it's not clear how to enforce this within the type system.
-                            let dev = $crate::drm::device::Device::from_raw(raw_dev);
+                            let dev: &$crate::drm::device::Device<_, $crate::drm::Normal> =
+                                $crate::drm::device::Device::from_raw(raw_dev);
 
                             // Enforce that the handler accepts higher-ranked
                             // lifetimes, preventing it from requiring 'static
Signature · 1 line
2.54.0
#25 lore raw

Re: [PATCH v5 10/19] rust: drm: pin ioctl Device reference to Normal context

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Potential unaligned reference creation from raw_data when pointing to stack_kdata. --

commit 8f60afaa1c169bb0b033e33cedd40ffff2147401
Author: Danilo Krummrich <[email protected]>

rust: drm: pin ioctl Device reference to Normal context

Explicitly annotate the Device reference produced by from_raw() in the ioctl dispatch macro as Device<_, Normal>. This prevents the context from being inferred from the handler's first parameter type, ensuring a handler cannot obtain a Registered reference without runtime proof.

Quoted 14 lines
rust/kernel/drm/ioctl.rs +2 -1
index ccf4150d83b69..6f5a9877bdae0 100644
@@ -134,7 +134,8 @@ macro_rules! declare_drm_ioctls {
                             // FIXME: Currently there is nothing enforcing that the types of the
                             // dev/file match the current driver these ioctls are being declared
                             // for, and it's not clear how to enforce this within the type system.
-                            let dev = $crate::drm::device::Device::from_raw(raw_dev);
+                            let dev: &$crate::drm::device::Device<_, $crate::drm::Normal> =
+                                $crate::drm::device::Device::from_raw(raw_dev);
 
                             // Enforce that the handler accepts higher-ranked
                             // lifetimes, preventing it from requiring 'static

[ ... ]

Quoted 6 lines
                             // The `ioctl` argument is exclusively owned by the handler
                             // and guaranteed by the C implementation (`drm_ioctl()`) to remain
                             // valid for the entire lifetime of the reference taken here.
                             // There is no concurrent access or aliasing; no other references
                             // to this object exist during this call.
                             let data = unsafe { &mut *(raw_data.cast::<$crate::uapi::$struct>()) };

[Severity: High]
This isn't a bug introduced by this patch, but does this create an unaligned reference?

The C function drm_ioctl() allocates char stack_kdata[128]; on the stack for ioctl payloads <= 128 bytes, which only guarantees 1-byte alignment.

When declare_drm_ioctls!() receives this pointer as raw_data and unconditionally casts it to create a mutable reference:

    let data = unsafe { &mut *(raw_data.cast::<$crate::uapi::$struct>()) };

it violates Rust's strict alignment rules for references.

Creating a reference to under-aligned memory is immediate undefined behavior in Rust, regardless of whether the target architecture supports unaligned accesses.

Because this path is reachable by any unprivileged userspace program calling DRM ioctls, could this lead to a denial of service if LLVM emits instructions that trap (such as vectorized operations)?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
maintainer Reviewed-by on v5 10/19
#26 lore raw

Re: [PATCH v5 10/19] rust: drm: pin ioctl Device reference to Normal context

In reply to Danilo Krummrich
Quoted 8 lines · Danilo Krummrich

Explicitly annotate the Device reference produced by from_raw() in the ioctl dispatch macro as Device<_, Normal>. Without this annotation, the context is inferred from the handler's first parameter type, which would allow a handler declaring &Device<T, Registered> to obtain a Registered reference without runtime proof via RegistrationGuard.

author maintainer
#27 lore raw

PATCH v5 11/19 rust: drm: add Ioctl device context typestate

In reply to Danilo Krummrich

Add the Ioctl DeviceContext for DRM devices that have been registered with userspace previously.

A Device<T, Ioctl> has been registered at some point, but may be concurrently unregistering or already unregistered. drm_dev_enter() can guard against this, ensuring the device remains registered for the duration of the critical section.

This typestate will be used in ioctl dispatch context where registration is guaranteed by the DRM core, and RegistrationGuard can safely be acquired.

---
 rust/kernel/drm/device.rs | 34 +++++++++++++++++++++++++++++++---
 rust/kernel/drm/mod.rs    |  1 +
 2 files changed, 32 insertions(+), 3 deletions(-)
rust/kernel/drm/device.rs +31 -3
index 86a7fca1d33f..d429b4655449 100644
@@ -74,14 +74,16 @@ macro_rules! drm_legacy_fields {
 
 /// A trait implemented by all possible contexts a [`Device`] can be used in.
 ///
-/// A [`Device`] can be in one of two contexts:
+/// A [`Device`] can be in one of the following contexts:
 ///
 /// - [`Normal`]: The general-purpose, reference-counted context. A [`Device`] in this context may
 ///   or may not be registered with userspace.
+/// - [`Ioctl`]: The device has been registered with userspace at some point; used in ioctl
+///   dispatch context.
 /// - [`Registered`]: The device has been registered with userspace at some point.
 ///
-/// `Device<T, Registered>` dereferences to `Device<T>` ([`Normal`]), so any method available on a
-/// [`Normal`] device is also available on a [`Registered`] one.
+/// Both `Device<T, Ioctl>` and `Device<T, Registered>` dereference to `Device<T>` ([`Normal`]),
+/// so any method available on a [`Normal`] device is also available in the other contexts.
 pub trait DeviceContext: Sealed + Send + Sync {}
 
 /// The general-purpose, reference-counted [`DeviceContext`].
@@ -113,6 +115,21 @@ impl DeviceContext for Normal {}
 impl Sealed for Registered {}
 impl DeviceContext for Registered {}
 
+/// The [`DeviceContext`] of a [`Device`] that has been registered with userspace previously.
+///
+/// A [`Device`] in this context has been registered at some point, but may be concurrently
+/// unregistering or already unregistered. `drm_dev_enter()` can guard against this, ensuring the
+/// device remains registered for the duration of the critical section.
+///
+/// # Invariants
+///
+/// A [`Device`] in this context has been registered with userspace via `drm_dev_register()` at
+/// some point.
+pub struct Ioctl;
+
+impl Sealed for Ioctl {}
+impl DeviceContext for Ioctl {}
+
 /// A [`Device`] which is known at compile-time to be unregistered with userspace.
 ///
 /// This type allows performing operations which are only safe to do before userspace registration,
@@ -342,6 +359,17 @@ fn deref(&self) -> &Self::Target {
     }
 }
 
+impl<T: drm::Driver> Deref for Device<T, Ioctl> {
+    type Target = Device<T>;
+
+    #[inline]
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: The caller holds a `Device<T, Ioctl>`, which guarantees all invariants
+        // of the weaker `Normal` context.
+        unsafe { self.assume_ctx() }
+    }
+}
+
 // SAFETY: DRM device objects are always reference counted and the get/put functions
 // satisfy the requirements.
 unsafe impl<T: drm::Driver> AlwaysRefCounted for Device<T> {
rust/kernel/drm/mod.rs +1 -0
index e5bfaf130342..a6693d2b84b8 100644
@@ -11,6 +11,7 @@
 
 pub use self::device::Device;
 pub use self::device::DeviceContext;
+pub use self::device::Ioctl;
 pub use self::device::Normal;
 pub use self::device::Registered;
 pub use self::device::UnregisteredDevice;
Signature · 1 line
2.54.0
maintainer Reviewed-by on v5 11/19
#28 lore raw

Re: [PATCH v5 11/19] rust: drm: add Ioctl device context typestate

Quoted 69 lines · Danilo Krummrich

Add the Ioctl DeviceContext for DRM devices that have been registered with userspace previously.

A Device<T, Ioctl> has been registered at some point, but may be concurrently unregistering or already unregistered. drm_dev_enter() can guard against this, ensuring the device remains registered for the duration of the critical section.

This typestate will be used in ioctl dispatch context where registration is guaranteed by the DRM core, and RegistrationGuard can safely be acquired.

---
 rust/kernel/drm/device.rs | 34 +++++++++++++++++++++++++++++++---
 rust/kernel/drm/mod.rs    |  1 +
 2 files changed, 32 insertions(+), 3 deletions(-)
rust/kernel/drm/device.rs +20 -3
index 86a7fca1d33f..d429b4655449 100644
@@ -74,14 +74,16 @@ macro_rules! drm_legacy_fields {
 
 /// A trait implemented by all possible contexts a [`Device`] can be used in.
 ///
-/// A [`Device`] can be in one of two contexts:
+/// A [`Device`] can be in one of the following contexts:
 ///
 /// - [`Normal`]: The general-purpose, reference-counted context. A [`Device`] in this context may
 ///   or may not be registered with userspace.
+/// - [`Ioctl`]: The device has been registered with userspace at some point; used in ioctl
+///   dispatch context.
 /// - [`Registered`]: The device has been registered with userspace at some point.
 ///
-/// `Device<T, Registered>` dereferences to `Device<T>` ([`Normal`]), so any method available on a
-/// [`Normal`] device is also available on a [`Registered`] one.
+/// Both `Device<T, Ioctl>` and `Device<T, Registered>` dereference to `Device<T>` ([`Normal`]),
+/// so any method available on a [`Normal`] device is also available in the other contexts.
 pub trait DeviceContext: Sealed + Send + Sync {}
 
 /// The general-purpose, reference-counted [`DeviceContext`].
@@ -113,6 +115,21 @@ impl DeviceContext for Normal {}
 impl Sealed for Registered {}
 impl DeviceContext for Registered {}
 
+/// The [`DeviceContext`] of a [`Device`] that has been registered with userspace previously.
+///
+/// A [`Device`] in this context has been registered at some point, but may be concurrently
+/// unregistering or already unregistered. `drm_dev_enter()` can guard against this, ensuring the
+/// device remains registered for the duration of the critical section.
+///
+/// # Invariants
+///
+/// A [`Device`] in this context has been registered with userspace via `drm_dev_register()` at
+/// some point.
+pub struct Ioctl;
+
+impl Sealed for Ioctl {}
+impl DeviceContext for Ioctl {}
+
 /// A [`Device`] which is known at compile-time to be unregistered with userspace.
 ///
 /// This type allows performing operations which are only safe to do before userspace registration,
@@ -342,6 +359,17 @@ fn deref(&self) -> &Self::Target {
     }
 }
 

rust/kernel/drm/device.rs fn deref(&self) -> &Self::Target { · line 371

Quoted 10 lines
rust/kernel/drm/device.rs +10 -0
+impl<T: drm::Driver> Deref for Device<T, Ioctl> {
+    type Target = Device<T>;
+
+    #[inline]
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: The caller holds a `Device<T, Ioctl>`, which guarantees all invariants
+        // of the weaker `Normal` context.
+        unsafe { self.assume_ctx() }
+    }
+}

Since the Deref implementation is the same for Ioctl and Registered, do we want to add a private marker trait for these two so we can factor out this impl block?

It wouldn't reduce the LoC count, but would make the logical connection between the two states explicit and set an anchor for potential future blocks with a similar relationship.

With or without this:

author maintainer
#29 lore raw

PATCH v5 12/19 rust: drm: Add RegistrationGuard for drm_dev_enter/exit critical sections

In reply to Danilo Krummrich

DRM ioctls do not guarantee that the parent bus device is still bound. However, since DRM device registration is managed through Devres, using drm_dev_unplug() on unregistration ensures that between drm_dev_enter() and drm_dev_exit() the parent device must be bound.

Add RegistrationGuard, a guard object representing a drm_dev_enter/exit SRCU critical section that dereferences to &Device<T, Registered>. The guard is obtained from Device<T, Ioctl> and proves at runtime that the device is still registered.

Switch Registration::drop from drm_dev_unregister() to drm_dev_unplug() to provide the SRCU barrier that RegistrationGuard's safety argument relies on.

---
 rust/kernel/drm/device.rs | 85 ++++++++++++++++++++++++++++++++++-----
 rust/kernel/drm/driver.rs | 10 ++++-
 rust/kernel/drm/mod.rs    |  1 +
 3 files changed, 83 insertions(+), 13 deletions(-)
rust/kernel/drm/device.rs +74 -11
index d429b4655449..c32cc0f0eba0 100644
@@ -80,7 +80,8 @@ macro_rules! drm_legacy_fields {
 ///   or may not be registered with userspace.
 /// - [`Ioctl`]: The device has been registered with userspace at some point; used in ioctl
 ///   dispatch context.
-/// - [`Registered`]: The device has been registered with userspace at some point.
+/// - [`Registered`]: The device is currently registered with userspace and the parent bus device
+///   is bound.
 ///
 /// Both `Device<T, Ioctl>` and `Device<T, Registered>` dereference to `Device<T>` ([`Normal`]),
 /// so any method available on a [`Normal`] device is also available in the other contexts.
@@ -98,18 +99,15 @@ pub trait DeviceContext: Sealed + Send + Sync {}
 impl Sealed for Normal {}
 impl DeviceContext for Normal {}
 
-/// The [`DeviceContext`] of a [`Device`] that was registered with userspace at some point.
+/// The [`DeviceContext`] of a [`Device`] that is currently registered with userspace.
 ///
-/// This represents a [`Device`] which is guaranteed to have been registered with userspace at
-/// some point in time. Such a DRM device is guaranteed to have been fully-initialized.
-///
-/// Note: A device in this context is not guaranteed to remain registered with userspace for its
-/// entire lifetime, as this is impossible to guarantee at compile-time.
+/// A [`Device`] in this context is guaranteed to be registered and its parent bus device is
+/// guaranteed to be bound. This is enforced at runtime by [`RegistrationGuard`], which holds a
+/// `drm_dev_enter()` / `drm_dev_exit()` SRCU critical section.
 ///
 /// # Invariants
 ///
-/// A [`Device`] in this [`DeviceContext`] is guaranteed to have been registered with userspace
-/// at some point in time.
+/// The parent bus device is bound for the duration of any reference to a `Device<T, Registered>`.
 pub struct Registered;
 
 impl Sealed for Registered {}
@@ -260,8 +258,8 @@ pub fn new(
 
 /// A typed DRM device with a specific [`drm::Driver`] implementation and [`DeviceContext`].
 ///
-/// A device in the [`Registered`] context is guaranteed to have been registered with userspace
-/// at some point. The [`Normal`] context is the general-purpose, reference-counted context.
+/// A device in the [`Registered`] context is currently registered with userspace and its parent
+/// bus device is bound. The [`Normal`] context is the general-purpose, reference-counted context.
 ///
 /// # Invariants
 ///
@@ -340,6 +338,71 @@ pub(crate) unsafe fn assume_ctx<NewCtx: DeviceContext>(&self) -> &Device<T, NewC
     }
 }
 
+impl<T: drm::Driver> Device<T, Ioctl> {
+    /// Guard against the parent bus device being unbound.
+    ///
+    /// Returns a [`RegistrationGuard`] if the device has not been unplugged, [`None`] otherwise.
+    ///
+    /// While [`RegistrationGuard`] is held the parent device is guaranteed to be bound.
+    #[must_use]
+    pub fn registration_guard(&self) -> Option<RegistrationGuard<'_, T>> {
+        let mut idx: i32 = 0;
+        // SAFETY: `self.as_raw()` is a valid pointer to a `struct drm_device`.
+        if unsafe { bindings::drm_dev_enter(self.as_raw(), &mut idx) } {
+            // INVARIANT:
+            // - `idx` is the SRCU index from the successful `drm_dev_enter()` above.
+            // - The parent bus device is bound: `drm_dev_enter()` succeeded, meaning
+            //   `drm_dev_unplug()` has not completed; since it is only called from
+            //   `Registration::drop()` during parent unbind, the parent is still bound.
+            Some(RegistrationGuard {
+                // SAFETY: See INVARIANT above; the `Registered` context invariant holds.
+                dev: unsafe { self.assume_ctx() },
+                idx,
+                _not_send: NotThreadSafe,
+            })
+        } else {
+            None
+        }
+    }
+}
+
+/// A guard proving the DRM device is registered and the parent bus device is bound.
+///
+/// The guard dereferences to [`Device<T, Registered>`], providing access to the DRM device with
+/// the guarantee that the parent bus device is bound for the entire duration of the critical
+/// section.
+///
+/// Internally this is backed by a `drm_dev_enter()` / `drm_dev_exit()` SRCU critical section.
+///
+/// # Invariants
+///
+/// - `idx` is the SRCU read lock index returned by a successful `drm_dev_enter()` call.
+/// - The parent bus device of `dev` is bound for the lifetime of this guard.
+#[must_use]
+pub struct RegistrationGuard<'a, T: drm::Driver> {
+    dev: &'a Device<T, Registered>,
+    idx: i32,
+    _not_send: NotThreadSafe,
+}
+
+impl<T: drm::Driver> Deref for RegistrationGuard<'_, T> {
+    type Target = Device<T, Registered>;
+
+    #[inline]
+    fn deref(&self) -> &Self::Target {
+        self.dev
+    }
+}
+
+impl<T: drm::Driver> Drop for RegistrationGuard<'_, T> {
+    #[inline]
+    fn drop(&mut self) {
+        // SAFETY: `self.idx` was returned by a successful `drm_dev_enter()` call, as guaranteed
+        // by the type invariants of `RegistrationGuard`.
+        unsafe { bindings::drm_dev_exit(self.idx) };
+    }
+}
+
 impl<T: drm::Driver> Deref for Device<T> {
     type Target = T::Data;
 
rust/kernel/drm/driver.rs +8 -2
index 5152a18a8312..3cda8dceb498 100644
@@ -199,8 +199,14 @@ unsafe impl<T: Driver> Send for Registration<T> {}
 
 impl<T: Driver> Drop for Registration<T> {
     fn drop(&mut self) {
+        // 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 `RegistrationGuard`, which relies on the SRCU barrier in
+        // `drm_dev_unplug()` to guarantee that the parent device is still bound within the
+        // critical section.
+        //
         // SAFETY: Safe by the invariant of `ARef<drm::Device<T>>`. The existence of this
-        // `Registration` also guarantees the this `drm::Device` is actually registered.
-        unsafe { bindings::drm_dev_unregister(self.0.as_raw()) };
+        // `Registration` also guarantees that this `drm::Device` is actually registered.
+        unsafe { bindings::drm_dev_unplug(self.0.as_raw()) };
     }
 }
rust/kernel/drm/mod.rs +1 -0
index a6693d2b84b8..fd6ed35bc35a 100644
@@ -14,6 +14,7 @@
 pub use self::device::Ioctl;
 pub use self::device::Normal;
 pub use self::device::Registered;
+pub use self::device::RegistrationGuard;
 pub use self::device::UnregisteredDevice;
 pub use self::driver::Driver;
 pub use self::driver::DriverInfo;
Signature · 1 line
2.54.0
#30 lore raw

Re: [PATCH v5 12/19] rust: drm: Add RegistrationGuard for drm_dev_enter/exit critical sections

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Using `drm_dev_unplug()` instead of `drm_dev_unregister()` breaks normal hardware shutdown for…

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Using drm_dev_unplug() instead of drm_dev_unregister() breaks normal hardware shutdown for display drivers on module unload. --

commit f4a9e1c28c30b5e7af0c36efbe74ccb2e5047e20
Author: Danilo Krummrich <[email protected]>
rust: drm: Add RegistrationGuard for drm_dev_enter/exit critical sections

This commit adds a RegistrationGuard to represent a drm_dev_enter/exit SRCU protection area. It also changes Registration::drop to use drm_dev_unplug() instead of drm_dev_unregister() to provide an SRCU barrier.

Quoted 4 lines
rust/kernel/drm/driver.rs +0 -0
index 5152a18a8312b..3cda8dceb498d 100644

[ ... ]

Quoted 5 lines
rust/kernel/drm/driver.rs +1 -0
@@ -199,8 +199,14 @@ unsafe impl<T: Driver> Send for Registration<T> {}
 
 impl<T: Driver> Drop for Registration<T> {
     fn drop(&mut self) {
+        // Use `drm_dev_unplug` rather than `drm_dev_unregister` to ensure that existing

rust/kernel/drm/driver.rs unsafe impl<T: Driver> Send for Registration<T> {} · line 210

Quoted 10 lines
rust/kernel/drm/driver.rs +7 -2
+        // `drm_dev_enter()` critical sections complete before unregistration proceeds. This
+        // is required for the safety of `RegistrationGuard`, which relies on the SRCU barrier in
+        // `drm_dev_unplug()` to guarantee that the parent device is still bound within the
+        // critical section.
+        //
         // SAFETY: Safe by the invariant of `ARef<drm::Device<T>>`. The existence of this
-        // `Registration` also guarantees the this `drm::Device` is actually registered.
-        unsafe { bindings::drm_dev_unregister(self.0.as_raw()) };
+        // `Registration` also guarantees that this `drm::Device` is actually registered.
+        unsafe { bindings::drm_dev_unplug(self.0.as_raw()) };

[Severity: High]
Will replacing drm_dev_unregister() with drm_dev_unplug() here break normal hardware shutdown for display drivers on module unload?

When drm_dev_unplug() is called, it sets dev->unplugged = true, which causes any subsequent calls to drm_dev_enter() to return false.

The standard pattern for DRM driver removal is to unregister the device, which now calls drm_dev_unplug() through this Drop implementation. The driver then typically calls drm_atomic_helper_shutdown() to gracefully disable the hardware.

Since dev->unplugged is now true, the KMS disable code paths (which rely on drm_dev_enter() to prevent accessing hot-unplugged hardware) will immediately abort.

Does this cause the shutdown sequence to silently fail and leave the display hardware enabled, potentially continuing to scan out from freed memory or trigger IOMMU faults?

Quoted 2 lines
rust/kernel/drm/driver.rs +0 -0
     }
 }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=12
maintainer Reviewed-by on v5 12/19
#31 lore raw

Re: [PATCH v5 12/19] rust: drm: Add RegistrationGuard for drm_dev_enter/exit critical sections

In reply to Danilo Krummrich
Quoted 10 lines · Danilo Krummrich

DRM ioctls do not guarantee that the parent bus device is still bound. However, since DRM device registration is managed through Devres, using drm_dev_unplug() on unregistration ensures that between drm_dev_enter() and drm_dev_exit() the parent device must be bound.

Add RegistrationGuard, a guard object representing a drm_dev_enter/exit SRCU critical section that dereferences to &Device<T, Registered>. The guard is obtained from Device<T, Ioctl> and proves at runtime that the device is still registered.

Quoted 6 lines

Switch Registration::drop from drm_dev_unregister() to drm_dev_unplug() to provide the SRCU barrier that RegistrationGuard's safety argument relies on.

#32 lore raw

Re: [PATCH v5 12/19] rust: drm: Add RegistrationGuard for drm_dev_enter/exit critical sections

In reply to Danilo Krummrich

On Sun Jun 28, 2026 at 11:53 PM JST, Danilo Krummrich wrote:
<...>

rust/kernel/drm/device.rs pub(crate) unsafe fn assume_ctx<NewCtx: DeviceContext>(&self) -> &Device<T, NewC · line 349

Quoted 9 lines
rust/kernel/drm/device.rs +9 -0
+impl<T: drm::Driver> Device<T, Ioctl> {
+    /// Guard against the parent bus device being unbound.
+    ///
+    /// Returns a [`RegistrationGuard`] if the device has not been unplugged, [`None`] otherwise.
+    ///
+    /// While [`RegistrationGuard`] is held the parent device is guaranteed to be bound.
+    #[must_use]
+    pub fn registration_guard(&self) -> Option<RegistrationGuard<'_, T>> {
+        let mut idx: i32 = 0;

Forgot this nit: since this is used as a parameter of a C function, don't we prefer to use ffi::c_int as the type?

Quoted 18 lines
rust/kernel/drm/device.rs +18 -0
+        // SAFETY: `self.as_raw()` is a valid pointer to a `struct drm_device`.
+        if unsafe { bindings::drm_dev_enter(self.as_raw(), &mut idx) } {
+            // INVARIANT:
+            // - `idx` is the SRCU index from the successful `drm_dev_enter()` above.
+            // - The parent bus device is bound: `drm_dev_enter()` succeeded, meaning
+            //   `drm_dev_unplug()` has not completed; since it is only called from
+            //   `Registration::drop()` during parent unbind, the parent is still bound.
+            Some(RegistrationGuard {
+                // SAFETY: See INVARIANT above; the `Registered` context invariant holds.
+                dev: unsafe { self.assume_ctx() },
+                idx,
+                _not_send: NotThreadSafe,
+            })
+        } else {
+            None
+        }
+    }
+}
author maintainer
#33 lore raw

PATCH v5 13/19 rust: drm: Wrap ioctl dispatch in RegistrationGuard

In reply to Danilo Krummrich

Ioctl handlers now receive a &Device<T, Registered> reference, proving at the type level that the device is registered and its parent bus device is bound.

This is achieved by calling registration_guard() on the Device<T, Ioctl> obtained in ioctl dispatch context. If the device has been unplugged, the ioctl returns -ENODEV without calling the handler.

To resolve the driver type parameter T for type inference, which the compiler cannot propagate through method resolution and associated-type projections alone, a dead-code closure and a helper function are used as a type-inference anchor.

---
 drivers/gpu/drm/nova/file.rs | 12 ++++++----
 drivers/gpu/drm/tyr/file.rs  |  7 ++++--
 rust/kernel/drm/ioctl.rs     | 45 +++++++++++++++++++++++++++++++++---
 3 files changed, 55 insertions(+), 9 deletions(-)
drivers/gpu/drm/nova/file.rs +8 -4
index a3b7bd36792c..19fb89b28984 100644
@@ -4,7 +4,11 @@
 use crate::gem::NovaObject;
 use kernel::{
     alloc::flags::*,
-    drm::{self, gem::BaseObject},
+    drm::{
+        self,
+        gem::BaseObject,
+        Registered, //
+    },
     pci,
     prelude::*,
     uapi,
@@ -23,7 +27,7 @@ fn open(_dev: &NovaDevice) -> Result<Pin<KBox<Self>>> {
 impl File {
     /// IOCTL: get_param: Query GPU / driver metadata.
     pub(crate) fn get_param(
-        dev: &NovaDevice,
+        dev: &NovaDevice<Registered>,
         getparam: &mut uapi::drm_nova_getparam,
         _file: &drm::File<File>,
     ) -> Result<u32> {
@@ -43,7 +47,7 @@ pub(crate) fn get_param(
 
     /// IOCTL: gem_create: Create a new DRM GEM object.
     pub(crate) fn gem_create(
-        dev: &NovaDevice,
+        dev: &NovaDevice<Registered>,
         req: &mut uapi::drm_nova_gem_create,
         file: &drm::File<File>,
     ) -> Result<u32> {
@@ -56,7 +60,7 @@ pub(crate) fn gem_create(
 
     /// IOCTL: gem_info: Query GEM metadata.
     pub(crate) fn gem_info(
-        _dev: &NovaDevice,
+        _dev: &NovaDevice<Registered>,
         req: &mut uapi::drm_nova_gem_info,
         file: &drm::File<File>,
     ) -> Result<u32> {
drivers/gpu/drm/tyr/file.rs +5 -2
index 31411da203c5..fb9233eae01c 100644
@@ -1,7 +1,10 @@
 // SPDX-License-Identifier: GPL-2.0 or MIT
 
 use kernel::{
-    drm,
+    drm::{
+        self,
+        Registered, //
+    },
     prelude::*,
     uaccess::UserSlice,
     uapi, //
@@ -28,7 +31,7 @@ fn open(_dev: &drm::Device<Self::Driver>) -> Result<Pin<KBox<Self>>> {
 
 impl TyrDrmFileData {
     pub(crate) fn dev_query(
-        ddev: &TyrDrmDevice,
+        ddev: &TyrDrmDevice<Registered>,
         devquery: &mut uapi::drm_panthor_dev_query,
         _file: &TyrDrmFile,
     ) -> Result<u32> {
rust/kernel/drm/ioctl.rs +42 -3

79 lines not on this page · raw message

Signature · 1 line
2.54.0
#34 lore raw

Re: [PATCH v5 13/19] rust: drm: Wrap ioctl dispatch in RegistrationGuard

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Wrapping the entire DRM ioctl dispatch in a RegistrationGuard holds the SRCU read lock…

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Wrapping the entire DRM ioctl dispatch in a RegistrationGuard holds the SRCU read lock (drm_dev_enter) for the entire duration of the ioctl handler, which will deadlock device hot-unplug. --

commit e380f2be4330be05befae4edf318decdc7aa75d1
Author: Danilo Krummrich <[email protected]>

rust: drm: Wrap ioctl dispatch in RegistrationGuard

This commit wraps the DRM ioctl dispatch in registration_guard() to ensure that handlers only receive a reference to a registered device. If the device has already been unplugged, it intercepts the call and returns -ENODEV.

Quoted 4 lines
rust/kernel/drm/ioctl.rs +0 -0
index 6f5a9877bdae0..f24cf7b146a8e 100644

[ ... ]

Quoted 1 line
rust/kernel/drm/ioctl.rs +0 -0
@@ -131,17 +143,44 @@ macro_rules! declare_drm_ioctls {

[ ... ]

rust/kernel/drm/ioctl.rs macro_rules! declare_drm_ioctls { · line 196

Quoted 13 lines
rust/kernel/drm/ioctl.rs +5 -1
+                            let Some(guard) = dev.registration_guard() else {
+                                return $crate::error::code::ENODEV.to_errno();
+                            };
+
                             // SAFETY: The ioctl argument has size `_IOC_SIZE(cmd)`, which we
                             // asserted above matches the size of this type, and all bit patterns of
                             // UAPI structs must be valid.
@@ -154,7 +193,7 @@ macro_rules! declare_drm_ioctls {
                             // SAFETY: This is just the DRM file structure
                             let file = unsafe { $crate::drm::File::from_raw(raw_file) };
 
-                            match $func(dev, data, file) {
+                            match $func(&*guard, data, file) {
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[Severity: High]
Will this deadlock device hot-unplug if the ioctl handler performs an unbounded wait?

The RegistrationGuard acquires the SRCU read lock via drm_dev_enter(). Because this guard is held across the entire execution of $func, any unbounded wait inside the ioctl (such as waiting for a hardware fence) will hold the SRCU read-side critical section indefinitely.

If the device is hot-unplugged during this wait, the driver's remove callback calls drm_dev_unplug(). This blocks on synchronize_srcu() waiting for the ioctl to drop the lock. The ioctl will remain blocked forever because the stalled remove callback cannot force-signal the fences, leading to a permanent hang.

Quoted 3 lines
rust/kernel/drm/ioctl.rs +0 -0
                                 Err(e) => e.to_errno(),
                                 Ok(i) => i.try_into()
                                             .unwrap_or($crate::error::code::ERANGE.to_errno()),
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=13
maintainer Reviewed-by on v5 13/19
#35 lore raw

Re: [PATCH v5 13/19] rust: drm: Wrap ioctl dispatch in RegistrationGuard

In reply to Danilo Krummrich
Quoted 3 lines · Danilo Krummrich

Ioctl handlers now receive a &Device<T, Registered> reference, proving at the type level that the device is registered and its parent bus device is bound.

nit: the non-imperative style makes it sound a bit like this is the pre-patch state. Maybe change to something like "Make ioctl handlers receive...".

Quoted 92 lines

This is achieved by calling registration_guard() on the Device<T, Ioctl> obtained in ioctl dispatch context. If the device has been unplugged, the ioctl returns -ENODEV without calling the handler.

To resolve the driver type parameter T for type inference, which the compiler cannot propagate through method resolution and associated-type projections alone, a dead-code closure and a helper function are used as a type-inference anchor.

---
 drivers/gpu/drm/nova/file.rs | 12 ++++++----
 drivers/gpu/drm/tyr/file.rs  |  7 ++++--
 rust/kernel/drm/ioctl.rs     | 45 +++++++++++++++++++++++++++++++++---
 3 files changed, 55 insertions(+), 9 deletions(-)
drivers/gpu/drm/nova/file.rs +8 -4
index a3b7bd36792c..19fb89b28984 100644
@@ -4,7 +4,11 @@
 use crate::gem::NovaObject;
 use kernel::{
     alloc::flags::*,
-    drm::{self, gem::BaseObject},
+    drm::{
+        self,
+        gem::BaseObject,
+        Registered, //
+    },
     pci,
     prelude::*,
     uapi,
@@ -23,7 +27,7 @@ fn open(_dev: &NovaDevice) -> Result<Pin<KBox<Self>>> {
 impl File {
     /// IOCTL: get_param: Query GPU / driver metadata.
     pub(crate) fn get_param(
-        dev: &NovaDevice,
+        dev: &NovaDevice<Registered>,
         getparam: &mut uapi::drm_nova_getparam,
         _file: &drm::File<File>,
     ) -> Result<u32> {
@@ -43,7 +47,7 @@ pub(crate) fn get_param(
 
     /// IOCTL: gem_create: Create a new DRM GEM object.
     pub(crate) fn gem_create(
-        dev: &NovaDevice,
+        dev: &NovaDevice<Registered>,
         req: &mut uapi::drm_nova_gem_create,
         file: &drm::File<File>,
     ) -> Result<u32> {
@@ -56,7 +60,7 @@ pub(crate) fn gem_create(
 
     /// IOCTL: gem_info: Query GEM metadata.
     pub(crate) fn gem_info(
-        _dev: &NovaDevice,
+        _dev: &NovaDevice<Registered>,
         req: &mut uapi::drm_nova_gem_info,
         file: &drm::File<File>,
     ) -> Result<u32> {
drivers/gpu/drm/tyr/file.rs +5 -2
index 31411da203c5..fb9233eae01c 100644
@@ -1,7 +1,10 @@
 // SPDX-License-Identifier: GPL-2.0 or MIT
 
 use kernel::{
-    drm,
+    drm::{
+        self,
+        Registered, //
+    },
     prelude::*,
     uaccess::UserSlice,
     uapi, //
@@ -28,7 +31,7 @@ fn open(_dev: &drm::Device<Self::Driver>) -> Result<Pin<KBox<Self>>> {
 
 impl TyrDrmFileData {
     pub(crate) fn dev_query(
-        ddev: &TyrDrmDevice,
+        ddev: &TyrDrmDevice<Registered>,
         devquery: &mut uapi::drm_panthor_dev_query,
         _file: &TyrDrmFile,
     ) -> Result<u32> {
rust/kernel/drm/ioctl.rs +0 -0
index 6f5a9877bdae..f24cf7b146a8 100644

rust/kernel/drm/ioctl.rs pub mod internal { · line 74

Quoted 5 lines
rust/kernel/drm/ioctl.rs +2 -0
@@ -70,6 +70,18 @@ pub mod internal {
     pub use bindings::drm_device;
     pub use bindings::drm_file;
     pub use bindings::drm_ioctl_desc;
+
+    /// Reinterpret a pointer to a DRM device with a different [`DeviceContext`], preserving the

This sentence makes it sound like the origin and destination contexts are parameters, but this helper only does Ioctl to Registered.

rust/kernel/drm/ioctl.rs pub mod internal { · line 85

Quoted 11 lines
rust/kernel/drm/ioctl.rs +10 -0
+    /// driver type parameter `T`.
+    ///
+    /// Used by [`declare_drm_ioctls!`] to anchor type inference.
+    #[doc(hidden)]
+    #[inline]
+    pub const fn __dev_ctx_cast<T: crate::drm::Driver>(
+        ptr: *const crate::drm::Device<T, crate::drm::Ioctl>,
+    ) -> *const crate::drm::Device<T, crate::drm::Registered> {
+        ptr.cast()
+    }
 }

The points above really are nits though.

author maintainer
#36 lore raw

PATCH v5 14/19 rust: drm: return ParentDevice from Device AsRef

In reply to Danilo Krummrich

Change AsRef for drm::Device to return &T::ParentDevice<device::Normal> instead of &device::Device, and restrict it to the Normal context. Device<T, Registered> still gets this through Deref coercion.

This provides access to the typed parent bus device rather than the raw base device.

---
 rust/kernel/drm/device.rs    | 10 +++++++---
 rust/kernel/drm/driver.rs    |  3 ++-
 rust/kernel/drm/gem/shmem.rs |  3 ++-
 3 files changed, 11 insertions(+), 5 deletions(-)
rust/kernel/drm/device.rs +7 -3
index c32cc0f0eba0..187c57cab736 100644
@@ -450,11 +450,15 @@ unsafe fn dec_ref(obj: NonNull<Self>) {
     }
 }
 
-impl<T: drm::Driver, C: DeviceContext> AsRef<device::Device> for Device<T, C> {
-    fn as_ref(&self) -> &device::Device {
+impl<T: drm::Driver> AsRef<T::ParentDevice<device::Normal>> for Device<T> {
+    fn as_ref(&self) -> &T::ParentDevice<device::Normal> {
         // SAFETY: `bindings::drm_device::dev` is valid as long as the DRM device itself is valid,
         // which is guaranteed by the type invariant.
-        unsafe { device::Device::from_raw((*self.as_raw()).dev) }
+        let dev = unsafe { device::Device::from_raw((*self.as_raw()).dev) };
+
+        // SAFETY: The DRM device was constructed in `UnregisteredDevice::new()` with a parent
+        // device of type `T::ParentDevice`, hence `dev` is contained in a `T::ParentDevice`.
+        unsafe { device::AsBusDevice::from_device(dev) }
     }
 }
 
rust/kernel/drm/driver.rs +2 -1
index 3cda8dceb498..9ba2eba84191 100644
@@ -170,7 +170,8 @@ pub fn new_foreign_owned<'a>(
     where
         T: 'static,
     {
-        if drm.as_ref().as_raw() != dev.as_raw() {
+        let parent = drm.as_ref();
+        if parent.as_ref().as_raw() != dev.as_raw() {
             return Err(EINVAL);
         }
 
rust/kernel/drm/gem/shmem.rs +2 -1
index e0ef47352e88..c1d82a04878b 100644
@@ -264,7 +264,8 @@ pub fn sg_table<'a>(
         &'a self,
         dev: &'a device::Device<Bound>,
     ) -> Result<&'a scatterlist::SGTable> {
-        if dev.as_raw() != self.dev().as_ref().as_raw() {
+        let parent = self.dev().as_ref();
+        if dev.as_raw() != parent.as_ref().as_raw() {
             return Err(EINVAL);
         }
 
Signature · 1 line
2.54.0
maintainer Reviewed-by on v5 14/19
#37 lore raw

Re: [PATCH v5 14/19] rust: drm: return ParentDevice from Device AsRef

Quoted 9 lines · Danilo Krummrich

Change AsRef for drm::Device to return &T::ParentDevice<device::Normal> instead of &device::Device, and restrict it to the Normal context. Device<T, Registered> still gets this through Deref coercion.

This provides access to the typed parent bus device rather than the raw base device.

author maintainer
#38 lore raw

PATCH v5 15/19 rust: drm: add AsRef<ParentDevice<Bound>> for Device<Registered>

In reply to Danilo Krummrich

Implement AsRef<T::ParentDevice<Bound>> for Device<T, Registered>, providing access to the bound parent bus device for registered DRM devices.

Since a Device<T, Registered> guarantees that the parent bus device is bound, the conversion to T::ParentDevice<Bound> is safe.

---
 rust/kernel/drm/device.rs | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
rust/kernel/drm/device.rs +14 -0
index 187c57cab736..5e8b75dab0a6 100644
@@ -462,6 +462,20 @@ fn as_ref(&self) -> &T::ParentDevice<device::Normal> {
     }
 }
 
+impl<T: drm::Driver> AsRef<T::ParentDevice<device::Bound>> for Device<T, Registered> {
+    #[inline]
+    fn as_ref(&self) -> &T::ParentDevice<device::Bound> {
+        let dev = (**self).as_ref().as_ref();
+
+        // SAFETY: A `Device<T, Registered>` guarantees that the parent device is bound.
+        let dev = unsafe { dev.as_bound() };
+
+        // SAFETY: The DRM device was constructed in `UnregisteredDevice::new()` with a parent
+        // device of type `T::ParentDevice`, hence `dev` is contained in a `T::ParentDevice`.
+        unsafe { device::AsBusDevice::from_device(dev) }
+    }
+}
+
 // SAFETY: A `drm::Device` can be released from any thread.
 unsafe impl<T: drm::Driver, C: DeviceContext> Send for Device<T, C> {}
 
Signature · 1 line
2.54.0
maintainer Reviewed-by on v5 15/19
#39 lore raw

Re: [PATCH v5 15/19] rust: drm: add AsRef<ParentDevice<Bound>> for Device<Registered>

Quoted 9 lines · Danilo Krummrich

Implement AsRef<T::ParentDevice<Bound>> for Device<T, Registered>, providing access to the bound parent bus device for registered DRM devices.

Since a Device<T, Registered> guarantees that the parent bus device is bound, the conversion to T::ParentDevice<Bound> is safe.

author maintainer
#40 lore raw

PATCH v5 16/19 drm: fix race between partial drm_dev_register() failure and ioctl

In reply to Danilo Krummrich

If drm_dev_register() fails after registering a minor (e.g. render minor registered, primary minor fails), userspace could have opened the first minor and entered a drm_dev_enter() critical section. Since the unplugged flag was never set, the ioctl proceeds while the error path tears down device resources.

Fix this by introducing drm_dev_synchronize_unplug(), which sets the unplugged flag and waits for the SRCU barrier, ensuring all in-flight drm_dev_enter() critical sections complete before cleanup proceeds; call it on the error path of drm_dev_register().

Fixes bee330f3d672 ("drm: Use srcu to protect drm_device.unplugged")
Reported-by [email protected]
---
 drivers/gpu/drm/drm_drv.c | 34 +++++++++++++++++++++++++---------
 1 file changed, 25 insertions(+), 9 deletions(-)
drivers/gpu/drm/drm_drv.c +25 -9
index 675675480da4..e890052061f3 100644
@@ -473,6 +473,22 @@ void drm_dev_exit(int idx)
 }
 EXPORT_SYMBOL(drm_dev_exit);
 
+/*
+ * Mark the device as unplugged and wait for any in-flight drm_dev_enter()
+ * critical sections to complete.
+ */
+static void drm_dev_synchronize_unplug(struct drm_device *dev)
+{
+	/*
+	 * After synchronizing any critical read section is guaranteed to see
+	 * the new value of ->unplugged, and any critical section which might
+	 * still have seen the old value of ->unplugged is guaranteed to have
+	 * finished.
+	 */
+	dev->unplugged = true;
+	synchronize_srcu(&drm_unplug_srcu);
+}
+
 /**
  * drm_dev_unplug - unplug a DRM device
  * @dev: DRM device
@@ -485,15 +501,7 @@ EXPORT_SYMBOL(drm_dev_exit);
  */
 void drm_dev_unplug(struct drm_device *dev)
 {
-	/*
-	 * After synchronizing any critical read section is guaranteed to see
-	 * the new value of ->unplugged, and any critical section which might
-	 * still have seen the old value of ->unplugged is guaranteed to have
-	 * finished.
-	 */
-	dev->unplugged = true;
-	synchronize_srcu(&drm_unplug_srcu);
-
+	drm_dev_synchronize_unplug(dev);
 	drm_dev_unregister(dev);
 
 	/* Clear all CPU mappings pointing to this device */
@@ -1091,6 +1099,7 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags)
 		goto err_minors;
 
 	dev->registered = true;
+	dev->unplugged = false;
 
 	if (driver->load) {
 		ret = driver->load(dev, flags);
@@ -1118,6 +1127,13 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags)
 	if (dev->driver->unload)
 		dev->driver->unload(dev);
 err_minors:
+	/*
+	 * If a minor was registered before the failure, userspace could have
+	 * opened it and entered a drm_dev_enter() critical section. Ensure all
+	 * such sections complete before we clean up.
+	 */
+	drm_dev_synchronize_unplug(dev);
+
 	remove_compat_control_link(dev);
 	drm_minor_unregister(dev, DRM_MINOR_ACCEL);
 	drm_minor_unregister(dev, DRM_MINOR_PRIMARY);
Signature · 1 line
2.54.0
#41 lore raw

Re: [PATCH v5 16/19] drm: fix race between partial drm_dev_register() failure and ioctl

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [High] drm_dev_register() fails to unmap CPU memory mappings on the error path, allowing userspace to retain access to physical memory that is subsequently freed.
- [High] drm_dev_synchronize_unplug() fails to protect against concurrent drm_open() calls, leaving dev->driver->open() vulnerable to use-after-free. --

commit e64c045955a50d4e3b92080a95ee364a79c625f5
Author: Danilo Krummrich <[email protected]>

drm: fix race between partial drm_dev_register() failure and ioctl

This commit introduces drm_dev_synchronize_unplug() to fix a race where userspace could enter a drm_dev_enter() critical section during a partial drm_dev_register() failure. It calls the new function on the error path to ensure all in-flight critical sections complete before cleanup proceeds.

Quoted 3 lines
drivers/gpu/drm/drm_drv.c +0 -0

[ ... ]

Quoted 9 lines
drivers/gpu/drm/drm_drv.c +5 -0
@@ -473,6 +473,22 @@ void drm_dev_exit(int idx)
 }
 EXPORT_SYMBOL(drm_dev_exit);
 
+/*
+ * Mark the device as unplugged and wait for any in-flight drm_dev_enter()
+ * critical sections to complete.
+ */
+static void drm_dev_synchronize_unplug(struct drm_device *dev)

drivers/gpu/drm/drm_drv.c void drm_dev_exit(int idx) · line 490

Quoted 10 lines
drivers/gpu/drm/drm_drv.c +10 -0
+{
+	/*
+	 * After synchronizing any critical read section is guaranteed to see
+	 * the new value of ->unplugged, and any critical section which might
+	 * still have seen the old value of ->unplugged is guaranteed to have
+	 * finished.
+	 */
+	dev->unplugged = true;
+	synchronize_srcu(&drm_unplug_srcu);
+}

[Severity: High]
This is a pre-existing issue, but does this function completely protect against concurrent drm_open() calls?

Looking at drm_open() in drivers/gpu/drm/drm_file.c, it checks the unplugged flag via drm_minor_acquire() without entering the SRCU critical section:

	minor = drm_minor_acquire(&drm_minors_xa, iminor(inode));
	if (IS_ERR(minor))
		return PTR_ERR(minor);

Could a thread calling open() read dev->unplugged == false just before this function sets it to true?

Since drm_open() doesn't hold the SRCU read lock, synchronize_srcu() would return immediately, allowing the teardown to proceed while dev->driver->open() executes concurrently.

[ ... ]

Quoted 4 lines
drivers/gpu/drm/drm_drv.c +0 -0
@@ -1118,6 +1127,13 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags)
 	if (dev->driver->unload)
 		dev->driver->unload(dev);
 err_minors:

drivers/gpu/drm/drm_drv.c int drm_dev_register(struct drm_device *dev, unsigned long flags) · line 1139

Quoted 10 lines
drivers/gpu/drm/drm_drv.c +7 -0
+	/*
+	 * If a minor was registered before the failure, userspace could have
+	 * opened it and entered a drm_dev_enter() critical section. Ensure all
+	 * such sections complete before we clean up.
+	 */
+	drm_dev_synchronize_unplug(dev);
+
 	remove_compat_control_link(dev);
 	drm_minor_unregister(dev, DRM_MINOR_ACCEL);
 	drm_minor_unregister(dev, DRM_MINOR_PRIMARY);

[Severity: High]
This isn't a bug introduced by this patch, but is there a missing call to unmap_mapping_range() on this error path?

If a minor was successfully registered before the failure, userspace could have opened it and mapped a GEM object. In drm_dev_unplug(), we unmap the CPU mappings:

	drm_dev_synchronize_unplug(dev);
	drm_dev_unregister(dev);
	unmap_mapping_range(dev->anon_inode->i_mapping, 0, 0, 1);

Does omitting this in the err_minors path in drm_dev_register() allow userspace to retain access to physical memory that devres will subsequently free?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=16
Reviewed-by on v5 16/19
#42 lore raw

Re: [PATCH v5 16/19] drm: fix race between partial drm_dev_register() failure and ioctl

In reply to Danilo Krummrich
Quoted 101 lines · Danilo Krummrich

If drm_dev_register() fails after registering a minor (e.g. render minor
registered, primary minor fails), userspace could have opened the first
minor and entered a drm_dev_enter() critical section. Since the unplugged flag was never set, the ioctl proceeds while the error path tears down device resources.

Fix this by introducing drm_dev_synchronize_unplug(), which sets the unplugged flag and waits for the SRCU barrier, ensuring all in-flight drm_dev_enter() critical sections complete before cleanup proceeds; call
it on the error path of drm_dev_register().

Fixes bee330f3d672 ("drm: Use srcu to protect drm_device.unplugged")
Reported-by [email protected]

Closes:
https://lore.kernel.org/all/[email protected]/

---
 drivers/gpu/drm/drm_drv.c | 34 +++++++++++++++++++++++++---------

 1 file changed, 25 insertions(+), 9 deletions(-)
drivers/gpu/drm/drm_drv.c +0 -0
index 675675480da4..e890052061f3 100644
@@ -473,6 +473,22 @@ void drm_dev_exit(int idx)
  }
  EXPORT_SYMBOL(drm_dev_exit);


+/*
+ * Mark the device as unplugged and wait for any in-flight drm_dev_enter()
+ * critical sections to complete.
+ */

drivers/gpu/drm/drm_drv.c +4 -0
+static void drm_dev_synchronize_unplug(struct drm_device *dev)
+{
+	/*
+	 * After synchronizing any critical read section is

guaranteed to see

drivers/gpu/drm/drm_drv.c +1 -0
+	 * the new value of ->unplugged, and any critical section

which might

drivers/gpu/drm/drm_drv.c +1 -0
+	 * still have seen the old value of ->unplugged is

guaranteed to have

drivers/gpu/drm/drm_drv.c +6 -0
+	 * finished.
+	 */
+	dev->unplugged = true;
+	synchronize_srcu(&drm_unplug_srcu);
+}
+

 /**
  * drm_dev_unplug - unplug a DRM device
  * @dev: DRM device

drivers/gpu/drm/drm_drv.c +0 -2
@@ -485,15 +501,7 @@ EXPORT_SYMBOL(drm_dev_exit);
   */
  void drm_dev_unplug(struct drm_device *dev)
  {
-	/*
-	 * After synchronizing any critical read section is

guaranteed to see

drivers/gpu/drm/drm_drv.c +0 -1
-	 * the new value of ->unplugged, and any critical section

which might

drivers/gpu/drm/drm_drv.c +0 -1
-	 * still have seen the old value of ->unplugged is

guaranteed to have

drivers/gpu/drm/drm_drv.c +1 -5
-	 * finished.
-	 */
-	dev->unplugged = true;
-	synchronize_srcu(&drm_unplug_srcu);
-
+	drm_dev_synchronize_unplug(dev);

  drm_dev_unregister(dev);

  /* Clear all CPU mappings pointing to this device */

drivers/gpu/drm/drm_drv.c +0 -0
@@ -1091,6 +1099,7 @@ int drm_dev_register(struct drm_device *dev,
 unsigned long flags)
  		goto err_minors;


  dev->registered = true;

drivers/gpu/drm/drm_drv.c +1 -0
+	dev->unplugged = false;


  if (driver->load) {
  ret = driver->load(dev, flags);

drivers/gpu/drm/drm_drv.c +0 -0
@@ -1118,6 +1127,13 @@ int drm_dev_register(struct drm_device *dev,
 unsigned long flags)
  	if (dev->driver->unload)
  		dev->driver->unload(dev);

 err_minors:

drivers/gpu/drm/drm_drv.c +2 -0
+	/*
+	 * If a minor was registered before the failure, userspace

could have

drivers/gpu/drm/drm_drv.c +1 -0
+	 * opened it and entered a drm_dev_enter() critical section.

Ensure all

drivers/gpu/drm/drm_drv.c +4 -0
+	 * such sections complete before we clean up.
+	 */
+	drm_dev_synchronize_unplug(dev);
+

  remove_compat_control_link(dev); drm_minor_unregister(dev, DRM_MINOR_ACCEL); drm_minor_unregister(dev, DRM_MINOR_PRIMARY);

maintainer Reviewed-by on v5 16/19
#43 lore raw

Re: [PATCH v5 16/19] drm: fix race between partial drm_dev_register() failure and ioctl

In reply to Danilo Krummrich
Quoted 11 lines · Danilo Krummrich

If drm_dev_register() fails after registering a minor (e.g. render minor registered, primary minor fails), userspace could have opened the first minor and entered a drm_dev_enter() critical section. Since the unplugged flag was never set, the ioctl proceeds while the error path tears down device resources.

Fix this by introducing drm_dev_synchronize_unplug(), which sets the unplugged flag and waits for the SRCU barrier, ensuring all in-flight drm_dev_enter() critical sections complete before cleanup proceeds; call it on the error path of drm_dev_register().

Quoted 5 lines
Fixes bee330f3d672 ("drm: Use srcu to protect drm_device.unplugged")
Reported-by [email protected]
author maintainer
#44 lore raw

PATCH v5 17/19 rust: drm: Add RegistrationData to drm::Driver

In reply to Danilo Krummrich

Add a RegistrationData GAT (Generic Associated Type) to drm::Driver. The lifetime parameter is tied to the parent bus device binding scope. Registration<'a, T> takes ownership of the data via Pin<KBox<_>>, storing it with its real lifetime. The pointer is written to drm::Device before drm_dev_register() to ensure it is already in place when ioctls arrive.

Device<T, Registered>::registration_data_with() provides access with the lifetime shortened from 'static via a pointer cast. Since Registration::drop() calls drm_dev_unplug(), which performs an SRCU barrier waiting for all drm_dev_enter() critical sections to complete, the data is guaranteed to remain valid for the duration of any RegistrationGuard.

---
 drivers/gpu/drm/nova/driver.rs | 17 ++++--
 drivers/gpu/drm/tyr/driver.rs  | 15 ++++--
 rust/kernel/drm/device.rs      | 48 +++++++++++++++++
 rust/kernel/drm/driver.rs      | 95 +++++++++++++++++++---------------
 rust/kernel/drm/gem/shmem.rs   |  1 +
 5 files changed, 125 insertions(+), 51 deletions(-)
drivers/gpu/drm/nova/driver.rs +12 -5
index e3c54303d70e..bd2a55405db8 100644
@@ -20,9 +20,10 @@
 
 pub(crate) struct NovaDriver;
 
-pub(crate) struct Nova {
+pub(crate) struct Nova<'bound> {
     #[expect(unused)]
     drm: ARef<drm::Device<NovaDriver>>,
+    _reg: drm::Registration<'bound, NovaDriver>,
 }
 
 /// Convienence type alias for the DRM device type for this driver
@@ -56,7 +57,7 @@ pub(crate) struct NovaData {
 
 impl auxiliary::Driver for NovaDriver {
     type IdInfo = ();
-    type Data<'bound> = Nova;
+    type Data<'bound> = Nova<'bound>;
     const ID_TABLE: auxiliary::IdTable<Self::IdInfo> = &AUX_TABLE;
 
     fn probe<'bound>(
@@ -66,15 +67,21 @@ fn probe<'bound>(
         let data = try_pin_init!(NovaData { adev: adev.into() });
 
         let drm = drm::UnregisteredDevice::<Self>::new(adev, data)?;
-        let drm = drm::Registration::new_foreign_owned(drm, adev.as_ref(), 0)?;
-
-        Ok(Nova { drm: drm.into() })
+        // SAFETY: `reg` is stored in `Nova` and dropped when the driver is unbound; it is
+        // never forgotten.
+        let reg = unsafe { drm::Registration::new(adev.as_ref(), drm, (), 0)? };
+
+        Ok(Nova {
+            drm: reg.device().into(),
+            _reg: reg,
+        })
     }
 }
 
 #[vtable]
 impl drm::Driver for NovaDriver {
     type Data = NovaData;
+    type RegistrationData<'a> = ();
     type File = File;
     type Object = gem::Object<NovaObject>;
     type ParentDevice<Ctx: DeviceContext> = auxiliary::Device<Ctx>;
drivers/gpu/drm/tyr/driver.rs +10 -5
index 7f082de6d6dc..8348c6cd3929 100644
@@ -52,8 +52,9 @@
 pub(crate) struct TyrPlatformDriver;
 
 #[pin_data(PinnedDrop)]
-pub(crate) struct TyrPlatformDriverData {
+pub(crate) struct TyrPlatformDriverData<'bound> {
     _device: ARef<TyrDrmDevice>,
+    _reg: drm::Registration<'bound, TyrDrmDriver>,
 }
 
 #[pin_data]
@@ -98,7 +99,7 @@ fn issue_soft_reset(dev: &Device, iomem: &IoMem<'_>) -> Result {
 
 impl platform::Driver for TyrPlatformDriver {
     type IdInfo = ();
-    type Data<'bound> = TyrPlatformDriverData;
+    type Data<'bound> = TyrPlatformDriverData<'bound>;
     const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
 
     fn probe<'bound>(
@@ -150,10 +151,13 @@ fn probe<'bound>(
         });
 
         let tdev = drm::UnregisteredDevice::<TyrDrmDriver>::new(pdev, data)?;
-        let tdev = drm::driver::Registration::new_foreign_owned(tdev, pdev.as_ref(), 0)?;
+        // SAFETY: `reg` is stored in `TyrPlatformDriverData` and dropped when the driver is
+        // unbound; it is never forgotten.
+        let reg = unsafe { drm::Registration::new(pdev.as_ref(), tdev, (), 0)? };
 
         let driver = TyrPlatformDriverData {
-            _device: tdev.into(),
+            _device: reg.device().into(),
+            _reg: reg,
         };
 
         // We need this to be dev_info!() because dev_dbg!() does not work at
@@ -164,7 +168,7 @@ fn probe<'bound>(
 }
 
 #[pinned_drop]
-impl PinnedDrop for TyrPlatformDriverData {
+impl PinnedDrop for TyrPlatformDriverData<'_> {
     fn drop(self: Pin<&mut Self>) {}
 }
 
@@ -181,6 +185,7 @@ fn drop(self: Pin<&mut Self>) {}
 #[vtable]
 impl drm::Driver for TyrDrmDriver {
     type Data = TyrDrmDeviceData;
+    type RegistrationData<'a> = ();
     type File = TyrDrmFileData;
     type Object = drm::gem::shmem::Object<BoData>;
     type ParentDevice<Ctx: DeviceContext> = platform::Device<Ctx>;
rust/kernel/drm/device.rs +48 -0

78 lines not on this page · raw message

rust/kernel/drm/driver.rs +54 -41

148 lines not on this page · raw message

rust/kernel/drm/gem/shmem.rs +1 -0
index c1d82a04878b..60dca8871b87 100644
@@ -665,6 +665,7 @@ fn new(
     #[vtable]
     impl drm::Driver for KunitDriver {
         type Data = KunitData;
+        type RegistrationData<'a> = ();
         type File = KunitFile;
         type Object = Object<KunitObject>;
         type ParentDevice<Ctx: device::DeviceContext> = faux::Device<Ctx>;
Signature · 1 line
2.54.0
#45 lore raw

Re: [PATCH v5 17/19] rust: drm: Add RegistrationData to drm::Driver

On Sun Jun 28, 2026 at 11:53 PM JST, Danilo Krummrich wrote:
<...>

Quoted 32 lines
rust/kernel/drm/device.rs +5 -0
index 5e8b75dab0a6..7a3a0e21e955 100644
@@ -32,6 +32,7 @@
 };
 use core::{
     alloc::Layout,
+    cell::UnsafeCell,
     marker::PhantomData,
     mem,
     ops::Deref,
@@ -247,6 +248,9 @@ pub fn new(
         // SAFETY: `drm_dev` is still private to this function.
         unsafe { (*drm_dev).driver = const { &Self::VTABLE } };
 
+        // SAFETY: `raw_drm` is valid; no concurrent access before registration.
+        unsafe { (*raw_drm.as_ptr()).registration_data = UnsafeCell::new(NonNull::dangling()) };
+
         // SAFETY: The reference count is one, and now we take ownership of that reference as a
         // `drm::Device`.
         // INVARIANT: We just created the device above, but have yet to call `drm_dev_register`.
@@ -270,6 +274,7 @@ pub fn new(
 pub struct Device<T: drm::Driver, C: DeviceContext = Normal> {
     dev: Opaque<bindings::drm_device>,
     data: T::Data,
+    pub(super) registration_data: UnsafeCell<NonNull<T::RegistrationData<'static>>>,
     _ctx: PhantomData<C>,
 }
 
@@ -278,6 +283,28 @@ pub(crate) fn as_raw(&self) -> *mut bindings::drm_device {
         self.dev.get()

rust/kernel/drm/device.rs pub(crate) fn as_raw(&self) -> *mut bindings::drm_device { · line 293

Quoted 10 lines
rust/kernel/drm/device.rs +8 -0
     }
 
+    /// Returns a reference to the registration data with lifetime shortened from `'static`.
+    ///
+    /// # Safety
+    ///
+    /// The caller must ensure that:
+    ///
+    /// - The parent bus device is bound (e.g. by holding an active `drm_dev_enter()` critical
+    ///   section via [`RegistrationGuard`]).

In practice, registration_data_unchecked is only ever called from a Registered context, so can't we move its definition to an impl<T> Device<T, Registered> block so this invariant is enforced by the type system?

author maintainer
#46 lore raw

PATCH v5 18/19 rust: drm: Pass registration data to ioctl handlers

In reply to Danilo Krummrich

Pass registration data to ioctl handlers via drm::Device<Registered>::registration_data_with(). The closure's HRTB ties the lifetime to the closure scope, and the pointer cast shortens it from 'static internally. The reference is valid for the duration of the drm_dev_enter/exit critical section held by RegistrationGuard.

---
 drivers/gpu/drm/nova/file.rs | 3 +++
 drivers/gpu/drm/tyr/file.rs  | 1 +
 rust/kernel/drm/ioctl.rs     | 8 ++++++--
 3 files changed, 10 insertions(+), 2 deletions(-)
drivers/gpu/drm/nova/file.rs +3 -0
index 19fb89b28984..208be4e38188 100644
@@ -28,6 +28,7 @@ impl File {
     /// IOCTL: get_param: Query GPU / driver metadata.
     pub(crate) fn get_param(
         dev: &NovaDevice<Registered>,
+        _reg_data: &(),
         getparam: &mut uapi::drm_nova_getparam,
         _file: &drm::File<File>,
     ) -> Result<u32> {
@@ -48,6 +49,7 @@ pub(crate) fn get_param(
     /// IOCTL: gem_create: Create a new DRM GEM object.
     pub(crate) fn gem_create(
         dev: &NovaDevice<Registered>,
+        _reg_data: &(),
         req: &mut uapi::drm_nova_gem_create,
         file: &drm::File<File>,
     ) -> Result<u32> {
@@ -61,6 +63,7 @@ pub(crate) fn gem_create(
     /// IOCTL: gem_info: Query GEM metadata.
     pub(crate) fn gem_info(
         _dev: &NovaDevice<Registered>,
+        _reg_data: &(),
         req: &mut uapi::drm_nova_gem_info,
         file: &drm::File<File>,
     ) -> Result<u32> {
drivers/gpu/drm/tyr/file.rs +1 -0
index fb9233eae01c..b686041d5d6b 100644
@@ -32,6 +32,7 @@ fn open(_dev: &drm::Device<Self::Driver>) -> Result<Pin<KBox<Self>>> {
 impl TyrDrmFileData {
     pub(crate) fn dev_query(
         ddev: &TyrDrmDevice<Registered>,
+        _reg_data: &(),
         devquery: &mut uapi::drm_panthor_dev_query,
         _file: &TyrDrmFile,
     ) -> Result<u32> {
rust/kernel/drm/ioctl.rs +6 -2
index f24cf7b146a8..9b45b741db1b 100644
@@ -95,6 +95,7 @@ pub const fn __dev_ctx_cast<T: crate::drm::Driver>(
 ///
 /// ```ignore
 /// fn foo(device: &kernel::drm::Device<Self, kernel::drm::Registered>,
+///        reg_data: &Self::RegistrationData<'_>,
 ///        data: &mut uapi::argument_type,
 ///        file: &kernel::drm::File<Self::File>,
 /// ) -> Result<u32>
@@ -169,13 +170,14 @@ macro_rules! declare_drm_ioctls {
                                     unsafe { &*__ptr },
                                     unreachable!(),
                                     unreachable!(),
+                                    unreachable!(),
                                 )
                             };
 
                             // Enforce that the handler accepts higher-ranked
                             // lifetimes, preventing it from requiring 'static
                             // references that could escape this scope.
-                            let _: for<'a> fn(&'a _, &'a mut _, &'a _) -> _ = $func;
+                            let _: for<'a> fn(&'a _, &'a _, &'a mut _, &'a _) -> _ = $func;
 
                             let Some(guard) = dev.registration_guard() else {
                                 return $crate::error::code::ENODEV.to_errno();
@@ -193,7 +195,9 @@ macro_rules! declare_drm_ioctls {
                             // SAFETY: This is just the DRM file structure
                             let file = unsafe { $crate::drm::File::from_raw(raw_file) };
 
-                            match $func(&*guard, data, file) {
+                            match guard.registration_data_with(|reg_data| {
+                                $func(&*guard, reg_data, data, file)
+                            }) {
                                 Err(e) => e.to_errno(),
                                 Ok(i) => i.try_into()
                                             .unwrap_or($crate::error::code::ERANGE.to_errno()),
Signature · 1 line
2.54.0
#47 lore raw

Re: [PATCH v5 18/19] rust: drm: Pass registration data to ioctl handlers

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Critical] Unsound lifetime shortening of the associated type `RegistrationData<'a>` allows safe code to…

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] Unsound lifetime shortening of the associated type RegistrationData<'a> allows safe code to cause a use-after-free. --

commit b0c6a9cfef59ba2cf3ed097deb2601fbd756cc0d
Author: Danilo Krummrich <[email protected]>

rust: drm: Pass registration data to ioctl handlers

This commit updates the DRM ioctl macro to pass registration data to the ioctl handlers using a closure-based HRTB pattern.

Quoted 4 lines
rust/kernel/drm/ioctl.rs +0 -0
index f24cf7b146a8e..9b45b741db1b9 100644

[ ... ]

rust/kernel/drm/ioctl.rs macro_rules! declare_drm_ioctls { · line 200

Quoted 8 lines
rust/kernel/drm/ioctl.rs +3 -1
@@ -193,7 +195,9 @@ macro_rules! declare_drm_ioctls {
                             // SAFETY: This is just the DRM file structure
                             let file = unsafe { $crate::drm::File::from_raw(raw_file) };
 
-                            match $func(&*guard, data, file) {
+                            match guard.registration_data_with(|reg_data| {
+                                $func(&*guard, reg_data, data, file)
+                            }) {

[Severity: Critical]
Does this HRTB closure pattern safely handle invariant types for RegistrationData<'a>?

The commit message states that the HRTB ties the lifetime to the closure scope and the internal pointer cast shortens it from 'static. However, if a driver uses an invariant type for RegistrationData<'a> (for example, something like Mutex<Option<&'a mut SomeStruct>>), couldn't this allow safe code to store the short-lived data or file reference into the underlying 'static registration data?

While the HRTB prevents references from returning out of the closure, it might not prevent storing external, longer-lived references into an invariant type provided by the closure. Once the ioctl returns and the local variable is dropped, the 'static registration data could still hold a dangling pointer to it, potentially leading to a use-after-free.

Quoted 3 lines
rust/kernel/drm/ioctl.rs +0 -0
                                 Err(e) => e.to_errno(),
                                 Ok(i) => i.try_into()
                                             .unwrap_or($crate::error::code::ERANGE.to_errno()),
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=18
maintainer Reviewed-by on v5 18/19
#48 lore raw

Re: [PATCH v5 18/19] rust: drm: Pass registration data to ioctl handlers

In reply to Danilo Krummrich
Quoted 8 lines · Danilo Krummrich

Pass registration data to ioctl handlers via drm::Device<Registered>::registration_data_with(). The closure's HRTB ties the lifetime to the closure scope, and the pointer cast shortens it from 'static internally. The reference is valid for the duration of the drm_dev_enter/exit critical section held by RegistrationGuard.

(Sashiko's report appears to be invalid - the HRTB is precisely what prevents the behavior it claims is possible)

author maintainer
#49 lore raw

PATCH v5 19/19 drm: nova: Use drm::Device<Registered> to access the parent bus device

In reply to Danilo Krummrich

The get_param ioctl needs access to the parent auxiliary device. Since ioctl handlers run inside a RegistrationGuard, accept &NovaDevice<Registered> to obtain &auxiliary::Device<Bound> via as_ref() directly. This removes the need for drm::Device data, hence set it to ().

---
 drivers/gpu/drm/nova/driver.rs | 11 ++---------
 drivers/gpu/drm/nova/file.rs   |  7 ++++---
 2 files changed, 6 insertions(+), 12 deletions(-)
drivers/gpu/drm/nova/driver.rs +2 -9
index bd2a55405db8..739690bc2db5 100644
@@ -29,11 +29,6 @@ pub(crate) struct Nova<'bound> {
 /// Convienence type alias for the DRM device type for this driver
 pub(crate) type NovaDevice<Ctx = drm::Normal> = drm::Device<NovaDriver, Ctx>;
 
-#[pin_data]
-pub(crate) struct NovaData {
-    pub(crate) adev: ARef<auxiliary::Device>,
-}
-
 const INFO: drm::DriverInfo = drm::DriverInfo {
     major: 0,
     minor: 0,
@@ -64,9 +59,7 @@ fn probe<'bound>(
         adev: &'bound auxiliary::Device<Core<'_>>,
         _info: &'bound Self::IdInfo,
     ) -> impl PinInit<Self::Data<'bound>, Error> + 'bound {
-        let data = try_pin_init!(NovaData { adev: adev.into() });
-
-        let drm = drm::UnregisteredDevice::<Self>::new(adev, data)?;
+        let drm = drm::UnregisteredDevice::<Self>::new(adev, Ok(()))?;
         // SAFETY: `reg` is stored in `Nova` and dropped when the driver is unbound; it is
         // never forgotten.
         let reg = unsafe { drm::Registration::new(adev.as_ref(), drm, (), 0)? };
@@ -80,7 +73,7 @@ fn probe<'bound>(
 
 #[vtable]
 impl drm::Driver for NovaDriver {
-    type Data = NovaData;
+    type Data = ();
     type RegistrationData<'a> = ();
     type File = File;
     type Object = gem::Object<NovaObject>;
drivers/gpu/drm/nova/file.rs +4 -3
index 208be4e38188..298c02bacb4b 100644
@@ -4,6 +4,8 @@
 use crate::gem::NovaObject;
 use kernel::{
     alloc::flags::*,
+    auxiliary,
+    device::Bound,
     drm::{
         self,
         gem::BaseObject,
@@ -32,9 +34,8 @@ pub(crate) fn get_param(
         getparam: &mut uapi::drm_nova_getparam,
         _file: &drm::File<File>,
     ) -> Result<u32> {
-        let adev = &dev.adev;
-        let parent = adev.parent();
-        let pdev: &pci::Device = parent.try_into()?;
+        let adev: &auxiliary::Device<Bound> = dev.as_ref();
+        let pdev: &pci::Device<Bound> = adev.parent().try_into()?;
 
         let value = match getparam.param as u32 {
             uapi::NOVA_GETPARAM_VRAM_BAR_SIZE => pdev.resource_len(1)?,
Signature · 1 line
2.54.0
maintainer Reviewed-by on v5 19/19
#50 lore raw

Re: [PATCH v5 19/19] drm: nova: Use drm::Device<Registered> to access the parent bus device

Quoted 18 lines · Danilo Krummrich

The get_param ioctl needs access to the parent auxiliary device. Since ioctl handlers run inside a RegistrationGuard, accept &NovaDevice<Registered> to obtain &auxiliary::Device<Bound> via as_ref() directly. This removes the need for drm::Device data, hence set it to ().

---
 drivers/gpu/drm/nova/driver.rs | 11 ++---------
 drivers/gpu/drm/nova/file.rs   |  7 ++++---
 2 files changed, 6 insertions(+), 12 deletions(-)
drivers/gpu/drm/nova/driver.rs +0 -0
index bd2a55405db8..739690bc2db5 100644

drivers/gpu/drm/nova/driver.rs pub(crate) struct Nova<'bound> { · line 35

Quoted 7 lines
drivers/gpu/drm/nova/driver.rs +0 -4
@@ -29,11 +29,6 @@ pub(crate) struct Nova<'bound> {
 /// Convienence type alias for the DRM device type for this driver
 pub(crate) type NovaDevice<Ctx = drm::Normal> = drm::Device<NovaDriver, Ctx>;
 
-#[pin_data]
-pub(crate) struct NovaData {
-    pub(crate) adev: ARef<auxiliary::Device>,
-}

Went through all of this series to see that! :)

Tested-by on the v5 series
#51 lore raw
In reply to Danilo Krummrich
Quoted 26 lines · Danilo Krummrich

DRM ioctls run in process context without any guarantee that the parent bus device is still bound. This series solves the problem by introducing RegistrationGuard -- a guard representing a drm_dev_enter/exit SRCU critical section that proves the parent bus device is bound for the lifetime of the guard.

As initial plumbing for this, the DRM DeviceContext typestates are
reworked: Uninit is renamed to Normal, defaults are adjusted, AlwaysRefCounted is restricted to Normal, and a Deref chain from Device<T, Registered> to Device<T, Normal> is established. This gives Device<T, Registered> the semantic that the device is currently registered and the parent bus device is bound, which makes the RegistrationGuard and ioctl dispatch much cleaner. An Ioctl context restricts registration_guard() to ioctl dispatch, where the DRM core guarantees prior registration.

On top of that, add RegistrationData as a GAT (Generic Associated Type) on drm::Driver, allowing drivers to store data whose lifetime is tied to the parent bus device binding scope. The data is allocated in Registration::new(), lifetime-erased to 'static for storage, and made accessible through Device<T, Registered>::registration_data_with(). The closure's HRTB ties the lifetime to the closure scope; internally the 'static pointer is cast back to the closure-scoped lifetime. The reference is valid for the duration of the drm_dev_enter/exit critical section held by RegistrationGuard.

Quoted 7 lines

Also update the ioctl dispatch macro to wrap every handler in a RegistrationGuard, returning ENODEV if the device has been unplugged, and pass the registration data to handlers.

A branch with all patches can be found in [1].

[1] https://git.kernel.org/pub/scm/linux/kernel/git/dakr/linux.git/log/?h=drm-lifetime

Thanks Danilo, I tested this whole series with Tyr today and it is working for me without any issues. It's included in latest branch
https://gitlab.freedesktop.org/panfrost/linux/-/commits/tyr-for-upstream

Quoted 96 lines

Changes in v5:
- Replace __call_ioctl() helper with a simple for<'a> fn(...) coercion
- Rebase onto latest drm-rust-next, which requires a faux::Device type with AsBusDevice impl and motivates the removal of the DeviceContext generic from shmem::Object entirely
- Replace ForLt with a GAT type RegistrationData<'a>
- Fix drm_dev_register() error path race on the C side
- Add a few missing #[inline] annotations

Changes in v4:
- Fix pre-existing unbounded lifetimes in ioctl handler arguments.
- Fix registration_guard() being callable on unregistered devices by introducing an Ioctl device context typestate; registration_guard() is now only available on Device<T, Ioctl>, which is exclusively constructed in ioctl dispatch context where the DRM core guarantees prior registration.
- Fix type inference allowing handlers to obtain Device<Registered> before RegistrationGuard is acquired.
- Make RegistrationGuard !Send via NotThreadSafe to prevent potential lockdep splats from cross-thread SRCU unlock.
- Store &Device<T, Registered> directly in RegistrationGuard instead of calling assume_ctx() in Deref.

Changes in v3:
- Rename UnbindGuard to RegistrationGuard
- RegistrationGuard no longer dereferences to &Device<Bound>; it dereferences to &drm::Device<T, Registered> instead
- Drop Registration::new() and rename Registration::new_with_lt() to Registration::new()
- Rework DeviceContext typestates: rename Uninit to Normal, restrict AlwaysRefCounted to Normal, establish Deref chain from Registered to Normal
- Add AsRef<ParentDevice<Bound>> on Device<T, Registered> for parent device access
- Move registration_data_with() from RegistrationGuard to drm::Device<T, Registered>
- Ioctl handlers no longer receive &Device<Bound>, only registration data and drm::Device<T, Registered>
- Use Device<Registered>::as_ref() to access parent device in nova-drm

Changes in v2:
- Replace unsafe direct registration data access in ioctl dispatch with safe UnbindGuard::registration_data_with() closure
- Eliminate unbind_guard() free function; use type-inference anchor to enable direct dev.unbind_guard() method call in the ioctl macro
- UnbindGuard::registration_data_with() provides both parent device and registration data to the closure
- Add nova-drm conversion patch demonstrating lifetime-aware registration data with &'bound auxiliary::Device<Bound>
- Various safety comment and documentation improvements

Danilo Krummrich (19):
rust: drm: ioctl: fix unbounded lifetimes in ioctl handler arguments
rust: drm: rename Uninit DeviceContext to Normal
rust: faux: add Device type with AsBusDevice support
rust: drm: Add Driver::ParentDevice associated type
rust: drm: change default DeviceContext to Normal
rust: drm: restrict AlwaysRefCounted to Normal Device context
rust: drm: restrict AlwaysRefCounted to Normal GEM Object context
rust: drm/gem: remove DeviceContext from shmem::Object
rust: drm: split Deref for Device context typestates
rust: drm: pin ioctl Device reference to Normal context
rust: drm: add Ioctl device context typestate
rust: drm: Add RegistrationGuard for drm_dev_enter/exit critical
sections
rust: drm: Wrap ioctl dispatch in RegistrationGuard
rust: drm: return ParentDevice from Device AsRef
rust: drm: add AsRef<ParentDevice<Bound>> for Device<Registered>
drm: fix race between partial drm_dev_register() failure and ioctl
rust: drm: Add RegistrationData to drm::Driver
rust: drm: Pass registration data to ioctl handlers
drm: nova: Use drm::Device<Registered> to access the parent bus device

 drivers/gpu/drm/drm_drv.c        |  34 +++-
 drivers/gpu/drm/nova/driver.rs   |  38 ++--
 drivers/gpu/drm/nova/file.rs     |  22 ++-
 drivers/gpu/drm/nova/gem.rs      |  18 +-
 drivers/gpu/drm/tyr/driver.rs    |  25 ++-
 drivers/gpu/drm/tyr/file.rs      |   8 +-
 drivers/gpu/drm/tyr/gem.rs       |  11 +-
 rust/kernel/drm/device.rs        | 293 +++++++++++++++++++++++--------
 rust/kernel/drm/driver.rs        | 111 +++++++-----
 rust/kernel/drm/gem/mod.rs       |  98 +++++------
 rust/kernel/drm/gem/shmem.rs     | 257 +++++++++++++--------------
 rust/kernel/drm/ioctl.rs         |  56 +++++-
 rust/kernel/drm/mod.rs           |   4 +-
 rust/kernel/faux.rs              |  69 ++++++--
 samples/rust/rust_driver_faux.rs |   3 +-
 15 files changed, 660 insertions(+), 387 deletions(-)

base-commit: fa8cc4e3067f958ea2057f37a8a6f9c6b10a9c03

Signature · 1 line
2.54.0
author maintainer Applied
#52 lore raw
In reply to Danilo Krummrich
Quoted 1 line · Danilo Krummrich

[PATCH v5 00/19] rust: drm: Higher-Ranked Lifetime private data

Applied, thanks!

Branch: drm-rust-next
Tree: https://gitlab.freedesktop.org/drm/rust/kernel.git

[1/19] rust: drm: ioctl: fix unbounded lifetimes in ioctl handler arguments

      commit: 68b151bc6145
[2/19] rust: drm: rename Uninit DeviceContext to Normal
      commit: fd0f827c532b

[3/19] rust: faux: add Device type with AsBusDevice support
commit: 893f39dadaa1

[ Drop redundant 'struct device' invariant; implied by valid
struct faux_device. - Danilo ]

[4/19] rust: drm: Add Driver::ParentDevice associated type

      commit: 49e27d58a06d
[5/19] rust: drm: change default DeviceContext to Normal
      commit: 903001300874
[6/19] rust: drm: restrict AlwaysRefCounted to Normal Device context
      commit: 506a7d63dab0
[7/19] rust: drm: restrict AlwaysRefCounted to Normal GEM Object context
      commit: ec8b2cc27c76
[8/19] rust: drm/gem: remove DeviceContext from shmem::Object
      commit: 7f994b8912eb
[9/19] rust: drm: split Deref for Device context typestates
      commit: 1c8a1f88ac32
[10/19] rust: drm: pin ioctl Device reference to Normal context
      commit: 499eb35cd477
[11/19] rust: drm: add Ioctl device context typestate
      commit: 86b20b11505d
[12/19] rust: drm: Add RegistrationGuard for drm_dev_enter/exit critical sections
      commit: 2455d5f2d5e8

[13/19] rust: drm: Wrap ioctl dispatch in RegistrationGuard
commit: 478da53e5b68

[ Use imperative mood in commit message; clarify __dev_ctx_cast() doc
comment to reflect Ioctl-to-Registered cast. - Danilo ]

[14/19] rust: drm: return ParentDevice from Device AsRef

      commit: 47f600d40bc6
[15/19] rust: drm: add AsRef<ParentDevice<Bound>> for Device<Registered>
      commit: 453197b7cc32
[16/19] drm: fix race between partial drm_dev_register() failure and ioctl
      commit: eb197f7d60f0

[17/19] rust: drm: Add RegistrationData to drm::Driver
commit: e15b88223dc1

[ Move registration_data_unchecked() to Device<T, Registered> impl
block. - Danilo ]

[18/19] rust: drm: Pass registration data to ioctl handlers

      commit: 3ba210061c29
[19/19] drm: nova: Use drm::Device<Registered> to access the parent bus device
      commit: 354a8f8b098b

The patches will appear in the next linux-next integration (typically within 24 hours on weekdays).

The patches are queued up for the upcoming merge window for the next major kernel release.