From nobody Wed Jun 10 16:01:34 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C7E893E1208 for ; Mon, 8 Jun 2026 18:31:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780943483; cv=none; b=ksKG8ROCSP2nmtlBQn6KiQUFWeJdUuCJExJINr9L7s6L4ZRQSv4Ir2s4uTAxaLugWgVJpCUwTOMIJdh8TBNFHG509qjCe9hfFcxyzt6WycryPtglBr18NnIrEtaBGUze4Uf8jdoIp2W4SJTFxIlSObF5L/KGuB/6hDDykmRxIPA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780943483; c=relaxed/simple; bh=8IRyv7LCSRO8a+LMUWXdARmSzx637HJkV6N13gEEkU0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=gQKPR4MmB0j+W/4MmkqRUVgpI3/7KU6bcK83b757LZDpkTHETPV4ivOBRbuiuAVW1hBOVWGzFi8vinN53zfZDoNoENexBcmDLAwfd7mpdEw22fpYjzwyeww1TEY+0C/xtuPZKVJvU3OxWjg7UhDwxl+hYgF0IHOMK4NhELoUYAM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=Fc5qMuNW; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="Fc5qMuNW" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780943480; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=VvkqqMaR/p4iNrcFd0EO2fuLaTlmARCcel/DKCsLETI=; b=Fc5qMuNW23kR2cya6nI8UHi+bKjYJuy6AWf2eyw6zEJUzLdsr1oEkwfIaJBpgTHX1L/l+r 4/vTsg2RDtUvXWHaZr8vlpqkg1mWgwCxPhAI6A08aYR5xlN+lXrSB+xllOImJ6UqFBN5iu 7vWkKJvJ+j20xyNZ4wqSzc/SGq0aILw= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-398-btYvUMdhNxmvG4gFfxuJCg-1; Mon, 08 Jun 2026 14:31:12 -0400 X-MC-Unique: btYvUMdhNxmvG4gFfxuJCg-1 X-Mimecast-MFC-AGG-ID: btYvUMdhNxmvG4gFfxuJCg_1780943469 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id A77BD18307E5; Mon, 8 Jun 2026 18:31:08 +0000 (UTC) Received: from GoldenWind.redhat.com (unknown [10.22.89.147]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 23F951800594; Mon, 8 Jun 2026 18:31:04 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org, nouveau@lists.freedesktop.org Cc: Alexandre Courbot , Gary Guo , =?UTF-8?q?Christian=20K=C3=B6nig?= , driver-core@lists.linux.dev, Miguel Ojeda , Maarten Lankhorst , Alice Ryhl , Simona Vetter , linux-kernel@vger.kernel.org, Sumit Semwal , linux-media@vger.kernel.org, "Rafael J . Wysocki" , Thomas Zimmermann , Maxime Ripard , David Airlie , Benno Lossin , linaro-mm-sig@lists.linaro.org, Danilo Krummrich , Mukesh Kumar Chaurasiya , Asahi Lina , Daniel Almeida , Lyude Paul , Greg Kroah-Hartman Subject: [PATCH v19 1/4] rust: drm: gem: shmem: Add DmaResvGuard helper Date: Mon, 8 Jun 2026 14:29:01 -0400 Message-ID: <20260608183057.2001376-2-lyude@redhat.com> In-Reply-To: <20260608183057.2001376-1-lyude@redhat.com> References: <20260608183057.2001376-1-lyude@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 Content-Type: text/plain; charset="utf-8" Just a temporary holdover to make locking/unlocking the dma_resv lock much easier. Signed-off-by: Lyude Paul Co-authored-by: Alexandre Courbot Signed-off-by: Alexandre Courbot Acked-by: Danilo Krummrich Tested-by: Deborah Brouwer --- V17: * Fix format of commit message title V19: * Add NotThreadSafe to DmaResvGuard rust/kernel/drm/gem/shmem.rs | 39 ++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index 084b798ce795b..da1acc223bad4 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -22,7 +22,10 @@ error::to_result, prelude::*, sync::aref::ARef, - types::Opaque, // + types::{ + NotThreadSafe, + Opaque, // + }, }; use core::{ marker::PhantomData, @@ -30,7 +33,10 @@ Deref, DerefMut, // }, - ptr::NonNull, // + ptr::{ + self, + NonNull, // + }, }; use gem::{ BaseObjectPrivate, @@ -244,3 +250,32 @@ impl driver::AllocI= mpl for Object { 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 suc= h bindings in the kernel. +struct DmaResvGuard<'a, T: DriverObject, C: DeviceContext =3D Registered>( + &'a Object, + NotThreadSafe, +); + +impl<'a, T: DriverObject, C: DeviceContext> DmaResvGuard<'a, T, C> { + #[inline(always)] + #[expect(unused)] + fn new(obj: &'a Object) -> Self { + // SAFETY: This lock is initialized throughout the lifetime of `ob= ject`. + unsafe { bindings::dma_resv_lock(obj.raw_dma_resv(), ptr::null_mut= ()) }; + + Self(obj, NotThreadSafe) + } +} + +impl<'a, T: DriverObject, C: DeviceContext> Drop for DmaResvGuard<'a, T, C= > { + #[inline(always)] + fn drop(&mut self) { + // SAFETY: We are releasing the lock grabbed during the creation o= f this object. + unsafe { bindings::dma_resv_unlock(self.0.raw_dma_resv()) }; + } +} --=20 2.54.0 From nobody Wed Jun 10 16:01:34 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EB9623E024E for ; Mon, 8 Jun 2026 18:31:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780943484; cv=none; b=IA3gb4Im2TQlV9d/MYJXZIq0mGUzQkwsUvBD3GE6lHYqLipjq043aBZgyJG/brh3nNS85pzhcpUwYzpH9WRAIhtYBUmoyb9ErvnHKwDDs/7JHLj95bZjIt3uat3NZ6+hiFE4ygYNw4jkOBh9NW3o1QeCvh80T4qzk8hWxQl0ueU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780943484; c=relaxed/simple; bh=pSTeCYNc+WSn6+SyLmx3TSK5gR+UqoxNMdPUaaqOYmI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=QahxV+kXsKkXTq1qakNHfyJjzxmy4zVOPkKN7l6P6Akzt+2ZxzTUlfsXqrMs4HrqQM0lDCTTaR7dM4U3Zf4vyRW/KyAfZdaMrVHXQ1XLY0I5rVlE6dl2TQbqML3rpmurd3L1N153JXjt/9r4GKx5TbMRLwQjhUVjczZ2n6BwiI0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=Sc3hej5v; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="Sc3hej5v" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780943481; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=OUXx80vtqyGLmK+0aL7i1YcASnErU3wPjg4X+wegQ7c=; b=Sc3hej5vmQFIvYnxTtvizuk3idF61gj3cMTpaCs/D05xg8AV3QuKxrsjed4zzgXmdQHugm hloxnIjAg2Fzjordp4CX8SnZ5m4DTqKvQiYpRekYgERBATx7LhbMOfDvTjNyahX0rtmwdC 9ogXfsaBc88BDCyiwO4lHFK/L7bMmXY= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-246-YCZvD1HnOEm9vJGGCRD7cg-1; Mon, 08 Jun 2026 14:31:16 -0400 X-MC-Unique: YCZvD1HnOEm9vJGGCRD7cg-1 X-Mimecast-MFC-AGG-ID: YCZvD1HnOEm9vJGGCRD7cg_1780943473 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 00303195DE3D; Mon, 8 Jun 2026 18:31:13 +0000 (UTC) Received: from GoldenWind.redhat.com (unknown [10.22.89.147]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 0AA41180058F; Mon, 8 Jun 2026 18:31:08 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org, nouveau@lists.freedesktop.org Cc: Alexandre Courbot , Gary Guo , =?UTF-8?q?Christian=20K=C3=B6nig?= , driver-core@lists.linux.dev, Miguel Ojeda , Maarten Lankhorst , Alice Ryhl , Simona Vetter , linux-kernel@vger.kernel.org, Sumit Semwal , linux-media@vger.kernel.org, "Rafael J . Wysocki" , Thomas Zimmermann , Maxime Ripard , David Airlie , Benno Lossin , linaro-mm-sig@lists.linaro.org, Danilo Krummrich , Mukesh Kumar Chaurasiya , Asahi Lina , Daniel Almeida , Lyude Paul , Greg Kroah-Hartman Subject: [PATCH v19 2/4] rust: drm: gem: shmem: Add vmap functions Date: Mon, 8 Jun 2026 14:29:02 -0400 Message-ID: <20260608183057.2001376-3-lyude@redhat.com> In-Reply-To: <20260608183057.2001376-1-lyude@redhat.com> References: <20260608183057.2001376-1-lyude@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 Content-Type: text/plain; charset="utf-8" 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 Reviewed-by: Alexandre Courbot Acked-by: Danilo Krummrich Tested-by: Deborah Brouwer --- 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 to ensure that sgt_res is always dropped before obj. * Fix typo in Object.raw_vmap() * s/raw_vmap()/make_vmap()/ Deduplicate code a bit more as well by using more generics here V15: * Add these patches back * We only have one VMap type now! * Use ObjectConfig::default() in unit tests since we unbroke it. V16: * Fix huge rebase error I made and did not notice that squashed 1.5 patches together that were definitely not supposed to be squashed * Update old commit message V17: * Rebase * Fix format of commit message title V20: * Drop outdated safety comment * Move impl_vmap_io_capable! definition to right before it gets used * Add missing `` in rustdoc for VMap type * Add a bunch of missing `` in make_vmap() * Remove one outdated safety comment about reading vaddr_iomem * Add some missing periods in safety comments in make_vmap(). * Use read_volatile/write_volatile() instead of read()/write() to prevent compiler reordering. * Remove impl argument from impl_vmap_io_capable!() * Check .owner() and .maxsize() in compile_time_vmap_sizes() * Use more varied pattern in vmap_io() rust/kernel/drm/gem/shmem.rs | 315 ++++++++++++++++++++++++++++++++++- 1 file changed, 314 insertions(+), 1 deletion(-) diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index da1acc223bad4..ffaa00a1af109 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -20,6 +20,11 @@ Registered, // }, error::to_result, + io::{ + Io, + IoCapable, + IoKnownSize, // + }, prelude::*, sync::aref::ARef, types::{ @@ -28,7 +33,9 @@ }, }; use core::{ + ffi::c_void, marker::PhantomData, + mem::MaybeUninit, // ops::{ Deref, DerefMut, // @@ -39,6 +46,7 @@ }, }; use gem::{ + BaseObject, BaseObjectPrivate, DriverObject, IntoGEMObject, // @@ -200,6 +208,77 @@ extern "C" fn free_callback(obj: *mut bindings::drm_ge= m_object) { // SAFETY: We're recovering the Kbox<> we created in gem_create_ob= ject() let _ =3D 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> + where + R: Deref + From<&'a Self>, + { + // INVARIANT: We check here that the gem object is at least as lar= ge as `SIZE`. + if self.size() < SIZE { + return Err(ENOSPC); + } + + let mut map: MaybeUninit =3D MaybeUninit::uni= nit(); + let guard =3D DmaResvGuard::new(self); + + // SAFETY: `drm_gem_shmem_vmap()` can be called with the DMA reser= vation lock held. + to_result(unsafe { + bindings::drm_gem_shmem_vmap_locked(self.as_raw_shmem(), map.a= s_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 abo= ve, so we are guaranteed + // that map is properly initialized. + let map =3D unsafe { map.assume_init() }; + + // XXX: We don't currently support iomem allocations + if map.is_iomem { + // SAFETY: The vmap operation above succeeded, guaranteeing th= at `map` points to a valid + // memory mapping. + 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 objec= t. + /// - The caller promises that the memory pointed to by map will no lo= nger be accesed through + /// this instance. + unsafe fn raw_vunmap(&self, mut map: bindings::iosys_map) { + let _guard =3D DmaResvGuard::new(self); + + // SAFETY: + // - This function is safe to call with the DMA reservation lock h= eld. + // - The caller promises that `map` is a valid vmap on this gem ob= ject. + unsafe { bindings::drm_gem_shmem_vunmap_locked(self.as_raw_shmem()= , &mut map) }; + } + + /// Creates and returns a virtual kernel memory mapping for this objec= t. + #[inline] + pub fn vmap(&self) -> Result> { + self.make_vmap() + } + + /// Creates and returns an owned reference to a virtual kernel memory = mapping for this object. + #[inline] + pub fn owned_vmap(&self) -> Result> { + self.make_vmap() + } } =20 impl Deref for Object { @@ -263,7 +342,6 @@ struct DmaResvGuard<'a, T: DriverObject, C: DeviceConte= xt =3D Registered>( =20 impl<'a, T: DriverObject, C: DeviceContext> DmaResvGuard<'a, T, C> { #[inline(always)] - #[expect(unused)] fn new(obj: &'a Object) -> Self { // SAFETY: This lock is initialized throughout the lifetime of `ob= ject`. unsafe { bindings::dma_resv_lock(obj.raw_dma_resv(), ptr::null_mut= ()) }; @@ -279,3 +357,238 @@ fn drop(&mut self) { unsafe { bindings::dma_resv_unlock(self.0.raw_dma_resv()) }; } } + +/// A reference to a virtual mapping for an shmem-based GEM object in kern= el address space. +/// +/// # Invariants +/// +/// - The size of `owner` is >=3D SIZE. +/// - The memory pointed to by `addr` remains valid at least until this ob= ject is dropped. +pub struct VMap +where + D: DriverObject, + C: DeviceContext, + R: Deref>, +{ + addr: *mut c_void, + owner: R, +} + +/// An alias type for a reference to a shmem-based GEM object's VMap. +pub type VMapRef<'a, D, C, const SIZE: usize =3D 0> =3D VMap, C, SIZE>; + +/// An alias type for an owned reference to a shmem-based GEM object's VMa= p. +pub type VMapOwned =3D VMap>, C, SIZE>; + +impl VMap +where + D: DriverObject, + C: DeviceContext, + R: Deref>, +{ + /// Borrows a reference to the object that owns this virtual mapping. + #[inline(always)] + pub fn owner(&self) -> &Object { + &self.owner + } +} + +impl Drop for VMap +where + D: DriverObject, + C: DeviceContext, + R: Deref>, +{ + #[inline(always)] + fn drop(&mut self) { + // SAFETY: + // - Our existence is proof that this map was previously created u= sing self.owner. + // - Since we are in Drop, we are guaranteed that no one will acce= ss 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 { vadd= r: self.addr }, + }) + }; + } +} + +impl Io for VMap +where + D: DriverObject, + C: DeviceContext, + R: Deref>, +{ + #[inline(always)] + fn addr(&self) -> usize { + self.addr as usize + } + + #[inline(always)] + fn maxsize(&self) -> usize { + self.owner.size() + } +} + +impl IoKnownSize for VMap +where + D: DriverObject, + C: DeviceContext, + R: Deref>, +{ + const MIN_SIZE: usize =3D SIZE; +} + +macro_rules! impl_vmap_io_capable { + ($ty:ty) =3D> { + impl IoCapable<$ty> for VMap + where + D: DriverObject, + C: DeviceContext, + R: Deref>, + { + #[inline(always)] + unsafe fn io_read(&self, address: usize) -> $ty { + let ptr =3D address as *mut $ty; + + // SAFETY: The safety contract of `io_read` guarantees tha= t address is a valid + // address within the bounds of `Self` of at least the siz= e of $ty, and is properly + // aligned. + unsafe { ptr::read_volatile(ptr) } + } + + #[inline(always)] + unsafe fn io_write(&self, value: $ty, address: usize) { + let ptr =3D address as *mut $ty; + + // SAFETY: The safety contract of `io_write` guarantees th= at address is a valid + // address within the bounds of `Self` of at least the siz= e of $ty, and is properly + // aligned. + unsafe { ptr::write_volatile(ptr, value) } + } + } + }; +} + +impl_vmap_io_capable!(u8); +impl_vmap_io_capable!(u16); +impl_vmap_io_capable!(u32); +#[cfg(CONFIG_64BIT)] +impl_vmap_io_capable!(u64); + +#[kunit_tests(rust_drm_gem_shmem)] +mod tests { + use super::*; + use crate::{ + drm::{ + self, + UnregisteredDevice, // + }, + faux, + page::PAGE_SIZE, // + }; + + // The bare minimum needed to create a fake drm driver for kunit + + #[pin_data] + struct KunitData {} + struct KunitDriver; + struct KunitFile; + #[pin_data] + struct KunitObject {} + + const INFO: drm::DriverInfo =3D drm::DriverInfo { + major: 0, + minor: 0, + patchlevel: 0, + name: c"kunit", + desc: c"Kunit", + }; + + impl drm::file::DriverFile for KunitFile { + type Driver =3D KunitDriver; + + fn open(_dev: &drm::Device) -> Result>= > { + Ok(KBox::new(Self, GFP_KERNEL)?.into()) + } + } + + impl gem::DriverObject for KunitObject { + type Driver =3D KunitDriver; + type Args =3D (); + + fn new( + _dev: &drm::Device, + _size: usize, + _args: Self::Args, + ) -> impl PinInit { + try_pin_init!(KunitObject {}) + } + } + + #[vtable] + impl drm::Driver for KunitDriver { + type Data =3D KunitData; + type File =3D KunitFile; + type Object =3D Object; + + const INFO: drm::DriverInfo =3D INFO; + const IOCTLS: &'static [drm::ioctl::DrmIoctlDescriptor] =3D &[]; + } + + fn create_drm_dev() -> Result<(faux::Registration, UnregisteredDevice<= KunitDriver>)> { + // Create a faux DRM device so we can test gem object creation. + let data =3D try_pin_init!(KunitData {}); + let dev =3D faux::Registration::new(c"Kunit", None)?; + let drm =3D UnregisteredDevice::new(dev.as_ref(), data)?; + + Ok((dev, drm)) + } + + #[test] + fn compile_time_vmap_sizes() -> Result { + let (_dev, drm) =3D create_drm_dev()?; + + let obj =3D Object::::new(&drm, PAGE_SIZE, ObjectC= onfig::default(), ())?; + + // Try creating a normal vmap + obj.vmap::()?; + + // Try creating a vmap that's smaller then the size we specified + let vmap =3D obj.vmap::<{ PAGE_SIZE - 100 }>()?; + + // Verify the owner matches + assert!(ptr::eq(vmap.owner(), obj.deref())); + + // Verify the max size matches the actual object size + assert_eq!(vmap.maxsize(), PAGE_SIZE); + + // 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) =3D create_drm_dev()?; + + let obj =3D Object::::new(&drm, PAGE_SIZE, ObjectC= onfig::default(), ())?; + + let vmap =3D obj.vmap::()?; + + vmap.write8(0xDE, 0x0); + assert_eq!(vmap.read8(0x0), 0xDE); + vmap.write32(0xFEDCBA98, 0x20); + + assert_eq!(vmap.read32(0x20), 0xFEDCBA98); + + assert_eq!(vmap.read8(0x20), 0x98); + assert_eq!(vmap.read8(0x21), 0xBA); + assert_eq!(vmap.read8(0x22), 0xDC); + assert_eq!(vmap.read8(0x23), 0xFE); + + Ok(()) + } +} --=20 2.54.0 From nobody Wed Jun 10 16:01:34 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A70FF3E1690 for ; Mon, 8 Jun 2026 18:31:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780943486; cv=none; b=ZM1LG+6fXnZ8mV/zFnGHzAD4ni7F/D66CPGdvNmFxdDngXdkw3vXu0yEQ1e0gsdu026RreXS7yH1ettMAbd7Gk4i6qYF0gtuyPpmSnujdVw4vgsInkeOpNE6WSshSD8O/+ESlLt41vV63xsG7glSWYFpnu3ozJeIDRgcEsnojqQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780943486; c=relaxed/simple; bh=Aj4PBe2fkWjoL26YUEqoqN9BiMSi2LM2cuwB8IxJ5x8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JX9TO4M618z79oe/kmpqSY9PlbZYTOhmeyimF4jr8/JwTGG/EzL9IqCEjXB7YSTAF0C0S1Phwmc+ym692t8H6ZZJG/QUdX8s7DGV2MosoyuCkbd8M6/zMojhvikwjA+F6Qbwl7X9tDFn4XMY/dPBxY3L5fwKBKer4PcGA7QAnWE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=WfdnSvBh; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="WfdnSvBh" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780943483; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=l0Q78Z8ifFWrqkScVlzVzhmSJQFRs/JAyzSzJkKaHgI=; b=WfdnSvBhBUJlkCQB2Ut2QuAJD9U29e6Ag4YF/ELOuBgMWoatywpL95A7ni+PHlohJCEl3d MHUH3bIzjzWI9pM8Jaht0Yce7+0ikPvcolmGOiFrkR+MtwWLsVIo2TRhJEnwKJ0r0DxQlF z7YqUZ9Zl/ytSmXmkYZt/5MlnsajL44= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-629-GkctutQXPFOOEZJN1ozAgQ-1; Mon, 08 Jun 2026 14:31:20 -0400 X-MC-Unique: GkctutQXPFOOEZJN1ozAgQ-1 X-Mimecast-MFC-AGG-ID: GkctutQXPFOOEZJN1ozAgQ_1780943477 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id C110F197702D; Mon, 8 Jun 2026 18:31:16 +0000 (UTC) Received: from GoldenWind.redhat.com (unknown [10.22.89.147]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 30682180059F; Mon, 8 Jun 2026 18:31:13 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org, nouveau@lists.freedesktop.org Cc: Alexandre Courbot , Gary Guo , =?UTF-8?q?Christian=20K=C3=B6nig?= , driver-core@lists.linux.dev, Miguel Ojeda , Maarten Lankhorst , Alice Ryhl , Simona Vetter , linux-kernel@vger.kernel.org, Sumit Semwal , linux-media@vger.kernel.org, "Rafael J . Wysocki" , Thomas Zimmermann , Maxime Ripard , David Airlie , Benno Lossin , linaro-mm-sig@lists.linaro.org, Danilo Krummrich , Mukesh Kumar Chaurasiya , Asahi Lina , Daniel Almeida , Lyude Paul , Greg Kroah-Hartman Subject: [PATCH v19 3/4] rust: faux: Allow retrieving a bound Device Date: Mon, 8 Jun 2026 14:29:03 -0400 Message-ID: <20260608183057.2001376-4-lyude@redhat.com> In-Reply-To: <20260608183057.2001376-1-lyude@redhat.com> References: <20260608183057.2001376-1-lyude@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 Content-Type: text/plain; charset="utf-8" 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 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 Reviewed-by: Alexandre Courbot Acked-by: Danilo Krummrich Tested-by: Deborah Brouwer --- V18: - Add notes from Danilo to safety comment. rust/kernel/faux.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/rust/kernel/faux.rs b/rust/kernel/faux.rs index 43b4974f48cd2..20ab638885354 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 register= ed [`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); @@ -59,10 +60,15 @@ fn as_raw(&self) -> *mut bindings::faux_device { } } =20 -impl AsRef for Registration { - fn as_ref(&self) -> &device::Device { - // SAFETY: The underlying `device` in `faux_device` is guaranteed = by the C API to be - // a valid initialized `device`. +impl AsRef> for Registration { + fn as_ref(&self) -> &device::Device { + // SAFETY: + // - The underlying `device` in `faux_device` is guaranteed by the= C API to be a valid + // initialized `device`. + // - faux_match() always returns 1, and probe runs synchronously (= PROBE_FORCE_SYNCHRONOUS). + // - suppress_bind_attrs =3D true on faux_driver prevents userspac= e-triggered unbind via sysfs + // - mem::forget(Registration) is not a problem; if the Registrati= on is leaked, the faux + // device stays bound forever. unsafe { device::Device::from_raw(addr_of_mut!((*self.as_raw()).de= v)) } } } --=20 2.54.0 From nobody Wed Jun 10 16:01:34 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 647273E3C73 for ; Mon, 8 Jun 2026 18:31:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780943490; cv=none; b=mno7FOz5EXiha3JrIVQN4PaQAsoXMQ6K5ebCkCyfzZi84V48zT7UhLKW7qwmn3+YTBoF0usJax8kizYziySG688p9ew4+1EODWMPlKL6zm42fdYFZ7Y3y6GF4MBCPiJqPwyEh2YAjZ8M5py/nWPrPOepkznlfh2oEGHad8FjvO0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780943490; c=relaxed/simple; bh=cvyoqj/Bb539PkmmKCc7MdUeVKTIB3KFkC0mL+VNqUo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=CcpLn5n8qAPhMCDRrt6DtgJ2M29fQYqS3VfCAMDJgiZidufwd5fTz+r0UtgRfZ57bsu7tBbyBASZhGQYdmoVQ+5zTZNT8tSMOt2jsT8qTKa1hLoMKvYsXNerQsHiPwi6jyhLQ3eII1452J+HOknmlftL5rcSSke6L+lFdgyQp/k= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=Gv0c5X7g; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="Gv0c5X7g" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780943487; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=xNXtOay3gnq7R0jyd1tgp6XVTjzltDEVLqTF84fZSVA=; b=Gv0c5X7g1Aj4ANYRDMhXN4eiZ5JPsSwAdsbdDsawJMJnLQ6kjjBtH9HDUi2ykK9wXTmk1u RVBuiLwXECtAp2UkK5XzAXLAaMfIoAR90+Qk/RmI6GvnB6HlhWAWeh7FP0dI1wXCu1re9i SQbRHT14VJUn80lJEZRVHf7UptDnpSA= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-656-S61WMudCM16dCpKliXhNvQ-1; Mon, 08 Jun 2026 14:31:24 -0400 X-MC-Unique: S61WMudCM16dCpKliXhNvQ-1 X-Mimecast-MFC-AGG-ID: S61WMudCM16dCpKliXhNvQ_1780943481 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 9A6391830473; Mon, 8 Jun 2026 18:31:20 +0000 (UTC) Received: from GoldenWind.redhat.com (unknown [10.22.89.147]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id F1A9F180058F; Mon, 8 Jun 2026 18:31:16 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org, nouveau@lists.freedesktop.org Cc: Alexandre Courbot , Gary Guo , =?UTF-8?q?Christian=20K=C3=B6nig?= , driver-core@lists.linux.dev, Miguel Ojeda , Maarten Lankhorst , Alice Ryhl , Simona Vetter , linux-kernel@vger.kernel.org, Sumit Semwal , linux-media@vger.kernel.org, "Rafael J . Wysocki" , Thomas Zimmermann , Maxime Ripard , David Airlie , Benno Lossin , linaro-mm-sig@lists.linaro.org, Danilo Krummrich , Mukesh Kumar Chaurasiya , Asahi Lina , Daniel Almeida , Lyude Paul , Greg Kroah-Hartman Subject: [PATCH v19 4/4] rust: drm: gem: Introduce shmem::Object::sg_table() Date: Mon, 8 Jun 2026 14:29:04 -0400 Message-ID: <20260608183057.2001376-5-lyude@redhat.com> In-Reply-To: <20260608183057.2001376-1-lyude@redhat.com> References: <20260608183057.2001376-1-lyude@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 Content-Type: text/plain; charset="utf-8" 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 Reviewed-by: Alexandre Courbot Acked-by: Danilo Krummrich Tested-by: Deborah Brouwer --- 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 =3D ` 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 * 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> mess. * Add the best we can do for unit tests w/r/t SGTable at the moment V16: * Get rid of LazyInit, go back to SetOnce, use trick that Alice recommended that is a lot cleaner. * Fix horrid rebasing mistake V17: * Rebase * Fix missing safety comment in free_callback() (we forgot to justify why &mut is safe in `unsafe { &mut (*this).sgt_res }.reset()`) V18: * Use ManuallyDrop instead of SetOnce::reset() V19: * Explain that populate() will always return true in sg_table() * Fix outdated comment in SGTableMap * Fix invariant comment in SGTableMap rust/kernel/drm/gem/shmem.rs | 174 +++++++++++++++++++++++++++++++++-- 1 file changed, 164 insertions(+), 10 deletions(-) diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index ffaa00a1af109..91441252482da 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -11,6 +11,11 @@ =20 use crate::{ container_of, + device::{ + self, + Bound, // + }, + devres::*, drm::{ driver, gem, @@ -19,14 +24,23 @@ DeviceContext, Registered, // }, - error::to_result, + error::{ + from_err_ptr, + to_result, // + }, io::{ Io, IoCapable, IoKnownSize, // }, prelude::*, - sync::aref::ARef, + scatterlist, + sync::{ + aref::ARef, + new_mutex, + Mutex, + SetOnce, // + }, types::{ NotThreadSafe, Opaque, // @@ -35,7 +49,10 @@ use core::{ ffi::c_void, marker::PhantomData, - mem::MaybeUninit, // + mem::{ + ManuallyDrop, + MaybeUninit, // + }, ops::{ Deref, DerefMut, // @@ -90,6 +107,11 @@ pub struct Object { obj: Opaque, /// Parent object that owns this object's DMA reservation object. parent_resv_obj: Option>>, + /// Devres object for unmapping any SGTable on driver-unbind. + sgt_res: ManuallyDrop>>>, + #[pin] + /// Lock for protecting initialization of `sgt_res`. + sgt_lock: Mutex<()>, #[pin] inner: T, _ctx: PhantomData, @@ -148,6 +170,8 @@ pub fn new( try_pin_init!(Self { obj <- Opaque::init_zeroed(), parent_resv_obj: config.parent_resv_obj.map(|p| p.into()), + sgt_res: ManuallyDrop::new(SetOnce::new()), + sgt_lock <- new_mutex!(()), inner <- T::new(dev, size, args), _ctx: PhantomData::, }), @@ -192,18 +216,26 @@ extern "C" fn free_callback(obj: *mut bindings::drm_g= em_object) { // - DRM always passes a valid gem object here // - We used drm_gem_shmem_create() in our create_gem_object callb= ack, so we know that // `obj` is contained within a drm_gem_shmem_object - let this =3D unsafe { container_of!(obj, bindings::drm_gem_shmem_o= bject, 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 =3D unsafe { container_of!(obj, bindings::drm_gem_shmem_o= bject, base) }; =20 // SAFETY: // - We verified above that `obj` is valid, which makes `this` val= id // - This function is set in AllocOps, so we know that `this` is c= ontained within a // `Object` - let this =3D unsafe { container_of!(Opaque::cast_from(this), Self,= obj) }.cast_mut(); + let this =3D unsafe { container_of!(Opaque::cast_from(base), Self,= obj) }.cast_mut(); + + // We need to drop `sgt_res` first, since doing so requires that t= he GEM object is still + // alive. + // SAFETY: + // - We verified above that `this` is valid. + // - We are in free_callback, guaranteeing we have exclusive acces= s to `this` and that + // `sgt_res` will not be used after dropping it here. + unsafe { ManuallyDrop::drop(&mut (*this).sgt_res) }; + + // SAFETY: + // - We're in free_callback - so this function is safe to call. + // - We won't be using the gem resources on `this` after this call. + unsafe { bindings::drm_gem_shmem_release(base) }; =20 // SAFETY: We're recovering the Kbox<> we created in gem_create_ob= ject() let _ =3D unsafe { KBox::from_raw(this) }; @@ -279,6 +311,46 @@ pub fn vmap(&self) -> Result> { pub fn owned_vmap(&self) -> Result> { self.make_vmap() } + + /// Creates (if necessary) and returns an immutable reference to a sca= tter-gather table of DMA + /// pages for this object. + /// + /// This will pin the object in memory. It is expected that `dev` shou= ld be a pointer to the + /// same [`device::Device`] which `self` belongs to, otherwise this fu= nction will return + /// `Err(EINVAL)`. + pub fn sg_table<'a>( + &'a self, + dev: &'a device::Device, + ) -> Result<&'a scatterlist::SGTable> { + if dev.as_raw() !=3D self.dev().as_ref().as_raw() { + return Err(EINVAL); + } + + let sgt_res =3D 'out: { + // Fast path: sgt_res is already initialized + if let Some(sgt_res) =3D self.sgt_res.as_ref() { + break 'out sgt_res; + } + + // Slow path: Grab the lock and see if we need to initialize s= gt_res. + let _guard =3D self.sgt_lock.lock(); + + // If someone initialized it while we were waiting, we can exi= t early. + if let Some(sgt_res) =3D self.sgt_res.as_ref() { + break 'out sgt_res; + } + + // If not, finish initializing and return. `populate()` cannot= return false, as + // `sgt_res` must be unpopulated, and we must hold `sgt_lock` = to reach this point. + 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)?) + } } =20 impl Deref for Object { @@ -477,6 +549,64 @@ unsafe fn io_write(&self, value: $ty, address: usize) { #[cfg(CONFIG_64BIT)] impl_vmap_io_capable!(u64); =20 +/// 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 t= hat 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_res` has an initialize= d and valid pointer to an +/// [`SGTable`]. +/// +/// [`SGTable`]: scatterlist::SGTable +pub struct SGTableMap { + obj: NonNull>, +} + +impl Deref for SGTableMap { + type Target =3D 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 o= ur type invariants. + unsafe { scatterlist::SGTable::from_raw((*self.obj.as_ref().as_raw= _shmem()).sgt) } + } +} + +impl Drop for SGTableMap { + fn drop(&mut self) { + // SAFETY: `obj` is always valid via our type invariants + let obj =3D unsafe { self.obj.as_ref() }; + let _lock =3D DmaResvGuard::new(obj); + + // SAFETY: We acquired the lock needed for calling this function a= bove + unsafe { bindings::__drm_gem_shmem_free_sgt_locked(obj.as_raw_shme= m()) }; + } +} + +impl SGTableMap { + fn new(obj: &Object) -> impl Init { + // INVARIANT: + // - We call drm_gem_shmem_get_pages_sgt below and check whether o= r 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 invar= iants, and the GEM object +// it points to is guaranteed to be thread-safe. +unsafe impl Send for SGTableMap {} +// SAFETY: The NonNull in SGTableMap is guaranteed valid by our type invar= iants, and the GEM object +// it points to is guaranteed to be thread-safe. +unsafe impl Sync for SGTableMap {} + #[kunit_tests(rust_drm_gem_shmem)] mod tests { use super::*; @@ -591,4 +721,28 @@ fn vmap_io() -> Result { =20 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 exp= ect, and also ensure it + // skips initializing `sgt_res` since we could otherwise create `sgt_r= es` with the wrong device + // bound to it. + #[test] + fn fail_sg_table_on_wrong_dev() -> Result { + let (_dev, drm) =3D create_drm_dev()?; + let wrong_dev =3D faux::Registration::new(c"EvilKunit", None)?; + + let obj =3D Object::::new(&drm, PAGE_SIZE, ObjectC= onfig::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 st= ill succeeds. + + Ok(()) + } } --=20 2.54.0