* [PATCH v16 0/6] Rust bindings for gem shmem
@ 2026-06-02 17:24 Lyude Paul
2026-06-02 17:25 ` [PATCH v16 1/6] rust: drm: gem/shmem: Add DmaResvGuard helper Lyude Paul
` (6 more replies)
0 siblings, 7 replies; 18+ messages in thread
From: Lyude Paul @ 2026-06-02 17:24 UTC (permalink / raw)
To: dri-devel, rust-for-linux, nouveau
Cc: Alexandre Courbot, Gary Guo, Christian König, driver-core,
Miguel Ojeda, Maarten Lankhorst, Alice Ryhl, Simona Vetter,
linux-kernel, Sumit Semwal, linux-media, Rafael J . Wysocki,
Thomas Zimmermann, Maxime Ripard, David Airlie, Benno Lossin,
linaro-mm-sig, Danilo Krummrich, Mukesh Kumar Chaurasiya,
Asahi Lina, Daniel Almeida, Lyude Paul, Greg Kroah-Hartman
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.
Lyude Paul (6):
rust: drm: gem/shmem: Add DmaResvGuard helper
rust: drm: gem: Add vmap functions to shmem bindings
rust: sync: Add SetOnce::reset()
rust: gem: shmem: Fix Default implementation for ObjectConfig
rust: faux: Allow retrieving a bound Device
rust: drm: gem: Introduce shmem::Object::sg_table()
rust/kernel/drm/gem/shmem.rs | 507 ++++++++++++++++++++++++++++++++++-
rust/kernel/faux.rs | 7 +-
rust/kernel/sync/set_once.rs | 60 ++++-
3 files changed, 552 insertions(+), 22 deletions(-)
base-commit: b78dab829760aee9b83f5cf15550a0fe36c6f4b0
--
2.54.0
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH v16 1/6] rust: drm: gem/shmem: Add DmaResvGuard helper 2026-06-02 17:24 [PATCH v16 0/6] Rust bindings for gem shmem Lyude Paul @ 2026-06-02 17:25 ` Lyude Paul 2026-06-04 2:29 ` Claude review: " Claude Code Review Bot 2026-06-02 17:25 ` [PATCH v16 2/6] rust: drm: gem: Add vmap functions to shmem bindings Lyude Paul ` (5 subsequent siblings) 6 siblings, 1 reply; 18+ messages in thread From: Lyude Paul @ 2026-06-02 17:25 UTC (permalink / raw) To: dri-devel, rust-for-linux, nouveau Cc: Alexandre Courbot, Gary Guo, Christian König, driver-core, Miguel Ojeda, Maarten Lankhorst, Alice Ryhl, Simona Vetter, linux-kernel, Sumit Semwal, linux-media, Rafael J . Wysocki, Thomas Zimmermann, Maxime Ripard, David Airlie, Benno Lossin, linaro-mm-sig, Danilo Krummrich, Mukesh Kumar Chaurasiya, Asahi Lina, Daniel Almeida, Lyude Paul, Greg Kroah-Hartman Just a temporary holdover to make locking/unlocking the dma_resv lock much easier. Signed-off-by: Lyude Paul <lyude@redhat.com> Co-authored-by: Alexandre Courbot <acourbot@nvidia.com> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> --- rust/kernel/drm/gem/shmem.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index 35d7523e164ff..066a820b0bffc 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -27,7 +27,10 @@ Deref, DerefMut, // }, - ptr::NonNull, + ptr::{ + self, + NonNull, // + }, }; use gem::{ BaseObjectPrivate, @@ -224,3 +227,29 @@ impl<T: DriverObject> driver::AllocImpl for Object<T> { 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>(&'a Object<T>); + +impl<'a, T: DriverObject> DmaResvGuard<'a, T> { + #[inline(always)] + #[expect(unused)] + fn new(obj: &'a Object<T>) -> Self { + // SAFETY: This lock is initialized throughout the lifetime of `object`. + unsafe { bindings::dma_resv_lock(obj.raw_dma_resv(), ptr::null_mut()) }; + + Self(obj) + } +} + +impl<'a, T: DriverObject> Drop for DmaResvGuard<'a, T> { + #[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()) }; + } +} -- 2.54.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Claude review: rust: drm: gem/shmem: Add DmaResvGuard helper 2026-06-02 17:25 ` [PATCH v16 1/6] rust: drm: gem/shmem: Add DmaResvGuard helper Lyude Paul @ 2026-06-04 2:29 ` Claude Code Review Bot 0 siblings, 0 replies; 18+ messages in thread From: Claude Code Review Bot @ 2026-06-04 2:29 UTC (permalink / raw) To: dri-devel-reviews Patch Review A simple RAII guard for the DMA reservation lock. Clean and correct. The return value of `dma_resv_lock` is discarded: ```rust unsafe { bindings::dma_resv_lock(obj.raw_dma_resv(), ptr::null_mut()) }; ``` With a NULL `ww_acquire_ctx`, `dma_resv_lock` degenerates to `mutex_lock` and always returns 0, so ignoring the return value is safe in practice. The TODO comment acknowledging this should be replaced with proper WwMutex bindings is appropriate. The `#[expect(unused)]` on `new()` is fine since patch 2 removes it immediately. The `#[inline(always)]` on both `new()` and `drop()` is reasonable for a thin lock wrapper. No issues. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v16 2/6] rust: drm: gem: Add vmap functions to shmem bindings 2026-06-02 17:24 [PATCH v16 0/6] Rust bindings for gem shmem Lyude Paul 2026-06-02 17:25 ` [PATCH v16 1/6] rust: drm: gem/shmem: Add DmaResvGuard helper Lyude Paul @ 2026-06-02 17:25 ` Lyude Paul 2026-06-04 2:29 ` Claude review: " Claude Code Review Bot 2026-06-02 17:25 ` [PATCH v16 3/6] rust: sync: Add SetOnce::reset() Lyude Paul ` (4 subsequent siblings) 6 siblings, 1 reply; 18+ messages in thread From: Lyude Paul @ 2026-06-02 17:25 UTC (permalink / raw) To: dri-devel, rust-for-linux, nouveau Cc: Alexandre Courbot, Gary Guo, Christian König, driver-core, Miguel Ojeda, Maarten Lankhorst, Alice Ryhl, Simona Vetter, linux-kernel, Sumit Semwal, linux-media, Rafael J . Wysocki, Thomas Zimmermann, Maxime Ripard, David Airlie, Benno Lossin, linaro-mm-sig, Danilo Krummrich, Mukesh Kumar Chaurasiya, Asahi Lina, Daniel Almeida, Lyude Paul, Greg Kroah-Hartman 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 <lyude@redhat.com> --- 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 rust/kernel/drm/gem/shmem.rs | 303 ++++++++++++++++++++++++++++++++++- 1 file changed, 302 insertions(+), 1 deletion(-) diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index 066a820b0bffc..843869056b1d7 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -18,11 +18,18 @@ Device, // }, error::to_result, + io::{ + Io, + IoCapable, + IoKnownSize, // + }, prelude::*, sync::aref::ARef, types::Opaque, // }; use core::{ + ffi::c_void, + mem::MaybeUninit, // ops::{ Deref, DerefMut, // @@ -33,6 +40,7 @@ }, }; use gem::{ + BaseObject, BaseObjectPrivate, DriverObject, IntoGEMObject, // @@ -177,6 +185,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, SIZE>> + where + R: Deref<Target = Self> + From<&'a Self>, + { + // 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, 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, SIZE>> { + self.make_vmap() + } } impl<T: DriverObject> Deref for Object<T> { @@ -237,7 +319,6 @@ impl<T: DriverObject> driver::AllocImpl for Object<T> { impl<'a, T: DriverObject> DmaResvGuard<'a, T> { #[inline(always)] - #[expect(unused)] fn new(obj: &'a Object<T>) -> Self { // SAFETY: This lock is initialized throughout the lifetime of `object`. unsafe { bindings::dma_resv_lock(obj.raw_dma_resv(), ptr::null_mut()) }; @@ -253,3 +334,223 @@ 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, const SIZE: usize> IoCapable<$ty> for $impl<D, R, SIZE> + where + D: DriverObject, + R: Deref<Target = Object<D>>, + { + #[inline(always)] + unsafe fn io_read(&self, address: usize) -> $ty { + let ptr = address as *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 = address as *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, const SIZE: usize = 0> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + addr: *mut c_void, + owner: R, +} + +/// An alias type for a reference to a shmem-based GEM object's VMap. +pub type VMapRef<'a, D, const SIZE: usize = 0> = VMap<D, &'a Object<D>, SIZE>; + +/// An alias type for an owned reference to a shmem-based GEM object's VMap. +pub type VMapOwned<D, const SIZE: usize = 0> = VMap<D, ARef<Object<D>>, SIZE>; + +impl<D, R, const SIZE: usize> VMap<D, R, SIZE> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + /// Borrows a reference to the object that owns this virtual mapping. + #[inline(always)] + pub fn owner(&self) -> &Object<D> { + &self.owner + } +} + +impl<D, R, const SIZE: usize> Drop for VMap<D, R, SIZE> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + #[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, const SIZE: usize> Io for VMap<D, R, SIZE> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + #[inline(always)] + fn addr(&self) -> usize { + self.addr as usize + } + + #[inline(always)] + fn maxsize(&self) -> usize { + self.owner.size() + } +} + +impl<D, R, const SIZE: usize> IoKnownSize for VMap<D, R, SIZE> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + 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, + 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( + _dev: &drm::Device<KunitDriver>, + _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 = Object<KunitObject>; + + const INFO: drm::DriverInfo = INFO; + const IOCTLS: &'static [drm::ioctl::DrmIoctlDescriptor] = &[]; + } + + fn create_drm_dev() -> Result<(faux::Registration, ARef<drm::Device<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 = drm::Device::<KunitDriver>::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(()) + } +} -- 2.54.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Claude review: rust: drm: gem: Add vmap functions to shmem bindings 2026-06-02 17:25 ` [PATCH v16 2/6] rust: drm: gem: Add vmap functions to shmem bindings Lyude Paul @ 2026-06-04 2:29 ` Claude Code Review Bot 0 siblings, 0 replies; 18+ messages in thread From: Claude Code Review Bot @ 2026-06-04 2:29 UTC (permalink / raw) To: dri-devel-reviews Patch Review This is the largest patch and adds the core vmap functionality. The design is well thought out. **`make_vmap` guard lifetime**: The explicit `drop(guard)` before the iomem error path is correct — `raw_vunmap` re-acquires the lock internally: ```rust let guard = DmaResvGuard::new(self); to_result(unsafe { bindings::drm_gem_shmem_vmap_locked(self.as_raw_shmem(), map.as_mut_ptr()) })?; drop(guard); ``` Good defensive pattern. **VMap::Drop reconstruction**: The `drop` reconstructs an `iosys_map` to pass to `raw_vunmap`: ```rust self.owner.raw_vunmap(bindings::iosys_map { is_iomem: false, __bindgen_anon_1: bindings::iosys_map__bindgen_ty_1 { vaddr: self.addr }, }) ``` This works because `make_vmap` already validated `!is_iomem` before constructing the `VMap`. The invariant is maintained. **IoCapable implementations**: The `impl_vmap_io_capable!` macro implements `ptr::read`/`ptr::write` through raw pointer casts: ```rust unsafe fn io_read(&self, address: usize) -> $ty { let ptr = address as *mut $ty; unsafe { ptr::read(ptr) } } ``` The safety is delegated to the `Io` trait's `io_read`/`io_write` contract (alignment, bounds). This is correct. **Minor**: The `VMap` struct's `addr` field is `*mut c_void` — this raw pointer doesn't implement `Send`/`Sync`, but `VMap` doesn't explicitly implement them either. Since the GEM object backing the mapping is thread-safe, and the mapping itself is valid for the lifetime of the owner, this might need explicit `Send`/`Sync` impls if callers need to share the mapping across threads. However, this may be intentionally restrictive for now. **Tests**: Good coverage — `compile_time_vmap_sizes` validates the size check and `vmap_io` tests read/write through the mapping. The byte-level verification of a `write32` is a nice touch. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v16 3/6] rust: sync: Add SetOnce::reset() 2026-06-02 17:24 [PATCH v16 0/6] Rust bindings for gem shmem Lyude Paul 2026-06-02 17:25 ` [PATCH v16 1/6] rust: drm: gem/shmem: Add DmaResvGuard helper Lyude Paul 2026-06-02 17:25 ` [PATCH v16 2/6] rust: drm: gem: Add vmap functions to shmem bindings Lyude Paul @ 2026-06-02 17:25 ` Lyude Paul 2026-06-04 2:29 ` Claude review: " Claude Code Review Bot 2026-06-02 17:25 ` [PATCH v16 4/6] rust: gem: shmem: Fix Default implementation for ObjectConfig Lyude Paul ` (3 subsequent siblings) 6 siblings, 1 reply; 18+ messages in thread From: Lyude Paul @ 2026-06-02 17:25 UTC (permalink / raw) To: dri-devel, rust-for-linux, nouveau Cc: Alexandre Courbot, Gary Guo, Christian König, driver-core, Miguel Ojeda, Maarten Lankhorst, Alice Ryhl, Simona Vetter, linux-kernel, Sumit Semwal, linux-media, Rafael J . Wysocki, Thomas Zimmermann, Maxime Ripard, David Airlie, Benno Lossin, linaro-mm-sig, Danilo Krummrich, Mukesh Kumar Chaurasiya, Asahi Lina, Daniel Almeida, Lyude Paul, Greg Kroah-Hartman This function simply drops the contents of the SetOnce, given a mutable reference - since that proves we have exclusive access to the SetOnce. Additionally, update the invariants for SetOnce to make it clear as to why this is safe. Signed-off-by: Lyude Paul <lyude@redhat.com> --- rust/kernel/sync/set_once.rs | 60 +++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/rust/kernel/sync/set_once.rs b/rust/kernel/sync/set_once.rs index 139cef05e935f..d6e4fc2695673 100644 --- a/rust/kernel/sync/set_once.rs +++ b/rust/kernel/sync/set_once.rs @@ -15,7 +15,7 @@ /// /// # Invariants /// -/// - `init` may only increase in value. +/// - `init` may only increase in value, unless modified through a mutable reference. /// - `init` may only assume values in the range `0..=2`. /// - `init == 0` if and only if `value` is uninitialized. /// - `init == 1` if and only if there is exactly one thread with exclusive @@ -110,17 +110,61 @@ pub fn copy(&self) -> Option<T> { self.as_ref().copied() } + + /// # Safety + /// + /// If this function returns `true`, `self` must be freed or `init` must be reset to `0`. + unsafe fn drop_val(&mut self) -> bool { + if *self.init.get_mut() != 2 { + return false; + } + + let value = self.value.get_mut(); + // SAFETY: Via our type invariants, `init` == 2 means `value` is initialized. + unsafe { value.assume_init_drop() }; + + true + } + + /// Unset the [`SetOnce`]. + /// + /// After this function is called, the [`SetOnce`] is empty and uninitialized. This function is + /// mainly intended for usage in destructors. + /// + /// Returns `true` if `self` was previously initialized. + /// + /// # Example + /// + /// ``` + /// # use kernel::sync::SetOnce; + /// let mut value = SetOnce::new(); + /// assert_eq!(value.populate(67), true); + /// + /// assert_eq!(value.reset(), true); + /// assert!(value.as_ref().is_none()); + /// assert_eq!(value.reset(), false); + /// + /// assert_eq!(value.populate(69), true); + /// ``` + pub fn reset(&mut self) -> bool { + // SAFETY: We write `0` to init below if this returns true. + let dropped = unsafe { self.drop_val() }; + if dropped { + // INVARIANT: + // - We set `init` back to 0 through a mutable reference. + // - We dropped `value` above. + *self.init.get_mut() = 0; + } + + dropped + } } impl<T> Drop for SetOnce<T> { + #[inline(always)] fn drop(&mut self) { - if *self.init.get_mut() == 2 { - let value = self.value.get_mut(); - // SAFETY: By the type invariants of `Self`, `self.init == 2` means that `self.value` - // contains a valid value. We have exclusive access, as we hold a `mut` reference to - // `self`. - unsafe { value.assume_init_drop() }; - } + // SAFETY: We are dropping this value. + unsafe { self.drop_val() }; } } -- 2.54.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Claude review: rust: sync: Add SetOnce::reset() 2026-06-02 17:25 ` [PATCH v16 3/6] rust: sync: Add SetOnce::reset() Lyude Paul @ 2026-06-04 2:29 ` Claude Code Review Bot 0 siblings, 0 replies; 18+ messages in thread From: Claude Code Review Bot @ 2026-06-04 2:29 UTC (permalink / raw) To: dri-devel-reviews Patch Review Adds `reset()` which takes `&mut self` (proving exclusive access), drops the value, and resets `init` to 0. **Invariant update**: The change from "init may only increase in value" to "init may only increase in value, unless modified through a mutable reference" is correct — `&mut self` guarantees no concurrent observers. **`drop_val` safety contract**: The `unsafe fn drop_val` has a critical safety requirement: ```rust /// If this function returns `true`, `self` must be freed or `init` must be reset to `0`. ``` Between `drop_val()` returning `true` and the caller resetting `init`, there's a window where `init == 2` but the value is dropped. This is safe because `&mut self` ensures exclusive access, but the contract is correctly documented. Both callers (`reset` and `Drop`) satisfy it. **Doctest**: The example is good and demonstrates the full reset/repopulate cycle: ```rust assert_eq!(value.populate(67), true); assert_eq!(value.reset(), true); assert!(value.as_ref().is_none()); assert_eq!(value.reset(), false); assert_eq!(value.populate(69), true); ``` No issues. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v16 4/6] rust: gem: shmem: Fix Default implementation for ObjectConfig 2026-06-02 17:24 [PATCH v16 0/6] Rust bindings for gem shmem Lyude Paul ` (2 preceding siblings ...) 2026-06-02 17:25 ` [PATCH v16 3/6] rust: sync: Add SetOnce::reset() Lyude Paul @ 2026-06-02 17:25 ` Lyude Paul 2026-06-04 2:29 ` Claude review: " Claude Code Review Bot 2026-06-02 17:25 ` [PATCH v16 5/6] rust: faux: Allow retrieving a bound Device Lyude Paul ` (2 subsequent siblings) 6 siblings, 1 reply; 18+ messages in thread From: Lyude Paul @ 2026-06-02 17:25 UTC (permalink / raw) To: dri-devel, rust-for-linux, nouveau Cc: Alexandre Courbot, Gary Guo, Christian König, driver-core, Miguel Ojeda, Maarten Lankhorst, Alice Ryhl, Simona Vetter, linux-kernel, Sumit Semwal, linux-media, Rafael J . Wysocki, Thomas Zimmermann, Maxime Ripard, David Airlie, Benno Lossin, linaro-mm-sig, Danilo Krummrich, Mukesh Kumar Chaurasiya, Asahi Lina, Daniel Almeida, Lyude Paul, Greg Kroah-Hartman I completely forgot when coming up with this type that #[derive(Default)] only works if all generics mentioned in the type implement Default (and T usually doesn't). This being said: We don't use `T` for anything besides using it for a reference type, so whether or not it implements `Default` shouldn't actually need to matter. So, fix this by just manually implementing Default instead of deriving it. Signed-off-by: Lyude Paul <lyude@redhat.com> --- rust/kernel/drm/gem/shmem.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index 843869056b1d7..d8bda8c8e2fb2 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -50,7 +50,6 @@ /// /// This is used with [`Object::new()`] to control various properties that can only be set when /// initially creating a shmem-backed GEM object. -#[derive(Default)] pub struct ObjectConfig<'a, T: DriverObject> { /// Whether to set the write-combine map flag. pub map_wc: bool, @@ -61,6 +60,16 @@ pub struct ObjectConfig<'a, T: DriverObject> { pub parent_resv_obj: Option<&'a Object<T>>, } +impl<'a, T: DriverObject> Default for ObjectConfig<'a, T> { + #[inline(always)] + fn default() -> Self { + Self { + map_wc: false, + parent_resv_obj: None, + } + } +} + /// A shmem-backed GEM object. /// /// # Invariants -- 2.54.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Claude review: rust: gem: shmem: Fix Default implementation for ObjectConfig 2026-06-02 17:25 ` [PATCH v16 4/6] rust: gem: shmem: Fix Default implementation for ObjectConfig Lyude Paul @ 2026-06-04 2:29 ` Claude Code Review Bot 0 siblings, 0 replies; 18+ messages in thread From: Claude Code Review Bot @ 2026-06-04 2:29 UTC (permalink / raw) To: dri-devel-reviews Patch Review Straightforward fix. `#[derive(Default)]` requires `T: Default` for all generic parameters, but `ObjectConfig` only uses `T` in `Option<&'a Object<T>>` which defaults to `None` regardless of `T`'s `Default` impl. The manual implementation correctly removes the spurious bound: ```rust impl<'a, T: DriverObject> Default for ObjectConfig<'a, T> { fn default() -> Self { Self { map_wc: false, parent_resv_obj: None, } } } ``` No issues. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v16 5/6] rust: faux: Allow retrieving a bound Device 2026-06-02 17:24 [PATCH v16 0/6] Rust bindings for gem shmem Lyude Paul ` (3 preceding siblings ...) 2026-06-02 17:25 ` [PATCH v16 4/6] rust: gem: shmem: Fix Default implementation for ObjectConfig Lyude Paul @ 2026-06-02 17:25 ` Lyude Paul 2026-06-04 2:29 ` Claude review: " Claude Code Review Bot 2026-06-02 17:25 ` [PATCH v16 6/6] rust: drm: gem: Introduce shmem::Object::sg_table() Lyude Paul 2026-06-04 2:29 ` Claude review: Rust bindings for gem shmem Claude Code Review Bot 6 siblings, 1 reply; 18+ messages in thread From: Lyude Paul @ 2026-06-02 17:25 UTC (permalink / raw) To: dri-devel, rust-for-linux, nouveau Cc: Alexandre Courbot, Gary Guo, Christian König, driver-core, Miguel Ojeda, Maarten Lankhorst, Alice Ryhl, Simona Vetter, linux-kernel, Sumit Semwal, linux-media, Rafael J . Wysocki, Thomas Zimmermann, Maxime Ripard, David Airlie, Benno Lossin, linaro-mm-sig, Danilo Krummrich, Mukesh Kumar Chaurasiya, Asahi Lina, Daniel Almeida, Lyude Paul, Greg Kroah-Hartman 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 <lyude@redhat.com> --- rust/kernel/faux.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rust/kernel/faux.rs b/rust/kernel/faux.rs index 43b4974f48cd2..e0856b2964a2c 100644 --- a/rust/kernel/faux.rs +++ b/rust/kernel/faux.rs @@ -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,8 +60,8 @@ fn as_raw(&self) -> *mut bindings::faux_device { } } -impl AsRef<device::Device> for Registration { - fn as_ref(&self) -> &device::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`. unsafe { device::Device::from_raw(addr_of_mut!((*self.as_raw()).dev)) } -- 2.54.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Claude review: rust: faux: Allow retrieving a bound Device 2026-06-02 17:25 ` [PATCH v16 5/6] rust: faux: Allow retrieving a bound Device Lyude Paul @ 2026-06-04 2:29 ` Claude Code Review Bot 0 siblings, 0 replies; 18+ messages in thread From: Claude Code Review Bot @ 2026-06-04 2:29 UTC (permalink / raw) To: dri-devel-reviews Patch Review Changes `AsRef<device::Device>` to `AsRef<device::Device<device::Bound>>`: ```rust -impl AsRef<device::Device> for Registration { - fn as_ref(&self) -> &device::Device { +impl AsRef<device::Device<device::Bound>> for Registration { + fn as_ref(&self) -> &device::Device<device::Bound> { ``` The justification is correct — a `Registration` is proof of device binding, so returning `Device<Bound>` is sound. The added invariant bullet point documents this: ``` +/// - This object is proof that the object described by this `Registration` is bound to a device. ``` **Potential concern**: This is a breaking API change — any existing code calling `registration.as_ref()` expecting `&device::Device` (without `Bound`) will break. Since this is in the kernel tree and patch 6 is the consumer, this is fine, but downstream users of the faux crate should be aware. No issues for this series. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v16 6/6] rust: drm: gem: Introduce shmem::Object::sg_table() 2026-06-02 17:24 [PATCH v16 0/6] Rust bindings for gem shmem Lyude Paul ` (4 preceding siblings ...) 2026-06-02 17:25 ` [PATCH v16 5/6] rust: faux: Allow retrieving a bound Device Lyude Paul @ 2026-06-02 17:25 ` Lyude Paul 2026-06-04 2:29 ` Claude review: " Claude Code Review Bot 2026-06-04 2:29 ` Claude review: Rust bindings for gem shmem Claude Code Review Bot 6 siblings, 1 reply; 18+ messages in thread From: Lyude Paul @ 2026-06-02 17:25 UTC (permalink / raw) To: dri-devel, rust-for-linux, nouveau Cc: Alexandre Courbot, Gary Guo, Christian König, driver-core, Miguel Ojeda, Maarten Lankhorst, Alice Ryhl, Simona Vetter, linux-kernel, Sumit Semwal, linux-media, Rafael J . Wysocki, Thomas Zimmermann, Maxime Ripard, David Airlie, Benno Lossin, linaro-mm-sig, Danilo Krummrich, Mukesh Kumar Chaurasiya, Asahi Lina, Daniel Almeida, Lyude Paul, Greg Kroah-Hartman 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::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. Signed-off-by: Lyude Paul <lyude@redhat.com> --- 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 rust/kernel/drm/gem/shmem.rs | 164 +++++++++++++++++++++++++++++++++-- 1 file changed, 155 insertions(+), 9 deletions(-) diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index d8bda8c8e2fb2..5f27e8d59e765 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -11,20 +11,34 @@ use crate::{ container_of, + device::{ + self, + Bound, // + }, + devres::*, drm::{ driver, gem, private::Sealed, Device, // }, - 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::{ @@ -83,6 +97,11 @@ pub struct Object<T: DriverObject> { obj: Opaque<bindings::drm_gem_shmem_object>, /// Parent object that owns this object's DMA reservation object. parent_resv_obj: Option<ARef<Object<T>>>, + /// Devres object for unmapping any SGTable on driver-unbind. + sgt_res: SetOnce<Devres<SGTableMap<T>>>, + #[pin] + /// Lock for protecting initialization of `sgt_res`. + sgt_lock: Mutex<()>, #[pin] inner: T, } @@ -135,6 +154,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: SetOnce::new(), + sgt_lock <- new_mutex!(()), inner <- T::new(dev, size, args), }), GFP_KERNEL, @@ -178,18 +199,23 @@ 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>` - 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. + unsafe { &mut (*this).sgt_res }.reset(); + + // 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) }; @@ -268,6 +294,45 @@ pub fn vmap<const SIZE: usize>(&self) -> Result<VMapRef<'_, T, SIZE>> { pub fn owned_vmap<const SIZE: usize>(&self) -> Result<VMapOwned<T, 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> Deref for Object<T> { @@ -457,6 +522,63 @@ impl<D, R, const SIZE: usize> IoKnownSize for VMap<D, R, 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> { + obj: NonNull<Object<T>>, +} + +impl<T: DriverObject> Deref for SGTableMap<T> { + 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> Drop for SGTableMap<T> { + 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> SGTableMap<T> { + fn new(obj: &Object<T>) -> 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> Send for SGTableMap<T> {} +// SAFETY: The NonNull in SGTableMap is guaranteed valid by our type invariants, and the GEM object +// it points to is guaranteed to be thread-safe. +unsafe impl<T: DriverObject> Sync for SGTableMap<T> {} + #[kunit_tests(rust_drm_gem_shmem)] mod tests { use super::*; @@ -562,4 +684,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(()) + } } -- 2.54.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Claude review: rust: drm: gem: Introduce shmem::Object::sg_table() 2026-06-02 17:25 ` [PATCH v16 6/6] rust: drm: gem: Introduce shmem::Object::sg_table() Lyude Paul @ 2026-06-04 2:29 ` Claude Code Review Bot 0 siblings, 0 replies; 18+ messages in thread From: Claude Code Review Bot @ 2026-06-04 2:29 UTC (permalink / raw) To: dri-devel-reviews Patch Review The most complex patch. Several observations: **Missing C-side function**: `SGTableMap::drop` calls: ```rust unsafe { bindings::__drm_gem_shmem_free_sgt_locked(obj.as_raw_shmem()) }; ``` This function does not exist in the current drm-next tree. It presumably lives in the "first half" of the series already pushed to drm-rust-next. If it's missing from the base, this won't compile. This should be verified against the actual base tree. **Import restructuring**: The patch changes `device` from `crate::drm::device` to `crate::device`: ```rust + device::{ + self, + Bound, // + }, drm::{ + driver, + gem, + private::Sealed, Device, // }, ``` This changes the meaning of `device::Device<T::Driver>` in the existing `dev()` method. On the actual drm-rust-next base, this may already be reconciled. Worth verifying. **Double-checked locking in `sg_table()`**: The pattern is correct: ```rust if let Some(sgt_res) = self.sgt_res.as_ref() { // fast path (Acquire) break 'out sgt_res; } let _guard = self.sgt_lock.lock(); // slow path if let Some(sgt_res) = self.sgt_res.as_ref() { // re-check break 'out sgt_res; } self.sgt_res.populate(...); // Release ``` The `SetOnce` uses `Acquire` on read and `Release` on write, which provides the necessary happens-before guarantees for the double-checked locking pattern. **SGTableMap lifetime safety**: `SGTableMap` holds `NonNull<Object<T>>` without an `ARef`: ```rust pub struct SGTableMap<T: DriverObject> { obj: NonNull<Object<T>>, } ``` This is safe because `SGTableMap` is stored inside the `Object<T>` itself (via `sgt_res`), so the object outlives the mapping. The `free_callback` explicitly resets `sgt_res` before releasing the GEM object: ```rust unsafe { &mut (*this).sgt_res }.reset(); // drops SGTableMap first unsafe { bindings::drm_gem_shmem_release(base) }; // then releases GEM let _ = unsafe { KBox::from_raw(this) }; // then frees memory ``` This ordering is correct and critical. **Device identity check**: ```rust if dev.as_raw() != self.dev().as_ref().as_raw() { return Err(EINVAL); } ``` Comparing raw device pointers to verify the caller passed the correct device is a good defensive check. The test `fail_sg_table_on_wrong_dev` verifies this works. **`SGTableMap::new` discards the sg_table pointer**: ```rust fn new(obj: &Object<T>) -> impl Init<Self, Error> { from_err_ptr(unsafe { bindings::drm_gem_shmem_get_pages_sgt(obj.as_raw_shmem()) })?; Ok(Self { obj: obj.into() }) } ``` The returned `sg_table*` is discarded. This is correct because `drm_gem_shmem_get_pages_sgt` stores the SGT internally in `shmem->sgt`, which is what `SGTableMap::deref` reads later. **Minor style**: The `#[pin]` attribute appears before the doc comment on `sgt_lock`: ```rust + #[pin] + /// Lock for protecting initialization of `sgt_res`. + sgt_lock: Mutex<()>, ``` Convention is doc comments before attributes, though both orderings are valid Rust. **Test coverage**: The `fail_sg_table_on_wrong_dev` test is good but limited — as the TODOs note, testing the success path requires mock DMA ops. The comment is honest about this limitation, which is appreciated. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 18+ messages in thread
* Claude review: Rust bindings for gem shmem 2026-06-02 17:24 [PATCH v16 0/6] Rust bindings for gem shmem Lyude Paul ` (5 preceding siblings ...) 2026-06-02 17:25 ` [PATCH v16 6/6] rust: drm: gem: Introduce shmem::Object::sg_table() Lyude Paul @ 2026-06-04 2:29 ` Claude Code Review Bot 6 siblings, 0 replies; 18+ messages in thread From: Claude Code Review Bot @ 2026-06-04 2:29 UTC (permalink / raw) To: dri-devel-reviews Overall Series Review Subject: Rust bindings for gem shmem Author: Lyude Paul <lyude@redhat.com> Patches: 7 Reviewed: 2026-06-04T12:29:53.038433 --- This is v16 of the Rust GEM shmem bindings series from Lyude Paul, building on work originally from Asahi Lina and Daniel Almeida. The series adds DMA reservation locking helpers, vmap support, `SetOnce::reset()`, a `Default` fix for `ObjectConfig`, faux device bound-context support, and scatter-gather table integration. The code is well-structured with clear safety documentation and thoughtful RAII patterns. The overall architecture is sound: `DmaResvGuard` for scoped locking, `VMap` for typed memory mappings with compile-time size validation, and `SGTableMap` wrapped in `Devres` to ensure revocation on driver unbind. The double-checked locking pattern for lazy SGT initialization is correct with proper acquire/release ordering. Key concerns: 1. Patch 6 references `__drm_gem_shmem_free_sgt_locked`, which does not exist in the current drm-next tree. This likely depends on a C-side export that should be in drm-rust-next but should be verified. 2. The import restructuring in patch 6 (changing `device` from `crate::drm::device` to `crate::device`) changes the meaning of the `dev()` return type. This may be correct on the intended base tree (drm-rust-next, base-commit b78dab829) but warrants attention. 3. Minor style and safety-comment nits throughout. --- --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v15 0/6] Rust bindings for gem shmem @ 2026-05-29 18:33 Lyude Paul 2026-05-29 18:34 ` [PATCH v15 3/6] rust: drm: gem: Add vmap functions to shmem bindings Lyude Paul 0 siblings, 1 reply; 18+ messages in thread From: Lyude Paul @ 2026-05-29 18:33 UTC (permalink / raw) To: dri-devel, rust-for-linux, nouveau Cc: Alexandre Courbot, Gary Guo, Christian König, driver-core, Miguel Ojeda, Maarten Lankhorst, Alice Ryhl, Simona Vetter, linux-kernel, Sumit Semwal, linux-media, Rafael J . Wysocki, Thomas Zimmermann, Maxime Ripard, David Airlie, Benno Lossin, linaro-mm-sig, Danilo Krummrich, Mukesh Kumar Chaurasiya, Asahi Lina, Daniel Almeida, Lyude Paul, Greg Kroah-Hartman 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 with the following dependencies applied: https://lore.kernel.org/rust-for-linux/20260529173137.303717-1-lyude@redhat.com/T/#t Lyude Paul (6): rust: gem: shmem: Fix Default implementation for ObjectConfig rust: drm: gem/shmem: Add DmaResvGuard helper rust: drm: gem: Add vmap functions to shmem bindings rust: faux: Allow retrieving a bound Device drm/gem/shmem: Introduce __drm_gem_shmem_free_sgt_locked() rust: drm: gem: Introduce shmem::Object::sg_table() drivers/gpu/drm/drm_gem_shmem_helper.c | 32 +- include/drm/drm_gem_shmem_helper.h | 1 + rust/kernel/drm/gem/shmem.rs | 497 ++++++++++++++++++++++++- rust/kernel/faux.rs | 7 +- 4 files changed, 512 insertions(+), 25 deletions(-) base-commit: 0e42ec83d46ab8877d38d37493328ed7d1a24de8 prerequisite-patch-id: c8ade07eec6e9c9e875800b114137c459d362e4e prerequisite-patch-id: c38da06dbc4cfd2589bf2e7e9f0ebaa4da521ddf -- 2.54.0 ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v15 3/6] rust: drm: gem: Add vmap functions to shmem bindings 2026-05-29 18:33 [PATCH v15 0/6] " Lyude Paul @ 2026-05-29 18:34 ` Lyude Paul 2026-06-04 6:06 ` Claude review: " Claude Code Review Bot 0 siblings, 1 reply; 18+ messages in thread From: Lyude Paul @ 2026-05-29 18:34 UTC (permalink / raw) To: dri-devel, rust-for-linux, nouveau Cc: Alexandre Courbot, Gary Guo, Christian König, driver-core, Miguel Ojeda, Maarten Lankhorst, Alice Ryhl, Simona Vetter, linux-kernel, Sumit Semwal, linux-media, Rafael J . Wysocki, Thomas Zimmermann, Maxime Ripard, David Airlie, Benno Lossin, linaro-mm-sig, Danilo Krummrich, Mukesh Kumar Chaurasiya, Asahi Lina, Daniel Almeida, Lyude Paul, Greg Kroah-Hartman 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. Similar to how we handle SGTables, we make sure there's two different types of mappings: owned mappings (kernel::drm::gem::shmem::VMap) and borrowed mappings (kernel::drm::gem::shmem::VMapRef). Signed-off-by: Lyude Paul <lyude@redhat.com> --- 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. rust/kernel/drm/gem/shmem.rs | 433 +++++++++++++++++++++++++++++++++-- 1 file changed, 418 insertions(+), 15 deletions(-) diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index a116b0752e46a..494e0d0d8d0d6 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -11,18 +11,39 @@ use crate::{ container_of, + device::{ + self, + Bound, // + }, + devres::*, drm::{ - device, driver, gem, - private::Sealed, // + private::Sealed, + Device, // + }, + error::{ + from_err_ptr, + to_result, // + }, + io::{ + Io, + IoCapable, + IoKnownSize, // }, - error::to_result, prelude::*, - sync::aref::ARef, + scatterlist, + sync::{ + aref::ARef, + new_lazy_init, + LazyInit, + LazyInitError, // + }, types::Opaque, // }; use core::{ + ffi::c_void, + mem::MaybeUninit, // ops::{ Deref, DerefMut, // @@ -33,6 +54,7 @@ }, }; use gem::{ + BaseObject, BaseObjectPrivate, DriverObject, IntoGEMObject, // @@ -75,6 +97,9 @@ pub struct Object<T: DriverObject> { obj: Opaque<bindings::drm_gem_shmem_object>, /// Parent object that owns this object's DMA reservation object. parent_resv_obj: Option<ARef<Object<T>>>, + /// Devres object for unmapping any SGTable on driver-unbind. + #[pin] + sgt_res: LazyInit<Devres<SGTableMap<T>>>, #[pin] inner: T, } @@ -118,7 +143,7 @@ fn as_raw_shmem(&self) -> *mut bindings::drm_gem_shmem_object { /// /// Additional config options can be specified using `config`. pub fn new( - dev: &device::Device<T::Driver>, + dev: &Device<T::Driver>, size: usize, config: ObjectConfig<'_, T>, args: T::Args, @@ -127,6 +152,7 @@ pub fn new( try_pin_init!(Self { obj <- Opaque::init_zeroed(), parent_resv_obj: config.parent_resv_obj.map(|p| p.into()), + sgt_res <- new_lazy_init!(), inner <- T::new(dev, size, args), }), GFP_KERNEL, @@ -160,9 +186,9 @@ pub fn new( } /// Returns the `Device` that owns this GEM object. - pub fn dev(&self) -> &device::Device<T::Driver> { + pub fn dev(&self) -> &Device<T::Driver> { // SAFETY: `dev` will have been initialized in `Self::new()` by `drm_gem_shmem_init()`. - unsafe { device::Device::from_raw((*self.as_raw()).dev) } + unsafe { Device::from_raw((*self.as_raw()).dev) } } extern "C" fn free_callback(obj: *mut bindings::drm_gem_object) { @@ -170,22 +196,123 @@ 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>` - 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 reset `sgt_res` first, since doing so requires that the GEM object is still + // alive. + // SAFETY: + // - We verified above that `this` is valid. + // - `&mut (*this)` does not implement Unpin. + unsafe { Pin::new_unchecked(&mut (*this).sgt_res) }.reset(); + + // 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) }; } + + /// 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, SIZE>> + where + R: Deref<Target = Self> + From<&'a Self>, + { + // 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, 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, 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); + } + + match self.sgt_res.init(Devres::new(dev, SGTableMap::new(self))) { + Ok(ret) | Err(LazyInitError::AlreadyInit(ret)) => Ok(ret.access(dev)?), + Err(LazyInitError::DuringInit(e)) => Err(e), + } + } } impl<T: DriverObject> Deref for Object<T> { @@ -246,7 +373,6 @@ impl<T: DriverObject> driver::AllocImpl for Object<T> { impl<'a, T: DriverObject> DmaResvGuard<'a, T> { #[inline(always)] - #[expect(unused)] fn new(obj: &'a Object<T>) -> Self { // SAFETY: This lock is initialized throughout the lifetime of `object`. unsafe { bindings::dma_resv_lock(obj.raw_dma_resv(), ptr::null_mut()) }; @@ -262,3 +388,280 @@ 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, const SIZE: usize> IoCapable<$ty> for $impl<D, R, SIZE> + where + D: DriverObject, + R: Deref<Target = Object<D>>, + { + #[inline(always)] + unsafe fn io_read(&self, address: usize) -> $ty { + let ptr = address as *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 = address as *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, const SIZE: usize = 0> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + addr: *mut c_void, + owner: R, +} + +/// An alias type for a reference to a shmem-based GEM object's VMap. +pub type VMapRef<'a, D, const SIZE: usize = 0> = VMap<D, &'a Object<D>, SIZE>; + +/// An alias type for an owned reference to a shmem-based GEM object's VMap. +pub type VMapOwned<D, const SIZE: usize = 0> = VMap<D, ARef<Object<D>>, SIZE>; + +impl<D, R, const SIZE: usize> VMap<D, R, SIZE> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + /// Borrows a reference to the object that owns this virtual mapping. + #[inline(always)] + pub fn owner(&self) -> &Object<D> { + &self.owner + } +} + +impl<D, R, const SIZE: usize> Drop for VMap<D, R, SIZE> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + #[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, const SIZE: usize> Io for VMap<D, R, SIZE> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + #[inline(always)] + fn addr(&self) -> usize { + self.addr as usize + } + + #[inline(always)] + fn maxsize(&self) -> usize { + self.owner.size() + } +} + +impl<D, R, const SIZE: usize> IoKnownSize for VMap<D, R, SIZE> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + 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); + +/// 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> { + obj: NonNull<Object<T>>, +} + +impl<T: DriverObject> Deref for SGTableMap<T> { + 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> Drop for SGTableMap<T> { + 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> SGTableMap<T> { + fn new(obj: &Object<T>) -> 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> Send for SGTableMap<T> {} +// SAFETY: The NonNull in SGTableMap is guaranteed valid by our type invariants, and the GEM object +// it points to is guaranteed to be thread-safe. +unsafe impl<T: DriverObject> Sync for SGTableMap<T> {} + +#[kunit_tests(rust_drm_gem_shmem)] +mod tests { + use super::*; + use crate::{ + drm, + 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( + _dev: &drm::Device<KunitDriver>, + _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 = Object<KunitObject>; + + const INFO: drm::DriverInfo = INFO; + const IOCTLS: &'static [drm::ioctl::DrmIoctlDescriptor] = &[]; + } + + fn create_drm_dev() -> Result<(faux::Registration, ARef<drm::Device<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 = drm::Device::<KunitDriver>::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(()) + } +} -- 2.54.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Claude review: rust: drm: gem: Add vmap functions to shmem bindings 2026-05-29 18:34 ` [PATCH v15 3/6] rust: drm: gem: Add vmap functions to shmem bindings Lyude Paul @ 2026-06-04 6:06 ` Claude Code Review Bot 0 siblings, 0 replies; 18+ messages in thread From: Claude Code Review Bot @ 2026-06-04 6:06 UTC (permalink / raw) To: dri-devel-reviews Patch Review **Status: Looks good overall, minor notes** This is the largest patch in the series. Key design decisions are sound: **VMap lifecycle**: The `make_vmap` method acquires the DMA reservation lock, calls `drm_gem_shmem_vmap_locked`, then explicitly drops the guard before potentially calling `raw_vunmap` (which re-acquires the lock). This avoids deadlock: ```rust let guard = DmaResvGuard::new(self); to_result(unsafe { bindings::drm_gem_shmem_vmap_locked(self.as_raw_shmem(), map.as_mut_ptr()) })?; drop(guard); ``` **iomem rejection**: The iomem path correctly unmaps before returning an error: ```rust if map.is_iomem { unsafe { self.raw_vunmap(map) }; Err(ENOTSUPP) } ``` **SGTableMap and Devres interaction**: The `SGTableMap` is wrapped in `Devres` and stored in a `LazyInit`. On device unbind, Devres drops the SGTableMap which calls `__drm_gem_shmem_free_sgt_locked()` and sets `shmem->sgt = NULL`. In `free_callback`, `sgt_res.reset()` is called before `drm_gem_shmem_release()`, ensuring no double-free since `drm_gem_shmem_release()` checks `if (shmem->sgt)`. The reordering in `free_callback` is critical and correct — `sgt_res` must be reset while the GEM object's dma_resv is still valid: ```rust unsafe { Pin::new_unchecked(&mut (*this).sgt_res) }.reset(); unsafe { bindings::drm_gem_shmem_release(base) }; let _ = unsafe { KBox::from_raw(this) }; ``` **Device validation in `sg_table()`**: The check that the caller's device matches the GEM object's device is a good safety measure: ```rust if dev.as_raw() != self.dev().as_ref().as_raw() { return Err(EINVAL); } ``` **Test coverage**: The kunit tests are well-structured, covering size validation, IO read/write, and wrong-device rejection. Minor observation on the vmap_io test: the test uses `0xFFFFFFFF` for write32 and checks individual bytes are all `0xFF` — this works regardless of endianness, which is nice. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v12 0/5] Rust bindings for gem shmem @ 2026-04-21 23:52 Lyude Paul 2026-04-21 23:52 ` [PATCH v12 5/5] rust: drm: gem: Add vmap functions to shmem bindings Lyude Paul 0 siblings, 1 reply; 18+ messages in thread From: Lyude Paul @ 2026-04-21 23:52 UTC (permalink / raw) To: nouveau, Gary Guo, Daniel Almeida, rust-for-linux, Danilo Krummrich, dri-devel Cc: Matthew Maurer, FUJITA Tomonori, Lorenzo Stoakes, christian.koenig, Asahi Lina, Miguel Ojeda, Andreas Hindborg, Simona Vetter, Alice Ryhl, Boqun Feng, Sumit Semwal, Krishna Ketan Rai, linux-media, Shankari Anand, David Airlie, Benno Lossin, Viresh Kumar, linaro-mm-sig, Asahi Lina, Greg Kroah-Hartman, kernel 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 sure this builds: https://gitlab.freedesktop.org/lyudess/linux/-/commits/rust/gem-shmem This patch series applies on top of drm-rust-next Lyude Paul (5): rust: drm: gem: s/device::Device/Device/ for shmem.rs drm/gem/shmem: Introduce __drm_gem_shmem_free_sgt_locked() drm/gem/shmem: Export drm_gem_shmem_get_pages_sgt_locked() rust: drm: gem: Introduce shmem::SGTable rust: drm: gem: Add vmap functions to shmem bindings drivers/gpu/drm/drm_gem_shmem_helper.c | 48 ++- include/drm/drm_gem_shmem_helper.h | 2 + rust/kernel/drm/gem/shmem.rs | 557 ++++++++++++++++++++++++- 3 files changed, 593 insertions(+), 14 deletions(-) base-commit: a7a080bb4236ebe577b6776d940d1717912ff6dd -- 2.53.0 ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v12 5/5] rust: drm: gem: Add vmap functions to shmem bindings 2026-04-21 23:52 [PATCH v12 0/5] Rust bindings for gem shmem Lyude Paul @ 2026-04-21 23:52 ` Lyude Paul 2026-04-22 22:05 ` Claude review: " Claude Code Review Bot 0 siblings, 1 reply; 18+ messages in thread From: Lyude Paul @ 2026-04-21 23:52 UTC (permalink / raw) To: nouveau, Gary Guo, Daniel Almeida, rust-for-linux, Danilo Krummrich, dri-devel Cc: Matthew Maurer, FUJITA Tomonori, Lorenzo Stoakes, christian.koenig, Asahi Lina, Miguel Ojeda, Andreas Hindborg, Simona Vetter, Alice Ryhl, Boqun Feng, Sumit Semwal, Krishna Ketan Rai, linux-media, Shankari Anand, David Airlie, Benno Lossin, Viresh Kumar, linaro-mm-sig, Asahi Lina, Greg Kroah-Hartman, kernel 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. Similar to how we handle SGTables, we make sure there's two different types of mappings: owned mappings (kernel::drm::gem::shmem::VMap) and borrowed mappings (kernel::drm::gem::shmem::VMapRef). Signed-off-by: Lyude Paul <lyude@redhat.com> --- 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() rust/kernel/drm/gem/shmem.rs | 355 +++++++++++++++++++++++++++++++++++ 1 file changed, 355 insertions(+) diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index a477312c8a09b..b96de8d33141d 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -26,6 +26,11 @@ from_err_ptr, to_result, // }, + io::{ + Io, + IoCapable, + IoKnownSize, // + }, prelude::*, scatterlist, types::{ @@ -35,6 +40,11 @@ }; use core::{ cell::UnsafeCell, + ffi::c_void, + mem::{ + self, + MaybeUninit, // + }, ops::{ Deref, DerefMut, // @@ -45,6 +55,7 @@ }, }; use gem::{ + BaseObject, BaseObjectPrivate, DriverObject, IntoGEMObject, // @@ -289,6 +300,84 @@ pub fn owned_sg_table(&self, dev: &device::Device<Bound>) -> Result<SGTable<T>> // `Some(Devres<SGTableMap<T>>)`. Ok(SGTable(self.into())) } + + /// Attempt to create a vmap from the gem object, and confirm the size of said vmap. + fn raw_vmap(&self, min_size: usize) -> Result<*mut c_void> { + if self.size() < min_size { + return Err(ENOSPC); + } + + let mut map: MaybeUninit<bindings::iosys_map> = MaybeUninit::uninit(); + + // SAFETY: drm_gem_shmem_vmap can be called with the DMA reservation lock held + to_result(unsafe { + // TODO: see top of file + bindings::dma_resv_lock(self.raw_dma_resv(), ptr::null_mut()); + let ret = bindings::drm_gem_shmem_vmap_locked(self.as_raw_shmem(), map.as_mut_ptr()); + bindings::dma_resv_unlock(self.raw_dma_resv()); + ret + })?; + + // 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 { + // SAFETY: We checked that this is not an iomem allocation, making it safe to read vaddr + Ok(unsafe { map.__bindgen_anon_1.vaddr }) + } + } + + /// 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 resv = self.raw_dma_resv(); + + // 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 { + // TODO: see top of file + bindings::dma_resv_lock(resv, ptr::null_mut()); + bindings::drm_gem_shmem_vunmap_locked(self.as_raw_shmem(), &mut map); + bindings::dma_resv_unlock(resv); + } + } + + /// Creates and returns a virtual kernel memory mapping for this object. + #[inline] + pub fn vmap<const SIZE: usize>(&self) -> Result<VMapRef<'_, T, SIZE>> { + Ok(VMap { + // INVARIANT: `raw_vmap()` checks that the gem object is at least as large as `SIZE`. + addr: self.raw_vmap(SIZE)?, + owner: self, + }) + } + + /// 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, SIZE>> { + Ok(VMap { + // INVARIANT: `raw_vmap()` checks that the gem object is at least as large as `SIZE`. + addr: self.raw_vmap(SIZE)?, + owner: self.into(), + }) + } } impl<T: DriverObject> Deref for Object<T> { @@ -387,6 +476,155 @@ unsafe impl<T: DriverObject> Send for SGTableMap<T> {} // it points to is guaranteed to be thread-safe. unsafe impl<T: DriverObject> Sync for SGTableMap<T> {} +macro_rules! impl_vmap_io_capable { + ($impl:ident, $ty:ty) => { + impl<D, R, const SIZE: usize> IoCapable<$ty> for $impl<D, R, SIZE> + where + D: DriverObject, + R: Deref<Target = Object<D>>, + { + #[inline(always)] + unsafe fn io_read(&self, address: usize) -> $ty { + let ptr = address as *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 = address as *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, const SIZE: usize = 0> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + addr: *mut c_void, + owner: R, +} + +/// An alias type for a reference to a shmem-based GEM object's VMap. +pub type VMapRef<'a, D, const SIZE: usize = 0> = VMap<D, &'a Object<D>, SIZE>; + +/// An alias type for an owned reference to a shmem-based GEM object's VMap. +pub type VMapOwned<D, const SIZE: usize = 0> = VMap<D, ARef<Object<D>>, SIZE>; + +impl<D, R, const SIZE: usize> VMap<D, R, SIZE> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + /// Borrows a reference to the object that owns this virtual mapping. + #[inline(always)] + pub fn owner(&self) -> &Object<D> { + &self.owner + } +} + +impl<D, R, const SIZE: usize> Drop for VMap<D, R, SIZE> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + #[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, const SIZE: usize> Io for VMap<D, R, SIZE> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + #[inline(always)] + fn addr(&self) -> usize { + self.addr as usize + } + + #[inline(always)] + fn maxsize(&self) -> usize { + self.owner.size() + } +} + +impl<D, R, const SIZE: usize> IoKnownSize for VMap<D, R, SIZE> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + 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); + +impl<D: DriverObject, const SIZE: usize> Clone for VMapOwned<D, SIZE> { + #[inline] + fn clone(&self) -> Self { + // SAFETY: We have a successful vmap already, so this can't fail. + unsafe { self.owner.owned_vmap().unwrap_unchecked() } + } +} + +impl<'a, D: DriverObject, const SIZE: usize> Clone for VMapRef<'a, D, SIZE> { + #[inline] + fn clone(&self) -> Self { + // SAFETY: We have a successful vmap already, so this can't fail. + unsafe { self.owner.vmap().unwrap_unchecked() } + } +} + +impl<'a, D: DriverObject, const SIZE: usize> From<VMapRef<'a, D, SIZE>> for VMapOwned<D, SIZE> { + #[inline] + fn from(value: VMapRef<'a, D, SIZE>) -> Self { + let this = Self { + addr: value.addr, + owner: value.owner.into(), + }; + + mem::forget(value); + this + } +} + +// SAFETY: VMap is thread-safe, and the fact that this VMap has an owned reference to the object +// means this object will remain valid until dropped. +unsafe impl<D: DriverObject, const SIZE: usize> Send for VMapOwned<D, SIZE> {} +// SAFETY: VMap is thread-safe, and the fact that this VMap has an owned reference to the object +// means this object will remain valid until dropped. +unsafe impl<D: DriverObject, const SIZE: usize> Sync for VMapOwned<D, SIZE> {} + /// An owned reference to a scatter-gather table of DMA address spans for a GEM shmem object. /// /// This object holds an owned reference to the underlying GEM shmem object, ensuring that the @@ -414,3 +652,120 @@ fn deref(&self) -> &Self::Target { unsafe { (*self.0.sgt_res.get()).as_ref().unwrap_unchecked() } } } + +#[kunit_tests(rust_drm_gem_shmem)] +mod tests { + use super::*; + use crate::{ + drm, + 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( + _dev: &drm::Device<KunitDriver>, + _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 = Object<KunitObject>; + + const INFO: drm::DriverInfo = INFO; + const IOCTLS: &'static [drm::ioctl::DrmIoctlDescriptor] = &[]; + } + + fn create_drm_dev() -> Result<(faux::Registration, ARef<drm::Device<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 = drm::Device::<KunitDriver>::new(dev.as_ref(), data)?; + + Ok((dev, drm)) + } + + #[test] + fn compile_time_vmap_sizes() -> Result { + let (_dev, drm) = create_drm_dev()?; + + // Create a gem object to test with + let cfg_ = ObjectConfig::<KunitObject> { + map_wc: false, + parent_resv_obj: None, + }; + let obj = Object::<KunitObject>::new(&drm, PAGE_SIZE, cfg_, ())?; + + // 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()?; + + // Create a gem object to test with + let cfg_ = ObjectConfig::<KunitObject> { + map_wc: false, + parent_resv_obj: None, + }; + let obj = Object::<KunitObject>::new(&drm, PAGE_SIZE, cfg_, ())?; + + 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(()) + } +} -- 2.53.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Claude review: rust: drm: gem: Add vmap functions to shmem bindings 2026-04-21 23:52 ` [PATCH v12 5/5] rust: drm: gem: Add vmap functions to shmem bindings Lyude Paul @ 2026-04-22 22:05 ` Claude Code Review Bot 0 siblings, 0 replies; 18+ messages in thread From: Claude Code Review Bot @ 2026-04-22 22:05 UTC (permalink / raw) To: dri-devel-reviews Patch Review **1. Clone for VMapOwned calls unwrap_unchecked**: ```rust impl<D: DriverObject, const SIZE: usize> Clone for VMapOwned<D, SIZE> { fn clone(&self) -> Self { unsafe { self.owner.owned_vmap().unwrap_unchecked() } } } ``` The safety comment says "We have a successful vmap already, so this can't fail." This relies on the fact that `drm_gem_shmem_vmap_locked` uses a refcount (`vmap_use_count`) and will succeed if there's already an active vmap. This is true for the non-iomem path but should be documented more carefully — if the existing vmap was non-iomem, the cloned one will also be non-iomem (since it's the same cached mapping). The real risk is that between clone attempts the vmap could theoretically be released and re-acquired as iomem, but since we hold a reference (`self` exists), the refcount prevents that. The reasoning is sound but subtle. **2. VMap::From<VMapRef> uses mem::forget**: ```rust impl<'a, D: DriverObject, const SIZE: usize> From<VMapRef<'a, D, SIZE>> for VMapOwned<D, SIZE> { fn from(value: VMapRef<'a, D, SIZE>) -> Self { let this = Self { addr: value.addr, owner: value.owner.into(), }; mem::forget(value); this } } ``` This is correct — the `mem::forget` prevents double-vunmap since we're transferring ownership of the mapping. Good pattern. **3. No Send/Sync for VMapRef**: `Send` and `Sync` are only implemented for `VMapOwned`, not `VMapRef`. This is correct since `VMapRef` borrows from the object and the lifetime prevents sending across threads. **4. Tests are well-structured**: The kunit tests verify compile-time size checking and basic I/O operations. The test for `vmap::<{ PAGE_SIZE + 200 }>().is_err()` is a good sanity check. The `vmap_io` test verifying byte-level reads within a u32 write is good for catching endianness assumptions — though the expected byte values (`0xFF` at all four positions) would be the same on both big and little endian for `0xFFFFFFFF`, so it doesn't actually test endianness. A value like `0xDEADBEEF` would be more revealing, but this is minor. **5. ENOTSUPP for iomem**: The use of `ENOTSUPP` (which is the internal kernel variant, not `EOPNOTSUPP`) is fine for internal kernel use. The comment `// XXX: We don't currently support iomem allocations` appropriately flags this as a known limitation. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v11 0/5] Rust bindings for gem shmem @ 2026-04-21 23:40 Lyude Paul 2026-04-21 23:40 ` [PATCH v11 5/5] rust: drm: gem: Add vmap functions to shmem bindings Lyude Paul 0 siblings, 1 reply; 18+ messages in thread From: Lyude Paul @ 2026-04-21 23:40 UTC (permalink / raw) To: nouveau, Gary Guo, Daniel Almeida, rust-for-linux, Danilo Krummrich, dri-devel Cc: Matthew Maurer, FUJITA Tomonori, Lorenzo Stoakes, christian.koenig, Asahi Lina, Miguel Ojeda, Andreas Hindborg, Simona Vetter, Alice Ryhl, Boqun Feng, Sumit Semwal, Krishna Ketan Rai, linux-media, Shankari Anand, David Airlie, Benno Lossin, Viresh Kumar, linaro-mm-sig, Asahi Lina, Greg Kroah-Hartman, kernel 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 (+ a hack required to make sure this builds: https://gitlab.freedesktop.org/lyudess/linux/-/commits/rust/gem-shmem This patch series applies on top of drm-rust-next Lyude Paul (5): rust: drm: gem: s/device::Device/Device/ for shmem.rs drm/gem/shmem: Introduce __drm_gem_shmem_free_sgt_locked() drm/gem/shmem: Export drm_gem_shmem_get_pages_sgt_locked() rust: drm: gem: Introduce shmem::SGTable rust: drm: gem: Add vmap functions to shmem bindings drivers/gpu/drm/drm_gem_shmem_helper.c | 48 ++- include/drm/drm_gem_shmem_helper.h | 2 + rust/kernel/drm/gem/shmem.rs | 557 ++++++++++++++++++++++++- 3 files changed, 593 insertions(+), 14 deletions(-) base-commit: a7a080bb4236ebe577b6776d940d1717912ff6dd -- 2.53.0 ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v11 5/5] rust: drm: gem: Add vmap functions to shmem bindings 2026-04-21 23:40 [PATCH v11 0/5] Rust bindings for gem shmem Lyude Paul @ 2026-04-21 23:40 ` Lyude Paul 2026-04-22 22:09 ` Claude review: " Claude Code Review Bot 0 siblings, 1 reply; 18+ messages in thread From: Lyude Paul @ 2026-04-21 23:40 UTC (permalink / raw) To: nouveau, Gary Guo, Daniel Almeida, rust-for-linux, Danilo Krummrich, dri-devel Cc: Matthew Maurer, FUJITA Tomonori, Lorenzo Stoakes, christian.koenig, Asahi Lina, Miguel Ojeda, Andreas Hindborg, Simona Vetter, Alice Ryhl, Boqun Feng, Sumit Semwal, Krishna Ketan Rai, linux-media, Shankari Anand, David Airlie, Benno Lossin, Viresh Kumar, linaro-mm-sig, Asahi Lina, Greg Kroah-Hartman, kernel 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. Similar to how we handle SGTables, we make sure there's two different types of mappings: owned mappings (kernel::drm::gem::shmem::VMap) and borrowed mappings (kernel::drm::gem::shmem::VMapRef). Signed-off-by: Lyude Paul <lyude@redhat.com> --- 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() rust/kernel/drm/gem/shmem.rs | 364 ++++++++++++++++++++++++++++++++++- 1 file changed, 360 insertions(+), 4 deletions(-) diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index ca9d66900f8ac..d9a7ba950bd6a 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -26,6 +26,11 @@ from_err_ptr, to_result, // }, + io::{ + Io, + IoCapable, + IoKnownSize, // + }, prelude::*, scatterlist, types::{ @@ -35,6 +40,11 @@ }; use core::{ cell::UnsafeCell, + ffi::c_void, + mem::{ + self, + MaybeUninit, // + }, ops::{ Deref, DerefMut, // @@ -45,6 +55,7 @@ }, }; use gem::{ + BaseObject, BaseObjectPrivate, DriverObject, IntoGEMObject, // @@ -74,14 +85,15 @@ pub struct ObjectConfig<'a, T: DriverObject> { #[repr(C)] #[pin_data] pub struct Object<T: DriverObject> { + /// Devres object for unmapping any SGTable on driver-unbind. + /// + /// This is protected by the object's dma_resv lock. Additionally, this needs to be at the top + /// of the structure to ensure that it is dropped before `obj`. + sgt_res: UnsafeCell<Option<Devres<SGTableMap<T>>>>, #[pin] obj: Opaque<bindings::drm_gem_shmem_object>, /// Parent object that owns this object's DMA reservation object. parent_resv_obj: Option<ARef<Object<T>>>, - /// Devres object for unmapping any SGTable on driver-unbind. - /// - /// This is protected by the object's dma_resv lock. - sgt_res: UnsafeCell<Option<Devres<SGTableMap<T>>>>, #[pin] inner: T, } @@ -288,6 +300,84 @@ pub fn owned_sg_table(&self, dev: &device::Device<Bound>) -> Result<SGTable<T>> // `Some(Devres<SGTableMap<T>>)`. Ok(SGTable(self.into())) } + + /// Attempt to create a vmap from the gem object, and confirm the size of said vmap. + fn raw_vmap(&self, min_size: usize) -> Result<*mut c_void> { + if self.size() < min_size { + return Err(ENOSPC); + } + + let mut map: MaybeUninit<bindings::iosys_map> = MaybeUninit::uninit(); + + // SAFETY: drm_gem_shmem_vmap can be called with the DMA reservation lock held + to_result(unsafe { + // TODO: see top of file + bindings::dma_resv_lock(self.raw_dma_resv(), ptr::null_mut()); + let ret = bindings::drm_gem_shmem_vmap_locked(self.as_raw_shmem(), map.as_mut_ptr()); + bindings::dma_resv_unlock(self.raw_dma_resv()); + ret + })?; + + // 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 { + // SAFETY: We checked that this is not an iomem allocation, making it safe to read vaddr + Ok(unsafe { map.__bindgen_anon_1.vaddr }) + } + } + + /// 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 resv = self.raw_dma_resv(); + + // 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 { + // TODO: see top of file + bindings::dma_resv_lock(resv, ptr::null_mut()); + bindings::drm_gem_shmem_vunmap_locked(self.as_raw_shmem(), &mut map); + bindings::dma_resv_unlock(resv); + } + } + + /// Creates and returns a virtual kernel memory mapping for this object. + #[inline] + pub fn vmap<const SIZE: usize>(&self) -> Result<VMapRef<'_, T, SIZE>> { + Ok(VMap { + // INVARIANT: `raw_vmap()` checks that the gem object is at least as large as `SIZE`. + addr: self.raw_vmap(SIZE)?, + owner: self, + }) + } + + /// 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, SIZE>> { + Ok(VMap { + // INVARIANT: `raw_vmap()` checks that the gem object is at least as large as `SIZE`. + addr: self.raw_vmap(SIZE)?, + owner: self.into(), + }) + } } impl<T: DriverObject> Deref for Object<T> { @@ -386,6 +476,155 @@ unsafe impl<T: DriverObject> Send for SGTableMap<T> {} // it points to is guaranteed to be thread-safe. unsafe impl<T: DriverObject> Sync for SGTableMap<T> {} +macro_rules! impl_vmap_io_capable { + ($impl:ident, $ty:ty) => { + impl<D, R, const SIZE: usize> IoCapable<$ty> for $impl<D, R, SIZE> + where + D: DriverObject, + R: Deref<Target = Object<D>>, + { + #[inline(always)] + unsafe fn io_read(&self, address: usize) -> $ty { + let ptr = address as *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 = address as *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, const SIZE: usize = 0> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + addr: *mut c_void, + owner: R, +} + +/// An alias type for a reference to a shmem-based GEM object's VMap. +pub type VMapRef<'a, D, const SIZE: usize = 0> = VMap<D, &'a Object<D>, SIZE>; + +/// An alias type for an owned reference to a shmem-based GEM object's VMap. +pub type VMapOwned<D, const SIZE: usize = 0> = VMap<D, ARef<Object<D>>, SIZE>; + +impl<D, R, const SIZE: usize> VMap<D, R, SIZE> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + /// Borrows a reference to the object that owns this virtual mapping. + #[inline(always)] + pub fn owner(&self) -> &Object<D> { + &self.owner + } +} + +impl<D, R, const SIZE: usize> Drop for VMap<D, R, SIZE> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + #[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, const SIZE: usize> Io for VMap<D, R, SIZE> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + #[inline(always)] + fn addr(&self) -> usize { + self.addr as usize + } + + #[inline(always)] + fn maxsize(&self) -> usize { + self.owner.size() + } +} + +impl<D, R, const SIZE: usize> IoKnownSize for VMap<D, R, SIZE> +where + D: DriverObject, + R: Deref<Target = Object<D>>, +{ + 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); + +impl<D: DriverObject, const SIZE: usize> Clone for VMapOwned<D, SIZE> { + #[inline] + fn clone(&self) -> Self { + // SAFETY: We have a successful vmap already, so this can't fail. + unsafe { self.owner.owned_vmap().unwrap_unchecked() } + } +} + +impl<'a, D: DriverObject, const SIZE: usize> Clone for VMapRef<'a, D, SIZE> { + #[inline] + fn clone(&self) -> Self { + // SAFETY: We have a successful vmap already, so this can't fail. + unsafe { self.owner.vmap().unwrap_unchecked() } + } +} + +impl<'a, D: DriverObject, const SIZE: usize> From<VMapRef<'a, D, SIZE>> for VMapOwned<D, SIZE> { + #[inline] + fn from(value: VMapRef<'a, D, SIZE>) -> Self { + let this = Self { + addr: value.addr, + owner: value.owner.into(), + }; + + mem::forget(value); + this + } +} + +// SAFETY: VMap is thread-safe, and the fact that this VMap has an owned reference to the object +// means this object will remain valid until dropped. +unsafe impl<D: DriverObject, const SIZE: usize> Send for VMapOwned<D, SIZE> {} +// SAFETY: VMap is thread-safe, and the fact that this VMap has an owned reference to the object +// means this object will remain valid until dropped. +unsafe impl<D: DriverObject, const SIZE: usize> Sync for VMapOwned<D, SIZE> {} + /// An owned reference to a scatter-gather table of DMA address spans for a GEM shmem object. /// /// This object holds an owned reference to the underlying GEM shmem object, ensuring that the @@ -413,3 +652,120 @@ fn deref(&self) -> &Self::Target { unsafe { (*self.0.sgt_res.get()).as_ref().unwrap_unchecked() } } } + +#[kunit_tests(rust_drm_gem_shmem)] +mod tests { + use super::*; + use crate::{ + drm, + 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( + _dev: &drm::Device<KunitDriver>, + _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 = Object<KunitObject>; + + const INFO: drm::DriverInfo = INFO; + const IOCTLS: &'static [drm::ioctl::DrmIoctlDescriptor] = &[]; + } + + fn create_drm_dev() -> Result<(faux::Registration, ARef<drm::Device<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 = drm::Device::<KunitDriver>::new(dev.as_ref(), data)?; + + Ok((dev, drm)) + } + + #[test] + fn compile_time_vmap_sizes() -> Result { + let (_dev, drm) = create_drm_dev()?; + + // Create a gem object to test with + let cfg_ = ObjectConfig::<KunitObject> { + map_wc: false, + parent_resv_obj: None, + }; + let obj = Object::<KunitObject>::new(&drm, PAGE_SIZE, cfg_, ())?; + + // 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()?; + + // Create a gem object to test with + let cfg_ = ObjectConfig::<KunitObject> { + map_wc: false, + parent_resv_obj: None, + }; + let obj = Object::<KunitObject>::new(&drm, PAGE_SIZE, cfg_, ())?; + + 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(()) + } +} -- 2.53.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Claude review: rust: drm: gem: Add vmap functions to shmem bindings 2026-04-21 23:40 ` [PATCH v11 5/5] rust: drm: gem: Add vmap functions to shmem bindings Lyude Paul @ 2026-04-22 22:09 ` Claude Code Review Bot 0 siblings, 0 replies; 18+ messages in thread From: Claude Code Review Bot @ 2026-04-22 22:09 UTC (permalink / raw) To: dri-devel-reviews Patch Review **1. Field reordering fix:** ```rust pub struct Object<T: DriverObject> { + sgt_res: UnsafeCell<Option<Devres<SGTableMap<T>>>>, obj: Opaque<bindings::drm_gem_shmem_object>, parent_resv_obj: Option<ARef<Object<T>>>, - sgt_res: UnsafeCell<Option<Devres<SGTableMap<T>>>>, inner: T, } ``` This ensures `sgt_res` is dropped before `obj`, which is critical for correctness. As noted above, this fix should arguably be in patch 4 to avoid a bisectability issue. **2. `Clone` for `VMapOwned` uses `unwrap_unchecked`:** ```rust impl<D: DriverObject, const SIZE: usize> Clone for VMapOwned<D, SIZE> { fn clone(&self) -> Self { unsafe { self.owner.owned_vmap().unwrap_unchecked() } } } ``` The comment says "We have a successful vmap already, so this can't fail." However, `drm_gem_shmem_vmap_locked` internally calls `drm_gem_shmem_get_pages_locked` which does `shmem_read_mapping_page` — this can fail under memory pressure even on a subsequent call (the pages_use_count refcount bump path skips this, but if someone called vunmap in between and pages were released...). Actually, in the vmap case, `drm_gem_shmem_vmap_locked` increments `vmap_use_count` and if it's already > 0, it just returns the existing vaddr. So cloning while holding an existing VMap should indeed be safe since `vmap_use_count > 0` means the pages are pinned. This is sound. **3. `raw_vmap` return value leak if `to_result` fails:** ```rust fn raw_vmap(&self, min_size: usize) -> Result<*mut c_void> { // ... let mut map: MaybeUninit<bindings::iosys_map> = MaybeUninit::uninit(); to_result(unsafe { bindings::dma_resv_lock(self.raw_dma_resv(), ptr::null_mut()); let ret = bindings::drm_gem_shmem_vmap_locked(self.as_raw_shmem(), map.as_mut_ptr()); bindings::dma_resv_unlock(self.raw_dma_resv()); ret })?; ``` If `drm_gem_shmem_vmap_locked` fails, `dma_resv_unlock` is still called — that's correct. The `to_result` then propagates the error and `map` (uninitialized) is not read. Good. **4. iomem handling returns `ENOTSUPP`:** ```rust if map.is_iomem { unsafe { self.raw_vunmap(map) }; Err(ENOTSUPP) } ``` This is a reasonable limitation to document. The `XXX` comment acknowledges it. In practice, shmem objects should never produce iomem mappings, so this is defensive. **5. The `From<VMapRef> for VMapOwned` uses `mem::forget`:** ```rust impl<'a, D: DriverObject, const SIZE: usize> From<VMapRef<'a, D, SIZE>> for VMapOwned<D, SIZE> { fn from(value: VMapRef<'a, D, SIZE>) -> Self { let this = Self { addr: value.addr, owner: value.owner.into(), }; mem::forget(value); this } } ``` This transfers the vmap from the borrowed to the owned variant without incrementing/decrementing the vmap refcount. The `mem::forget` prevents the `VMapRef`'s `Drop` from running `raw_vunmap`. This is correct — the mapping is logically transferred. **6. The kunit tests are well-structured** and test both compile-time size validation and actual I/O through the vmap. The `vmap_io` test doing byte-level reads after a 32-bit write to verify byte ordering is a nice touch, though it's architecture-dependent (assumes little-endian). This should be fine for the test environments this runs on. **7. The `impl_vmap_io_capable!` macro:** ```rust unsafe fn io_read(&self, address: usize) -> $ty { let ptr = address as *mut $ty; unsafe { ptr::read(ptr) } } ``` Using `ptr::read`/`ptr::write` (not volatile) is intentional for non-iomem shmem mappings. This is correct — shmem vmap memory is regular kernel memory, not MMIO. **Minor nit:** The `BaseObject` import in patch 5 (`use gem::BaseObject`) isn't visibly used in the diff. It might be needed for `self.size()` to be available, which goes through the `BaseObject` trait. Worth confirming it's actually needed and not a stale import. --- **Summary of actionable items:** 1. Patch 2: Add a NULL check or document that `shmem->sgt` must be non-NULL in `__drm_gem_shmem_free_sgt_locked()`. 2. Patch 4: The field ordering issue means patch 4 alone has a potential drop-order bug; consider moving the field reorder from patch 5 into patch 4 for bisectability. 3. The interaction between `drm_gem_shmem_release()` freeing the sgt and the `Devres<SGTableMap>` drop trying to free it again deserves a safety comment explaining why double-free doesn't occur. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v10 0/5] Rust bindings for gem shmem @ 2026-04-09 0:12 Lyude Paul 2026-04-09 0:12 ` [PATCH v10 5/5] rust: drm: gem: Add vmap functions to shmem bindings Lyude Paul 0 siblings, 1 reply; 18+ messages in thread From: Lyude Paul @ 2026-04-09 0:12 UTC (permalink / raw) To: nouveau, Gary Guo, Daniel Almeida, rust-for-linux, Danilo Krummrich, dri-devel Cc: Matthew Maurer, FUJITA Tomonori, Lorenzo Stoakes, christian.koenig, Asahi Lina, Miguel Ojeda, Andreas Hindborg, Simona Vetter, Alice Ryhl, Boqun Feng, Sumit Semwal, Krishna Ketan Rai, linux-media, Shankari Anand, David Airlie, Benno Lossin, Viresh Kumar, linaro-mm-sig, Asahi Lina, Greg Kroah-Hartman, kernel 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: (apparently it mistakenly was not sent to dri-devel, so no patchwork ): https://patchwork.freedesktop.org/series/156093/ Branch with patches applied available here (+ a hack required to make sure this builds: https://gitlab.freedesktop.org/lyudess/linux/-/commits/rust/gem-shmem Lyude Paul (5): rust: drm: gem: s/device::Device/Device/ for shmem.rs drm/gem/shmem: Introduce __drm_gem_shmem_free_sgt_locked() drm/gem/shmem: Export drm_gem_shmem_get_pages_sgt_locked() rust: drm: gem: Introduce shmem::SGTable rust: drm: gem: Add vmap functions to shmem bindings drivers/gpu/drm/drm_gem_shmem_helper.c | 48 ++- include/drm/drm_gem_shmem_helper.h | 2 + rust/kernel/drm/gem/shmem.rs | 553 ++++++++++++++++++++++++- 3 files changed, 590 insertions(+), 13 deletions(-) base-commit: a7a080bb4236ebe577b6776d940d1717912ff6dd -- 2.53.0 ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v10 5/5] rust: drm: gem: Add vmap functions to shmem bindings 2026-04-09 0:12 [PATCH v10 0/5] Rust bindings for gem shmem Lyude Paul @ 2026-04-09 0:12 ` Lyude Paul 2026-04-12 2:01 ` Claude review: " Claude Code Review Bot 0 siblings, 1 reply; 18+ messages in thread From: Lyude Paul @ 2026-04-09 0:12 UTC (permalink / raw) To: nouveau, Gary Guo, Daniel Almeida, rust-for-linux, Danilo Krummrich, dri-devel Cc: Matthew Maurer, FUJITA Tomonori, Lorenzo Stoakes, christian.koenig, Asahi Lina, Miguel Ojeda, Andreas Hindborg, Simona Vetter, Alice Ryhl, Boqun Feng, Sumit Semwal, Krishna Ketan Rai, linux-media, Shankari Anand, David Airlie, Benno Lossin, Viresh Kumar, linaro-mm-sig, Asahi Lina, Greg Kroah-Hartman, kernel 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. Similar to how we handle SGTables, we make sure there's two different types of mappings: owned mappings (kernel::drm::gem::shmem::VMap) and borrowed mappings (kernel::drm::gem::shmem::VMapRef). Signed-off-by: Lyude Paul <lyude@redhat.com> --- 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. rust/kernel/drm/gem/shmem.rs | 354 +++++++++++++++++++++++++++++++++++ 1 file changed, 354 insertions(+) diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index 111be446213df..6c186d83f528e 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -26,6 +26,11 @@ from_err_ptr, // to_result, }, + io::{ + Io, + IoCapable, + IoKnownSize, // + }, prelude::*, scatterlist, types::{ @@ -35,6 +40,11 @@ }; use core::{ cell::UnsafeCell, + ffi::c_void, + mem::{ + self, + MaybeUninit, // + }, ops::{ Deref, DerefMut, // @@ -45,6 +55,7 @@ }, }; use gem::{ + BaseObject, BaseObjectPrivate, DriverObject, IntoGEMObject, // @@ -288,6 +299,84 @@ pub fn owned_sg_table(&self, dev: &device::Device<Bound>) -> Result<SGTable<T>> // `Some(Devres<SGTableMap<T>>)`. Ok(SGTable(self.into())) } + + /// Attempt to create a vmap from the gem object, and confirm the size of said vmap. + fn raw_vmap(&self, min_size: usize) -> Result<*mut c_void> { + if self.size() < min_size { + return Err(ENOSPC); + } + + let mut map: MaybeUninit<bindings::iosys_map> = MaybeUninit::uninit(); + + // SAFETY: drm_gem_shmem_vmap can be called with the DMA reservation lock held + to_result(unsafe { + // TODO: see top of file + bindings::dma_resv_lock(self.raw_dma_resv(), ptr::null_mut()); + let ret = bindings::drm_gem_shmem_vmap_locked(self.as_raw_shmem(), map.as_mut_ptr()); + bindings::dma_resv_unlock(self.raw_dma_resv()); + ret + })?; + + // SAFETY: The call to drm_gem_shmem_vunmap_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 { + // SAFETY: We checked that this is not an iomem allocation, making it safe to read vaddr + Ok(unsafe { map.__bindgen_anon_1.vaddr }) + } + } + + /// 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 resv = self.raw_dma_resv(); + + // 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 { + // TODO: see top of file + bindings::dma_resv_lock(resv, ptr::null_mut()); + bindings::drm_gem_shmem_vunmap_locked(self.as_raw_shmem(), &mut map); + bindings::dma_resv_unlock(resv); + } + } + + /// Creates and returns a virtual kernel memory mapping for this object. + #[inline] + pub fn vmap<const SIZE: usize>(&self) -> Result<VMapRef<'_, T, SIZE>> { + Ok(VMapRef { + // INVARIANT: `raw_vmap()` checks that the gem object is at least as large as `SIZE`. + addr: self.raw_vmap(SIZE)?, + owner: self, + }) + } + + /// 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<VMap<T, SIZE>> { + Ok(VMap { + // INVARIANT: `raw_vmap()` checks that the gem object is at least as large as `SIZE`. + addr: self.raw_vmap(SIZE)?, + owner: self.into(), + }) + } } impl<T: DriverObject> Deref for Object<T> { @@ -386,6 +475,154 @@ unsafe impl<T: DriverObject> Send for SGTableMap<T> {} // it points to is guaranteed to be thread-safe. unsafe impl<T: DriverObject> Sync for SGTableMap<T> {} +macro_rules! impl_vmap_io_capable { + ($impl:ident, $ty:ty $(, $lifetime:lifetime )?) => { + impl<$( $lifetime ,)? D: DriverObject, const SIZE: usize> IoCapable<$ty> + for $impl<$( $lifetime ,)? D, SIZE> + { + #[inline(always)] + unsafe fn io_read(&self, address: usize) -> $ty { + let ptr = address as *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 = address as *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) } + } + } + }; +} + +// Implement various traits common to both VMap types +macro_rules! impl_vmap_common { + ($impl:ident $(, $lifetime:lifetime )?) => { + impl<$( $lifetime ,)? D, const SIZE: usize> $impl<$( $lifetime ,)? D, SIZE> + where + D: DriverObject, + { + /// Borrows a reference to the object that owns this virtual mapping. + #[inline(always)] + pub fn owner(&self) -> &Object<D> { + &self.owner + } + } + + impl<$( $lifetime ,)? D, const SIZE: usize> Drop for $impl<$( $lifetime ,)? D, SIZE> + where + D: DriverObject, + { + #[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<$( $lifetime ,)? D, const SIZE: usize> Io for $impl<$( $lifetime ,)? D, SIZE> + where + D: DriverObject, + { + #[inline(always)] + fn addr(&self) -> usize { + self.addr as usize + } + + #[inline(always)] + fn maxsize(&self) -> usize { + self.owner.size() + } + } + + impl<$( $lifetime ,)? D, const SIZE: usize> IoKnownSize for $impl<$( $lifetime ,)? D, SIZE> + where + D: DriverObject, + { + const MIN_SIZE: usize = SIZE; + } + + impl_vmap_io_capable!($impl, u8 $( , $lifetime )?); + impl_vmap_io_capable!($impl, u16 $( , $lifetime )?); + impl_vmap_io_capable!($impl, u32 $( , $lifetime )?); + #[cfg(CONFIG_64BIT)] + impl_vmap_io_capable!($impl, u64 $( , $lifetime )?); + }; +} + +/// An owned reference to a virtual mapping for a 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: DriverObject, const SIZE: usize = 0> { + addr: *mut c_void, + owner: ARef<Object<D>>, +} + +impl_vmap_common!(VMap); + +impl<D: DriverObject, const SIZE: usize> Clone for VMap<D, SIZE> { + #[inline] + fn clone(&self) -> Self { + // SAFETY: We have a successful vmap already, so this can't fail + unsafe { self.owner.owned_vmap().unwrap_unchecked() } + } +} + +impl<'a, D: DriverObject, const SIZE: usize> From<VMapRef<'a, D, SIZE>> for VMap<D, SIZE> { + #[inline] + fn from(value: VMapRef<'a, D, SIZE>) -> Self { + let this = Self { + addr: value.addr, + owner: value.owner.into(), + }; + + mem::forget(value); + this + } +} + +// SAFETY: addr is guaranteed to be valid and accessible for the lifetime of VMap, ensuring its +// safe to send across threads. +unsafe impl<D: DriverObject, const SIZE: usize> Send for VMap<D, SIZE> {} +// SAFETY: addr is guaranteed to be valid and accessible for the lifetime of VMap, ensuring its +// safe to send across threads. +unsafe impl<D: DriverObject, const SIZE: usize> Sync for VMap<D, SIZE> {} + +/// A borrowed reference to a virtual mapping for a shmem-based GEM object in kernel address space. +pub struct VMapRef<'a, D: DriverObject, const SIZE: usize = 0> { + addr: *mut c_void, + owner: &'a Object<D>, +} + +impl_vmap_common!(VMapRef, 'a); + +impl<'a, D: DriverObject, const SIZE: usize> Clone for VMapRef<'a, D, SIZE> { + #[inline] + fn clone(&self) -> Self { + // SAFETY: We have a successful vmap already, so this can't fail + unsafe { self.owner.vmap().unwrap_unchecked() } + } +} + /// An owned reference to a scatter-gather table of DMA address spans for a GEM shmem object. /// /// This object holds an owned reference to the underlying GEM shmem object, ensuring that the @@ -413,3 +650,120 @@ fn deref(&self) -> &Self::Target { unsafe { (*self.0.sgt_res.get()).as_ref().unwrap_unchecked() } } } + +#[kunit_tests(rust_drm_gem_shmem)] +mod tests { + use super::*; + use crate::{ + drm, + 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( + _dev: &drm::Device<KunitDriver>, + _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 = Object<KunitObject>; + + const INFO: drm::DriverInfo = INFO; + const IOCTLS: &'static [drm::ioctl::DrmIoctlDescriptor] = &[]; + } + + fn create_drm_dev() -> Result<(faux::Registration, ARef<drm::Device<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 = drm::Device::<KunitDriver>::new(dev.as_ref(), data)?; + + Ok((dev, drm)) + } + + #[test] + fn compile_time_vmap_sizes() -> Result { + let (_dev, drm) = create_drm_dev()?; + + // Create a gem object to test with + let cfg_ = ObjectConfig::<KunitObject> { + map_wc: false, + parent_resv_obj: None, + }; + let obj = Object::<KunitObject>::new(&drm, PAGE_SIZE, cfg_, ())?; + + // 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()?; + + // Create a gem object to test with + let cfg_ = ObjectConfig::<KunitObject> { + map_wc: false, + parent_resv_obj: None, + }; + let obj = Object::<KunitObject>::new(&drm, PAGE_SIZE, cfg_, ())?; + + 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(()) + } +} -- 2.53.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Claude review: rust: drm: gem: Add vmap functions to shmem bindings 2026-04-09 0:12 ` [PATCH v10 5/5] rust: drm: gem: Add vmap functions to shmem bindings Lyude Paul @ 2026-04-12 2:01 ` Claude Code Review Bot 0 siblings, 0 replies; 18+ messages in thread From: Claude Code Review Bot @ 2026-04-12 2:01 UTC (permalink / raw) To: dri-devel-reviews Patch Review Good patch overall with solid kunit tests. A few issues: **Incorrect safety comment in `raw_vmap`:** ```rust // SAFETY: The call to drm_gem_shmem_vunmap_locked succeeded above, so we are guaranteed // that map is properly initialized. let map = unsafe { map.assume_init() }; ``` This should say `drm_gem_shmem_vmap_locked`, not `drm_gem_shmem_vunmap_locked`. Copy-paste error. **`unwrap_unchecked` in `VMap::clone` is risky:** ```rust impl<D: DriverObject, const SIZE: usize> Clone for VMap<D, SIZE> { fn clone(&self) -> Self { // SAFETY: We have a successful vmap already, so this can't fail unsafe { self.owner.owned_vmap().unwrap_unchecked() } } } ``` While subsequent `drm_gem_shmem_vmap_locked` calls just bump a refcount when a vmap already exists (making failure practically impossible), this is still an `unsafe` `unwrap_unchecked` on a `Result` from an operation that goes through FFI and lock acquisition. If the vmap refcount implementation ever changes, this becomes UB instead of a panic. The same applies to `VMapRef::clone`. Consider whether a plain `unwrap()` (with a `// This cannot fail because...` comment) would be acceptable here, to get a panic instead of UB in the unexpected case. **Send/Sync safety comments say "send across threads" for both:** ```rust // SAFETY: addr is guaranteed to be valid and accessible for the lifetime of VMap, ensuring its // safe to send across threads. unsafe impl<D: DriverObject, const SIZE: usize> Send for VMap<D, SIZE> {} // SAFETY: addr is guaranteed to be valid and accessible for the lifetime of VMap, ensuring its // safe to send across threads. unsafe impl<D: DriverObject, const SIZE: usize> Sync for VMap<D, SIZE> {} ``` The Sync comment should justify shared-reference safety (e.g., "safe to share references across threads"), not "send across threads". Minor nit. **The kunit tests are good** — they verify compile-time size validation, basic I/O read/write, and the byte-level view of a u32 write. The test infrastructure is minimal and well-structured. One observation: the `vmap_io` test assumes little-endian byte ordering (`0xFFFFFFFF` → four `0xFF` bytes), which is true on most test platforms but could be noted. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-06-04 6:06 UTC | newest] Thread overview: 18+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-02 17:24 [PATCH v16 0/6] Rust bindings for gem shmem Lyude Paul 2026-06-02 17:25 ` [PATCH v16 1/6] rust: drm: gem/shmem: Add DmaResvGuard helper Lyude Paul 2026-06-04 2:29 ` Claude review: " Claude Code Review Bot 2026-06-02 17:25 ` [PATCH v16 2/6] rust: drm: gem: Add vmap functions to shmem bindings Lyude Paul 2026-06-04 2:29 ` Claude review: " Claude Code Review Bot 2026-06-02 17:25 ` [PATCH v16 3/6] rust: sync: Add SetOnce::reset() Lyude Paul 2026-06-04 2:29 ` Claude review: " Claude Code Review Bot 2026-06-02 17:25 ` [PATCH v16 4/6] rust: gem: shmem: Fix Default implementation for ObjectConfig Lyude Paul 2026-06-04 2:29 ` Claude review: " Claude Code Review Bot 2026-06-02 17:25 ` [PATCH v16 5/6] rust: faux: Allow retrieving a bound Device Lyude Paul 2026-06-04 2:29 ` Claude review: " Claude Code Review Bot 2026-06-02 17:25 ` [PATCH v16 6/6] rust: drm: gem: Introduce shmem::Object::sg_table() Lyude Paul 2026-06-04 2:29 ` Claude review: " Claude Code Review Bot 2026-06-04 2:29 ` Claude review: Rust bindings for gem shmem Claude Code Review Bot -- strict thread matches above, loose matches on Subject: below -- 2026-05-29 18:33 [PATCH v15 0/6] " Lyude Paul 2026-05-29 18:34 ` [PATCH v15 3/6] rust: drm: gem: Add vmap functions to shmem bindings Lyude Paul 2026-06-04 6:06 ` Claude review: " Claude Code Review Bot 2026-04-21 23:52 [PATCH v12 0/5] Rust bindings for gem shmem Lyude Paul 2026-04-21 23:52 ` [PATCH v12 5/5] rust: drm: gem: Add vmap functions to shmem bindings Lyude Paul 2026-04-22 22:05 ` Claude review: " Claude Code Review Bot 2026-04-21 23:40 [PATCH v11 0/5] Rust bindings for gem shmem Lyude Paul 2026-04-21 23:40 ` [PATCH v11 5/5] rust: drm: gem: Add vmap functions to shmem bindings Lyude Paul 2026-04-22 22:09 ` Claude review: " Claude Code Review Bot 2026-04-09 0:12 [PATCH v10 0/5] Rust bindings for gem shmem Lyude Paul 2026-04-09 0:12 ` [PATCH v10 5/5] rust: drm: gem: Add vmap functions to shmem bindings Lyude Paul 2026-04-12 2:01 ` Claude review: " Claude Code Review Bot
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox