Improvements for Devres
Subsystems (8)
Danilo Krummrich
maintainerv3 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
Danilo Krummrich
maintainerPATCH 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
@@ -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
@@ -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
Danilo Krummrich
maintainerPATCH v3 2/4 rust: devres: replace Devres::new_foreign_owned()
In reply to Danilo KrummrichReplace 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
@@ -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
@@ -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
@@ -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
Danilo Krummrich
maintainerPATCH v3 3/4 rust: devres: get rid of Devres' inner Arc
In reply to Danilo KrummrichSo 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
@@ -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
@@ -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
@@ -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: &'aCStr , + ) ->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: &'aCStr , + ) ->impl PinInit <Devres <Bar >,Error > + 'a { self.iomap_region_sized ::<0 >(bar, name) } }
samples/rust/rust_driver_pci.rs +10 -9
@@ -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
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 ()) }; } }
[...]
Danilo Krummrich
maintainerRe: [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
TbeingSend? Because the devm callback can happen on a different thread other thanDevres::new()and the callback may dropTbecause ofrevoke(), so we are essientially sendingT. Alternatively we can makeDevres::new()and its friend requireTbeingSend.
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()inDevres::drop()as well, because once we useUnsafePinned, we don't need thatdrop_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.
Benno Lossin
reviewerRe: [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
TbeingSend? Because the devm callback can happen on a different thread other thanDevres::new()and the callback may dropTbecause ofrevoke(), so we are essientially sendingT. Alternatively we can makeDevres::new()and its friend requireTbeingSend.
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
Danilo Krummrich
maintainerRe: [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
TbeingSend? Because the devm callback can happen on a different thread other thanDevres::new()and the callback may dropTbecause ofrevoke(), so we are essientially sendingT. Alternatively we can makeDevres::new()and its friend requireTbeingSend.
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
'staticbound onTeither... 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?
Danilo Krummrich
maintainerRe: [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
TbeingSend? Because the devm callback can happen on a different thread other thanDevres::new()and the callback may dropTbecause ofrevoke(), so we are essientially sendingT. Alternatively we can makeDevres::new()and its friend requireTbeingSend.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
'staticbound onTeither... 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 becauseDevres::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.
Benno Lossin
reviewerRe: [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
TbeingSend? Because the devm callback can happen on a different thread other thanDevres::new()and the callback may dropTbecause ofrevoke(), so we are essientially sendingT. Alternatively we can makeDevres::new()and its friend requireTbeingSend.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
'staticbound onTeither... 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 becauseDevres::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
Benno Lossin
reviewerRe: [PATCH v3 3/4] rust: devres: get rid of Devres' inner Arc
In reply to Danilo KrummrichQuoted 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
TbeingSend? Because the devm callback can happen on a different thread other thanDevres::new()and the callback may dropTbecause ofrevoke(), so we are essientially sendingT. Alternatively we can makeDevres::new()and its friend requireTbeingSend.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
'staticbound onTeither... 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 becauseDevres::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
Danilo Krummrich
maintainerRe: [PATCH v3 3/4] rust: devres: get rid of Devres' inner Arc
In reply to Boqun FengQuoted 3 lines · Boqun Feng
On Tue, Jun 24, 2025 at 11:54:01PM +0200, Danilo Krummrich wrote:
Talso need to beSendforDevres<T>to beSyncbecause that's whatRevocable<T>requires.
I think that's because of Revocable::revoke(), however you can't call Devres::revoke().
Quoted 2 lines
(Unless we want
Talways beingSend
because of the issue I mentioned above)
Yes, we do.
Quoted 1 line
The rest looks good to me.
Great! :)
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:
Talso need to beSendforDevres<T>to beSyncbecause that's whatRevocable<T>requires.I think that's because of
Revocable::revoke(), however you can't callDevres::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
Talways beingSend
because of the issue I mentioned above)Yes, we do.
Quoted 1 line
The rest looks good to me.
Great! :)
Danilo Krummrich
maintainerPATCH v3 4/4 rust: devres: implement register_release()
In reply to Danilo Krummrichregister_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
@@ -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: Flagregister_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
Benno Lossin
reviewerRe: [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 ()) + }) +}
Danilo Krummrich
maintainerRe: [PATCH v3 4/4] rust: devres: implement register_release()
Quoted 8 lines · Benno Lossin
Or, we could change
Releaseto be:pub trait Release { type Ptr: ForeignOwnable; fn release(this: Self::Ptr); }
Quoted 5 lines
and then
register_releaseis:pub fn register_release<T: Release>(dev: &Device<Bound>, data: T::Ptr) -> ResultThis way, one can store a
Box<T>and get access to theTat 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
ForeignOwnablefor&'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
'staticinForeignOwnable? 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.
Benno Lossin
reviewerRe: [PATCH v3 4/4] rust: devres: implement register_release()
Quoted 9 lines · Danilo Krummrich
Quoted 8 lines · Benno Lossin
Or, we could change
Releaseto be:pub trait Release { type Ptr: ForeignOwnable; fn release(this: Self::Ptr); }
Quoted 7 lines
Quoted 5 lines
and then
register_releaseis:pub fn register_release<T: Release>(dev: &Device<Bound>, data: T::Ptr) -> ResultThis way, one can store a
Box<T>and get access to theTat the end.I think this was also the case before? Well, it was
P::Borrowedinstead.
Well you had access to a &T, but not the T directly.
Quoted 7 lines
Quoted 3 lines
Related questions:
* should we implement
ForeignOwnablefor&'static T?There's already a patch on the list doing this in the context of DebugFS [1].
Ah, I'm not following that series at the moment.
---
Cheers,
Benno
Danilo Krummrich
maintainerRe: [PATCH v3 4/4] rust: devres: implement register_release()
In reply to Danilo KrummrichQuoted 13 lines · Danilo Krummrich
Quoted 12 lines · Benno Lossin
Or, we could change
Releaseto be:pub trait Release { type Ptr: ForeignOwnable; fn release(this: Self::Ptr); }and then
register_releaseis: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 theTat the end.I think this was also the case before? Well, it was
P::Borrowedinstead.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.
Benno Lossin
reviewerRe: [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
Releaseto be:pub trait Release { type Ptr: ForeignOwnable; fn release(this: Self::Ptr); }Quoted 5 lines
and then
register_releaseis:pub fn register_release<T: Release>(dev: &Device<Bound>, data: T::Ptr) -> ResultThis way, one can store a
Box<T>and get access to theTat the end.I think this was also the case before? Well, it was
P::Borrowedinstead.
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
Danilo Krummrich
maintainerRe: [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
Releaseto be:pub trait Release { type Ptr: ForeignOwnable; fn release(this: Self::Ptr); }Quoted 5 lines
and then
register_releaseis:pub fn register_release<T: Release>(dev: &Device<Bound>, data: T::Ptr) -> ResultThis way, one can store a
Box<T>and get access to theTat the end.I think this was also the case before? Well, it was
P::Borrowedinstead.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
ForeignOwnablelikeTargetorInneror 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
Danilo Krummrich
maintainerRe: [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
Releaseto be:pub trait Release { type Ptr: ForeignOwnable; fn release(this: Self::Ptr); }and then
register_releaseis:pub fn register_release<T: Release>(dev: &Device<Bound>, data: T::Ptr) -> ResultQuoted 8 lines
Quoted 1 line
This way, one can store a
Box<T>and get access to theTat the end.I think this was also the case before? Well, it was
P::Borrowedinstead.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
ForeignOwnablelikeTargetorInneror 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
Benno Lossin
reviewerRe: [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
Releaseto be:pub trait Release { type Ptr: ForeignOwnable; fn release(this: Self::Ptr); }Quoted 5 lines
and then
register_releaseis:pub fn register_release<T: Release>(dev: &Device<Bound>, data: T::Ptr) -> ResultThis way, one can store a
Box<T>and get access to theTat the end.I think this was also the case before? Well, it was
P::Borrowedinstead.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
ForeignOwnablelikeTargetorInneror 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