Rust bindings for gem shmem
Subsystems (5)
v18 cover Rust bindings for gem shmem
Most of this patch series has already been pushed upstream, this is just the second half of the patch series that has not been pushed yet + some additional changes which were required to implement changes requested by the mailing list. This patch series is originally from Asahi, previously posted by Daniel Almeida.
The previous version of the patch series can be found here:
https://patchwork.freedesktop.org/series/164580/
Branch with patches applied available here:
https://gitlab.freedesktop.org/lyudess/linux/-/commits/rust/gem-shmem
This patch series applies on top of drm-rust-next
Patch-series wide changes since V15:
* Fix some major rebasing errors I somehow didn't notice :(
* Drop the dependency on LazyInit, use the trick that Alice suggested instead.
* Fix dependency ordering so that Tyr can get the vmap stuff first without the other bits.
Patch-series wide changes since V16:
* Fix ordering one more time (SetOnce::reset() doesn't need to come before adding vmap functions)
* Rebase against the latest DeviceContext changes from me that got pushed.
Lyude Paul (4):
rust: drm: gem: shmem: Add DmaResvGuard helper
rust: drm: gem: shmem: Add vmap functions
rust: faux: Allow retrieving a bound Device
rust: drm: gem: Introduce shmem::Object::sg_table()
rust/kernel/drm/gem/shmem.rs | 511 ++++++++++++++++++++++++++++++++++- rust/kernel/faux.rs | 16 +- 2 files changed, 512 insertions(+), 15 deletions(-)
base-commit: fea3a2dd7d3fc1936211ced5f84420e610435730
Signature · 1 line
2.54.0
PATCH v18 1/4 rust: drm: gem: shmem: Add DmaResvGuard helper
Just a temporary holdover to make locking/unlocking the dma_resv lock much easier.
---
V17:
* Fix format of commit message title
rust/kernel/drm/gem/shmem.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-)
rust/kernel/drm/gem/shmem.rs +30 -1
@@ -30,7 +30,10 @@ Deref, DerefMut, // }, - ptr::NonNull, // + ptr::{ + self, +NonNull ,// + }, };use gem::{BaseObjectPrivate ,
@@ -244,3 +247,29 @@ impl<T: DriverObject, C: DeviceContext> driver::AllocImpl for Object<T, C> { dumb_map_offset: None, }; } + +/// Private helper-type for holding the `dma_resv` object for a GEM shmem object. +/// +/// When this is dropped, the `dma_resv` lock is dropped as well. +/// +// TODO: This should be replace with a WwMutex equivalent once we have such bindings in the kernel. +struct DmaResvGuard <'a,T :DriverObject ,C :DeviceContext =Registered >(&'aObject <T ,C >); + +impl <'a,T :DriverObject ,C :DeviceContext >DmaResvGuard <'a,T ,C > { +#[inline(always)] +#[expect(unused)] +fn new (obj: &'aObject <T ,C >) ->Self { +// SAFETY: This lock is initialized throughout the lifetime of `object`. +unsafe { bindings::dma_resv_lock (obj.raw_dma_resv (), ptr::null_mut ()) }; + +Self (obj) + } +} + +impl <'a,T :DriverObject ,C :DeviceContext >Drop for DmaResvGuard <'a,T ,C > { +#[inline(always)] +fn drop (&mut self) { +// SAFETY: We are releasing the lock grabbed during the creation of this object. +unsafe { bindings::dma_resv_unlock (self.0 .raw_dma_resv ()) }; + } +}
Signature · 1 line
2.54.0
[email protected]
AI reviewRe: [PATCH v18 1/4] rust: drm: gem: shmem: Add DmaResvGuard helper
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] `DmaResvGuard` implicitly implements `Send`, allowing the `ww_mutex` to be unlocked on a different…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] DmaResvGuard implicitly implements Send, allowing the ww_mutex to be unlocked on a different thread than the one that acquired it, violating strict kernel mutex semantics.
- [Medium] Missing #[must_use] attribute on the DmaResvGuard lock guard.
- [Low] Incorrect use of #[inline(always)] for small abstraction functions that do not use build_assert!().
--
commit 83465c025b759f686444735d2b770912987eb833
Author: Lyude Paul <[email protected]>
rust: drm: gem: shmem: Add DmaResvGuard helper
Just a temporary holdover to make locking/unlocking the dma_resv lock much easier.
Quoted 4 lines
rust/kernel/drm/gem/shmem.rs +0
-0
[ ... ]
rust/kernel/drm/gem/shmem.rs impl<T: DriverObject, C: DeviceContext> driver::AllocImpl for Object<T, C> { · line 247
Quoted 11 lines
rust/kernel/drm/gem/shmem.rs +7
-0@@ -235,3 +238,29 @@ impl<T: DriverObject, C: DeviceContext> driver::AllocImpl for Object<T, C> { dumb_map_offset: None, }; } + +/// Private helper-type for holding the `dma_resv` object for a GEM shmem object. +/// +/// When this is dropped, the `dma_resv` lock is dropped as well. +/// +// TODO: This should be replace with a WwMutex equivalent once we have such bindings in the kernel. +struct DmaResvGuard <'a,T :DriverObject ,C :DeviceContext =Registered >(&'aObject <T ,C >);
[Severity: High]
Does this struct implicitly implement Send?
Since the wrapped type Object<T, C> is Sync, a shared reference &'a Object<T, C> is both Send and Sync. This makes DmaResvGuard implicitly Send. However, bindings::dma_resv_lock acquires a ww_mutex, and its Drop implementation releases it. Linux kernel mutexes are strictly bound to the task (current) that acquired them.
If safe Rust code sends the DmaResvGuard to another thread and drops it there, mutex_unlock will be called by a non-owner thread. Could this cause kernel panics or break mutual exclusion, and should DmaResvGuard be explicitly marked as !Send?
[Severity: Medium]
Should this struct be marked with #[must_use]?
Without #[must_use], users might inadvertently call DmaResvGuard::new(obj) as a standalone statement without binding it to a variable, causing the guard to drop immediately. This would leave the subsequent critical section unprotected.
rust/kernel/drm/gem/shmem.rs impl<T: DriverObject, C: DeviceContext> driver::AllocImpl for Object<T, C> { · line 259
Quoted 3 lines
rust/kernel/drm/gem/shmem.rs +3
-0+ +impl <'a,T :DriverObject ,C :DeviceContext >DmaResvGuard <'a,T ,C > { +#[inline(always)]
[Severity: Low]
This isn't a bug, but does this violate the Rust subsystem coding guidelines?
According to Documentation/rust/coding-guidelines.rst, for abstractions
only: functions that are small or forwarding to a binding call should be annotated with #[inline]. The #[inline(always)] annotation is strictly reserved for functions using build_assert!() that depend on function parameters.
rust/kernel/drm/gem/shmem.rs impl<T: DriverObject, C: DeviceContext> driver::AllocImpl for Object<T, C> { · line 270
Quoted 11 lines
rust/kernel/drm/gem/shmem.rs +11
-0+#[expect(unused)] +fn new (obj: &'aObject <T ,C >) ->Self { +// SAFETY: This lock is initialized throughout the lifetime of `object`. +unsafe { bindings::dma_resv_lock (obj.raw_dma_resv (), ptr::null_mut ()) }; + +Self (obj) + } +} + +impl <'a,T :DriverObject ,C :DeviceContext >Drop for DmaResvGuard <'a,T ,C > { +#[inline(always)]
[Severity: Low]
This isn't a bug, but should this be #[inline] instead of #[inline(always)]?
As mentioned above, the guidelines reserve #[inline(always)] for functions using build_assert!() that depend on function parameters.
Quoted 5 lines
rust/kernel/drm/gem/shmem.rs +5
-0+fn drop (&mut self) { +// SAFETY: We are releasing the lock grabbed during the creation of this object. +unsafe { bindings::dma_resv_unlock (self.0 .raw_dma_resv ()) }; + } +}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Gary Guo
reviewerRe: [PATCH v18 1/4] rust: drm: gem: shmem: Add DmaResvGuard helper
In reply to Lyude PaulQuoted 34 lines · Lyude Paul
Just a temporary holdover to make locking/unlocking the
dma_resvlock much easier.Signed-off-by Lyude Paul <[email protected]>Co-authored-by Alexandre Courbot <[email protected]>Signed-off-by Alexandre Courbot <[email protected]>---
V17:
* Fix format of commit message titlerust/kernel/drm/gem/shmem.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-)rust/kernel/drm/gem/shmem.rs +4
-1@@ -30,7 +30,10 @@ Deref, DerefMut, // }, - ptr::NonNull, // + ptr::{ + self, +NonNull ,// + }, };use gem::{BaseObjectPrivate ,@@ -244,3 +247,29 @@ impl<T: DriverObject, C: DeviceContext> driver::AllocImpl for Object<T, C> { dumb_map_offset: None, }; }
rust/kernel/drm/gem/shmem.rs impl<T: DriverObject, C: DeviceContext> driver::AllocImpl for Object<T, C> { · line 259
Quoted 10 lines
rust/kernel/drm/gem/shmem.rs +10
-0+ +/// Private helper-type for holding the `dma_resv` object for a GEM shmem object. +/// +/// When this is dropped, the `dma_resv` lock is dropped as well. +/// +// TODO: This should be replace with a WwMutex equivalent once we have such bindings in the kernel. +struct DmaResvGuard <'a,T :DriverObject ,C :DeviceContext =Registered >(&'aObject <T ,C >); + +impl <'a,T :DriverObject ,C :DeviceContext >DmaResvGuard <'a,T ,C > { +#[inline(always)]
Why always here?
Best,
Gary
Quoted 16 lines
rust/kernel/drm/gem/shmem.rs +16
-0+#[expect(unused)] +fn new (obj: &'aObject <T ,C >) ->Self { +// SAFETY: This lock is initialized throughout the lifetime of `object`. +unsafe { bindings::dma_resv_lock (obj.raw_dma_resv (), ptr::null_mut ()) }; + +Self (obj) + } +} + +impl <'a,T :DriverObject ,C :DeviceContext >Drop for DmaResvGuard <'a,T ,C > { +#[inline(always)] +fn drop (&mut self) { +// SAFETY: We are releasing the lock grabbed during the creation of this object. +unsafe { bindings::dma_resv_unlock (self.0 .raw_dma_resv ()) }; + } +}
Re: [PATCH v18 1/4] rust: drm: gem: shmem: Add DmaResvGuard helper
I think I have just been defaulting to always for anything that seems like it should be unquestionably inlined - should I be defaulting to #[inline] instead?
Quoted 8 lines · Gary Guo
Quoted 3 lines
+impl<'a, T: DriverObject, C: DeviceContext> DmaResvGuard<'a, T, C> {
+ #[inline(always)]Why
alwayshere?Best,
Gary
Alexandre Courbot
reviewerRe: [PATCH v18 1/4] rust: drm: gem: shmem: Add DmaResvGuard helper
In reply to Lyude PaulQuoted 31 lines · Lyude Paul
Just a temporary holdover to make locking/unlocking the
dma_resvlock much easier.Signed-off-by Lyude Paul <[email protected]>Co-authored-by Alexandre Courbot <[email protected]>Signed-off-by Alexandre Courbot <[email protected]>---
V17:
* Fix format of commit message titlerust/kernel/drm/gem/shmem.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-)rust/kernel/drm/gem/shmem.rs +4
-1@@ -30,7 +30,10 @@ Deref, DerefMut, // }, - ptr::NonNull, // + ptr::{ + self, +NonNull ,// + }, };use gem::{BaseObjectPrivate ,
rust/kernel/drm/gem/shmem.rs impl<T: DriverObject, C: DeviceContext> driver::AllocImpl for Object<T, C> { · line 256
Quoted 10 lines
rust/kernel/drm/gem/shmem.rs +7
-0@@ -244,3 +247,29 @@ impl<T: DriverObject, C: DeviceContext> driver::AllocImpl for Object<T, C> { dumb_map_offset: None, }; } + +/// Private helper-type for holding the `dma_resv` object for a GEM shmem object. +/// +/// When this is dropped, the `dma_resv` lock is dropped as well. +/// +// TODO: This should be replace with a WwMutex equivalent once we have such bindings in the kernel. +struct DmaResvGuard <'a,T :DriverObject ,C :DeviceContext =Registered >(&'aObject <T ,C >);
Should this be made NotThreadSafe as suggested by Alice? [1]
PATCH v18 2/4 rust: drm: gem: shmem: Add vmap functions
In reply to Lyude PaulOne of the more obvious use cases for gem shmem objects is the ability to create mappings into their contents. So, let's hook this up in our rust bindings.
---
V7:
* Switch over to the new iosys map bindings that use the Io trait V8:
* Get rid of iosys_map bindings for now, only support non-iomem types
* s/as_shmem()/as_raw_shmem()
V9:
* Get rid of some outdated comments I missed
* Add missing SIZE check to raw_vmap()
* Add a proper unit test that ensures that we actually validate SIZE at compile-time.
Turns out it takes only 34 lines to make a boilerplate DRM driver for a kunit test :)
* Add unit tests
* Add some missing #[inline]s
V10:
* Correct issue with iomem error path
We previously called raw_vunmap() if we got an iomem allocation, but raw_vunmap() was written such that it assumed all allocations were sysmem allocations. Fix this by just making raw_vunmap() accept a iosys_map.
V11:
* Use Alexandre's clever solution to remove the macros we were using for maintaining two different VMap types.
* Change the order of items in Object<T> to ensure that sgt_res is always dropped before obj.
* Fix typo in Object.raw_vmap()
* s/raw_vmap()/make_vmap()/
Deduplicate code a bit more as well by using more generics here
V15:
* Add these patches back
* We only have one VMap type now!
* Use ObjectConfig::default() in unit tests since we unbroke it. V16:
* Fix huge rebase error I made and did not notice that squashed 1.5 patches together that were definitely not supposed to be squashed
* Update old commit message
V17:
* Rebase
* Fix format of commit message title
rust/kernel/drm/gem/shmem.rs | 312 ++++++++++++++++++++++++++++++++++- 1 file changed, 311 insertions(+), 1 deletion(-)
rust/kernel/drm/gem/shmem.rs +311 -1
@@ -20,12 +20,19 @@ Registered, // }, error::to_result, + io::{ + Io, + IoCapable, + IoKnownSize, // + }, prelude::*, sync::aref::ARef, types::Opaque, // };use core::{ + ffi::c_void, marker::PhantomData , + mem::MaybeUninit ,// ops::{Deref ,DerefMut ,//
@@ -36,6 +43,7 @@ }, };use gem::{ +BaseObject ,BaseObjectPrivate ,DriverObject ,IntoGEMObject ,//
@@ -197,6 +205,80 @@ extern "C" fn free_callback(obj: *mut bindings::drm_gem_object) {// SAFETY: We're recovering the Kbox<> we created in gem_create_object() let _ =unsafe {KBox ::from_raw (this) }; } + +/// Attempt to create a vmap from the gem object, and confirm the size of said vmap. +fn make_vmap <'a,R ,const SIZE :usize >(&'a self) ->Result <VMap <T ,R ,C ,SIZE >> +where +R :Deref <Target =Self > +From <&'aSelf >, + { +// INVARIANT: We check here that the gem object is at least as large as `SIZE`. +if self.size () <SIZE { +return Err (ENOSPC ); + } + +let mut map:MaybeUninit <bindings::iosys_map > =MaybeUninit ::uninit (); +let guard =DmaResvGuard ::new (self); + +// SAFETY: drm_gem_shmem_vmap can be called with the DMA reservation lock held +to_result (unsafe { + bindings::drm_gem_shmem_vmap_locked (self.as_raw_shmem (), map.as_mut_ptr ()) + })?; + +// Drop the guard explicitly here, since we may need to call raw_vunmap() (which re-acquires +// the lock). +drop (guard); + +// SAFETY: The call to drm_gem_shmem_vmap_locked succeeded above, so we are guaranteed that +// map is properly initialized. +let map =unsafe { map.assume_init () }; + +// XXX: We don't currently support iomem allocations +if map.is_iomem { +// SAFETY: +// - The vmap operation above succeeded, guaranteeing that `map` points to a valid +// memory mapping. +// - We checked that this is an iomem allocation, making it safe to read vaddr_iomem +unsafe { self.raw_vunmap (map) }; + +Err (ENOTSUPP ) + }else { +Ok (VMap { +// SAFETY: We checked that this is not an iomem allocation, making it safe to read +// vaddr + addr:unsafe { map.__bindgen_anon_1.vaddr }, + owner: self.into (), + }) + } + } + +/// Unmap a vmap from the gem object. +/// +/// # Safety +/// +/// - The caller promises that `map` is a valid vmap on this gem object. +/// - The caller promises that the memory pointed to by map will no longer be accesed through +/// this instance. +unsafe fn raw_vunmap (&self,mut map: bindings::iosys_map ) { +let _guard =DmaResvGuard ::new (self); + +// SAFETY: +// - This function is safe to call with the DMA reservation lock held. +// - Our `ARef` is proof that the underlying gem object here is initialized and thus safe to +// dereference. +unsafe { bindings::drm_gem_shmem_vunmap_locked (self.as_raw_shmem (), &mut map) }; + } + +/// Creates and returns a virtual kernel memory mapping for this object. +#[inline] +pub fn vmap <const SIZE :usize >(&self) ->Result <VMapRef <'_,T ,C ,SIZE >> { + self.make_vmap () + } + +/// Creates and returns an owned reference to a virtual kernel memory mapping for this object. +#[inline] +pub fn owned_vmap <const SIZE :usize >(&self) ->Result <VMapOwned <T ,C ,SIZE >> { + self.make_vmap () + } }impl <T :DriverObject ,C :DeviceContext >Deref for Object <T ,C > {
@@ -257,7 +339,6 @@ impl<T: DriverObject, C: DeviceContext> driver::AllocImpl for Object<T, C> {impl <'a,T :DriverObject ,C :DeviceContext >DmaResvGuard <'a,T ,C > {#[inline(always)] -#[expect(unused)] fn new (obj: &'aObject <T ,C >) ->Self {// SAFETY: This lock is initialized throughout the lifetime of `object`. unsafe { bindings::dma_resv_lock (obj.raw_dma_resv (), ptr::null_mut ()) };
@@ -273,3 +354,232 @@ fn drop(&mut self) {unsafe { bindings::dma_resv_unlock (self.0 .raw_dma_resv ()) }; } } + +macro_rules! impl_vmap_io_capable { + ($impl:ident, $ty:ty) => { +impl <D ,R ,C ,const SIZE :usize >IoCapable <$ty>for $impl<D ,R ,C ,SIZE > +where +D :DriverObject , +C :DeviceContext , +R :Deref <Target =Object <D ,C >>, + { +#[inline(always)] +unsafe fn io_read (&self, address:usize ) -> $ty { +let ptr = addressas *mut $ty; + +// SAFETY: The safety contract of `io_read` guarantees that address is a valid +// address within the bounds of `Self` of at least the size of $ty, and is properly +// aligned. +unsafe { ptr::read (ptr) } + } + +#[inline(always)] +unsafe fn io_write (&self, value: $ty, address:usize ) { +let ptr = addressas *mut $ty; + +// SAFETY: The safety contract of `io_write` guarantees that address is a valid +// address within the bounds of `Self` of at least the size of $ty, and is properly +// aligned. +unsafe { ptr::write (ptr, value) } + } + } + }; +} + +/// A reference to a virtual mapping for an shmem-based GEM object in kernel address space. +/// +/// # Invariants +/// +/// - The size of `owner` is >= SIZE. +/// - The memory pointed to by addr remains valid at least until this object is dropped. +pub struct VMap <D ,R ,C =Registered ,const SIZE :usize =0 > +where +D :DriverObject , +C :DeviceContext , +R :Deref <Target =Object <D ,C >>, +{ + addr: *mut c_void , + owner:R , +} + +/// An alias type for a reference to a shmem-based GEM object's VMap. +pub type VMapRef <'a,D ,C ,const SIZE :usize =0 > =VMap <D , &'aObject <D ,C >,C ,SIZE >; + +/// An alias type for an owned reference to a shmem-based GEM object's VMap. +pub type VMapOwned <D ,C ,const SIZE :usize =0 > =VMap <D ,ARef <Object <D ,C >>,C ,SIZE >; + +impl <D ,R ,C ,const SIZE :usize >VMap <D ,R ,C ,SIZE > +where +D :DriverObject , +C :DeviceContext , +R :Deref <Target =Object <D ,C >>, +{ +/// Borrows a reference to the object that owns this virtual mapping. +#[inline(always)] +pub fn owner (&self) -> &Object <D ,C > { + &self.owner + } +} + +impl <D ,R ,C ,const SIZE :usize >Drop for VMap <D ,R ,C ,SIZE > +where +D :DriverObject , +C :DeviceContext , +R :Deref <Target =Object <D ,C >>, +{ +#[inline(always)] +fn drop (&mut self) { +// SAFETY: +// - Our existence is proof that this map was previously created using self.owner. +// - Since we are in Drop, we are guaranteed that no one will access the memory +// through this mapping after calling this. +unsafe { + self.owner.raw_vunmap (bindings::iosys_map { + is_iomem:false , + __bindgen_anon_1: bindings::iosys_map__bindgen_ty_1 { vaddr: self.addr }, + }) + }; + } +} + +impl <D ,R ,C ,const SIZE :usize >Io for VMap <D ,R ,C ,SIZE > +where +D :DriverObject , +C :DeviceContext , +R :Deref <Target =Object <D ,C >>, +{ +#[inline(always)] +fn addr (&self) ->usize { + self.addras usize + } + +#[inline(always)] +fn maxsize (&self) ->usize { + self.owner.size () + } +} + +impl <D ,R ,C ,const SIZE :usize >IoKnownSize for VMap <D ,R ,C ,SIZE > +where +D :DriverObject , +C :DeviceContext , +R :Deref <Target =Object <D ,C >>, +{ +const MIN_SIZE :usize =SIZE ; +} + +impl_vmap_io_capable! (VMap ,u8 ); +impl_vmap_io_capable! (VMap ,u16 ); +impl_vmap_io_capable! (VMap ,u32 ); +#[cfg( CONFIG_64BIT )] +impl_vmap_io_capable! (VMap ,u64 ); + +#[kunit_tests(rust_drm_gem_shmem)] +mod tests { +use super ::*; +use crate ::{ + drm::{ + self, +UnregisteredDevice ,// + }, + faux, + page::PAGE_SIZE ,// + }; + +// The bare minimum needed to create a fake drm driver for kunit + +#[pin_data] +struct KunitData {} +struct KunitDriver ; +struct KunitFile ; +#[pin_data] +struct KunitObject {} + +const INFO : drm::DriverInfo = drm::DriverInfo { + major:0 , + minor:0 , + patchlevel:0 , + name:c"kunit" , + desc:c"Kunit" , + }; + +impl drm::file::DriverFile for KunitFile { +type Driver =KunitDriver ; + +fn open (_dev: &drm::Device <KunitDriver >) ->Result <Pin <KBox <Self >>> { +Ok (KBox ::new (Self ,GFP_KERNEL )?.into ()) + } + } + +impl gem::DriverObject for KunitObject { +type Driver =KunitDriver ; +type Args = (); + +fn new <C :DeviceContext >( + _dev: &drm::Device <KunitDriver ,C >, + _size:usize , + _args:Self ::Args , + ) ->impl PinInit <Self ,Error > { +try_pin_init! (KunitObject {}) + } + } + +#[vtable] +impl drm::Driver for KunitDriver { +type Data =KunitData ; +type File =KunitFile ; +type Object <Ctx :DeviceContext > =Object <KunitObject ,Ctx >; + +const INFO : drm::DriverInfo =INFO ; +const IOCTLS : &'static [drm::ioctl::DrmIoctlDescriptor ] = &[]; + } + +fn create_drm_dev () ->Result <(faux::Registration ,UnregisteredDevice <KunitDriver >)> { +// Create a faux DRM device so we can test gem object creation. +let data =try_pin_init! (KunitData {}); +let dev = faux::Registration ::new (c"Kunit" ,None )?; +let drm =UnregisteredDevice ::new (dev.as_ref (), data)?; + +Ok ((dev, drm)) + } + +#[test] +fn compile_time_vmap_sizes () ->Result { +let (_dev, drm) =create_drm_dev ()?; + +let obj =Object ::<KunitObject ,_ >::new (&drm,PAGE_SIZE ,ObjectConfig ::default (), ())?; + +// Try creating a normal vmap + obj.vmap ::<PAGE_SIZE >()?; + +// Try creating a vmap that's smaller then the size we specified + obj.vmap ::<{PAGE_SIZE -100 }>()?; + +// Make sure creating a vmap that's too large fails +assert! (obj.vmap ::<{PAGE_SIZE +200 }>().is_err ()); + +Ok (()) + } + +#[test] +fn vmap_io () ->Result { +let (_dev, drm) =create_drm_dev ()?; + +let obj =Object ::<KunitObject ,_ >::new (&drm,PAGE_SIZE ,ObjectConfig ::default (), ())?; + +let vmap = obj.vmap ::<PAGE_SIZE >()?; + + vmap.write8 (0xDE ,0x0 ); +assert_eq! (vmap.read8 (0x0 ),0xDE ); + vmap.write32 (0xFFFFFFFF ,0x20 ); + +assert_eq! (vmap.read32 (0x20 ),0xFFFFFFFF ); + +assert_eq! (vmap.read8 (0x20 ),0xFF ); +assert_eq! (vmap.read8 (0x21 ),0xFF ); +assert_eq! (vmap.read8 (0x22 ),0xFF ); +assert_eq! (vmap.read8 (0x23 ),0xFF ); + +Ok (()) + } +}
Signature · 1 line
2.54.0
[email protected]
AI reviewRe: [PATCH v18 2/4] rust: drm: gem: shmem: Add vmap functions
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] The `IoCapable` implementation for `VMap` uses non-volatile `ptr::read` and `ptr::write`, allowing…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The IoCapable implementation for VMap uses non-volatile ptr::read and ptr::write, allowing the compiler to optimize away or reorder memory accesses to shared device memory.
- [Medium] VMap and VMapOwned are implicitly !Send and !Sync due to the *mut c_void field, artificially restricting the mapping to the thread that created it.
- [Low] Missing // INVARIANT: comment explaining VMap memory validity at its construction site, violating subsystem guidelines.
--
commit b418d73acbe560c4eda85331900333d19c6ae108
Author: Lyude Paul <[email protected]>
rust: drm: gem: shmem: Add vmap functions
One of the more obvious use cases for gem shmem objects is the ability to create mappings into their contents. So, let's hook this up in our rust bindings.
Quoted 4 lines
rust/kernel/drm/gem/shmem.rs +0
-0
[ ... ]
Quoted 10 lines
rust/kernel/drm/gem/shmem.rs +6
-0@@ -188,6 +196,80 @@ extern "C" fn free_callback(obj: *mut bindings::drm_gem_object) {// SAFETY: We're recovering the Kbox<> we created in gem_create_object() let _ =unsafe {KBox ::from_raw (this) }; } + +/// Attempt to create a vmap from the gem object, and confirm the size of said vmap. +fn make_vmap <'a,R ,const SIZE :usize >(&'a self) ->Result <VMap <T ,R ,C ,SIZE >> +where +R :Deref <Target =Self > +From <&'aSelf >, + {
[ ... ]
Quoted 6 lines
rust/kernel/drm/gem/shmem.rs +6
-0+// XXX: We don't currently support iomem allocations +if map.is_iomem { +// SAFETY: +// - The vmap operation above succeeded, guaranteeing that `map` points to a valid +// memory mapping. +// - We checked that this is an iomem allocation, making it safe to read vaddr_iomem
rust/kernel/drm/gem/shmem.rs extern "C" fn free_callback(obj: *mut bindings::drm_gem_object) { · line 250
Quoted 10 lines
rust/kernel/drm/gem/shmem.rs +10
-0+unsafe { self.raw_vunmap (map) }; + +Err (ENOTSUPP ) + }else { +Ok (VMap { +// SAFETY: We checked that this is not an iomem allocation, making it safe to read +// vaddr + addr:unsafe { map.__bindgen_anon_1.vaddr }, + owner: self.into (), + })
[Severity: Low]
This isn't a bug, but there's a missing invariant comment here.
The Rust subsystem guidelines mention that when a struct with an # Invariants documentation section is constructed, the code should have an // INVARIANT: comment explaining why the invariants are satisfied.
Would it make sense to add a comment here explaining why the memory remains valid until dropped?
Quoted 2 lines
rust/kernel/drm/gem/shmem.rs +2
-0+ } + }
[ ... ]
Quoted 12 lines
rust/kernel/drm/gem/shmem.rs +8
-0@@ -264,3 +345,232 @@ fn drop(&mut self) {unsafe { bindings::dma_resv_unlock (self.0 .raw_dma_resv ()) }; } } + +macro_rules! impl_vmap_io_capable { + ($impl:ident, $ty:ty) => { +impl <D ,R ,C ,const SIZE :usize >IoCapable <$ty>for $impl<D ,R ,C ,SIZE > +where +D :DriverObject , +C :DeviceContext , +R :Deref <Target =Object <D ,C >>,
rust/kernel/drm/gem/shmem.rs fn drop(&mut self) { · line 365
Quoted 10 lines
rust/kernel/drm/gem/shmem.rs +10
-0+ { +#[inline(always)] +unsafe fn io_read (&self, address:usize ) -> $ty { +let ptr = addressas *mut $ty; + +// SAFETY: The safety contract of `io_read` guarantees that address is a valid +// address within the bounds of `Self` of at least the size of $ty, and is properly +// aligned. +unsafe { ptr::read (ptr) } + }
[Severity: High]
Can this allow the compiler to improperly optimize these memory accesses?
Since VMap represents GEM shmem objects mapped into the kernel virtual address space, this memory might be concurrently accessed by hardware or userspace.
Using standard non-volatile ptr::read and ptr::write allows the compiler to cache values in registers, coalesce writes, or elide them entirely. The C equivalent (iosys_map_rd and iosys_map_wr) uses READ_ONCE() and WRITE_ONCE() for this reason.
Should this use ptr::read_volatile() to safely access the shared memory?
rust/kernel/drm/gem/shmem.rs fn drop(&mut self) { · line 384
Quoted 10 lines
rust/kernel/drm/gem/shmem.rs +10
-0+ +#[inline(always)] +unsafe fn io_write (&self, value: $ty, address:usize ) { +let ptr = addressas *mut $ty; + +// SAFETY: The safety contract of `io_write` guarantees that address is a valid +// address within the bounds of `Self` of at least the size of $ty, and is properly +// aligned. +unsafe { ptr::write (ptr, value) } + }
[Severity: High]
Similar to the io_read() implementation above, does this write need to use ptr::write_volatile() to prevent the compiler from caching or coalescing these shared memory writes?
Quoted 9 lines
rust/kernel/drm/gem/shmem.rs +9
-0+ } + }; +} + +/// A reference to a virtual mapping for an shmem-based GEM object in kernel address space. +/// +/// # Invariants +/// +/// - The size of `owner` is >= SIZE.
rust/kernel/drm/gem/shmem.rs fn drop(&mut self) { · line 403
Quoted 10 lines
rust/kernel/drm/gem/shmem.rs +10
-0+/// - The memory pointed to by addr remains valid at least until this object is dropped. +pub struct VMap <D ,R ,C =Registered ,const SIZE :usize =0 > +where +D :DriverObject , +C :DeviceContext , +R :Deref <Target =Object <D ,C >>, +{ + addr: *mut c_void , + owner:R , +}
[Severity: Medium]
Does this unintentionally restrict the mapping to the thread that created it?
Because VMap stores its mapping address as a raw pointer
addr: *mut c_void, it implicitly lacks the Send and Sync traits in Rust. This means VMap and VMapOwned cannot be safely transferred or shared across concurrency boundaries.
Since the kernel's virtual mapping should be globally valid as long as the underlying owner object provides safe concurrent access, should explicit unsafe impl Send and unsafe impl Sync be provided for this struct?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Re: [PATCH v18 2/4] rust: drm: gem: shmem: Add vmap functions
In reply to Lyude PaulQuoted 5 lines · Lyude Paul
One of the more obvious use cases for gem shmem objects is the ability to create mappings into their contents. So, let's hook this up in our rust bindings.
Signed-off-by Lyude Paul <[email protected]>
A few final nits below.
<...>
rust/kernel/drm/gem/shmem.rs extern "C" fn free_callback(obj: *mut bindings::drm_gem_object) { · line 266
Quoted 13 lines
rust/kernel/drm/gem/shmem.rs +13
-0+/// Unmap a vmap from the gem object. +/// +/// # Safety +/// +/// - The caller promises that `map` is a valid vmap on this gem object. +/// - The caller promises that the memory pointed to by map will no longer be accesed through +/// this instance. +unsafe fn raw_vunmap (&self,mut map: bindings::iosys_map ) { +let _guard =DmaResvGuard ::new (self); + +// SAFETY: +// - This function is safe to call with the DMA reservation lock held. +// - Our `ARef` is proof that the underlying gem object here is initialized and thus safe to
We aren't necessarily backed by a ARef anymore, but maybe we can refer to the Safety comment instead.
Quoted 51 lines
rust/kernel/drm/gem/shmem.rs +36
-1+// dereference. +unsafe { bindings::drm_gem_shmem_vunmap_locked (self.as_raw_shmem (), &mut map) }; + } + +/// Creates and returns a virtual kernel memory mapping for this object. +#[inline] +pub fn vmap <const SIZE :usize >(&self) ->Result <VMapRef <'_,T ,C ,SIZE >> { + self.make_vmap () + } + +/// Creates and returns an owned reference to a virtual kernel memory mapping for this object. +#[inline] +pub fn owned_vmap <const SIZE :usize >(&self) ->Result <VMapOwned <T ,C ,SIZE >> { + self.make_vmap () + } }impl <T :DriverObject ,C :DeviceContext >Deref for Object <T ,C > {@@ -257,7 +339,6 @@ impl<T: DriverObject, C: DeviceContext> driver::AllocImpl for Object<T, C> {impl <'a,T :DriverObject ,C :DeviceContext >DmaResvGuard <'a,T ,C > {#[inline(always)] -#[expect(unused)] fn new (obj: &'aObject <T ,C >) ->Self {// SAFETY: This lock is initialized throughout the lifetime of `object`. unsafe { bindings::dma_resv_lock (obj.raw_dma_resv (), ptr::null_mut ()) };@@ -273,3 +354,232 @@ fn drop(&mut self) {unsafe { bindings::dma_resv_unlock (self.0 .raw_dma_resv ()) }; } } + +macro_rules! impl_vmap_io_capable { + ($impl:ident, $ty:ty) => { +impl <D ,R ,C ,const SIZE :usize >IoCapable <$ty>for $impl<D ,R ,C ,SIZE > +where +D :DriverObject , +C :DeviceContext , +R :Deref <Target =Object <D ,C >>, + { +#[inline(always)] +unsafe fn io_read (&self, address:usize ) -> $ty { +let ptr = addressas *mut $ty; + +// SAFETY: The safety contract of `io_read` guarantees that address is a valid +// address within the bounds of `Self` of at least the size of $ty, and is properly +// aligned. +unsafe { ptr::read (ptr) } + } + +#[inline(always)] +unsafe fn io_write (&self, value: $ty, address:usize ) {
rust/kernel/drm/gem/shmem.rs fn drop(&mut self) { · line 387
Quoted 10 lines
rust/kernel/drm/gem/shmem.rs +10
-0+let ptr = addressas *mut $ty; + +// SAFETY: The safety contract of `io_write` guarantees that address is a valid +// address within the bounds of `Self` of at least the size of $ty, and is properly +// aligned. +unsafe { ptr::write (ptr, value) } + } + } + }; +}
I would maybe move the macro definition right before its use, since it is very local and the code reads more naturally if VMap is introduced before imho.
rust/kernel/drm/gem/shmem.rs fn drop(&mut self) { · line 394
Quoted 7 lines
rust/kernel/drm/gem/shmem.rs +7
-0+ +/// A reference to a virtual mapping for an shmem-based GEM object in kernel address space. +/// +/// # Invariants +/// +/// - The size of `owner` is >= SIZE. +/// - The memory pointed to by addr remains valid at least until this object is dropped.
nit: addr.
(also noticed a few other missing `` quotes in the patch)
<...>
rust/kernel/drm/gem/shmem.rs fn drop(&mut self) { · line 475
Quoted 5 lines
rust/kernel/drm/gem/shmem.rs +5
-0+impl_vmap_io_capable! (VMap ,u8 ); +impl_vmap_io_capable! (VMap ,u16 ); +impl_vmap_io_capable! (VMap ,u32 ); +#[cfg( CONFIG_64BIT )] +impl_vmap_io_capable! (VMap ,u64 );
Having to specify VMap seems a bit redundant. Since the macro is only usable on VMap due to its constraints, and even has it in its name, I guess you can just hardcode it.
Quoted 2 lines
(quoted diff) +2
-0+ +#[kunit_tests(rust_drm_gem_shmem)]
<...>
rust/kernel/drm/gem/shmem.rs fn drop(&mut self) { · line 556
Quoted 11 lines
rust/kernel/drm/gem/shmem.rs +11
-0+#[test] +fn compile_time_vmap_sizes () ->Result { +let (_dev, drm) =create_drm_dev ()?; + +let obj =Object ::<KunitObject ,_ >::new (&drm,PAGE_SIZE ,ObjectConfig ::default (), ())?; + +// Try creating a normal vmap + obj.vmap ::<PAGE_SIZE >()?; + +// Try creating a vmap that's smaller then the size we specified + obj.vmap ::<{PAGE_SIZE -100 }>()?;
For these two, maybe also check that maxsize() and owner() have the expected value?
owned_vmap also doesn't appear to be tested, although I am not sure whether that would bring much more coverage, so please take this as just a sidenote.
Quoted 8 lines
rust/kernel/drm/gem/shmem.rs +8
-0+ +// Make sure creating a vmap that's too large fails +assert! (obj.vmap ::<{PAGE_SIZE +200 }>().is_err ()); + +Ok (()) + } + +#[test]
rust/kernel/drm/gem/shmem.rs fn drop(&mut self) { · line 574
Quoted 10 lines
rust/kernel/drm/gem/shmem.rs +10
-0+fn vmap_io () ->Result { +let (_dev, drm) =create_drm_dev ()?; + +let obj =Object ::<KunitObject ,_ >::new (&drm,PAGE_SIZE ,ObjectConfig ::default (), ())?; + +let vmap = obj.vmap ::<PAGE_SIZE >()?; + + vmap.write8 (0xDE ,0x0 ); +assert_eq! (vmap.read8 (0x0 ),0xDE ); + vmap.write32 (0xFFFFFFFF ,0x20 );
Let's maybe use a more varied pattern (e.g. 0xFEDCBA98) so the ordering is also properly tested by the tests below.
Quoted 13 lines
rust/kernel/drm/gem/shmem.rs +11
-0+ +assert_eq! (vmap.read32 (0x20 ),0xFFFFFFFF ); + +assert_eq! (vmap.read8 (0x20 ),0xFF ); +assert_eq! (vmap.read8 (0x21 ),0xFF ); +assert_eq! (vmap.read8 (0x22 ),0xFF ); +assert_eq! (vmap.read8 (0x23 ),0xFF ); + +Ok (()) + } +}Signature · 1 line
2.54.0
PATCH v18 3/4 rust: faux: Allow retrieving a bound Device
In reply to Lyude PaulWhen writing up some rust code that used faux devices for unit testing, I noticed that we never actually added the Bound device context to faux::Registration's AsRef<device::Device> implementation. This being said:
the Registration object itself is proof that a driver is bound to the device - so this should be safe.
---
V18:
- Add notes from Danilo to safety comment.
rust/kernel/faux.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-)
rust/kernel/faux.rs +11 -5
@@ -25,7 +25,8 @@/// /// # Invariants /// -/// `self.0` always holds a valid pointer to an initialized and registered [`struct faux_device`]. +/// - `self.0` always holds a valid pointer to an initialized and registered [`struct faux_device`]. +/// - This object is proof that the object described by this `Registration` is bound to a device. /// /// [`struct faux_device`]: srctree/include/linux/device/faux.h pub struct Registration (NonNull <bindings::faux_device >);
@@ -59,10 +60,15 @@ fn as_raw(&self) -> *mut bindings::faux_device { } } -impl AsRef <device::Device >for Registration { -fn as_ref (&self) -> &device::Device { -// SAFETY: The underlying `device` in `faux_device` is guaranteed by the C API to be -// a valid initialized `device`. +impl AsRef <device::Device <device::Bound >>for Registration { +fn as_ref (&self) -> &device::Device <device::Bound > { +// SAFETY: +// - The underlying `device` in `faux_device` is guaranteed by the C API to be a valid +// initialized `device`. +// - faux_match() always returns 1, and probe runs synchronously (PROBE_FORCE_SYNCHRONOUS). +// - suppress_bind_attrs = true on faux_driver prevents userspace-triggered unbind via sysfs +// - mem::forget(Registration) is not a problem; if the Registration is leaked, the faux +// device stays bound forever. unsafe { device::Device ::from_raw (addr_of_mut! ((*self.as_raw ()).dev)) } } }
Signature · 1 line
2.54.0
[email protected]
AI reviewRe: [PATCH v18 3/4] rust: faux: Allow retrieving a bound Device
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Low] The `Registration` struct's `# Invariants` documentation was updated with a new invariant, but the…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] The Registration struct's # Invariants documentation was updated with a new invariant, but the // INVARIANT: comment in the Registration::new constructor was not updated to explain why this new invariant is satisfied.
- [Low] Typo in the new type invariant comment: it incorrectly states the object is 'bound to a device' instead of 'bound to a driver'.
--
commit d541fc9c1199eb4e3a390c6348b6b03f9492aacc
Author: Lyude Paul <[email protected]>
rust: faux: Allow retrieving a bound Device
This commit updates faux::Registration to implement AsRef for a bound device instead of a generic device. It also adds a new invariant to the Registration struct to document that the object is proof of a driver being bound to the device.
rust/kernel/faux.rs line 29
Quoted 11 lines
rust/kernel/faux.rs +2
-1@@ -25,7 +25,8 @@/// /// # Invariants /// -/// `self.0` always holds a valid pointer to an initialized and registered [`struct faux_device`]. +/// - `self.0` always holds a valid pointer to an initialized and registered [`struct faux_device`]. +/// - This object is proof that the object described by this `Registration` is bound to a device.
[Severity: Low]
This isn't a bug, but there appears to be a typo here. The commit message states that the Registration object is proof that "a driver is bound to the device". Should this invariant say it is bound to a "driver" instead of a "device"?
[Severity: Low]
Since a new invariant was added to the documentation here, does the // INVARIANT: comment in Registration::new() also need to be updated to explain why this new invariant is satisfied?
Currently, the comment in Registration::new() only explains the first invariant:
// INVARIANT: The device will remain registered until faux_device_destroy()
// is called, which happens in our Drop implementation.[ ... ]
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
Re: [PATCH v18 3/4] rust: faux: Allow retrieving a bound Device
In reply to Lyude PaulQuoted 7 lines · Lyude Paul
When writing up some rust code that used faux devices for unit testing, I noticed that we never actually added the Bound device context to faux::Registration's AsRef<device::Device> implementation. This being said:
the Registration object itself is proof that a driver is bound to the device - so this should be safe.Signed-off-by Lyude Paul <[email protected]>
PATCH v18 4/4 rust: drm: gem: Introduce shmem::Object::sg_table()
In reply to Lyude PaulIn order to do this, we need to be careful to ensure that any interface we expose for scatterlists ensures that any mappings created from one are destroyed on driver-unbind. To do this, we introduce a Devres resource into shmem::Object that we use in order to ensure that we release any SGTable mappings on driver-unbind.
There's some other slightly unfortunate caveats of this:
* Drivers don't have explicit control at the moment over when unmapping happens (which is exactly the same as the C side atm, so it might not be a problem).
* We can't just return SGTableMap to the user through an Arc to attempt to fix the last caveat - because that implies the gem object would need to hold a reference count to the scatterlist mapping, which just leaves us with the same problem.
---
V3:
* Rename OwnedSGTable to shmem::SGTable. Since the current version of the SGTable abstractions now has a Owned and Borrowed variant, I think renaming this to shmem::SGTable makes things less confusing. We do however, keep the name of owned_sg_table() as-is.
V4:
* Clarify safety comments for SGTable to explain why the object is thread-safe.
* Rename from SGTableRef to SGTable
V10:
* Use Devres in order to ensure that SGTables are revocable, and are unmapped on driver-unbind.
V11:
* s/create_sg_table()/get_sg_table()
* Get rid of extraneous ret = in shmem::Object::get_sg_table() V12:
* Actually move sgt_res in this patch and not the next one
V13:
* Use DmaResvGuard suggestion from Alexander
* Use Alexander's (much better) solution for get_sg_table()
* Use SetOnce instead of UnsafeCell
* s/SGTableRef/SGTableMap
* Fix typo in SGTableMap documentation
* Create fallible constructor for SGTableMap
* Don't reuse dma_resv lock for protecting Object contents, just use Mutex
+ SetOnce
* Drop use of drm_gem_shmem_get_pages_sgt_locked(), since we don't need to hold the dma_resv lock ourselves for anything but this function.
* Check that the device we receive in the bounds for sg_table() and owned_sg_table() that said Device is in fact, the correct device.
* Remove redundant docs in owned_sg_table(), just point it back to sg_table().
* Implement Deborah's suggestion to fix double-free in free_callback()
* Restore original order of Object<T>
* Fix doc typo for SGTableMap
V14:
* Use new InitOnce container over the Mutex/SetOnce horror show we had before.
* Start using LazyInit container for storing Devres for sgt unmap
* Add some kunit tests for sg_table (not sure why I didn't do this before) using some of the boilerplate code leftover from the vmap bindings
* Get rid of the owned SGTable variant for now, we'll add it back in a future patch if people actually need it.
* Use new LazyInit container from me to get rid of the horrid Mutex<SetOnce<>> mess.
* Add the best we can do for unit tests w/r/t SGTable at the moment V16:
* Get rid of LazyInit, go back to SetOnce, use trick that Alice recommended that is a lot cleaner.
* Fix horrid rebasing mistake
V17:
* Rebase
* Fix missing safety comment in free_callback() (we forgot to justify why &mut is safe in unsafe { &mut (*this).sgt_res }.reset())
V18:
* Use ManuallyDrop instead of SetOnce::reset()
rust/kernel/drm/gem/shmem.rs | 172 +++++++++++++++++++++++++++++++++-- 1 file changed, 162 insertions(+), 10 deletions(-)
rust/kernel/drm/gem/shmem.rs +162 -10
@@ -11,6 +11,11 @@use crate ::{ container_of, + device::{ + self, +Bound ,// + }, + devres::*, drm::{ driver, gem,
@@ -19,20 +24,32 @@ DeviceContext, Registered, // }, - error::to_result, + error::{ + from_err_ptr, + to_result, // + }, io::{ Io, IoCapable, IoKnownSize, // }, prelude::*, - sync::aref::ARef, + scatterlist, + sync::{ + aref::ARef, + new_mutex, + Mutex, + SetOnce, // + }, types::Opaque, // };use core::{ ffi::c_void, marker::PhantomData , - mem::MaybeUninit ,// + mem::{ +ManuallyDrop , +MaybeUninit ,// + }, ops::{Deref ,DerefMut ,//
@@ -87,6 +104,11 @@ pub struct Object<T: DriverObject, C: DeviceContext = Registered> { obj: Opaque<bindings::drm_gem_shmem_object>, /// Parent object that owns this object's DMA reservation object. parent_resv_obj: Option<ARef<Object<T, C>>>, + /// Devres object for unmapping any SGTable on driver-unbind. + sgt_res: ManuallyDrop<SetOnce<Devres<SGTableMap<T, C>>>>, +#[pin] +/// Lock for protecting initialization of `sgt_res`. + sgt_lock: Mutex<()>,#[pin] inner: T, _ctx: PhantomData<C>,
@@ -145,6 +167,8 @@ pub fn new(try_pin_init! (Self { obj <-Opaque ::init_zeroed (), parent_resv_obj: config.parent_resv_obj.map (|p| p.into ()), + sgt_res:ManuallyDrop ::new (SetOnce ::new ()), + sgt_lock <-new_mutex! (()), inner <-T ::new (dev, size, args), _ctx:PhantomData ::<C >, }),
@@ -189,18 +213,26 @@ extern "C" fn free_callback(obj: *mut bindings::drm_gem_object) {// - DRM always passes a valid gem object here // - We used drm_gem_shmem_create() in our create_gem_object callback, so we know that // `obj` is contained within a drm_gem_shmem_object -let this =unsafe {container_of! (obj, bindings::drm_gem_shmem_object, base) }; - -// SAFETY: -// - We're in free_callback - so this function is safe to call. -// - We won't be using the gem resources on `this` after this call. -unsafe { bindings::drm_gem_shmem_release (this) }; +let base =unsafe {container_of! (obj, bindings::drm_gem_shmem_object, base) };// SAFETY: // - We verified above that `obj` is valid, which makes `this` valid // - This function is set in AllocOps, so we know that `this` is contained within a // `Object<T, C>` -let this =unsafe {container_of! (Opaque ::cast_from (this),Self , obj) }.cast_mut (); +let this =unsafe {container_of! (Opaque ::cast_from (base),Self , obj) }.cast_mut (); + +// We need to drop `sgt_res` first, since doing so requires that the GEM object is still +// alive. +// SAFETY: +// - We verified above that `this` is valid. +// - We are in free_callback, guaranteeing we have exclusive access to `this` and that +// `sgt_res` will not be used after dropping it here. +unsafe {ManuallyDrop ::drop (&mut (*this).sgt_res) }; + +// SAFETY: +// - We're in free_callback - so this function is safe to call. +// - We won't be using the gem resources on `this` after this call. +unsafe { bindings::drm_gem_shmem_release (base) };// SAFETY: We're recovering the Kbox<> we created in gem_create_object() let _ =unsafe {KBox ::from_raw (this) };
@@ -279,6 +311,45 @@ pub fn vmap<const SIZE: usize>(&self) -> Result<VMapRef<'_, T, C, SIZE>> {pub fn owned_vmap <const SIZE :usize >(&self) ->Result <VMapOwned <T ,C ,SIZE >> { self.make_vmap () } + +/// Creates (if necessary) and returns an immutable reference to a scatter-gather table of DMA +/// pages for this object. +/// +/// This will pin the object in memory. It is expected that `dev` should be a pointer to the +/// same [`device::Device`] which `self` belongs to, otherwise this function will return +/// `Err(EINVAL)`. +pub fn sg_table <'a>( + &'a self, + dev: &'a device::Device <Bound >, + ) ->Result <&'a scatterlist::SGTable > { +if dev.as_raw () != self.dev ().as_ref ().as_raw () { +return Err (EINVAL ); + } + +let sgt_res = 'out: { +// Fast path: sgt_res is already initialized +if let Some (sgt_res) = self.sgt_res.as_ref () { +break 'out sgt_res; + } + +// Slow path: Grab the lock and see if we need to initialize sgt_res. +let _guard = self.sgt_lock.lock (); + +// If someone initialized it while we were waiting, we can exit early. +if let Some (sgt_res) = self.sgt_res.as_ref () { +break 'out sgt_res; + } + +// If not, finish initializing and return. + self.sgt_res + .populate (Devres ::new (dev,SGTableMap ::new (self))?); + +// SAFETY: We just populated sgt_res above. +unsafe { self.sgt_res.as_ref ().unwrap_unchecked () } + }; + +Ok (sgt_res.access (dev)?) + } }impl <T :DriverObject ,C :DeviceContext >Deref for Object <T ,C > {
@@ -474,6 +545,63 @@ impl<D, R, C, const SIZE: usize> IoKnownSize for VMap<D, R, C, SIZE>#[cfg( CONFIG_64BIT )] impl_vmap_io_capable! (VMap ,u64 ); +/// A reference to a GEM object that is known to have a mapped [`SGTable`]. +/// +/// This is used by the Rust bindings with [`Devres`] in order to ensure that mappings for SGTables +/// on GEM shmem objects are revoked on driver-unbind. +/// +/// # Invariants +/// +/// - `self.obj` always points to a valid GEM object. +/// - This object is proof that `self.obj.owner.sgt` has an initialized and valid +/// [`scatterlist::SGTable`]. +pub struct SGTableMap <T :DriverObject ,C :DeviceContext > { + obj:NonNull <Object <T ,C >>, +} + +impl <T :DriverObject ,C :DeviceContext >Deref for SGTableMap <T ,C > { +type Target = scatterlist::SGTable ; + +fn deref (&self) -> &Self ::Target { +// SAFETY: +// - The NonNull is guaranteed to be valid via our type invariants. +// - The sgt field is guaranteed to be initialized and valid via our type invariants. +unsafe { scatterlist::SGTable ::from_raw ((*self.obj.as_ref ().as_raw_shmem ()).sgt) } + } +} + +impl <T :DriverObject ,C :DeviceContext >Drop for SGTableMap <T ,C > { +fn drop (&mut self) { +// SAFETY: `obj` is always valid via our type invariants +let obj =unsafe { self.obj.as_ref () }; +let _lock =DmaResvGuard ::new (obj); + +// SAFETY: We acquired the lock needed for calling this function above +unsafe { bindings::__drm_gem_shmem_free_sgt_locked (obj.as_raw_shmem ()) }; + } +} + +impl <T :DriverObject ,C :DeviceContext >SGTableMap <T ,C > { +fn new (obj: &Object <T ,C >) ->impl Init <Self ,Error > { +// INVARIANT: +// - We call drm_gem_shmem_get_pages_sgt_locked below and check whether or not it +// succeeds, fulfilling the invariant of SGTableMap that the object's `sgt` field is +// initialized. +// SAFETY: +// - `obj` is fully initialized, making this function safe to call. +from_err_ptr (unsafe { bindings::drm_gem_shmem_get_pages_sgt (obj.as_raw_shmem ()) })?; + +Ok (Self { obj: obj.into () }) + } +} + +// SAFETY: The NonNull in SGTableMap is guaranteed valid by our type invariants, and the GEM object +// it points to is guaranteed to be thread-safe. +unsafe impl <T :DriverObject ,C :DeviceContext >Send for SGTableMap <T ,C > {} +// SAFETY: The NonNull in SGTableMap is guaranteed valid by our type invariants, and the GEM object +// it points to is guaranteed to be thread-safe. +unsafe impl <T :DriverObject ,C :DeviceContext >Sync for SGTableMap <T ,C > {} +#[kunit_tests(rust_drm_gem_shmem)] mod tests {use super ::*;
@@ -582,4 +710,28 @@ fn vmap_io() -> Result {Ok (()) } + +// TODO: I would love to actually test the success paths of sg_table(), but that would require +// also implementing dummy dma_ops so that trying to create a mapping doesn't explode. So, leave +// that for someone else. + +// Ensures that passing the wrong device to sg_table() fails as we expect, and also ensure it +// skips initializing `sgt_res` since we could otherwise create `sgt_res` with the wrong device +// bound to it. +#[test] +fn fail_sg_table_on_wrong_dev () ->Result { +let (_dev, drm) =create_drm_dev ()?; +let wrong_dev = faux::Registration ::new (c"EvilKunit" ,None )?; + +let obj =Object ::<KunitObject ,_ >::new (&drm,PAGE_SIZE ,ObjectConfig ::default (), ())?; + +assert_eq! (obj.sg_table (wrong_dev.as_ref ()).err ().unwrap (),EINVAL ); + +// If sgt_res was not initialized mistakenly with the wrong device, this should still fail. +assert_eq! (obj.sg_table (wrong_dev.as_ref ()).err ().unwrap (),EINVAL ); + +// TODO: Someday, we should test that creating an sg_table here still succeeds. + +Ok (()) + } }
Signature · 1 line
2.54.0
[email protected]
AI reviewRe: [PATCH v18 4/4] rust: drm: gem: Introduce shmem::Object::sg_table()
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] SGTableMap::drop frees the scatter-gather table without decrementing the backing pages reference…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] SGTableMap::drop frees the scatter-gather table without decrementing the backing pages reference count, causing a permanent memory leak if Devres initialization fails. --
commit 28826d4b9c036cf868c70bbd1c12d628314a0735
Author: Lyude Paul <[email protected]>
rust: drm: gem: Introduce shmem::Object::sg_table()
This commit introduces sg_table() to shmem::Object and utilizes a Devres resource to ensure SGTable mappings are released on driver-unbind.
Quoted 4 lines
rust/kernel/drm/gem/shmem.rs +0
-0
[ ... ]
Quoted 20 lines
rust/kernel/drm/gem/shmem.rs +16
-0@@ -270,6 +302,45 @@ pub fn vmap<const SIZE: usize>(&self) -> Result<VMapRef<'_, T, C, SIZE>> {pub fn owned_vmap <const SIZE :usize >(&self) ->Result <VMapOwned <T ,C ,SIZE >> { self.make_vmap () } + +/// Creates (if necessary) and returns an immutable reference to a scatter-gather table of DMA +/// pages for this object. +/// +/// This will pin the object in memory. It is expected that `dev` should be a pointer to the +/// same [`device::Device`] which `self` belongs to, otherwise this function will return +/// `Err(EINVAL)`. +pub fn sg_table <'a>( + &'a self, + dev: &'a device::Device <Bound >, + ) ->Result <&'a scatterlist::SGTable > { +if dev.as_raw () != self.dev ().as_ref ().as_raw () { +return Err (EINVAL ); + } + +let sgt_res = 'out: {
[ ... ]
rust/kernel/drm/gem/shmem.rs pub fn vmap<const SIZE: usize>(&self) -> Result<VMapRef<'_, T, C, SIZE>> { · line 345
Quoted 3 lines
rust/kernel/drm/gem/shmem.rs +3
-0+// If not, finish initializing and return. + self.sgt_res + .populate (Devres ::new (dev,SGTableMap ::new (self))?);
[Severity: Medium]
If Devres::new() returns an error here, will the newly created SGTableMap be immediately dropped?
When SGTableMap::new() calls drm_gem_shmem_get_pages_sgt(), it pins the backing pages by incrementing pages_use_count and caches the new sgt. If the SGTableMap is dropped during this error path, it frees the SGT without decrementing pages_use_count, leading to a permanent memory leak of the backing pages.
Quoted 4 lines
rust/kernel/drm/gem/shmem.rs +4
-0+ +// SAFETY: We just populated sgt_res above. +unsafe { self.sgt_res.as_ref ().unwrap_unchecked () } + };
[ ... ]
rust/kernel/drm/gem/shmem.rs impl<D, R, C, const SIZE: usize> IoKnownSize for VMap<D, R, C, SIZE> · line 580
Quoted 8 lines
rust/kernel/drm/gem/shmem.rs +8
-0+impl <T :DriverObject ,C :DeviceContext >Drop for SGTableMap <T ,C > { +fn drop (&mut self) { +// SAFETY: `obj` is always valid via our type invariants +let obj =unsafe { self.obj.as_ref () }; +let _lock =DmaResvGuard ::new (obj); + +// SAFETY: We acquired the lock needed for calling this function above +unsafe { bindings::__drm_gem_shmem_free_sgt_locked (obj.as_raw_shmem ()) };
[Severity: Medium]
Does this code leak the backing pages?
This function frees the SGT and sets shmem->sgt = NULL, but it omits a paired call to drm_gem_shmem_put_pages_locked(). If this drop is triggered early (such as when Devres::new() fails in Object::sg_table()), pages_use_count is permanently incremented.
If the caller retries sg_table() after the failure, it will see shmem->sgt == NULL and increment pages_use_count a second time. When the GEM object is eventually released, drm_gem_shmem_release() will only decrement it once, failing to reach 0 and triggering a drm_WARN_ON.
Quoted 2 lines
rust/kernel/drm/gem/shmem.rs +2
-0+ } +}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
Re: [PATCH v18 4/4] rust: drm: gem: Introduce shmem::Object::sg_table()
In reply to Lyude PaulQuoted 16 lines · Lyude Paul
In order to do this, we need to be careful to ensure that any interface we expose for scatterlists ensures that any mappings created from one are destroyed on driver-unbind. To do this, we introduce a Devres resource into
shmem::Objectthat we use in order to ensure that we release any SGTable mappings on driver-unbind.There's some other slightly unfortunate caveats of this:
* Drivers don't have explicit control at the moment over when unmapping happens (which is exactly the same as the C side atm, so it might not be a problem).
* We can't just returnSGTableMapto the user through an Arc to attempt to fix the last caveat - because that implies the gem object would need to hold a reference count to the scatterlist mapping, which just leaves us with the same problem.
Quoted 1 line
Signed-off-by Lyude Paul <[email protected]>
I really like how simplified sg_table has become!
With the customary final nits below.
<...>
Quoted 22 lines
rust/kernel/drm/gem/shmem.rs +22
-0+ +/// Creates (if necessary) and returns an immutable reference to a scatter-gather table of DMA +/// pages for this object. +/// +/// This will pin the object in memory. It is expected that `dev` should be a pointer to the +/// same [`device::Device`] which `self` belongs to, otherwise this function will return +/// `Err(EINVAL)`. +pub fn sg_table <'a>( + &'a self, + dev: &'a device::Device <Bound >, + ) ->Result <&'a scatterlist::SGTable > { +if dev.as_raw () != self.dev ().as_ref ().as_raw () { +return Err (EINVAL ); + } + +let sgt_res = 'out: { +// Fast path: sgt_res is already initialized +if let Some (sgt_res) = self.sgt_res.as_ref () { +break 'out sgt_res; + } + +// Slow path: Grab the lock and see if we need to initialize sgt_res.
rust/kernel/drm/gem/shmem.rs pub fn vmap<const SIZE: usize>(&self) -> Result<VMapRef<'_, T, C, SIZE>> { · line 345
Quoted 10 lines
rust/kernel/drm/gem/shmem.rs +10
-0+let _guard = self.sgt_lock.lock (); + +// If someone initialized it while we were waiting, we can exit early. +if let Some (sgt_res) = self.sgt_res.as_ref () { +break 'out sgt_res; + } + +// If not, finish initializing and return. + self.sgt_res + .populate (Devres ::new (dev,SGTableMap ::new (self))?);
Maybe add a comment explaining that populate cannot return false, as its invocation it protected by the mutex? This helps understanding that the following unsafe block is ok.
Quoted 14 lines
rust/kernel/drm/gem/shmem.rs +7
-0+ +// SAFETY: We just populated sgt_res above. +unsafe { self.sgt_res.as_ref ().unwrap_unchecked () } + }; + +Ok (sgt_res.access (dev)?) + } }impl <T :DriverObject ,C :DeviceContext >Deref for Object <T ,C > {@@ -474,6 +545,63 @@ impl<D, R, C, const SIZE: usize> IoKnownSize for VMap<D, R, C, SIZE>#[cfg( CONFIG_64BIT )] impl_vmap_io_capable! (VMap ,u64 );
rust/kernel/drm/gem/shmem.rs impl<D, R, C, const SIZE: usize> IoKnownSize for VMap<D, R, C, SIZE> · line 557
Quoted 10 lines
rust/kernel/drm/gem/shmem.rs +10
-0+/// A reference to a GEM object that is known to have a mapped [`SGTable`]. +/// +/// This is used by the Rust bindings with [`Devres`] in order to ensure that mappings for SGTables +/// on GEM shmem objects are revoked on driver-unbind. +/// +/// # Invariants +/// +/// - `self.obj` always points to a valid GEM object. +/// - This object is proof that `self.obj.owner.sgt` has an initialized and valid +/// [`scatterlist::SGTable`].
The SGTable is not in owner.sgt anymore.
Quoted 20 lines
rust/kernel/drm/gem/shmem.rs +20
-0+pub struct SGTableMap <T :DriverObject ,C :DeviceContext > { + obj:NonNull <Object <T ,C >>, +} + +impl <T :DriverObject ,C :DeviceContext >Deref for SGTableMap <T ,C > { +type Target = scatterlist::SGTable ; + +fn deref (&self) -> &Self ::Target { +// SAFETY: +// - The NonNull is guaranteed to be valid via our type invariants. +// - The sgt field is guaranteed to be initialized and valid via our type invariants. +unsafe { scatterlist::SGTable ::from_raw ((*self.obj.as_ref ().as_raw_shmem ()).sgt) } + } +} + +impl <T :DriverObject ,C :DeviceContext >Drop for SGTableMap <T ,C > { +fn drop (&mut self) { +// SAFETY: `obj` is always valid via our type invariants +let obj =unsafe { self.obj.as_ref () }; +let _lock =DmaResvGuard ::new (obj);
rust/kernel/drm/gem/shmem.rs impl<D, R, C, const SIZE: usize> IoKnownSize for VMap<D, R, C, SIZE> · line 587
Quoted 10 lines
rust/kernel/drm/gem/shmem.rs +10
-0+ +// SAFETY: We acquired the lock needed for calling this function above +unsafe { bindings::__drm_gem_shmem_free_sgt_locked (obj.as_raw_shmem ()) }; + } +} + +impl <T :DriverObject ,C :DeviceContext >SGTableMap <T ,C > { +fn new (obj: &Object <T ,C >) ->impl Init <Self ,Error > { +// INVARIANT: +// - We call drm_gem_shmem_get_pages_sgt_locked below and check whether or not it
s/drm_gem_shmem_get_pages_sgt_locked/drm_gem_shmem_get_pages_sgt.
Alexandre Courbot
reviewerQuoted 16 lines · Lyude Paul
Most of this patch series has already been pushed upstream, this is just the second half of the patch series that has not been pushed yet + some additional changes which were required to implement changes requested by the mailing list. This patch series is originally from Asahi, previously posted by Daniel Almeida.
The previous version of the patch series can be found here:
https://patchwork.freedesktop.org/series/164580/Branch with patches applied available here:
https://gitlab.freedesktop.org/lyudess/linux/-/commits/rust/gem-shmem
This patch series applies on top of drm-rust-next
Quoted 12 lines
Patch-series wide changes since V15:
* Fix some major rebasing errors I somehow didn't notice :(
* Drop the dependency on LazyInit, use the trick that Alice suggested instead.
* Fix dependency ordering so that Tyr can get the vmap stuff first without the other bits.
Patch-series wide changes since V16:
* Fix ordering one more time (SetOnce::reset()doesn't need to come before adding vmap functions)
* Rebase against the latest DeviceContext changes from me that got pushed.
Not a problem of this series, but when trying to build it I initially got these warnings/errors:
CLIPPY L rust/kernel.o
warning: gendwarfksyms: symbol_print_versions: no information for symbol _RNvMs1_NtNtCsjYlAz7NZ3Sw_4core3mem12maybe_uninitINtB5_11MaybeUninitINtNtCsbuTvttuFvbr_6kernel6devres6DevresINtNtNtNtB18_3drm3gem5shmem10SGTableMapNtNtB1K_5tests11KunitObjectNtNtB1O_6device6UninitEEE16assume_init_dropB18_
...
.vmlinux.export.c:8577:500: warning: null character ignored [-Wnull-character]
8577 | ...<U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000>...
| ^
.vmlinux.export.c:8577:501: warning: null character ignored [-Wnull-character]
8577 | ...<U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000>...
| ^
.vmlinux.export.c:8577:502: warning: null character ignored [-Wnull-character]
8577 | ...<U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000>...
| ^
.vmlinux.export.c:8577:503: warning: null character ignored [-Wnull-character]
8577 | ...<U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000>...
| ^
...
.vmlinux.export.c:24529:2: error: embedding a #include directive within macro arguments is not supported
24529 | #include <linux/module.h>
| ^
.vmlinux.export.c:8577:1: note: expansion of macro 'KSYMTAB_FUNC' requested here
8577 | KSYMTAB_FUNC(_RINvXsi_NtNtCsbuTvttuFvbr_6kernel4sync3arcINtB6_9UniqueArcINtNtNtCsjYlAz7NZ3Sw_4core3mem12maybe_uninit11MaybeUninitIN...
| ^
.vmlinux.export.c:8577:1: error: unterminated function-like macro invocation
8577 | KSYMTAB_FUNC(_RINvXsi_NtNtCsbuTvttuFvbr_6kernel4sync3arcINtB6_9UniqueArcINtNtNtCsjYlAz7NZ3Sw_4core3mem12maybe_uninit11MaybeUninitIN...
| ^../include/linux/export-internal.h:62:9: note: macro 'KSYMTAB_FUNC' defined here
62 | #define KSYMTAB_FUNC(name, ns) __KSYMTAB(name, KSYM_FUNC(name), ns)
| ^
This is fixed by [1]. Maybe we should merge that one patch separately and before the rest? I seem to be seeing these long symbol problems more often recently.
Quoted 6 lines · Alexandre Courbot
This is fixed by [1]. Maybe we should merge that one patch separately and before the rest? I seem to be seeing these long symbol problems more often recently.
I can take that one via rust-next unless someone shouts -- Ack's appreciated.
Cheers,
Miguel
Gary Guo
reviewerQuoted 30 lines · Alexandre Courbot
Quoted 16 lines · Lyude Paul
Most of this patch series has already been pushed upstream, this is just the second half of the patch series that has not been pushed yet + some additional changes which were required to implement changes requested by the mailing list. This patch series is originally from Asahi, previously posted by Daniel Almeida.
The previous version of the patch series can be found here:
https://patchwork.freedesktop.org/series/164580/Branch with patches applied available here:
https://gitlab.freedesktop.org/lyudess/linux/-/commits/rust/gem-shmem
This patch series applies on top of drm-rust-next
Quoted 12 lines
Patch-series wide changes since V15:
* Fix some major rebasing errors I somehow didn't notice :(
* Drop the dependency on LazyInit, use the trick that Alice suggested instead.
* Fix dependency ordering so that Tyr can get the vmap stuff first without the other bits.
Patch-series wide changes since V16:
* Fix ordering one more time (SetOnce::reset()doesn't need to come before adding vmap functions)
* Rebase against the latest DeviceContext changes from me that got pushed.
Quoted 5 lines
Not a problem of this series, but when trying to build it I initially got these warnings/errors:
CLIPPY L
rust/kernel.o
warning: gendwarfksyms:symbol_print_versions: no information for symbol _RNvMs1_NtNtCsjYlAz7NZ3Sw_4core3mem12maybe_uninitINtB5_11MaybeUninitINtNtCsbuTvttuFvbr_6kernel6devres6DevresINtNtNtNtB18_3drm3gem5shmem10SGTableMapNtNtB1K_5tests11KunitObjectNtNtB1O_6device6UninitEEE16assume_init_dropB18_
IMO we really shouldn't use Clippy to generate code. We spent too much effort in fixing codegen issues that are only present when driven by clippy.
We should just run a check phase with clippy to get the lints only, and then use rustc to generate actual code.
Best,
Gary
Quoted 32 lines
...
.vmlinux.export.c:8577:500: warning: null character ignored [-Wnull-character]
8577 | ...<U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000>...
| ^
.vmlinux.export.c:8577:501: warning: null character ignored [-Wnull-character]
8577 | ...<U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000>...
| ^
.vmlinux.export.c:8577:502: warning: null character ignored [-Wnull-character]
8577 | ...<U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000>...
| ^
.vmlinux.export.c:8577:503: warning: null character ignored [-Wnull-character]
8577 | ...<U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000><U+0000>...
| ^
...
.vmlinux.export.c:24529:2: error: embedding a #include directive within macro arguments is not supported
24529 | #include <linux/module.h>| ^ .vmlinux.export.c:8577:1: note: expansion of macro 'KSYMTAB_FUNC' requested here 8577 | KSYMTAB_FUNC(_RINvXsi_NtNtCsbuTvttuFvbr_6kernel4sync3arcINtB6_9UniqueArcINtNtNtCsjYlAz7NZ3Sw_4core3mem12maybe_uninit11MaybeUninitIN... | ^ .vmlinux.export.c:8577:1: error: unterminated function-like macro invocation 8577 | KSYMTAB_FUNC(_RINvXsi_NtNtCsbuTvttuFvbr_6kernel4sync3arcINtB6_9UniqueArcINtNtNtCsjYlAz7NZ3Sw_4core3mem12maybe_uninit11MaybeUninitIN... | ^
../include/linux/export-internal.h:62:9: note: macro 'KSYMTAB_FUNC' defined here
62 | #defineKSYMTAB_FUNC(name, ns)__KSYMTAB(name, KSYM_FUNC(name), ns)
| ^This is fixed by [1]. Maybe we should merge that one patch separately and before the rest? I seem to be seeing these long symbol problems more often recently.
Quoted 6 lines · Gary Guo
IMO we really shouldn't use Clippy to generate code. We spent too much effort in fixing codegen issues that are only present when driven by clippy.
We should just run a check phase with clippy to get the lints only, and then use rustc to generate actual code.
Yeah, given the trouble it is giving, I think that will probably be a better approach medium term. I don't love it, because the original idea was to avoid such an extra phase, but it is definitely better vs. random source code changes...
We could also increase the symbol size-related variables (KSYM_NAME_LEN, SZ in modpost...) only for Clippy builds to a ludicrous value. The extra phase approach would avoid surprises more generally though, so I like it in that sense, and has the implicit advantage of avoiding someone using a Clippy kernel in production by mistake.
Ok, I can take a look at that. For now, I will pick the other patch.
Cheers,
Miguel