* [PATCH 1/6] rust: drm: Add Driver::ParentDevice associated type
2026-05-06 22:05 [PATCH 0/6] rust: drm: Higher-Ranked Lifetime private data Danilo Krummrich
@ 2026-05-06 22:05 ` Danilo Krummrich
2026-05-07 2:45 ` Claude review: " Claude Code Review Bot
2026-05-06 22:06 ` [PATCH 2/6] rust: drm: Add UnbindGuard for drm_dev_enter/exit critical sections Danilo Krummrich
` (5 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Danilo Krummrich @ 2026-05-06 22:05 UTC (permalink / raw)
To: aliceryhl, airlied, simona, daniel.almeida, acourbot, apopple,
ecourtney, deborah.brouwer, lyude, ojeda, boqun, gary, bjorn3_gh,
lossin, a.hindborg, tmgross
Cc: driver-core, nova-gpu, dri-devel, linux-kernel, rust-for-linux,
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.
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
---
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 +++
4 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/nova/driver.rs b/drivers/gpu/drm/nova/driver.rs
index 9eef28b506bb..9d4100f01ea7 100644
--- a/drivers/gpu/drm/nova/driver.rs
+++ b/drivers/gpu/drm/nova/driver.rs
@@ -2,7 +2,10 @@
use kernel::{
auxiliary,
- device::Core,
+ device::{
+ Core,
+ DeviceContext, //
+ },
drm::{
self,
gem,
@@ -59,7 +62,7 @@ fn probe(
) -> impl PinInit<Self, 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(Self { drm: drm.into() })
@@ -71,6 +74,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;
diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs
index 2743178af325..747745d23f31 100644
--- a/drivers/gpu/drm/tyr/driver.rs
+++ b/drivers/gpu/drm/tyr/driver.rs
@@ -8,7 +8,8 @@
device::{
Bound,
Core,
- Device, //
+ Device,
+ DeviceContext, //
},
drm,
drm::ioctl,
@@ -131,7 +132,7 @@ fn probe(
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 {
@@ -176,6 +177,7 @@ impl drm::Driver for TyrDrmDriver {
type Data = TyrDrmDeviceData;
type File = TyrDrmFileData;
type Object<R: drm::DeviceContext> = drm::gem::Object<TyrObject, R>;
+ type ParentDevice<Ctx: DeviceContext> = platform::Device<Ctx>;
const INFO: drm::DriverInfo = INFO;
diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs
index d91f19dcc375..492c2f2c7ca4 100644
--- a/rust/kernel/drm/device.rs
+++ b/rust/kernel/drm/device.rs
@@ -197,7 +197,10 @@ impl<T: drm::Driver> UnregisteredDevice<T> {
/// 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, Uninit>>());
@@ -207,7 +210,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, Uninit> = unsafe {
bindings::__drm_dev_alloc(
- dev.as_raw(),
+ dev.as_ref().as_raw(),
&Self::VTABLE,
layout.size(),
mem::offset_of!(Device<T, Uninit>, dev),
diff --git a/rust/kernel/drm/driver.rs b/rust/kernel/drm/driver.rs
index 294e17af86a3..9d06f8c5b2da 100644
--- a/rust/kernel/drm/driver.rs
+++ b/rust/kernel/drm/driver.rs
@@ -114,6 +114,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;
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Claude review: rust: drm: Add Driver::ParentDevice associated type
2026-05-06 22:05 ` [PATCH 1/6] rust: drm: Add Driver::ParentDevice associated type Danilo Krummrich
@ 2026-05-07 2:45 ` Claude Code Review Bot
0 siblings, 0 replies; 14+ messages in thread
From: Claude Code Review Bot @ 2026-05-07 2:45 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
Clean mechanical patch. Adds `ParentDevice<Ctx: DeviceContext>: device::AsBusDevice<Ctx>` to the `Driver` trait, constraining each DRM driver to declare its parent bus device type.
The change to `UnregisteredDevice::new()`:
```rust
pub fn new(
dev: &T::ParentDevice<device::Bound>,
data: impl PinInit<T::Data, Error>,
) -> Result<Self> {
```
is correct — it now takes the concrete bus device type instead of a generic `&device::Device`, providing compile-time assurance that the parent matches what the driver declared. The `dev.as_ref().as_raw()` call on line 280 correctly chains through `AsBusDevice -> AsRef<Device> -> as_raw()`.
The driver-side changes in nova and tyr are straightforward: they drop the `.as_ref()` from the `new()` call since the function now accepts the bus-typed device directly.
No issues.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 2/6] rust: drm: Add UnbindGuard for drm_dev_enter/exit critical sections
2026-05-06 22:05 [PATCH 0/6] rust: drm: Higher-Ranked Lifetime private data Danilo Krummrich
2026-05-06 22:05 ` [PATCH 1/6] rust: drm: Add Driver::ParentDevice associated type Danilo Krummrich
@ 2026-05-06 22:06 ` Danilo Krummrich
2026-05-07 2:45 ` Claude review: " Claude Code Review Bot
2026-05-06 22:06 ` [PATCH 3/6] rust: drm: Add RegistrationData to drm::Driver Danilo Krummrich
` (4 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Danilo Krummrich @ 2026-05-06 22:06 UTC (permalink / raw)
To: aliceryhl, airlied, simona, daniel.almeida, acourbot, apopple,
ecourtney, deborah.brouwer, lyude, ojeda, boqun, gary, bjorn3_gh,
lossin, a.hindborg, tmgross
Cc: driver-core, nova-gpu, dri-devel, linux-kernel, rust-for-linux,
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 UnbindGuard, a guard object representing a drm_dev_enter/exit SRCU
critical section that dereferences to &Device<Bound> of the parent bus
device. The guard is only available on Device<T, Registered>, ensuring
it cannot be used on unregistered devices.
Also add with_unbind_guard() as a convenience helper that executes a
closure with the bound device reference.
Switch Registration::drop from drm_dev_unregister() to drm_dev_unplug()
to provide the SRCU barrier that UnbindGuard's safety argument relies on.
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
---
rust/kernel/drm/device.rs | 80 ++++++++++++++++++++++++++++++++++++++-
rust/kernel/drm/driver.rs | 10 ++++-
2 files changed, 87 insertions(+), 3 deletions(-)
diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs
index 492c2f2c7ca4..bb685165032d 100644
--- a/rust/kernel/drm/device.rs
+++ b/rust/kernel/drm/device.rs
@@ -6,7 +6,11 @@
use crate::{
alloc::allocator::Kmalloc,
- bindings, device,
+ bindings,
+ device::{
+ self,
+ AsBusDevice as _, //
+ },
drm::{
self,
driver::AllocImpl,
@@ -334,6 +338,80 @@ pub(crate) unsafe fn assume_ctx<NewCtx: DeviceContext>(&self) -> &Device<T, NewC
}
}
+impl<T: drm::Driver> Device<T, Registered> {
+ /// Guard against the parent bus device being unbound.
+ ///
+ /// Returns an [`UnbindGuard`] if the device has not been unplugged, [`None`] otherwise.
+ ///
+ /// The returned guard dereferences to the parent bus device in the [`device::Bound`] context
+ /// (see [`Driver::ParentDevice`](drm::Driver::ParentDevice)).
+ /// Between `drm_dev_enter()` and `drm_dev_exit()` the parent device is guaranteed to be bound.
+ #[must_use]
+ pub fn unbind_guard(&self) -> Option<UnbindGuard<'_, T>> {
+ let mut idx: i32 = 0;
+ // SAFETY: `self.as_raw()` is a valid pointer to a `struct drm_device` by the type
+ // invariants of `Device<T, Registered>`.
+ if unsafe { bindings::drm_dev_enter(self.as_raw(), &mut idx) } {
+ Some(UnbindGuard { dev: self, idx })
+ } else {
+ None
+ }
+ }
+
+ /// Execute a closure while the parent bus device is guaranteed to be bound.
+ ///
+ /// Acquires the [`UnbindGuard`] and, if the device has not been unplugged, calls `f` with the
+ /// parent bus device. Returns `None` if the device has been unplugged.
+ pub fn with_unbind_guard<R>(
+ &self,
+ f: impl FnOnce(&T::ParentDevice<device::Bound>) -> R,
+ ) -> Option<R> {
+ let guard = self.unbind_guard()?;
+ Some(f(&guard))
+ }
+}
+
+/// A guard preventing the parent bus device from being unbound.
+///
+/// The guard dereferences to [`Driver::ParentDevice<Bound>`](drm::Driver::ParentDevice), providing
+/// access to the parent bus device with the guarantee that it 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.
+///
+/// See [`Device::unbind_guard`] for details on the safety argument.
+///
+/// # 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 UnbindGuard<'a, T: drm::Driver> {
+ dev: &'a Device<T, Registered>,
+ idx: i32,
+}
+
+impl<T: drm::Driver> Deref for UnbindGuard<'_, T> {
+ type Target = T::ParentDevice<device::Bound>;
+
+ fn deref(&self) -> &Self::Target {
+ // SAFETY:
+ // - The parent `struct device` is embedded in a `T::ParentDevice`, as guaranteed by
+ // `UnregisteredDevice::new` taking a `&T::ParentDevice<device::Bound>`.
+ // - By the type invariants of `UnbindGuard`, the parent device is bound for the lifetime
+ // of this guard.
+ unsafe { T::ParentDevice::from_device(self.dev.as_ref().as_bound()) }
+ }
+}
+
+impl<T: drm::Driver> Drop for UnbindGuard<'_, T> {
+ fn drop(&mut self) {
+ // SAFETY: `self.idx` was returned by a successful `drm_dev_enter()` call, as guaranteed
+ // by the type invariants of `UnbindGuard`.
+ unsafe { bindings::drm_dev_exit(self.idx) };
+ }
+}
+
impl<T: drm::Driver, C: DeviceContext> Deref for Device<T, C> {
type Target = T::Data;
diff --git a/rust/kernel/drm/driver.rs b/rust/kernel/drm/driver.rs
index 9d06f8c5b2da..751a68bb27e1 100644
--- a/rust/kernel/drm/driver.rs
+++ b/rust/kernel/drm/driver.rs
@@ -187,8 +187,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 `UnbindGuard`, 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()) };
}
}
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Claude review: rust: drm: Add UnbindGuard for drm_dev_enter/exit critical sections
2026-05-06 22:06 ` [PATCH 2/6] rust: drm: Add UnbindGuard for drm_dev_enter/exit critical sections Danilo Krummrich
@ 2026-05-07 2:45 ` Claude Code Review Bot
0 siblings, 0 replies; 14+ messages in thread
From: Claude Code Review Bot @ 2026-05-07 2:45 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
This is the central safety-critical patch.
**UnbindGuard design**: The guard struct is clean:
```rust
pub struct UnbindGuard<'a, T: drm::Driver> {
dev: &'a Device<T, Registered>,
idx: i32,
}
```
The `Deref` implementation on `UnbindGuard` is the most safety-sensitive part:
```rust
unsafe { T::ParentDevice::from_device(self.dev.as_ref().as_bound()) }
```
This calls `as_bound()` on the parent `device::Device`, which presumably returns `&Device<Bound>`. The safety argument is that within the `drm_dev_enter/exit` critical section, the parent device must be bound because `Registration::drop` uses `drm_dev_unplug()` (which performs an SRCU barrier). This is correct: `drm_dev_unplug()` marks the device as unplugged and then calls `synchronize_srcu()`, so any existing `drm_dev_enter()` critical section will complete before unregistration proceeds.
**Concern**: The `as_bound()` method call assumes that `Device<Core>` or `Device<Normal>` can be safely cast to `Device<Bound>`. The safety here relies entirely on the SRCU critical section guarantee. The commit message documents this well, but I'd note that this depends on `Registration::drop` **always** using `drm_dev_unplug()` and never having an alternate code path that bypasses it (e.g., error cleanup paths). The code looks correct for this.
**Switch to drm_dev_unplug()**: The change in `Registration::drop`:
```rust
unsafe { bindings::drm_dev_unplug(self.0.as_raw()) };
```
This is essential and correct. Note that `drm_dev_unplug()` is a superset of `drm_dev_unregister()` — it calls `drm_dev_unregister()` internally after marking the device as unplugged. The switch is backward-compatible.
**Minor**: The typo fix from "the this" to "that this" in the existing SAFETY comment is a nice cleanup.
**`with_unbind_guard` convenience**: Simple and correct, no issues.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 3/6] rust: drm: Add RegistrationData to drm::Driver
2026-05-06 22:05 [PATCH 0/6] rust: drm: Higher-Ranked Lifetime private data Danilo Krummrich
2026-05-06 22:05 ` [PATCH 1/6] rust: drm: Add Driver::ParentDevice associated type Danilo Krummrich
2026-05-06 22:06 ` [PATCH 2/6] rust: drm: Add UnbindGuard for drm_dev_enter/exit critical sections Danilo Krummrich
@ 2026-05-06 22:06 ` Danilo Krummrich
2026-05-07 2:45 ` Claude review: " Claude Code Review Bot
2026-05-06 22:06 ` [PATCH 4/6] rust: drm: Wrap ioctl dispatch in UnbindGuard Danilo Krummrich
` (3 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Danilo Krummrich @ 2026-05-06 22:06 UTC (permalink / raw)
To: aliceryhl, airlied, simona, daniel.almeida, acourbot, apopple,
ecourtney, deborah.brouwer, lyude, ojeda, boqun, gary, bjorn3_gh,
lossin, a.hindborg, tmgross
Cc: driver-core, nova-gpu, dri-devel, linux-kernel, rust-for-linux,
Danilo Krummrich
Add a RegistrationData associated type to drm::Driver. This is a ForLt
type whose lifetime is tied to the parent bus device binding scope.
Registration<T> takes ownership of the data via Pin<KBox<_>>, erasing
the lifetime to 'static for storage. The pointer is written to
drm::Device before drm_dev_register() to ensure it is already in place
when ioctls arrive.
UnbindGuard::registration_data() provides access with the lifetime
shortened from 'static via ForLt::cast_ref. 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 UnbindGuard.
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
---
drivers/gpu/drm/nova/driver.rs | 6 ++-
drivers/gpu/drm/tyr/driver.rs | 6 ++-
rust/kernel/drm/device.rs | 40 +++++++++++++++
rust/kernel/drm/driver.rs | 89 +++++++++++++++++++++++++++-------
rust/kernel/drm/mod.rs | 1 +
5 files changed, 121 insertions(+), 21 deletions(-)
diff --git a/drivers/gpu/drm/nova/driver.rs b/drivers/gpu/drm/nova/driver.rs
index 9d4100f01ea7..54a3391371ba 100644
--- a/drivers/gpu/drm/nova/driver.rs
+++ b/drivers/gpu/drm/nova/driver.rs
@@ -12,7 +12,8 @@
ioctl, //
},
prelude::*,
- sync::aref::ARef, //
+ sync::aref::ARef,
+ types::ForLt, //
};
use crate::file::File;
@@ -63,7 +64,7 @@ fn probe(
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)?;
+ let drm = drm::Registration::new_foreign_owned(drm, adev.as_ref(), (), 0)?;
Ok(Self { drm: drm.into() })
}
@@ -72,6 +73,7 @@ fn probe(
#[vtable]
impl drm::Driver for NovaDriver {
type Data = NovaData;
+ type RegistrationData = ForLt!(());
type File = File;
type Object<Ctx: drm::DeviceContext> = gem::Object<NovaObject, Ctx>;
type ParentDevice<Ctx: DeviceContext> = auxiliary::Device<Ctx>;
diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs
index 747745d23f31..7ac3707823b6 100644
--- a/drivers/gpu/drm/tyr/driver.rs
+++ b/drivers/gpu/drm/tyr/driver.rs
@@ -25,7 +25,8 @@
aref::ARef,
Mutex, //
},
- time, //
+ time,
+ types::ForLt, //
};
use crate::{
@@ -133,7 +134,7 @@ fn probe(
});
let tdev = drm::UnregisteredDevice::<TyrDrmDriver>::new(pdev, data)?;
- let tdev = drm::driver::Registration::new_foreign_owned(tdev, pdev.as_ref(), 0)?;
+ let tdev = drm::driver::Registration::new_foreign_owned(tdev, pdev.as_ref(), (), 0)?;
let driver = TyrPlatformDriverData {
_device: tdev.into(),
@@ -175,6 +176,7 @@ fn drop(self: Pin<&mut Self>) {
#[vtable]
impl drm::Driver for TyrDrmDriver {
type Data = TyrDrmDeviceData;
+ type RegistrationData = ForLt!(());
type File = TyrDrmFileData;
type Object<R: drm::DeviceContext> = drm::gem::Object<TyrObject, R>;
type ParentDevice<Ctx: DeviceContext> = platform::Device<Ctx>;
diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs
index bb685165032d..11edbe6f9f42 100644
--- a/rust/kernel/drm/device.rs
+++ b/rust/kernel/drm/device.rs
@@ -23,6 +23,7 @@
AlwaysRefCounted, //
},
types::{
+ ForLt,
NotThreadSafe,
Opaque, //
},
@@ -35,6 +36,7 @@
};
use core::{
alloc::Layout,
+ cell::UnsafeCell,
marker::PhantomData,
mem,
ops::Deref,
@@ -239,6 +241,9 @@ pub fn new(
unsafe { bindings::drm_dev_put(drm_dev) };
})?;
+ // 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 +275,7 @@ pub fn new(
pub struct Device<T: drm::Driver, C: DeviceContext = Registered> {
dev: Opaque<bindings::drm_device>,
data: T::Data,
+ pub(super) registration_data: UnsafeCell<NonNull<<T::RegistrationData as ForLt>::Of<'static>>>,
_ctx: PhantomData<C>,
}
@@ -278,6 +284,23 @@ pub(crate) fn as_raw(&self) -> *mut bindings::drm_device {
self.dev.get()
}
+ /// Returns a reference to the registration data with lifetime shortened
+ /// from `'static`.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure the parent bus device is bound. This is
+ /// typically guaranteed by holding an active `drm_dev_enter()` critical
+ /// section (e.g. via [`UnbindGuard`]).
+ #[doc(hidden)]
+ pub unsafe fn raw_registration_data(&self) -> &<T::RegistrationData as ForLt>::Of<'_> {
+ // SAFETY: Caller guarantees the parent bus device is bound, hence
+ // the pointer is valid.
+ let static_ref = unsafe { (*self.registration_data.get()).as_ref() };
+
+ T::RegistrationData::cast_ref(static_ref)
+ }
+
/// # Safety
///
/// `ptr` must be a valid pointer to a `struct device` embedded in `Self`.
@@ -391,6 +414,23 @@ pub struct UnbindGuard<'a, T: drm::Driver> {
idx: i32,
}
+impl<T: drm::Driver> UnbindGuard<'_, T> {
+ /// Returns a reference to the registration data with its lifetime shortened from `'static`
+ /// to the guard's borrow lifetime.
+ ///
+ /// The data is owned by [`Registration`](drm::driver::Registration) and is guaranteed to
+ /// remain valid for the duration of this guard, since
+ /// [`Registration`](drm::driver::Registration)'s `drop` calls
+ /// `drm_dev_unplug()` which waits for all `drm_dev_enter()` critical sections to complete.
+ pub fn registration_data(&self) -> &<T::RegistrationData as ForLt>::Of<'_> {
+ // SAFETY: The pointer was set in `Registration::new()` before `drm_dev_register()`, and
+ // is only invalidated after `drm_dev_unplug()` in `Registration::drop()`. Since we hold
+ // an active `drm_dev_enter()` critical section, the SRCU barrier in `drm_dev_unplug()`
+ // guarantees the pointer is still valid.
+ unsafe { self.dev.raw_registration_data() }
+ }
+}
+
impl<T: drm::Driver> Deref for UnbindGuard<'_, T> {
type Target = T::ParentDevice<device::Bound>;
diff --git a/rust/kernel/drm/driver.rs b/rust/kernel/drm/driver.rs
index 751a68bb27e1..3a49ef324ada 100644
--- a/rust/kernel/drm/driver.rs
+++ b/rust/kernel/drm/driver.rs
@@ -11,7 +11,8 @@
drm,
error::to_result,
prelude::*,
- sync::aref::ARef, //
+ sync::aref::ARef,
+ types::ForLt, //
};
use core::{
mem,
@@ -108,6 +109,16 @@ pub trait Driver {
/// Context data associated with the DRM driver
type Data: Sync + Send;
+ /// Data owned by the [`Registration`] and accessible through [`drm::device::UnbindGuard`].
+ ///
+ /// This is a [`ForLt`](trait@ForLt) type whose lifetime is tied to the parent bus
+ /// device binding scope.
+ /// The data is only accessible while the parent bus device is bound (i.e. within a
+ /// `drm_dev_enter/exit` critical section), and references handed out by
+ /// [`UnbindGuard::registration_data()`](drm::device::UnbindGuard::registration_data) have
+ /// their lifetime shortened accordingly via [`ForLt::cast_ref`].
+ type RegistrationData: ForLt;
+
/// The type used to manage memory for this driver.
type Object<Ctx: drm::DeviceContext>: AllocImpl;
@@ -127,12 +138,44 @@ pub trait Driver {
/// The registration type of a `drm::Device`.
///
/// Once the `Registration` structure is dropped, the device is unregistered.
-pub struct Registration<T: Driver>(ARef<drm::Device<T>>);
+pub struct Registration<T: Driver> {
+ drm: ARef<drm::Device<T>>,
+ #[allow(dead_code)]
+ reg_data: Pin<KBox<<T::RegistrationData as ForLt>::Of<'static>>>,
+}
-impl<T: Driver> Registration<T> {
- fn new(drm: drm::UnregisteredDevice<T>, flags: usize) -> Result<Self> {
- // SAFETY: `drm.as_raw()` is valid by the invariants of `drm::Device`.
- to_result(unsafe { bindings::drm_dev_register(drm.as_raw(), flags) })?;
+impl<T: Driver> Registration<T>
+where
+ for<'a> <T::RegistrationData as ForLt>::Of<'a>: Send,
+{
+ fn new<'bound, E>(
+ drm: drm::UnregisteredDevice<T>,
+ reg_data: impl PinInit<<T::RegistrationData as ForLt>::Of<'bound>, E>,
+ flags: usize,
+ ) -> Result<Self>
+ where
+ Error: From<E>,
+ {
+ let reg_data: Pin<KBox<<T::RegistrationData as ForLt>::Of<'bound>>> =
+ KBox::pin_init(reg_data, GFP_KERNEL)?;
+
+ // SAFETY: `ForLt` guarantees covariance; lifetimes do not affect layout.
+ let reg_data: Pin<KBox<<T::RegistrationData as ForLt>::Of<'static>>> =
+ unsafe { mem::transmute(reg_data) };
+
+ // Store the registration data pointer in the device before registration, so that it is
+ // visible once ioctls can be called.
+ //
+ // SAFETY: No concurrent access; the device is not yet registered.
+ unsafe { *drm.registration_data.get() = NonNull::from(Pin::get_ref(reg_data.as_ref())) }
+
+ // SAFETY: `drm` is a valid, initialized but not yet registered DRM device.
+ let ret = unsafe { bindings::drm_dev_register(drm.as_raw(), flags) };
+ if let Err(e) = to_result(ret) {
+ // SAFETY: No concurrent access; registration failed.
+ unsafe { *drm.registration_data.get() = NonNull::dangling() };
+ return Err(e);
+ }
// SAFETY: We just called `drm_dev_register` above
let new = NonNull::from(unsafe { drm.assume_ctx() });
@@ -144,46 +187,55 @@ fn new(drm: drm::UnregisteredDevice<T>, flags: usize) -> Result<Self> {
// one reference to the device - which we take ownership over here.
let new = unsafe { ARef::from_raw(new) };
- Ok(Self(new))
+ Ok(Self { drm: new, reg_data })
}
/// Registers a new [`UnregisteredDevice`](drm::UnregisteredDevice) with userspace.
///
/// Ownership of the [`Registration`] object is passed to [`devres::register`].
- pub fn new_foreign_owned<'a>(
+ pub fn new_foreign_owned<'bound, E>(
drm: drm::UnregisteredDevice<T>,
- dev: &'a device::Device<device::Bound>,
+ dev: &'bound device::Device<device::Bound>,
+ reg_data: impl PinInit<<T::RegistrationData as ForLt>::Of<'bound>, E>,
flags: usize,
- ) -> Result<&'a drm::Device<T>>
+ ) -> Result<&'bound drm::Device<T>>
where
T: 'static,
+ Error: From<E>,
{
if drm.as_ref().as_raw() != dev.as_raw() {
return Err(EINVAL);
}
- let reg = Registration::<T>::new(drm, flags)?;
+ let reg = Registration::<T>::new(drm, reg_data, flags)?;
let drm = NonNull::from(reg.device());
- devres::register(dev, reg, GFP_KERNEL)?;
+ devres::register::<_, core::convert::Infallible>(dev, reg, GFP_KERNEL)?;
// SAFETY: Since `reg` was passed to devres::register(), the device now owns the lifetime
- // of the DRM registration - ensuring that this references lives for at least as long as 'a.
+ // of the DRM registration - ensuring that this reference lives for
+ // at least as long as 'bound.
Ok(unsafe { drm.as_ref() })
}
/// Returns a reference to the `Device` instance for this registration.
pub fn device(&self) -> &drm::Device<T> {
- &self.0
+ &self.drm
}
}
// SAFETY: `Registration` doesn't offer any methods or access to fields when shared between
// threads, hence it's safe to share it.
-unsafe impl<T: Driver> Sync for Registration<T> {}
+unsafe impl<T: Driver> Sync for Registration<T> where
+ for<'a> <T::RegistrationData as ForLt>::Of<'a>: Send
+{
+}
// SAFETY: Registration with and unregistration from the DRM subsystem can happen from any thread.
-unsafe impl<T: Driver> Send for Registration<T> {}
+unsafe impl<T: Driver> Send for Registration<T> where
+ for<'a> <T::RegistrationData as ForLt>::Of<'a>: Send
+{
+}
impl<T: Driver> Drop for Registration<T> {
fn drop(&mut self) {
@@ -195,6 +247,9 @@ fn drop(&mut self) {
//
// SAFETY: Safe by the invariant of `ARef<drm::Device<T>>`. The existence of this
// `Registration` also guarantees that this `drm::Device` is actually registered.
- unsafe { bindings::drm_dev_unplug(self.0.as_raw()) };
+ unsafe { bindings::drm_dev_unplug(self.drm.as_raw()) };
+ // After drm_dev_unplug(), the SRCU barrier guarantees that all UnbindGuard critical
+ // sections have completed, so no one holds a reference to reg_data anymore.
+ // reg_data is dropped here automatically.
}
}
diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs
index 64a43cb0fe57..6c0ba9c82b92 100644
--- a/rust/kernel/drm/mod.rs
+++ b/rust/kernel/drm/mod.rs
@@ -11,6 +11,7 @@
pub use self::device::Device;
pub use self::device::DeviceContext;
pub use self::device::Registered;
+pub use self::device::UnbindGuard;
pub use self::device::Uninit;
pub use self::device::UnregisteredDevice;
pub use self::driver::Driver;
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Claude review: rust: drm: Add RegistrationData to drm::Driver
2026-05-06 22:06 ` [PATCH 3/6] rust: drm: Add RegistrationData to drm::Driver Danilo Krummrich
@ 2026-05-07 2:45 ` Claude Code Review Bot
0 siblings, 0 replies; 14+ messages in thread
From: Claude Code Review Bot @ 2026-05-07 2:45 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
This is the most complex patch, involving higher-ranked lifetime (HRL) manipulation.
**ForLt lifetime erasure**: The key transmute:
```rust
let reg_data: Pin<KBox<<T::RegistrationData as ForLt>::Of<'bound>>> =
KBox::pin_init(reg_data, GFP_KERNEL)?;
let reg_data: Pin<KBox<<T::RegistrationData as ForLt>::Of<'static>>> =
unsafe { mem::transmute(reg_data) };
```
The safety argument is that `ForLt` guarantees covariance and lifetimes don't affect layout. This is correct: `ForLt::Of<'a>` is guaranteed to have the same representation for all `'a`, and `Pin<KBox<T>>` is `repr(transparent)` over a pointer.
**UnsafeCell for registration_data field**:
```rust
pub(super) registration_data: UnsafeCell<NonNull<<T::RegistrationData as ForLt>::Of<'static>>>,
```
Using `UnsafeCell` here is appropriate since the field is written before registration and read during ioctl dispatch. However, I note that the field is `pub(super)` — this makes it accessible from within the `drm` module but not outside, which is reasonable.
**Initialization with dangling pointer**:
```rust
unsafe { (*raw_drm.as_ptr()).registration_data = UnsafeCell::new(NonNull::dangling()) };
```
This is initialized to a dangling pointer before the real data pointer is stored in `Registration::new()`. The window between `UnregisteredDevice::new()` and `Registration::new()` has a dangling pointer. This is safe because the device isn't registered yet (no ioctls can arrive), but it's worth noting.
**Error handling in Registration::new()**: On failure after `drm_dev_register()`, the code resets the pointer to dangling:
```rust
if let Err(e) = to_result(ret) {
unsafe { *drm.registration_data.get() = NonNull::dangling() };
return Err(e);
}
```
This is correct — if registration fails, the caller will drop the `UnregisteredDevice`, and the dangling pointer is never dereferenced.
**Registration::drop ordering concern**: In the drop impl:
```rust
unsafe { bindings::drm_dev_unplug(self.drm.as_raw()) };
// reg_data is dropped here automatically.
```
The SRCU barrier from `drm_dev_unplug()` happens before `reg_data` is dropped, which is correct. The comment documents this. The field drop order in Rust is declaration order (`drm` then `reg_data`), so `drm` would be dropped first... but wait, the manual `drm_dev_unplug()` call is in the explicit `drop()` implementation, and field drops happen *after* the explicit `drop()` body completes. So the actual order is: `drm_dev_unplug()` (in drop body) → `drm` field dropped (ARef put) → `reg_data` field dropped. This is correct.
**devres::register type annotation change**:
```rust
devres::register::<_, core::convert::Infallible>(dev, reg, GFP_KERNEL)?;
```
This explicit `Infallible` type annotation suggests the `devres::register` signature changed to accept a generic error type. This looks like an adaptation to the new `Registration` struct.
**Send/Sync bounds**: The `where for<'a> <T::RegistrationData as ForLt>::Of<'a>: Send` bound is correctly applied to the `impl` block, `Send`, and `Sync` implementations. This ensures registration data is safe to access from any thread.
**The `#[allow(dead_code)]` on reg_data**: This annotation on the `reg_data` field:
```rust
#[allow(dead_code)]
reg_data: Pin<KBox<<T::RegistrationData as ForLt>::Of<'static>>>,
```
This is needed because the field is only read through the raw pointer stored in `Device`, not through the struct field directly. The field exists purely for ownership/drop semantics. Correct usage.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 4/6] rust: drm: Wrap ioctl dispatch in UnbindGuard
2026-05-06 22:05 [PATCH 0/6] rust: drm: Higher-Ranked Lifetime private data Danilo Krummrich
` (2 preceding siblings ...)
2026-05-06 22:06 ` [PATCH 3/6] rust: drm: Add RegistrationData to drm::Driver Danilo Krummrich
@ 2026-05-06 22:06 ` Danilo Krummrich
2026-05-07 2:45 ` Claude review: " Claude Code Review Bot
2026-05-06 22:06 ` [PATCH 5/6] rust: drm: Pass registration data to ioctl handlers Danilo Krummrich
` (2 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Danilo Krummrich @ 2026-05-06 22:06 UTC (permalink / raw)
To: aliceryhl, airlied, simona, daniel.almeida, acourbot, apopple,
ecourtney, deborah.brouwer, lyude, ojeda, boqun, gary, bjorn3_gh,
lossin, a.hindborg, tmgross
Cc: driver-core, nova-gpu, dri-devel, linux-kernel, rust-for-linux,
Danilo Krummrich
Run every ioctl handler inside a drm_dev_enter/exit critical section via
UnbindGuard. If the device has been unplugged, the ioctl returns ENODEV
without calling the handler.
A free-function wrapper is added because the macro context prevents
method resolution from inferring the driver type.
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
---
rust/kernel/drm/device.rs | 7 +++++++
rust/kernel/drm/ioctl.rs | 13 +++++++++++--
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs
index 11edbe6f9f42..0049ea69f716 100644
--- a/rust/kernel/drm/device.rs
+++ b/rust/kernel/drm/device.rs
@@ -452,6 +452,13 @@ fn drop(&mut self) {
}
}
+/// Free-function equivalent of [`Device::unbind_guard()`] for use in macro contexts where method
+/// resolution cannot infer the driver type.
+#[doc(hidden)]
+pub fn unbind_guard<T: drm::Driver>(dev: &Device<T, Registered>) -> Option<UnbindGuard<'_, T>> {
+ dev.unbind_guard()
+}
+
impl<T: drm::Driver, C: DeviceContext> Deref for Device<T, C> {
type Target = T::Data;
diff --git a/rust/kernel/drm/ioctl.rs b/rust/kernel/drm/ioctl.rs
index cf328101dde4..aa72b44f645d 100644
--- a/rust/kernel/drm/ioctl.rs
+++ b/rust/kernel/drm/ioctl.rs
@@ -87,7 +87,10 @@ pub mod internal {
/// file: &kernel::drm::File<Self::File>,
/// ) -> Result<u32>
/// ```
-/// where `Self` is the drm::drv::Driver implementation these ioctls are being declared within.
+/// where `Self` is the `drm::Driver` implementation these ioctls are being declared within.
+///
+/// The ioctl runs inside a `drm_dev_enter/exit` critical section. If the device has been
+/// unplugged, the ioctl returns `ENODEV` without calling the handler.
///
/// # Examples
///
@@ -134,7 +137,13 @@ 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 = unsafe {
+ $crate::drm::device::Device::from_raw(raw_dev)
+ };
+ let _guard = match $crate::drm::device::unbind_guard(dev) {
+ Some(g) => g,
+ None => 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.
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Claude review: rust: drm: Wrap ioctl dispatch in UnbindGuard
2026-05-06 22:06 ` [PATCH 4/6] rust: drm: Wrap ioctl dispatch in UnbindGuard Danilo Krummrich
@ 2026-05-07 2:45 ` Claude Code Review Bot
0 siblings, 0 replies; 14+ messages in thread
From: Claude Code Review Bot @ 2026-05-07 2:45 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
The ioctl macro change:
```rust
let dev = unsafe {
$crate::drm::device::Device::from_raw(raw_dev)
};
let _guard = match $crate::drm::device::unbind_guard(dev) {
Some(g) => g,
None => return $crate::error::code::ENODEV.to_errno(),
};
```
**Free-function wrapper**: The `unbind_guard` free function is a reasonable workaround for macro type inference limitations. It's marked `#[doc(hidden)]` appropriately.
**ENODEV return**: Returning `ENODEV` when the device has been unplugged is the standard behavior for unplugged DRM devices. Correct.
**Guard naming**: The guard is named `_guard` (with underscore prefix) because in this patch it's not used for anything other than keeping the critical section open. This changes in patch 5 where it becomes `guard`. Clean incremental development.
No issues.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 5/6] rust: drm: Pass registration data to ioctl handlers
2026-05-06 22:05 [PATCH 0/6] rust: drm: Higher-Ranked Lifetime private data Danilo Krummrich
` (3 preceding siblings ...)
2026-05-06 22:06 ` [PATCH 4/6] rust: drm: Wrap ioctl dispatch in UnbindGuard Danilo Krummrich
@ 2026-05-06 22:06 ` Danilo Krummrich
2026-05-07 2:45 ` Claude review: " Claude Code Review Bot
2026-05-06 22:06 ` [PATCH 6/6] rust: drm: Pass bound parent device " Danilo Krummrich
2026-05-07 2:45 ` Claude review: rust: drm: Higher-Ranked Lifetime private data Claude Code Review Bot
6 siblings, 1 reply; 14+ messages in thread
From: Danilo Krummrich @ 2026-05-06 22:06 UTC (permalink / raw)
To: aliceryhl, airlied, simona, daniel.almeida, acourbot, apopple,
ecourtney, deborah.brouwer, lyude, ojeda, boqun, gary, bjorn3_gh,
lossin, a.hindborg, tmgross
Cc: driver-core, nova-gpu, dri-devel, linux-kernel, rust-for-linux,
Danilo Krummrich
Pass &<Self::RegistrationData as ForLt>::Of<'_> after the device
argument to ioctl handlers. The lifetime is shortened from 'static via
ForLt::cast_ref; the reference is valid for the duration of the
drm_dev_enter/exit critical section held by UnbindGuard.
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
---
drivers/gpu/drm/nova/file.rs | 3 +++
drivers/gpu/drm/tyr/file.rs | 1 +
rust/kernel/drm/ioctl.rs | 5 +++--
3 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/nova/file.rs b/drivers/gpu/drm/nova/file.rs
index a3b7bd36792c..4c9fa409a8be 100644
--- a/drivers/gpu/drm/nova/file.rs
+++ b/drivers/gpu/drm/nova/file.rs
@@ -24,6 +24,7 @@ impl File {
/// IOCTL: get_param: Query GPU / driver metadata.
pub(crate) fn get_param(
dev: &NovaDevice,
+ _reg_data: &(),
getparam: &mut uapi::drm_nova_getparam,
_file: &drm::File<File>,
) -> Result<u32> {
@@ -44,6 +45,7 @@ pub(crate) fn get_param(
/// IOCTL: gem_create: Create a new DRM GEM object.
pub(crate) fn gem_create(
dev: &NovaDevice,
+ _reg_data: &(),
req: &mut uapi::drm_nova_gem_create,
file: &drm::File<File>,
) -> Result<u32> {
@@ -57,6 +59,7 @@ pub(crate) fn gem_create(
/// IOCTL: gem_info: Query GEM metadata.
pub(crate) fn gem_info(
_dev: &NovaDevice,
+ _reg_data: &(),
req: &mut uapi::drm_nova_gem_info,
file: &drm::File<File>,
) -> Result<u32> {
diff --git a/drivers/gpu/drm/tyr/file.rs b/drivers/gpu/drm/tyr/file.rs
index 31411da203c5..6262114c6a8d 100644
--- a/drivers/gpu/drm/tyr/file.rs
+++ b/drivers/gpu/drm/tyr/file.rs
@@ -29,6 +29,7 @@ fn open(_dev: &drm::Device<Self::Driver>) -> Result<Pin<KBox<Self>>> {
impl TyrDrmFileData {
pub(crate) fn dev_query(
ddev: &TyrDrmDevice,
+ _reg_data: &(),
devquery: &mut uapi::drm_panthor_dev_query,
_file: &TyrDrmFile,
) -> Result<u32> {
diff --git a/rust/kernel/drm/ioctl.rs b/rust/kernel/drm/ioctl.rs
index aa72b44f645d..dd177bc399b2 100644
--- a/rust/kernel/drm/ioctl.rs
+++ b/rust/kernel/drm/ioctl.rs
@@ -83,6 +83,7 @@ pub mod internal {
///
/// ```ignore
/// fn foo(device: &kernel::drm::Device<Self>,
+/// reg_data: &<Self::RegistrationData as kernel::types::ForLt>::Of<'_>,
/// data: &mut uapi::argument_type,
/// file: &kernel::drm::File<Self::File>,
/// ) -> Result<u32>
@@ -140,7 +141,7 @@ macro_rules! declare_drm_ioctls {
let dev = unsafe {
$crate::drm::device::Device::from_raw(raw_dev)
};
- let _guard = match $crate::drm::device::unbind_guard(dev) {
+ let guard = match $crate::drm::device::unbind_guard(dev) {
Some(g) => g,
None => return $crate::error::code::ENODEV.to_errno(),
};
@@ -156,7 +157,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(dev, guard.registration_data(), data, file) {
Err(e) => e.to_errno(),
Ok(i) => i.try_into()
.unwrap_or($crate::error::code::ERANGE.to_errno()),
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Claude review: rust: drm: Pass registration data to ioctl handlers
2026-05-06 22:06 ` [PATCH 5/6] rust: drm: Pass registration data to ioctl handlers Danilo Krummrich
@ 2026-05-07 2:45 ` Claude Code Review Bot
0 siblings, 0 replies; 14+ messages in thread
From: Claude Code Review Bot @ 2026-05-07 2:45 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
The `_guard` is renamed to `guard` and the ioctl call becomes:
```rust
match $func(dev, guard.registration_data(), data, file) {
```
The handler signature changes to add `_reg_data: &()` in nova and tyr — both use `ForLt!(())` as their `RegistrationData`, so the concrete type is `&()`. This is a no-op placeholder for drivers that don't need registration data yet.
**Signature documentation update** is correct:
```rust
/// fn foo(device: &kernel::drm::Device<Self>,
/// reg_data: &<Self::RegistrationData as kernel::types::ForLt>::Of<'_>,
/// data: &mut uapi::argument_type,
/// file: &kernel::drm::File<Self::File>,
/// ) -> Result<u32>
```
No issues.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 6/6] rust: drm: Pass bound parent device to ioctl handlers
2026-05-06 22:05 [PATCH 0/6] rust: drm: Higher-Ranked Lifetime private data Danilo Krummrich
` (4 preceding siblings ...)
2026-05-06 22:06 ` [PATCH 5/6] rust: drm: Pass registration data to ioctl handlers Danilo Krummrich
@ 2026-05-06 22:06 ` Danilo Krummrich
2026-05-07 2:45 ` Claude review: " Claude Code Review Bot
2026-05-07 2:45 ` Claude review: rust: drm: Higher-Ranked Lifetime private data Claude Code Review Bot
6 siblings, 1 reply; 14+ messages in thread
From: Danilo Krummrich @ 2026-05-06 22:06 UTC (permalink / raw)
To: aliceryhl, airlied, simona, daniel.almeida, acourbot, apopple,
ecourtney, deborah.brouwer, lyude, ojeda, boqun, gary, bjorn3_gh,
lossin, a.hindborg, tmgross
Cc: driver-core, nova-gpu, dri-devel, linux-kernel, rust-for-linux,
Danilo Krummrich
Pass &Self::ParentDevice<Bound> as the second argument to ioctl
handlers, obtained from the UnbindGuard via Deref. This gives ioctl
handlers direct access to the bound parent bus device.
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
---
drivers/gpu/drm/nova/file.rs | 5 +++++
drivers/gpu/drm/tyr/file.rs | 3 +++
rust/kernel/drm/ioctl.rs | 3 ++-
3 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/nova/file.rs b/drivers/gpu/drm/nova/file.rs
index 4c9fa409a8be..df9760947d58 100644
--- a/drivers/gpu/drm/nova/file.rs
+++ b/drivers/gpu/drm/nova/file.rs
@@ -4,6 +4,8 @@
use crate::gem::NovaObject;
use kernel::{
alloc::flags::*,
+ auxiliary,
+ device::Bound,
drm::{self, gem::BaseObject},
pci,
prelude::*,
@@ -24,6 +26,7 @@ impl File {
/// IOCTL: get_param: Query GPU / driver metadata.
pub(crate) fn get_param(
dev: &NovaDevice,
+ _adev: &auxiliary::Device<Bound>,
_reg_data: &(),
getparam: &mut uapi::drm_nova_getparam,
_file: &drm::File<File>,
@@ -45,6 +48,7 @@ pub(crate) fn get_param(
/// IOCTL: gem_create: Create a new DRM GEM object.
pub(crate) fn gem_create(
dev: &NovaDevice,
+ _adev: &auxiliary::Device<Bound>,
_reg_data: &(),
req: &mut uapi::drm_nova_gem_create,
file: &drm::File<File>,
@@ -59,6 +63,7 @@ pub(crate) fn gem_create(
/// IOCTL: gem_info: Query GEM metadata.
pub(crate) fn gem_info(
_dev: &NovaDevice,
+ _adev: &auxiliary::Device<Bound>,
_reg_data: &(),
req: &mut uapi::drm_nova_gem_info,
file: &drm::File<File>,
diff --git a/drivers/gpu/drm/tyr/file.rs b/drivers/gpu/drm/tyr/file.rs
index 6262114c6a8d..9f53da7633ab 100644
--- a/drivers/gpu/drm/tyr/file.rs
+++ b/drivers/gpu/drm/tyr/file.rs
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: GPL-2.0 or MIT
use kernel::{
+ device,
drm,
+ platform,
prelude::*,
uaccess::UserSlice,
uapi, //
@@ -29,6 +31,7 @@ fn open(_dev: &drm::Device<Self::Driver>) -> Result<Pin<KBox<Self>>> {
impl TyrDrmFileData {
pub(crate) fn dev_query(
ddev: &TyrDrmDevice,
+ _pdev: &platform::Device<device::Bound>,
_reg_data: &(),
devquery: &mut uapi::drm_panthor_dev_query,
_file: &TyrDrmFile,
diff --git a/rust/kernel/drm/ioctl.rs b/rust/kernel/drm/ioctl.rs
index dd177bc399b2..181918303f2f 100644
--- a/rust/kernel/drm/ioctl.rs
+++ b/rust/kernel/drm/ioctl.rs
@@ -83,6 +83,7 @@ pub mod internal {
///
/// ```ignore
/// fn foo(device: &kernel::drm::Device<Self>,
+/// parent: &Self::ParentDevice<kernel::device::Bound>,
/// reg_data: &<Self::RegistrationData as kernel::types::ForLt>::Of<'_>,
/// data: &mut uapi::argument_type,
/// file: &kernel::drm::File<Self::File>,
@@ -157,7 +158,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, guard.registration_data(), data, file) {
+ match $func(dev, &*guard, guard.registration_data(), data, file) {
Err(e) => e.to_errno(),
Ok(i) => i.try_into()
.unwrap_or($crate::error::code::ERANGE.to_errno()),
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Claude review: rust: drm: Pass bound parent device to ioctl handlers
2026-05-06 22:06 ` [PATCH 6/6] rust: drm: Pass bound parent device " Danilo Krummrich
@ 2026-05-07 2:45 ` Claude Code Review Bot
0 siblings, 0 replies; 14+ messages in thread
From: Claude Code Review Bot @ 2026-05-07 2:45 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
The final ioctl call becomes:
```rust
match $func(dev, &*guard, guard.registration_data(), data, file) {
```
**`&*guard` usage**: This dereferences the `UnbindGuard` through its `Deref` impl to get `&T::ParentDevice<Bound>`, then re-borrows it. This is correct.
**Borrow concern**: Both `&*guard` and `guard.registration_data()` borrow the guard simultaneously — the first immutably through `Deref`, the second through a method call. Since both are immutable borrows, this is fine from a borrow-checking perspective. However, `registration_data()` internally calls `self.dev.raw_registration_data()`, which goes through `UnsafeCell::get()`. The safety here relies on the fact that no mutable access to `registration_data` occurs during the SRCU critical section, which is guaranteed because `Registration::drop` calls `drm_dev_unplug()` (SRCU barrier) before modifying anything.
**Driver-side changes**: Nova and tyr add the parent device parameter (`_adev: &auxiliary::Device<Bound>` and `_pdev: &platform::Device<device::Bound>` respectively). These are unused with `_` prefix, which is expected for this infrastructure patch.
**Argument order**: The final ioctl handler signature is:
```
fn foo(dev, parent_device, reg_data, ioctl_data, file) -> Result<u32>
```
This is a 5-argument function. While not a problem per se, it's worth considering if a context struct would be cleaner for future evolution. But for now, this is fine.
**Minor nit**: The imports in `nova/file.rs` add `auxiliary` and `device::Bound` — straightforward.
No blocking issues.
### Summary
The series is well-designed with careful safety arguments. The main architectural dependency is on the `ForLt` trait and `DeviceContext` work that isn't in drm-next yet. The `drm_dev_unregister` → `drm_dev_unplug` switch is the key safety enabler and is correct. The lifetime erasure/recovery pattern via `mem::transmute` is the most subtle part but is justified by `ForLt`'s covariance guarantee. The incremental ioctl handler signature evolution across patches 4-6 is clean. No blocking issues found.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 14+ messages in thread
* Claude review: rust: drm: Higher-Ranked Lifetime private data
2026-05-06 22:05 [PATCH 0/6] rust: drm: Higher-Ranked Lifetime private data Danilo Krummrich
` (5 preceding siblings ...)
2026-05-06 22:06 ` [PATCH 6/6] rust: drm: Pass bound parent device " Danilo Krummrich
@ 2026-05-07 2:45 ` Claude Code Review Bot
6 siblings, 0 replies; 14+ messages in thread
From: Claude Code Review Bot @ 2026-05-07 2:45 UTC (permalink / raw)
To: dri-devel-reviews
Overall Series Review
Subject: rust: drm: Higher-Ranked Lifetime private data
Author: Danilo Krummrich <dakr@kernel.org>
Patches: 7
Reviewed: 2026-05-07T12:45:08.215356
---
This is a well-structured 6-patch series from Danilo Krummrich that adds lifetime-safe access to parent bus device state from DRM ioctl handlers in the Rust DRM abstractions. The core problem is real and important: DRM ioctls run in process context with no guarantee the parent bus device is still bound, so drivers need a safe mechanism to check/guard against unbinding.
The design is sound:
1. **Type-level parent device declaration** (patch 1) — ensures the DRM device knows its parent bus type at compile time.
2. **UnbindGuard** (patch 2) — wraps `drm_dev_enter/exit` SRCU critical sections, providing a safe Rust guard that proves the device is bound.
3. **RegistrationData with ForLt** (patch 3) — uses higher-ranked lifetimes to store data whose logical lifetime is tied to the device binding scope, with `mem::transmute` for lifetime erasure/recovery.
4. **Ioctl integration** (patches 4-6) — wraps every ioctl dispatch in an UnbindGuard and passes both the bound parent device and registration data to handlers.
The switch from `drm_dev_unregister()` to `drm_dev_unplug()` in patch 2 is the critical enabler — it provides the SRCU barrier that makes the `UnbindGuard` safety argument valid.
**Key dependency**: This series requires both the `ForLt` trait (from a separate series [1]) and Lyude's `drm::DeviceContext` work, neither of which are in the current drm-next tree. The patches cannot be applied standalone.
**Overall**: The design is architecturally sound and well-documented. The safety arguments are carefully stated. I have a few specific concerns noted below.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 14+ messages in thread