Skip to contentSkip to conversation

Improvements for Devres

PATCH Superseded Danilo Krummrich 3 participants 22 messages Jun 24, 2025 – Jun 26, 2025

Subsystems (8)

v3 of 4 Superseded Latest is v4 →

lore
Share & tools
lore

Danilo Krummrich

author maintainer
#0 lore raw

v3 cover Improvements for Devres

This patch series provides some optimizations for Devres:

1) Provide a more lightweight replacement for Devres::new_foreign_owned().

2) Get rid of Devres' inner Arc and instead consume and provide an impl PinInit instead.

Additionally, having the resulting explicit synchronization in Devres::drop() prevents potential subtle undesired side effects of the devres callback dropping the final Arc reference asynchronously within the devres callback.

3) An optimization for when we never need to access the resource or release it manually.

Thanks to Alice and Benno for some great offline discussions on this topic.

This patch series depends on the Opaque patch in [1] and the pin-init patch in [2], which Benno will provide a signed tag for. A branch containing the patches can be found in [3].

[1] https://lore.kernel.org/lkml/20250610-b4-rust_miscdevice_registrationdata-v6-1-b03f5dfce998@gmail.com/
[2] https://lore.kernel.org/rust-for-linux/[email protected]/
[3] https://git.kernel.org/pub/scm/linux/kernel/git/dakr/linux.git/log/?h=rust/devres

Changes in v3:
- devres::register:
- add 'static bound for ForeignOwnable
- use drop() instead of let _ =
- Devres:
- use ptr::from_ref() instead of Inner::as_ptr()
- use &raw mut instead of addr_of_mut!()
- use SAFETY comment proposal from Benno
- add invariant for Devres::inner
- use ScopeGuard in devres_callback()
- devres::register_release():
- add impl<T: Release> Release for &T
- add 'static bound for ForeignOwnable
- use drop() instead of let _ =

Changes in v2:
- Revocable:
- remove Error: From<E> bound
- devres::register:
- rename devres::register_foreign_boxed() to just devres::register()
- move T: 'static bound to the function rather than the impl block
- Devres:
- Fix aliasing issue by using an Opaque<Inner>; should be UnsafePinned<Inner> once available.
- Add doc-comments for a couple of private fields.
- Link Revocable on 'revoke' in Devres::new().
- devres::register_release():
- expand documentation of Release
- rename devres::register_foreign_release() for devres::register_release()

Danilo Krummrich (4):
rust: revocable: support fallible PinInit types
rust: devres: replace Devres::new_foreign_owned()
rust: devres: get rid of Devres' inner Arc
rust: devres: implement register_release()

 drivers/gpu/nova-core/driver.rs |   7 +-
 drivers/gpu/nova-core/gpu.rs    |   6 +-
 rust/helpers/device.c           |   7 +
 rust/kernel/cpufreq.rs          |  11 +-
 rust/kernel/devres.rs           | 372 +++++++++++++++++++++++---------
 rust/kernel/drm/driver.rs       |  14 +-
 rust/kernel/pci.rs              |  20 +-
 rust/kernel/revocable.rs        |   6 +-
 samples/rust/rust_driver_pci.rs |  19 +-
 9 files changed, 327 insertions(+), 135 deletions(-)

base-commit: d752224c81ec2e5c0e86635c4b1cc835f9ca4151

Signature · 1 line
2.49.0
author maintainer
#1 lore raw

PATCH v3 1/4 rust: revocable: support fallible PinInit types

Currently, Revocable::new() only supports infallible PinInit implementations, i.e. impl PinInit<T, Infallible>.

This has been sufficient so far, since users such as Devres do not support fallibility.

Since this is about to change, make Revocable::new() generic over the error type E.

---
 rust/kernel/devres.rs    | 2 +-
 rust/kernel/revocable.rs | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)
