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. We store this in an UnsafeCell and protect access to it using the dma_resv lock that we already have from the shmem gem object, which is the same lock that currently protects drm_gem_object_shmem->sgt.
We also provide two different methods for acquiring an sg table: self.sg_table(), and self.owned_sg_table(). The first function is for short-term uses of mapped SGTables, the second is for callers that need to hold onto the mapped SGTable for an extended period of time. The second variant uses Devres of course, whereas the first simply relies on rust's borrow checker to prevent driver-unbind when using the mapped SGTable.
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
rust/kernel/drm/gem/shmem.rs | 212 +++++++++++++++++++++++++++++++++-- 1 file changed, 203 insertions(+), 9 deletions(-)
diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index 92ec2b67ed023..c0187ff22e526 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -11,18 +11,33 @@
use crate::{ container_of, + device::{ + self, + Bound, // + }, + devres::*, drm::{ driver, gem, private::Sealed, Device, // }, - error::to_result, + error::{ + from_err_ptr, + to_result, // + }, prelude::*, - sync::aref::ARef, + scatterlist, + sync::{ + aref::ARef, + new_mutex, + Mutex, + SetOnce, // + }, types::Opaque, // }; use core::{ + mem, ops::{ Deref, DerefMut, // @@ -66,6 +81,10 @@ pub struct Object<T: DriverObject> { obj: Opaquebindings::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. + /// TODO: Drop the mutex once we can use Init with SetOnce. + #[pin] + sgt_res: Mutex<SetOnce<Devres<SGTableMap<T>>>>, #[pin] inner: T, } @@ -118,6 +137,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_mutex!(SetOnce::new()), inner <- T::new(dev, size, args), }), GFP_KERNEL, @@ -161,22 +181,99 @@ 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(); + + // SAFETY: We are in free_callback(), which means that we have exclusive access to `this`. + let mut sgt_guard = unsafe { (*this).sgt_res.lock() }; + + // drm_gem_shmem_release() will clear any existing sgt, so we need to clear sgt_res before + // calling it to prevent a double-free. + drop(mem::take(sgt_guard.deref_mut())); + + // Drop the lock we acquired, we don't need it anymore and it will be acquired again when we + // perform the final drop in this function. + drop(sgt_guard); + + // 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) }; } + + // If necessary, create an SGTable for the gem object and register a Devres for it to ensure + // that it is unmapped on driver unbind. + fn get_sg_table<'a>( + &'a self, + dev: &'a device::Device<Bound>, + ) -> Result<&'a Devres<SGTableMap<T>>> { + if dev.as_raw() != self.dev().as_ref().as_raw() { + return Err(EINVAL); + } + + let sgt_res = self.sgt_res.lock(); + let sgt_map = if let Some(devres) = sgt_res.as_ref() { + devres + } else { + // INVARIANT: We store this Devres in the object itself and don't move it, ensuring that + // the object it points to remains valid for the lifetime of the SGTableMap. + sgt_res.populate(Devres::new(dev, SGTableMap::new(self))?); + + // SAFETY: We just populated `sgt_res` above, making this safe to unwrap. + unsafe { sgt_res.as_ref().unwrap_unchecked() } + }; + + // SAFETY: + // We only write to `sgt_res` in two places: + // - The above code. + // - `free_callback()`, which can't be called as long as `self` is alive. + // Therefore, it's safe to hold a reference to the contents of `sgt_res` without holding + // the lock for the lifetime of 'a, making this lifetime extension safe. + Ok(unsafe { mem::transmute::<&_, &'a _>(sgt_map) }) + } + + /// 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)`. + #[inline] + pub fn sg_table<'a>( + &'a self, + dev: &'a device::Device<Bound>, + ) -> Result<&'a scatterlist::SGTable> { + let sgt = self.get_sg_table(dev)?; + + Ok(sgt.access(dev)?.deref()) + } + + /// Creates (if necessary) and returns an owned reference to a scatter-gather table of DMA pages + /// for this object. + /// + /// This is the same as [`sg_table`], except that it instead returns an + /// [`shmem::SGTable`] which holds a reference to the associated gem object, instead of a + /// reference to an [`scatterlist::SGTable`]. + /// + /// See [`sg_table`] for more info. + /// + /// [`shmem::SGTable`]: SGTable + /// [`sg_table`]: Self::sg_table + pub fn owned_sg_table(&self, dev: &device::Device<Bound>) -> Result<SGTable<T>> { + self.get_sg_table(dev)?; + + // INVARIANT: We just ensured above that `self.sgt_res` is initialized with + // `Devres<SGTableMap<T>>`. + Ok(SGTable(self.into())) + } }
impl<T: DriverObject> Deref for Object<T> { @@ -252,3 +349,100 @@ fn drop(&mut self) { unsafe { bindings::dma_resv_unlock(self.0.raw_dma_resv()) }; } } + +/// 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> {} + +/// 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 +/// [`scatterlist::SGTable`] referenced by this type remains valid for the lifetime of this object. +/// +/// # Invariants +/// +/// - This type is proof that `self.0.sgt_res` is initialized with a `Devres<SGTableMap<T>>`. +/// - This object is only exposed in situations where we know the underlying `SGTable` will not be +/// modified for the lifetime of this object. Thus, it is safe to send/access this type across +/// threads. +pub struct SGTable<T: DriverObject>(ARef<Object<T>>); + +// SAFETY: This object is thread-safe via our type invariants. +unsafe impl<T: DriverObject> Send for SGTable<T> {} +// SAFETY: This object is thread-safe via our type invariants. +unsafe impl<T: DriverObject> Sync for SGTable<T> {} + +impl<T: DriverObject> Deref for SGTable<T> { + type Target = Devres<SGTableMap<T>>; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + // TODO: This is Bad, but we'll replace this and do better once we can pass an Init to + // SetOnce. + // SAFETY: + // - We only write to this location in two places: + // - Object::<T>::get_sg_table() + // - Object::<T>::free_callback() (which gets called by gem shmem helpers after the last + // gem object ref drop). + // Our type is proof that get_sg_table() has been called previously, and also proof that + // `free_callback()` has not been called - meaning that we can assume no races will occur + // for the lifetime of this object and we can safely access the Mutex contents without + // actually locking it. + // - Since we proved get_sg_table() has been called, we know that sgt_res is already + // populated and thus unwrap_unchecked() is safe to call. + unsafe { (*self.0.sgt_res.data.get()).as_ref().unwrap_unchecked() } + } +}