rust/kernel/devres.rs +1 -1
index 57502534d985..544e50efab43 100644
@@ -100,7 +100,7 @@ struct DevresInner<T> {
 impl<T> DevresInner<T> {
     fn new(dev: &Device<Bound>, data: T, flags: Flags) -> Result<Arc<DevresInner<T>>> {
         let inner = Arc::pin_init(
-            pin_init!( DevresInner {
+            try_pin_init!( DevresInner {
                 dev: dev.into(),
                 callback: Self::devres_callback,
                 data <- Revocable::new(data),
rust/kernel/revocable.rs +3 -3
index fa1fd70efa27..46768b374656 100644
@@ -82,11 +82,11 @@ unsafe impl<T: Sync + Send> Sync for Revocable<T> {}
 
 impl<T> Revocable<T> {
     /// Creates a new revocable instance of the given data.
-    pub fn new(data: impl PinInit<T>) -> impl PinInit<Self> {
-        pin_init!(Self {
+    pub fn new<E>(data: impl PinInit<T, E>) -> impl PinInit<Self, E> {
+        try_pin_init!(Self {
             is_available: AtomicBool::new(true),
             data <- Opaque::pin_init(data),
-        })
+        }? E)
     }
 
     /// Tries to access the revocable wrapped object.
Signature · 1 line
2.49.0
author maintainer
#2 lore raw

PATCH v3 2/4 rust: devres: replace Devres::new_foreign_owned()

In reply to Danilo Krummrich

Replace Devres::new_foreign_owned() with devres::register().

The current implementation of Devres::new_foreign_owned() creates a full Devres container instance, including the internal Revocable and completion.

However, none of that is necessary for the intended use of giving full ownership of an object to devres and getting it dropped once the given device is unbound.

Hence, implement devres::register(), which is limited to consume the given data, wrap it in a KBox and drop the KBox once the given device is unbound, without any other synchronization.

---
 rust/helpers/device.c     |  7 ++++
 rust/kernel/cpufreq.rs    | 11 +++---
 rust/kernel/devres.rs     | 70 +++++++++++++++++++++++++++++++++------
 rust/kernel/drm/driver.rs | 14 ++++----
 4 files changed, 82 insertions(+), 20 deletions(-)
rust/helpers/device.c +7 -0
index b2135c6686b0..502fef7e9ae8 100644
@@ -8,3 +8,10 @@ int rust_helper_devm_add_action(struct device *dev,
 {
 	return devm_add_action(dev, action, data);
 }
+
+int rust_helper_devm_add_action_or_reset(struct device *dev,
+					 void (*action)(void *),
+					 void *data)
+{
+	return devm_add_action_or_reset(dev, action, data);
+}
rust/kernel/cpufreq.rs +7 -4
index 11b03e9d7e89..dd84e2b4d7ae 100644
@@ -13,7 +13,7 @@
     cpu::CpuId,
     cpumask,
     device::{Bound, Device},
-    devres::Devres,
+    devres,
     error::{code::*, from_err_ptr, from_result, to_result, Result, VTABLE_DEFAULT_ERROR},
     ffi::{c_char, c_ulong},
     prelude::*,
@@ -1046,10 +1046,13 @@ pub fn new() -> Result<Self> {
 
     /// Same as [`Registration::new`], but does not return a [`Registration`] instance.
     ///
-    /// Instead the [`Registration`] is owned by [`Devres`] and will be revoked / dropped, once the
+    /// Instead the [`Registration`] is owned by [`devres::register`] and will be dropped, once the
     /// device is detached.
-    pub fn new_foreign_owned(dev: &Device<Bound>) -> Result {
-        Devres::new_foreign_owned(dev, Self::new()?, GFP_KERNEL)
+    pub fn new_foreign_owned(dev: &Device<Bound>) -> Result
+    where
+        T: 'static,
+    {
+        devres::register(dev, Self::new()?, GFP_KERNEL)
     }
 }
 
rust/kernel/devres.rs +60 -10

89 lines not on this page · raw message

rust/kernel/drm/driver.rs +8 -6
index acb638086131..f63addaf7235 100644
@@ -5,9 +5,7 @@
 //! C header: [`include/linux/drm/drm_drv.h`](srctree/include/linux/drm/drm_drv.h)
 
 use crate::{
-    bindings, device,
-    devres::Devres,
-    drm,
+    bindings, device, devres, drm,
     error::{to_result, Result},
     prelude::*,
     str::CStr,
@@ -130,18 +128,22 @@ fn new(drm: &drm::Device<T>, flags: usize) -> Result<Self> {
     }
 
     /// Same as [`Registration::new`}, but transfers ownership of the [`Registration`] to
-    /// [`Devres`].
+    /// [`devres::register`].
     pub fn new_foreign_owned(
         drm: &drm::Device<T>,
         dev: &device::Device<device::Bound>,
         flags: usize,
-    ) -> Result {
+    ) -> Result
+    where
+        T: 'static,
+    {
         if drm.as_ref().as_raw() != dev.as_raw() {
             return Err(EINVAL);
         }
 
         let reg = Registration::<T>::new(drm, flags)?;
-        Devres::new_foreign_owned(dev, reg, GFP_KERNEL)
+
+        devres::register(dev, reg, GFP_KERNEL)
     }
 
     /// Returns a reference to the `Device` instance for this registration.
Signature · 1 line
2.49.0
author maintainer
#3 lore raw

PATCH v3 3/4 rust: devres: get rid of Devres' inner Arc

In reply to Danilo Krummrich

So far Devres uses an inner memory allocation and reference count, i.e. an inner Arc, in order to ensure that the devres callback can't run into a use-after-free in case where the Devres object is dropped while the devres callback runs concurrently.

Instead, use a completion in order to avoid a potential UAF: In Devres::drop(), if we detect that we can't remove the devres action anymore, we wait for the completion that is completed from the devres callback. If, in turn, we were able to successfully remove the devres action, we can just go ahead.

This, again, allows us to get rid of the internal Arc, and instead let Devres consume an impl PinInit<T, E> in order to return an impl PinInit<Devres<T>, E>, which enables us to get away with less memory allocations.

Additionally, having the resulting explicit synchronization in Devres::drop() prevents potential subtle undesired side effects of the devres callback dropping the final Arc reference asynchronously within the devres callback.

---
 drivers/gpu/nova-core/driver.rs |   7 +-
 drivers/gpu/nova-core/gpu.rs    |   6 +-
 rust/kernel/devres.rs           | 210 +++++++++++++++++++-------------
 rust/kernel/pci.rs              |  20 +--
 samples/rust/rust_driver_pci.rs |  19 +--
 5 files changed, 151 insertions(+), 111 deletions(-)
drivers/gpu/nova-core/driver.rs +5 -2
index 8c86101c26cb..110f2b355db4 100644
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 
-use kernel::{auxiliary, bindings, c_str, device::Core, pci, prelude::*};
+use kernel::{auxiliary, bindings, c_str, device::Core, pci, prelude::*, sync::Arc};
 
 use crate::gpu::Gpu;
 
@@ -34,7 +34,10 @@ fn probe(pdev: &pci::Device<Core>, _info: &Self::IdInfo) -> Result<Pin<KBox<Self
         pdev.enable_device_mem()?;
         pdev.set_master();
 
-        let bar = pdev.iomap_region_sized::<BAR0_SIZE>(0, c_str!("nova-core/bar0"))?;
+        let bar = Arc::pin_init(
+            pdev.iomap_region_sized::<BAR0_SIZE>(0, c_str!("nova-core/bar0")),
+            GFP_KERNEL,
+        )?;
 
         let this = KBox::pin_init(
             try_pin_init!(Self {
drivers/gpu/nova-core/gpu.rs +3 -3
index 60b86f370284..47653c14838b 100644
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 
-use kernel::{device, devres::Devres, error::code::*, pci, prelude::*};
+use kernel::{device, devres::Devres, error::code::*, pci, prelude::*, sync::Arc};
 
 use crate::driver::Bar0;
 use crate::firmware::{Firmware, FIRMWARE_VERSION};
@@ -161,14 +161,14 @@ fn new(bar: &Bar0) -> Result<Spec> {
 pub(crate) struct Gpu {
     spec: Spec,
     /// MMIO mapping of PCI BAR 0
-    bar: Devres<Bar0>,
+    bar: Arc<Devres<Bar0>>,
     fw: Firmware,
 }
 
 impl Gpu {
     pub(crate) fn new(
         pdev: &pci::Device<device::Bound>,
-        devres_bar: Devres<Bar0>,
+        devres_bar: Arc<Devres<Bar0>>,
     ) -> Result<impl PinInit<Self>> {
         let bar = devres_bar.access(pdev.as_ref())?;
         let spec = Spec::new(bar)?;
rust/kernel/devres.rs +123 -87

289 lines not on this page · raw message

rust/kernel/pci.rs +10 -10
index 8435f8132e38..db0eb7eaf9b1 100644
@@ -5,7 +5,6 @@
 //! C header: [`include/linux/pci.h`](srctree/include/linux/pci.h)
 
 use crate::{
-    alloc::flags::*,
     bindings, container_of, device,
     device_id::RawDeviceId,
     devres::Devres,
@@ -398,19 +397,20 @@ pub fn resource_len(&self, bar: u32) -> Result<bindings::resource_size_t> {
 impl Device<device::Bound> {
     /// Mapps an entire PCI-BAR after performing a region-request on it. I/O operation bound checks
     /// can be performed on compile time for offsets (plus the requested type size) < SIZE.
-    pub fn iomap_region_sized<const SIZE: usize>(
-        &self,
+    pub fn iomap_region_sized<'a, const SIZE: usize>(
+        &'a self,
         bar: u32,
-        name: &CStr,
-    ) -> Result<Devres<Bar<SIZE>>> {
-        let bar = Bar::<SIZE>::new(self, bar, name)?;
-        let devres = Devres::new(self.as_ref(), bar, GFP_KERNEL)?;
-
-        Ok(devres)
+        name: &'a CStr,
+    ) -> impl PinInit<Devres<Bar<SIZE>>, Error> + 'a {
+        Devres::new(self.as_ref(), Bar::<SIZE>::new(self, bar, name))
     }
 
     /// Mapps an entire PCI-BAR after performing a region-request on it.
-    pub fn iomap_region(&self, bar: u32, name: &CStr) -> Result<Devres<Bar>> {
+    pub fn iomap_region<'a>(
+        &'a self,
+        bar: u32,
+        name: &'a CStr,
+    ) -> impl PinInit<Devres<Bar>, Error> + 'a {
         self.iomap_region_sized::<0>(bar, name)
     }
 }
samples/rust/rust_driver_pci.rs +10 -9
index 15147e4401b2..5c35f1414172 100644
@@ -25,8 +25,10 @@ impl TestIndex {
     const NO_EVENTFD: Self = Self(0);
 }
 
+#[pin_data(PinnedDrop)]
 struct SampleDriver {
     pdev: ARef<pci::Device>,
+    #[pin]
     bar: Devres<Bar0>,
 }
 
@@ -73,13 +75,11 @@ fn probe(pdev: &pci::Device<Core>, info: &Self::IdInfo) -> Result<Pin<KBox<Self>
         pdev.enable_device_mem()?;
         pdev.set_master();
 
-        let bar = pdev.iomap_region_sized::<{ Regs::END }>(0, c_str!("rust_driver_pci"))?;
-
-        let drvdata = KBox::new(
-            Self {
+        let drvdata = KBox::pin_init(
+            try_pin_init!(Self {
                 pdev: pdev.into(),
-                bar,
-            },
+                bar <- pdev.iomap_region_sized::<{ Regs::END }>(0, c_str!("rust_driver_pci")),
+            }),
             GFP_KERNEL,
         )?;
 
@@ -90,12 +90,13 @@ fn probe(pdev: &pci::Device<Core>, info: &Self::IdInfo) -> Result<Pin<KBox<Self>
             Self::testdev(info, bar)?
         );
 
-        Ok(drvdata.into())
+        Ok(drvdata)
     }
 }
 
-impl Drop for SampleDriver {
-    fn drop(&mut self) {
+#[pinned_drop]
+impl PinnedDrop for SampleDriver {
+    fn drop(self: Pin<&mut Self>) {
         dev_dbg!(self.pdev.as_ref(), "Remove Rust PCI driver sample.\n");
     }
 }
Signature · 1 line
2.49.0
#4 lore raw

Re: [PATCH v3 3/4] rust: devres: get rid of Devres' inner Arc

On Tue, Jun 24, 2025 at 11:54:01PM +0200, Danilo Krummrich wrote:
[...]

rust/kernel/devres.rs struct DevresInner<T> { · line 108

Quoted 2 lines
rust/kernel/devres.rs +2 -0
+#[pin_data(PinnedDrop)]
+pub struct Devres<T> {

It makes me realize: I think we need to make T being Send? Because the devm callback can happen on a different thread other than Devres::new() and the callback may drop T because of revoke(), so we are essientially sending T. Alternatively we can make Devres::new() and its friend require T being Send.

If it's true, we need a separate patch that "Fixes" this.

(Imagine a Devres<MutexGuard>)

rust/kernel/devres.rs struct DevresInner<T> { · line 116

Quoted 8 lines
rust/kernel/devres.rs +8 -0
+    dev: ARef<Device>,
+    /// Pointer to [`Self::devres_callback`].
+    ///
+    /// Has to be stored, since Rust does not guarantee to always return the same address for a
+    /// function. However, the C API uses the address as a key.
+    callback: unsafe extern "C" fn(*mut c_void),
+    /// Contains all the fields shared with [`Self::callback`].
+    // TODO: Replace with `UnsafePinned`, once available.

nit: Maybe also reference the drop_in_place() in Devres::drop() as well, because once we use UnsafePinned, we don't need that drop_in_place(). But not a big deal, just trying to help the people who would handle that "TODO" ;-)

Quoted 4 lines
rust/kernel/devres.rs +4 -0
+    #[pin]
+    inner: Opaque<Inner<T>>,
+}
+

[...]

rust/kernel/devres.rs pub fn new(dev: &Device<Bound>, data: T, flags: Flags) -> Result<Self> { · line 263

Quoted 5 lines
rust/kernel/devres.rs +5 -0
+// SAFETY: `Devres` can be send to any task, if `T: Send`.
+unsafe impl<T: Send> Send for Devres<T> {}
+
+// SAFETY: `Devres` can be shared with any task, if `T: Sync`.
+unsafe impl<T: Sync> Sync for Devres<T> {}

T also need to be Send for Devres<T> to be Sync because that's what Revocable<T> requires. (Unless we want T always being Send because of the issue I mentioned above)

The rest looks good to me.

Regards,
Boqun

Quoted 34 lines
rust/kernel/devres.rs +21 -5
+
+#[pinned_drop]
+impl<T> PinnedDrop for Devres<T> {
+    fn drop(self: Pin<&mut Self>) {
         // SAFETY: When `drop` runs, it is guaranteed that nobody is accessing the revocable data
         // anymore, hence it is safe not to wait for the grace period to finish.
-        if unsafe { self.0.data.revoke_nosync() } {
-            // We revoked `self.0.data` before the devres action did, hence try to remove it.
-            if !DevresInner::remove_action(&self.0) {
+        if unsafe { self.data().revoke_nosync() } {
+            // We revoked `self.data` before the devres action did, hence try to remove it.
+            if !self.remove_action() {
                 // We could not remove the devres action, which means that it now runs concurrently,
-                // hence signal that `self.0.data` has been revoked successfully.
-                self.0.revoke.complete_all();
+                // hence signal that `self.data` has been revoked by us successfully.
+                self.inner().revoke.complete_all();
+
+                // Wait for `Self::devres_callback` to be done using this object.
+                self.inner().devm.wait_for_completion();
             }
+        } else {
+            // `Self::devres_callback` revokes `self.data` for us, hence wait for it to be done
+            // using this object.
+            self.inner().devm.wait_for_completion();
         }
+
+        // INVARIANT: At this point it is guaranteed that `inner` can't be accessed any more.
+        //
+        // SAFETY: `inner` is valid for dropping.
+        unsafe { core::ptr::drop_in_place(self.inner.get()) };
     }
 }

[...]

author maintainer
#5 lore raw

Re: [PATCH v3 3/4] rust: devres: get rid of Devres' inner Arc

Quoted 11 lines · Boqun Feng

On Tue, Jun 24, 2025 at 11:54:01PM +0200, Danilo Krummrich wrote:
[...]

rust/kernel/devres.rs struct DevresInner<T> { · line 108

Quoted 2 lines
rust/kernel/devres.rs +2 -0
+#[pin_data(PinnedDrop)]
+pub struct Devres<T> {

It makes me realize: I think we need to make T being Send? Because the devm callback can happen on a different thread other than Devres::new() and the callback may drop T because of revoke(), so we are essientially sending T. Alternatively we can make Devres::new() and its friend require T being Send.

Quoted 1 line

If it's true, we need a separate patch that "Fixes" this.

Indeed, that needs a fix.

Quoted 11 lines

(Imagine a Devres<MutexGuard>)

rust/kernel/devres.rs struct DevresInner<T> { · line 116

Quoted 8 lines
rust/kernel/devres.rs +8 -0
+    dev: ARef<Device>,
+    /// Pointer to [`Self::devres_callback`].
+    ///
+    /// Has to be stored, since Rust does not guarantee to always return the same address for a
+    /// function. However, the C API uses the address as a key.
+    callback: unsafe extern "C" fn(*mut c_void),
+    /// Contains all the fields shared with [`Self::callback`].
+    // TODO: Replace with `UnsafePinned`, once available.
Quoted 4 lines

nit: Maybe also reference the drop_in_place() in Devres::drop() as well, because once we use UnsafePinned, we don't need that drop_in_place(). But not a big deal, just trying to help the people who would handle that "TODO" ;-)

Makes sense, the same is true for the Send and Sync impls, I think. AFAIK, UnsafePinned should cover them automatically.

reviewer
#6 lore raw

Re: [PATCH v3 3/4] rust: devres: get rid of Devres' inner Arc

Quoted 12 lines · Danilo Krummrich
Quoted 11 lines · Boqun Feng

On Tue, Jun 24, 2025 at 11:54:01PM +0200, Danilo Krummrich wrote:
[...]

rust/kernel/devres.rs struct DevresInner<T> { · line 108

Quoted 2 lines
rust/kernel/devres.rs +2 -0
+#[pin_data(PinnedDrop)]
+pub struct Devres<T> {

It makes me realize: I think we need to make T being Send? Because the devm callback can happen on a different thread other than Devres::new() and the callback may drop T because of revoke(), so we are essientially sending T. Alternatively we can make Devres::new() and its friend require T being Send.

Quoted 3 lines
Quoted 1 line

If it's true, we need a separate patch that "Fixes" this.

Indeed, that needs a fix.

Oh and we have no 'static bound on T either... We should require that as well.

---
Cheers,
Benno

author maintainer
#7 lore raw

Re: [PATCH v3 3/4] rust: devres: get rid of Devres' inner Arc

Quoted 13 lines · Benno Lossin
Quoted 12 lines · Danilo Krummrich
Quoted 11 lines · Boqun Feng

On Tue, Jun 24, 2025 at 11:54:01PM +0200, Danilo Krummrich wrote:
[...]

rust/kernel/devres.rs struct DevresInner<T> { · line 108

Quoted 2 lines
rust/kernel/devres.rs +2 -0
+#[pin_data(PinnedDrop)]
+pub struct Devres<T> {

It makes me realize: I think we need to make T being Send? Because the devm callback can happen on a different thread other than Devres::new() and the callback may drop T because of revoke(), so we are essientially sending T. Alternatively we can make Devres::new() and its friend require T being Send.

Quoted 6 lines
Quoted 3 lines
Quoted 1 line

If it's true, we need a separate patch that "Fixes" this.

Indeed, that needs a fix.

Oh and we have no 'static bound on T either... We should require that as well.

I don't think we actually need that, The Devres instance can't out-live a &T passed into it. And the &T can't out-live the &Device<Bound>, hence we're guaranteed that devres_callback() is never called because Devres::drop() will be able successfully unregister the callback given that we're still in the &Device<Bound> scope.

The only thing that could technically out-live the &Device<Bound> would be &'static T, but that would obviously be fine.

Do I miss anything?

author maintainer
#8 lore raw

Re: [PATCH v3 3/4] rust: devres: get rid of Devres' inner Arc

Quoted 27 lines · Danilo Krummrich
Quoted 13 lines · Benno Lossin
Quoted 12 lines · Danilo Krummrich
Quoted 11 lines · Boqun Feng

On Tue, Jun 24, 2025 at 11:54:01PM +0200, Danilo Krummrich wrote:
[...]

rust/kernel/devres.rs struct DevresInner<T> { · line 108

Quoted 2 lines
rust/kernel/devres.rs +2 -0
+#[pin_data(PinnedDrop)]
+pub struct Devres<T> {

It makes me realize: I think we need to make T being Send? Because the devm callback can happen on a different thread other than Devres::new() and the callback may drop T because of revoke(), so we are essientially sending T. Alternatively we can make Devres::new() and its friend require T being Send.

Quoted 6 lines
Quoted 3 lines
Quoted 1 line

If it's true, we need a separate patch that "Fixes" this.

Indeed, that needs a fix.

Oh and we have no 'static bound on T either... We should require that as well.

I don't think we actually need that, The Devres instance can't out-live a &T passed into it. And the &T can't out-live the &Device<Bound>, hence we're guaranteed that devres_callback() is never called because Devres::drop() will be able successfully unregister the callback given that we're still in the &Device<Bound> scope.

Quoted 4 lines

The only thing that could technically out-live the &Device<Bound> would be &'static T, but that would obviously be fine.

Do I miss anything?

Thinking a bit more about it, a similar argumentation is true for not needing
T: Send. The only way to leave the &Device<Bound> scope and hence the thread would be to stuff the Devres into a ForeignOwnable container, no?

Analogous to Benno asking for ForeignOwnable: 'static, should we also require
ForeignOwnable: Send + Sync?

Alternatively, the safety requirements of ForeignOwnable:::from_foreign() and ForeignOwnable::borrow() would need to cover this, which they currently they are not.

reviewer
#9 lore raw

Re: [PATCH v3 3/4] rust: devres: get rid of Devres' inner Arc

Quoted 28 lines · Danilo Krummrich
Quoted 27 lines · Danilo Krummrich
Quoted 13 lines · Benno Lossin
Quoted 12 lines · Danilo Krummrich
Quoted 11 lines · Boqun Feng

On Tue, Jun 24, 2025 at 11:54:01PM +0200, Danilo Krummrich wrote:
[...]

rust/kernel/devres.rs struct DevresInner<T> { · line 108

Quoted 2 lines
rust/kernel/devres.rs +2 -0
+#[pin_data(PinnedDrop)]
+pub struct Devres<T> {

It makes me realize: I think we need to make T being Send? Because the devm callback can happen on a different thread other than Devres::new() and the callback may drop T because of revoke(), so we are essientially sending T. Alternatively we can make Devres::new() and its friend require T being Send.

Quoted 6 lines
Quoted 3 lines
Quoted 1 line

If it's true, we need a separate patch that "Fixes" this.

Indeed, that needs a fix.

Oh and we have no 'static bound on T either... We should require that as well.

I don't think we actually need that, The Devres instance can't out-live a &T passed into it. And the &T can't out-live the &Device<Bound>, hence we're guaranteed that devres_callback() is never called because Devres::drop() will be able successfully unregister the callback given that we're still in the &Device<Bound> scope.

Quoted 8 lines
Quoted 4 lines

The only thing that could technically out-live the &Device<Bound> would be &'static T, but that would obviously be fine.

Do I miss anything?

Thinking a bit more about it, a similar argumentation is true for not needing
T: Send. The only way to leave the &Device<Bound> scope and hence the thread would be to stuff the Devres into a ForeignOwnable container, no?

I think T: Send is required, since we drop the T in the other thread when devres_callback is called from the device unbinding.

Quoted 2 lines

Analogous to Benno asking for ForeignOwnable: 'static, should we also require
ForeignOwnable: Send + Sync?

I don't think so, you could have a type that stores the pointer in C, but only ever allows access from the same thread.

Quoted 3 lines

Alternatively, the safety requirements of ForeignOwnable:::from_foreign() and ForeignOwnable::borrow() would need to cover this, which they currently they are not.

Oh right, yeah they should cover that.

---
Cheers,
Benno

reviewer
#10 lore raw

Re: [PATCH v3 3/4] rust: devres: get rid of Devres' inner Arc

In reply to Danilo Krummrich
Quoted 18 lines · Danilo Krummrich
Quoted 17 lines · Benno Lossin
Quoted 15 lines · Danilo Krummrich
Quoted 11 lines · Boqun Feng

On Tue, Jun 24, 2025 at 11:54:01PM +0200, Danilo Krummrich wrote:
[...]

rust/kernel/devres.rs struct DevresInner<T> { · line 108

Quoted 2 lines
rust/kernel/devres.rs +2 -0
+#[pin_data(PinnedDrop)]
+pub struct Devres<T> {

It makes me realize: I think we need to make T being Send? Because the devm callback can happen on a different thread other than Devres::new() and the callback may drop T because of revoke(), so we are essientially sending T. Alternatively we can make Devres::new() and its friend require T being Send.

Quoted 1 line

If it's true, we need a separate patch that "Fixes" this.

Indeed, that needs a fix.

Quoted 8 lines
Quoted 2 lines

Oh and we have no 'static bound on T either... We should require that as well.

I don't think we actually need that, The Devres instance can't out-live a &T passed into it. And the &T can't out-live the &Device<Bound>, hence we're guaranteed that devres_callback() is never called because Devres::drop() will be able successfully unregister the callback given that we're still in the &Device<Bound> scope.

Yeah that's correct, I got confused.

Quoted 4 lines

The only thing that could technically out-live the &Device<Bound> would be &'static T, but that would obviously be fine.

Do I miss anything?

Nope :)

---
Cheers,
Benno

author maintainer
#11 lore raw

Re: [PATCH v3 3/4] rust: devres: get rid of Devres' inner Arc

In reply to Boqun Feng
Quoted 3 lines · Boqun Feng

On Tue, Jun 24, 2025 at 11:54:01PM +0200, Danilo Krummrich wrote:
T also need to be Send for Devres<T> to be Sync because that's what Revocable<T> requires.

I think that's because of Revocable::revoke(), however you can't call Devres::revoke().

Quoted 2 lines

(Unless we want T always being Send
because of the issue I mentioned above)

Yes, we do.

Quoted 1 line

The rest looks good to me.

Great! :)

#12 lore raw

Re: [PATCH v3 3/4] rust: devres: get rid of Devres' inner Arc

Quoted 7 lines · Danilo Krummrich
Quoted 3 lines · Boqun Feng

On Tue, Jun 24, 2025 at 11:54:01PM +0200, Danilo Krummrich wrote:
T also need to be Send for Devres<T> to be Sync because that's what Revocable<T> requires.

I think that's because of Revocable::revoke(), however you can't call Devres::revoke().

Well, what could happen is we create a Arc<Devres<T>> on one thread, and because it's Sync, we can have a clone of it in another thread, and if that's the last one call Arc::drop(), the Revocable will be revoke()d in a different thread than it's created.

But with T always being Send, this is not a problem now.

Regards,
Boqun

Quoted 9 lines
Quoted 3 lines

(Unless we want T always being Send
because of the issue I mentioned above)

Yes, we do.

Quoted 1 line

The rest looks good to me.

Great! :)

author maintainer
#13 lore raw

PATCH v3 4/4 rust: devres: implement register_release()

In reply to Danilo Krummrich

register_release() is useful when a device resource has associated data, but does not require the capability of accessing it or manually releasing it.

If we would want to be able to access the device resource and release the device resource manually before the device is unbound, but still keep access to the associated data, we could implement it as follows.

	struct Registration<T> {
	   inner: Devres<RegistrationInner>,
	   data: T,
	}

However, if we never need to access the resource or release it manually, register_release() is great optimization for the above, since it does not require the synchronization of the Devres type.

---
 rust/kernel/devres.rs | 90 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 90 insertions(+)
rust/kernel/devres.rs +90 -0
index eeffdc8115aa..ff33e835c44f 100644
@@ -16,6 +16,7 @@
     sync::{rcu, Completion},
     types::{ARef, ForeignOwnable, ScopeGuard, Opaque},
 };
+use core::ops::Deref;
 
 use pin_init::Wrapper;
 
@@ -347,3 +348,92 @@ pub fn register<T, E>(dev: &Device<Bound>, data: impl PinInit<T, E>, flags: Flag
 
     register_foreign(dev, data)
 }
+
+/// [`Devres`]-releaseable resource.
+///
+/// Register an object implementing this trait with [`register_release`]. Its `release`
+/// function will be called once the device is being unbound.
+pub trait Release {
+    /// Called once the [`Device`] given to [`register_release`] is unbound.
+    fn release(&self);
+}
+
+impl<T: Release> Release for crate::sync::ArcBorrow<'_, T> {
+    fn release(&self) {
+        self.deref().release();
+    }
+}
+
+impl<T: Release> Release for Pin<&'_ T> {
+    fn release(&self) {
+        self.deref().release();
+    }
+}
+
+impl<T: Release> Release for &'_ T {
+    fn release(&self) {
+        (*self).release();
+    }
+}
+
+/// Consume the `data`, [`Release::release`] and [`Drop::drop`] `data` once `dev` is unbound.
+///
+/// # Examples
+///
+/// ```no_run
+/// use kernel::{device::{Bound, Device}, devres, devres::Release, sync::Arc};
+///
+/// /// Registration of e.g. a class device, IRQ, etc.
+/// struct Registration<T> {
+///     data: T,
+/// }
+///
+/// impl<T> Registration<T> {
+///     fn new(data: T) -> Result<Arc<Self>> {
+///         // register
+///
+///         Ok(Arc::new(Self { data }, GFP_KERNEL)?)
+///     }
+/// }
+///
+/// impl<T> Release for Registration<T> {
+///     fn release(&self) {
+///        // unregister
+///     }
+/// }
+///
+/// fn from_bound_context(dev: &Device<Bound>) -> Result {
+///     let reg = Registration::new(0x42)?;
+///
+///     devres::register_release(dev, reg.clone())
+/// }
+/// ```
+pub fn register_release<P>(dev: &Device<Bound>, data: P) -> Result
+where
+    P: ForeignOwnable + 'static,
+    for<'a> P::Borrowed<'a>: Release,
+{
+    let ptr = data.into_foreign();
+
+    #[allow(clippy::missing_safety_doc)]
+    unsafe extern "C" fn callback<P>(ptr: *mut kernel::ffi::c_void)
+    where
+        P: ForeignOwnable,
+        for<'a> P::Borrowed<'a>: Release,
+    {
+        // SAFETY: `ptr` is the pointer to the `ForeignOwnable` leaked above and hence valid.
+        unsafe { P::borrow(ptr.cast()) }.release();
+
+        // SAFETY: `ptr` is the pointer to the `ForeignOwnable` leaked above and hence valid.
+        drop(unsafe { P::from_foreign(ptr.cast()) });
+    }
+
+    // SAFETY:
+    // - `dev.as_raw()` is a pointer to a valid and bound device.
+    // - `ptr` is a valid pointer the `ForeignOwnable` devres takes ownership of.
+    to_result(unsafe {
+        // `devm_add_action_or_reset()` also calls `callback` on failure, such that the
+        // `ForeignOwnable` is released eventually.
+        bindings::devm_add_action_or_reset(dev.as_raw(), Some(callback::<P>), ptr.cast())
+    })
+}
Signature · 1 line
2.49.0
reviewer
#14 lore raw

Re: [PATCH v3 4/4] rust: devres: implement register_release()

Quoted 8 lines · Danilo Krummrich
rust/kernel/devres.rs +8 -0
+pub fn register_release<P>(dev: &Device<Bound>, data: P) -> Result
+where
+    P: ForeignOwnable + 'static,
+    for<'a> P::Borrowed<'a>: Release,
+{
+    let ptr = data.into_foreign();
+
+    #[allow(clippy::missing_safety_doc)]

rust/kernel/devres.rs pub fn register<T, E>(dev: &Device<Bound>, data: impl PinInit<T, E>, flags: Flag · line 428

Quoted 10 lines
rust/kernel/devres.rs +10 -0
+    unsafe extern "C" fn callback<P>(ptr: *mut kernel::ffi::c_void)
+    where
+        P: ForeignOwnable,
+        for<'a> P::Borrowed<'a>: Release,
+    {
+        // SAFETY: `ptr` is the pointer to the `ForeignOwnable` leaked above and hence valid.
+        unsafe { P::borrow(ptr.cast()) }.release();
+
+        // SAFETY: `ptr` is the pointer to the `ForeignOwnable` leaked above and hence valid.
+        drop(unsafe { P::from_foreign(ptr.cast()) });

Maybe this function should just be:

    let p = unsafe { P::from_foreign(ptr.cast()) };
    p.release();

And we require P: ForeignOwnable + Release + 'static?

We then need these impls instead:

    impl<T: Release, A: Allocator> Release for Pin<Box<T, A>>;
    impl<T: Release, A: Allocator> Release for Box<T, A>;
    impl<T: Release> Release for Arc<T>;

Or, we could change Release to be:

    pub trait Release {
        type Ptr: ForeignOwnable;

        fn release(this: Self::Ptr);
    }

and then register_release is:

    pub fn register_release<T: Release>(dev: &Device<Bound>, data: T::Ptr) -> Result

This way, one can store a Box<T> and get access to the T at the end. Or if they store the value in an Arc<T>, they have the option to clone it and give it to somewhere else.

Related questions:

* should we implement ForeignOwnable for &'static T?
* should we require 'static in ForeignOwnable? At the moment we only have those kinds supported and it only makes sense, a foreign owned object can be owned for any amount of time (so it must stay valid indefinitely).

---
Cheers,
Benno

Quoted 11 lines
rust/kernel/devres.rs +11 -0
+    }
+
+    // SAFETY:
+    // - `dev.as_raw()` is a pointer to a valid and bound device.
+    // - `ptr` is a valid pointer the `ForeignOwnable` devres takes ownership of.
+    to_result(unsafe {
+        // `devm_add_action_or_reset()` also calls `callback` on failure, such that the
+        // `ForeignOwnable` is released eventually.
+        bindings::devm_add_action_or_reset(dev.as_raw(), Some(callback::<P>), ptr.cast())
+    })
+}
author maintainer
#15 lore raw

Re: [PATCH v3 4/4] rust: devres: implement register_release()

Quoted 8 lines · Benno Lossin

Or, we could change Release to be:

    pub trait Release {
        type Ptr: ForeignOwnable;

        fn release(this: Self::Ptr);
    }
Quoted 5 lines

and then register_release is:

    pub fn register_release<T: Release>(dev: &Device<Bound>, data: T::Ptr) -> Result

This way, one can store a Box<T> and get access to the T at the end.

I think this was also the case before? Well, it was P::Borrowed instead.

Quoted 2 lines

Or if they store the value in an Arc<T>, they have the option to clone it and give it to somewhere else.

Anyways, I really like this proposal of implementing the Release trait.

Quoted 3 lines

Related questions:

* should we implement ForeignOwnable for &'static T?

There's already a patch on the list doing this in the context of DebugFS [1].

[1] https://lore.kernel.org/lkml/[email protected]/

Quoted 4 lines

* should we require 'static in ForeignOwnable? At the moment we only have those kinds supported and it only makes sense, a foreign owned object can be owned for any amount of time (so it must stay valid indefinitely).

Sounds reasonable to me.

reviewer
#16 lore raw

Re: [PATCH v3 4/4] rust: devres: implement register_release()

Quoted 9 lines · Danilo Krummrich
Quoted 8 lines · Benno Lossin

Or, we could change Release to be:

    pub trait Release {
        type Ptr: ForeignOwnable;

        fn release(this: Self::Ptr);
    }
Quoted 7 lines
Quoted 5 lines

and then register_release is:

    pub fn register_release<T: Release>(dev: &Device<Bound>, data: T::Ptr) -> Result

This way, one can store a Box<T> and get access to the T at the end.

I think this was also the case before? Well, it was P::Borrowed instead.

Well you had access to a &T, but not the T directly.

Quoted 7 lines
Quoted 3 lines

Related questions:

* should we implement ForeignOwnable for &'static T?

There's already a patch on the list doing this in the context of DebugFS [1].

[1] https://lore.kernel.org/lkml/[email protected]/

Ah, I'm not following that series at the moment.

---
Cheers,
Benno

author maintainer
#17 lore raw

Re: [PATCH v3 4/4] rust: devres: implement register_release()

In reply to Danilo Krummrich
Quoted 13 lines · Danilo Krummrich
Quoted 12 lines · Benno Lossin

Or, we could change Release to be:

    pub trait Release {
        type Ptr: ForeignOwnable;

        fn release(this: Self::Ptr);
    }

and then register_release is:

    pub fn register_release<T: Release>(dev: &Device<Bound>, data: T::Ptr) -> Result
Quoted 8 lines
Quoted 1 line

This way, one can store a Box<T> and get access to the T at the end.

I think this was also the case before? Well, it was P::Borrowed instead.

Quoted 2 lines

Or if they store the value in an Arc<T>, they have the option to clone it and give it to somewhere else.

Anyways, I really like this proposal of implementing the Release trait.

One downside seems to be that the compiler cannot infer T anymore with this function signature.

reviewer
#18 lore raw

Re: [PATCH v3 4/4] rust: devres: implement register_release()

Quoted 18 lines · Danilo Krummrich
Quoted 17 lines · Danilo Krummrich
Quoted 8 lines · Benno Lossin

Or, we could change Release to be:

    pub trait Release {
        type Ptr: ForeignOwnable;

        fn release(this: Self::Ptr);
    }
Quoted 5 lines

and then register_release is:

    pub fn register_release<T: Release>(dev: &Device<Bound>, data: T::Ptr) -> Result

This way, one can store a Box<T> and get access to the T at the end.

I think this was also the case before? Well, it was P::Borrowed instead.

Quoted 7 lines
Quoted 4 lines
Quoted 2 lines

Or if they store the value in an Arc<T>, they have the option to clone it and give it to somewhere else.

Anyways, I really like this proposal of implementing the Release trait.

One downside seems to be that the compiler cannot infer T anymore with this function signature.

Yeah... That's a bit annoying.

We might be able to add an associated type to ForeignOwnable like Target or Inner or whatever.

Then we could do:

    pub fn register_release<P>(dev: &Device<Bound>, data: P) -> Result
    where
        P: ForeignOwnable,
        P::Inner: Release<Ptr = P>,

---
Cheers,
Benno

author maintainer
#19 lore raw

Re: [PATCH v3 4/4] rust: devres: implement register_release()

Quoted 24 lines · Benno Lossin
Quoted 23 lines · Danilo Krummrich
Quoted 21 lines · Danilo Krummrich
Quoted 8 lines · Benno Lossin

Or, we could change Release to be:

    pub trait Release {
        type Ptr: ForeignOwnable;

        fn release(this: Self::Ptr);
    }
Quoted 5 lines

and then register_release is:

    pub fn register_release<T: Release>(dev: &Device<Bound>, data: T::Ptr) -> Result

This way, one can store a Box<T> and get access to the T at the end.

I think this was also the case before? Well, it was P::Borrowed instead.

Quoted 2 lines

Or if they store the value in an Arc<T>, they have the option to clone it and give it to somewhere else.

Anyways, I really like this proposal of implementing the Release trait.

Quoted 7 lines
Quoted 2 lines

One downside seems to be that the compiler cannot infer T anymore with this function signature.

Yeah... That's a bit annoying.

We might be able to add an associated type to ForeignOwnable like Target or Inner or whatever.

I think we already have PointedTo [1]? But I remember that I've seen a patch to remove it again [2].

[1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/rust/kernel/types.rs#n32
[2] https://lore.kernel.org/all/[email protected]/

Quoted 10 lines

Then we could do:

    pub fn register_release<P>(dev: &Device<Bound>, data: P) -> Result
    where
        P: ForeignOwnable,
        P::Inner: Release<Ptr = P>,

---
Cheers,
Benno

author maintainer
#20 lore raw

Re: [PATCH v3 4/4] rust: devres: implement register_release()

Quoted 28 lines · Danilo Krummrich
Quoted 27 lines · Benno Lossin
Quoted 25 lines · Danilo Krummrich
Quoted 13 lines · Danilo Krummrich
Quoted 12 lines · Benno Lossin

Or, we could change Release to be:

    pub trait Release {
        type Ptr: ForeignOwnable;

        fn release(this: Self::Ptr);
    }

and then register_release is:

    pub fn register_release<T: Release>(dev: &Device<Bound>, data: T::Ptr) -> Result
Quoted 8 lines
Quoted 1 line

This way, one can store a Box<T> and get access to the T at the end.

I think this was also the case before? Well, it was P::Borrowed instead.

Quoted 2 lines

Or if they store the value in an Arc<T>, they have the option to clone it and give it to somewhere else.

Anyways, I really like this proposal of implementing the Release trait.

One downside seems to be that the compiler cannot infer T anymore with this function signature.

Quoted 7 lines
Quoted 4 lines

Yeah... That's a bit annoying.

We might be able to add an associated type to ForeignOwnable like Target or Inner or whatever.

I think we already have PointedTo [1]? But I remember that I've seen a patch to remove it again [2].

Well, not exactly, I think. Arc, for instance, defines PointedTo as ArcInner<T>. So, I think we indeed want something different.

Quoted 13 lines

[1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/rust/kernel/types.rs#n32
[2] https://lore.kernel.org/all/[email protected]/

Quoted 10 lines

Then we could do:

    pub fn register_release<P>(dev: &Device<Bound>, data: P) -> Result
    where
        P: ForeignOwnable,
        P::Inner: Release<Ptr = P>,

---
Cheers,
Benno

reviewer
#21 lore raw

Re: [PATCH v3 4/4] rust: devres: implement register_release()

Quoted 31 lines · Danilo Krummrich
Quoted 30 lines · Danilo Krummrich
Quoted 29 lines · Benno Lossin
Quoted 18 lines · Danilo Krummrich
Quoted 17 lines · Danilo Krummrich
Quoted 8 lines · Benno Lossin

Or, we could change Release to be:

    pub trait Release {
        type Ptr: ForeignOwnable;

        fn release(this: Self::Ptr);
    }
Quoted 5 lines

and then register_release is:

    pub fn register_release<T: Release>(dev: &Device<Bound>, data: T::Ptr) -> Result

This way, one can store a Box<T> and get access to the T at the end.

I think this was also the case before? Well, it was P::Borrowed instead.

Quoted 7 lines
Quoted 4 lines
Quoted 2 lines

Or if they store the value in an Arc<T>, they have the option to clone it and give it to somewhere else.

Anyways, I really like this proposal of implementing the Release trait.

One downside seems to be that the compiler cannot infer T anymore with this function signature.

Yeah... That's a bit annoying.

Quoted 8 lines
Quoted 5 lines
Quoted 2 lines

We might be able to add an associated type to ForeignOwnable like Target or Inner or whatever.

I think we already have PointedTo [1]? But I remember that I've seen a patch to remove it again [2].

Well, not exactly, I think. Arc, for instance, defines PointedTo as ArcInner<T>. So, I think we indeed want something different.

Yeah PointedTo is used to specify the type of the foreign pointer and should go away soon.

---
Cheers,
Benno