From: Danilo Krummrich <dakr@kernel.org>
To: dakr@kernel.org, acourbot@nvidia.com, aliceryhl@google.com,
abdiel.janulgue@gmail.com, daniel.almeida@collabora.com,
robin.murphy@arm.com, a.hindborg@kernel.org, ojeda@kernel.org,
boqun@kernel.org, gary@garyguo.net, bjorn3_gh@protonmail.com,
lossin@kernel.org, tmgross@umich.edu, tamird@kernel.org,
work@onurozkan.dev
Cc: driver-core@lists.linux.dev, nova-gpu@lists.linux.dev,
dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org,
rust-for-linux@vger.kernel.org
Subject: [PATCH 1/2] rust: scatterlist: tie owned tables to the device's bound lifetime
Date: Sun, 15 Nov 2026 00:42:53 +0100 [thread overview]
Message-ID: <20261114234324.82387-1-dakr@kernel.org> (raw)
Add a lifetime parameter to Owned that ties owned scatter-gather tables
to the device's bound scope, ensuring their DMA mappings are released
before the device is unbound.
The DMA mapping currently holds a pointer into Owned, requiring the
table to stay at a fixed address and its cleanup to be ordered around
the device-managed mapping.
Let DmaMappedSgt own RawSGTable and borrow Device<Bound>. Its destructor
unmaps the entries before RawSGTable frees them, and Owned releases the
backing pages last. This removes the internal pointer and makes the
mapping constructor safe without a self-reference.
Carry the device lifetime through Owned and its users. Remove pin
initialization from Owned and SGTable, return tables by value, and update
the callers accordingly.
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
---
drivers/gpu/nova-core/firmware/radix3.rs | 29 +++--
rust/kernel/scatterlist.rs | 141 ++++++++---------------
samples/rust/rust_dma.rs | 11 +-
3 files changed, 62 insertions(+), 119 deletions(-)
diff --git a/drivers/gpu/nova-core/firmware/radix3.rs b/drivers/gpu/nova-core/firmware/radix3.rs
index 94235bd1209d..a3e43fc8ec86 100644
--- a/drivers/gpu/nova-core/firmware/radix3.rs
+++ b/drivers/gpu/nova-core/firmware/radix3.rs
@@ -35,14 +35,11 @@
#[pin_data]
pub(crate) struct Radix3<'a> {
/// The mapped data.
- #[pin]
- data: SGTable<Owned<VVec<u8>>>,
+ data: SGTable<Owned<'a, VVec<u8>>>,
/// Level 2: one entry per page of `data`.
- #[pin]
- level2: SGTable<Owned<VVec<u8>>>,
+ level2: SGTable<Owned<'a, VVec<u8>>>,
/// Level 1: one entry per page of `level2`.
- #[pin]
- level1: SGTable<Owned<VVec<u8>>>,
+ level1: SGTable<Owned<'a, VVec<u8>>>,
/// Level 0: one page, whose single entry is the DMA address of the first `level1` page.
level0: Coherent<'a, [u64]>,
/// Length of `data`, in bytes.
@@ -59,14 +56,14 @@ pub(crate) fn new(
pin_init::pin_init_scope(move || {
Ok(try_pin_init!(Self {
- data <- SGTable::new(dev, data, DataDirection::ToDevice, GFP_KERNEL),
- level2 <- {
- build_lvl(&data)
- .map(|l2| SGTable::new(dev, l2, DataDirection::ToDevice, GFP_KERNEL))?
+ data: SGTable::new(dev, data, DataDirection::ToDevice, GFP_KERNEL)?,
+ level2: {
+ let l2 = build_lvl(data)?;
+ SGTable::new(dev, l2, DataDirection::ToDevice, GFP_KERNEL)?
},
- level1 <- {
- build_lvl(&level2)
- .map(|l1| SGTable::new(dev, l1, DataDirection::ToDevice, GFP_KERNEL))?
+ level1: {
+ let l1 = build_lvl(level2)?;
+ SGTable::new(dev, l1, DataDirection::ToDevice, GFP_KERNEL)?
},
level0: {
// Allocate the level 0 page table as a device-visible DMA object, and map the
@@ -81,7 +78,7 @@ pub(crate) fn new(
let mut level0 = CoherentBox::<'_, [u64]>::zeroed_slice(
dev,
GSP_PAGE_SIZE / size_of::<u64>(),
- GFP_KERNEL
+ GFP_KERNEL,
)?;
level0[0] = level1_entry_addr.to_le();
@@ -105,7 +102,7 @@ pub(crate) fn size(&self) -> usize {
/// Returns the size of the level that maps `sg_table`: one `u64` entry per [`GSP_PAGE_SIZE`]
/// page of each DMA-mapped region, rounded up to whole pages.
-fn lvl_size(sg_table: &SGTable<Owned<VVec<u8>>>) -> usize {
+fn lvl_size(sg_table: &SGTable<Owned<'_, VVec<u8>>>) -> usize {
let entries: usize = sg_table
.iter()
.map(|sg_entry| usize::from_safe_cast(sg_entry.dma_len()).div_ceil(GSP_PAGE_SIZE))
@@ -122,7 +119,7 @@ fn lvl_size(sg_table: &SGTable<Owned<VVec<u8>>>) -> usize {
///
/// - `ENOMEM` if the level cannot be allocated.
/// - `EINVAL` if `sg_table` yields more entries than [`lvl_size`] sized the level for.
-fn build_lvl(sg_table: &SGTable<Owned<VVec<u8>>>) -> Result<VVec<u8>> {
+fn build_lvl(sg_table: &SGTable<Owned<'_, VVec<u8>>>) -> Result<VVec<u8>> {
let mut dst = VVec::<u8>::zeroed(lvl_size(sg_table), GFP_KERNEL).map_err(|_| ENOMEM)?;
let mut entries = dst.chunks_exact_mut(size_of::<u64>());
diff --git a/rust/kernel/scatterlist.rs b/rust/kernel/scatterlist.rs
index 6379a8748117..93f431d7f548 100644
--- a/rust/kernel/scatterlist.rs
+++ b/rust/kernel/scatterlist.rs
@@ -19,7 +19,8 @@
//! The main entry point is the [`SGTable`] type, which represents a complete scatter-gather table.
//! It can be either:
//!
-//! - An owned table ([`SGTable<Owned<P>>`]), created from a Rust memory buffer (e.g., [`VVec`]).
+//! - An owned table ([`SGTable<Owned<'a, P>>`]), created from a Rust memory buffer (e.g.,
+//! [`VVec`]) and tied to the [`Device<Bound>`] lifetime.
//! This type manages the allocation of the `struct sg_table`, the DMA mapping of the buffer, and
//! the automatic cleanup of all resources.
//! - A borrowed reference (&[`SGTable`]), which provides safe, read-only access to a table that was
@@ -33,15 +34,13 @@
alloc::allocator::VmallocPageIter,
bindings,
device::{Bound, Device},
- devres::Devres,
dma,
error,
page,
prelude::*,
- sync::aref::ARef,
types::Opaque, //
};
-use core::{ops::Deref, ptr::NonNull};
+use core::ops::Deref;
/// A single entry in a scatter-gather list.
///
@@ -123,9 +122,7 @@ unsafe impl Sync for Borrowed {}
///
/// All [`SGTable`] variants can be iterated over the individual [`SGEntry`]s.
#[repr(transparent)]
-#[pin_data]
pub struct SGTable<T: private::Sealed = Borrowed> {
- #[pin]
inner: T,
}
@@ -177,63 +174,41 @@ pub fn iter(&self) -> SGTableIter<'_> {
///
/// # Invariants
///
-/// - `sgt` is a valid pointer to a `struct sg_table` for the entire lifetime of the
-/// [`DmaMappedSgt`].
/// - `sgt` is always DMA mapped.
-struct DmaMappedSgt {
- sgt: NonNull<bindings::sg_table>,
- dev: ARef<Device>,
+struct DmaMappedSgt<'a> {
+ dev: &'a Device<Bound>,
+ sgt: RawSGTable,
dir: dma::DataDirection,
}
-// SAFETY: `DmaMappedSgt` can be sent to any task.
-unsafe impl Send for DmaMappedSgt {}
-
-// SAFETY: `DmaMappedSgt` has no interior mutability and can be accessed concurrently.
-unsafe impl Sync for DmaMappedSgt {}
-
-impl DmaMappedSgt {
- /// # Safety
- ///
- /// - `sgt` must be a valid pointer to a `struct sg_table` for the entire lifetime of the
- /// returned [`DmaMappedSgt`].
- /// - The caller must guarantee that `sgt` remains DMA mapped for the entire lifetime of
- /// [`DmaMappedSgt`].
- unsafe fn new(
- sgt: NonNull<bindings::sg_table>,
- dev: &Device<Bound>,
- dir: dma::DataDirection,
- ) -> Result<Self> {
+impl<'a> DmaMappedSgt<'a> {
+ /// Takes ownership of `sgt` and maps it for DMA.
+ fn new(dev: &'a Device<Bound>, sgt: RawSGTable, dir: dma::DataDirection) -> Result<Self> {
// SAFETY:
// - `dev.as_raw()` is a valid pointer to a `struct device`, which is guaranteed to be
// bound to a driver for the duration of this call.
- // - `sgt` is a valid pointer to a `struct sg_table`.
+ // - `sgt.as_raw()` is a valid pointer to a `struct sg_table`.
error::to_result(unsafe {
- bindings::dma_map_sgtable(dev.as_raw(), sgt.as_ptr(), dir.into(), 0)
+ bindings::dma_map_sgtable(dev.as_raw(), sgt.as_raw(), dir.into(), 0)
})?;
- // INVARIANT: By the safety requirements of this function it is guaranteed that `sgt` is
- // valid for the entire lifetime of this object instance.
- Ok(Self {
- sgt,
- dev: dev.into(),
- dir,
- })
+ // INVARIANT: `sgt` was successfully DMA mapped and is owned by this object until drop.
+ Ok(Self { dev, sgt, dir })
}
}
-impl Drop for DmaMappedSgt {
+impl Drop for DmaMappedSgt<'_> {
#[inline]
fn drop(&mut self) {
// SAFETY:
// - `self.dev.as_raw()` is a pointer to a valid `struct device`.
// - `self.dev` is the same device the mapping has been created for in `Self::new()`.
- // - `self.sgt.as_ptr()` is a valid pointer to a `struct sg_table` by the type invariants
- // of `Self`.
+ // - `self.sgt.as_raw()` is a valid pointer to a `struct sg_table` for the lifetime of
+ // `self`.
// - `self.dir` is the same `dma::DataDirection` the mapping has been created with in
// `Self::new()`.
unsafe {
- bindings::dma_unmap_sgtable(self.dev.as_raw(), self.sgt.as_ptr(), self.dir.into(), 0)
+ bindings::dma_unmap_sgtable(self.dev.as_raw(), self.sgt.as_raw(), self.dir.into(), 0)
};
}
}
@@ -308,38 +283,27 @@ fn drop(&mut self) {
///
/// - The backing memory pages.
/// - The `struct sg_table` allocation (`sgt`).
-/// - The DMA mapping, managed through a [`Devres`]-managed `DmaMappedSgt`.
+/// - The DMA mapping, released before the table and backing pages are freed.
///
/// Users interact with this type through the [`SGTable`] handle and do not need to manage
/// [`Owned`] directly.
-#[pin_data]
-pub struct Owned<P> {
+pub struct Owned<'a, P> {
// Note: The drop order is relevant; we first have to unmap the `struct sg_table`, then free the
// `struct sg_table` and finally free the backing pages.
- #[pin]
- dma: Devres<DmaMappedSgt>,
- #[pin]
- sgt: RawSGTable,
+ dma: DmaMappedSgt<'a>,
_pages: P,
}
-// SAFETY: `Owned` can be sent to any task if `P` can be send to any task.
-unsafe impl<P: Send> Send for Owned<P> {}
-
-// SAFETY: `Owned` has no interior mutability and can be accessed concurrently if `P` can be
-// accessed concurrently.
-unsafe impl<P: Sync> Sync for Owned<P> {}
-
-impl<P> Owned<P>
+impl<'a, P> Owned<'a, P>
where
- for<'a> P: page::AsPageIter<Iter<'a> = VmallocPageIter<'a>> + 'static,
+ for<'b> P: page::AsPageIter<Iter<'b> = VmallocPageIter<'b>> + 'static,
{
fn new(
- dev: &Device<Bound>,
+ dev: &'a Device<Bound>,
mut pages: P,
dir: dma::DataDirection,
flags: alloc::Flags,
- ) -> Result<impl PinInit<Self, Error> + '_> {
+ ) -> Result<Self> {
let page_iter = pages.page_iter();
let size = page_iter.size();
@@ -367,34 +331,22 @@ fn new(
let max_segment = max_mapping_size.min(max_seg_size);
- Ok(try_pin_init!(&this in Self {
- // SAFETY:
- // - `page_vec` is a `KVec` of valid `struct page *` obtained from `pages`.
- // - The pages contained in `pages` remain valid for the entire lifetime of the
- // `RawSGTable`.
- sgt: unsafe { RawSGTable::new(&mut page_vec, size, max_segment, flags) }?,
- dma <- {
- // SAFETY: `this` is a valid pointer to uninitialized memory.
- let sgt = unsafe { &raw mut (*this.as_ptr()).sgt }.cast();
-
- // SAFETY: `sgt` is guaranteed to be non-null.
- let sgt = unsafe { NonNull::new_unchecked(sgt) };
-
- // SAFETY:
- // - It is guaranteed that the object returned by `DmaMappedSgt::new` won't out-live
- // `sgt`.
- // - `sgt` is never DMA unmapped manually.
- // - `sgt` is structurally pinned and therefore cannot move.
- Devres::new(dev, unsafe { DmaMappedSgt::new(sgt, dev, dir) })
- },
+ // SAFETY:
+ // - `page_vec` is a `KVec` of valid `struct page *` obtained from `pages`.
+ // - The pages contained in `pages` remain valid for the entire lifetime of the
+ // `RawSGTable`.
+ let sgt = unsafe { RawSGTable::new(&mut page_vec, size, max_segment, flags) }?;
+
+ Ok(Self {
+ dma: DmaMappedSgt::new(dev, sgt, dir)?,
_pages: pages,
- }))
+ })
}
}
-impl<P> SGTable<Owned<P>>
+impl<'a, P> SGTable<Owned<'a, P>>
where
- for<'a> P: page::AsPageIter<Iter<'a> = VmallocPageIter<'a>> + 'static,
+ for<'b> P: page::AsPageIter<Iter<'b> = VmallocPageIter<'b>> + 'static,
{
/// Allocates a new scatter-gather table from the given pages and maps it for DMA.
///
@@ -403,8 +355,7 @@ impl<P> SGTable<Owned<P>>
/// pages of `P`, and maps the table for DMA with the specified [`Device`] and
/// [`dma::DataDirection`].
///
- /// The DMA mapping is managed through [`Devres`], ensuring that the DMA mapping is unmapped
- /// once the associated [`Device`] is unbound, or when the [`SGTable<Owned>`] is dropped.
+ /// The table is tied to the [`Device<Bound>`] lifetime and unmaps the DMA mapping when dropped.
///
/// # Parameters
///
@@ -428,38 +379,38 @@ impl<P> SGTable<Owned<P>>
/// let size = 4 * page::PAGE_SIZE;
/// let pages = VVec::<u8>::with_capacity(size, GFP_KERNEL)?;
///
- /// let sgt = KBox::pin_init(SGTable::new(
+ /// let sgt = SGTable::new(
/// dev,
/// pages,
/// dma::DataDirection::ToDevice,
/// GFP_KERNEL,
- /// ), GFP_KERNEL)?;
+ /// )?;
///
/// Ok(())
/// }
/// ```
pub fn new(
- dev: &Device<Bound>,
+ dev: &'a Device<Bound>,
pages: P,
dir: dma::DataDirection,
flags: alloc::Flags,
- ) -> impl PinInit<Self, Error> + '_ {
- try_pin_init!(Self {
- inner <- Owned::new(dev, pages, dir, flags)?
+ ) -> Result<Self> {
+ Ok(Self {
+ inner: Owned::new(dev, pages, dir, flags)?,
})
}
}
-impl<P> Deref for SGTable<Owned<P>> {
+impl<P> Deref for SGTable<Owned<'_, P>> {
type Target = SGTable;
#[inline]
fn deref(&self) -> &Self::Target {
// SAFETY:
- // - `self.inner.sgt.as_raw()` is a valid pointer to a `struct sg_table` for the entire
+ // - `self.inner.dma.sgt.as_raw()` is a valid pointer to a `struct sg_table` for the entire
// lifetime of `self`.
// - The backing `struct sg_table` is not modified for the entire lifetime of `self`.
- unsafe { SGTable::from_raw(self.inner.sgt.as_raw()) }
+ unsafe { SGTable::from_raw(self.inner.dma.sgt.as_raw()) }
}
}
@@ -467,7 +418,7 @@ mod private {
pub trait Sealed {}
impl Sealed for super::Borrowed {}
- impl<P> Sealed for super::Owned<P> {}
+ impl<P> Sealed for super::Owned<'_, P> {}
}
/// An [`Iterator`] over the DMA mapped [`SGEntry`] items of an [`SGTable`].
diff --git a/samples/rust/rust_dma.rs b/samples/rust/rust_dma.rs
index ffb693544673..844e137cddc8 100644
--- a/samples/rust/rust_dma.rs
+++ b/samples/rust/rust_dma.rs
@@ -35,8 +35,7 @@
struct DmaSampleData<'bound> {
pdev: &'bound pci::Device<Bound>,
ca: Coherent<'bound, [MyStruct]>,
- #[pin]
- sgt: SGTable<Owned<VVec<u8>>>,
+ sgt: SGTable<Owned<'bound, VVec<u8>>>,
}
const TEST_VALUES: [(u32, u32); 5] = [
@@ -96,13 +95,9 @@ fn probe<'bound>(
let size = 4 * page::PAGE_SIZE;
let pages = VVec::with_capacity(size, GFP_KERNEL)?;
- let sgt = SGTable::new(pdev.as_ref(), pages, DataDirection::ToDevice, GFP_KERNEL);
+ let sgt = SGTable::new(pdev.as_ref(), pages, DataDirection::ToDevice, GFP_KERNEL)?;
- Ok(try_pin_init!(Self::Data {
- pdev,
- ca,
- sgt <- sgt,
- }))
+ Ok(try_pin_init!(Self::Data { pdev, ca, sgt }))
})
}
}
base-commit: 2268cf8e3d7b4192629e4beff4a413efe5501f11
--
2.56.1
next reply other threads:[~2026-11-14 23:43 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-14 23:42 Danilo Krummrich [this message]
2026-11-14 23:42 ` [PATCH 2/2] gpu: nova-core: remove unnecessary firmware pin-init Danilo Krummrich
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261114234324.82387-1-dakr@kernel.org \
--to=dakr@kernel.org \
--cc=a.hindborg@kernel.org \
--cc=abdiel.janulgue@gmail.com \
--cc=acourbot@nvidia.com \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=driver-core@lists.linux.dev \
--cc=gary@garyguo.net \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=nova-gpu@lists.linux.dev \
--cc=ojeda@kernel.org \
--cc=robin.murphy@arm.com \
--cc=rust-for-linux@vger.kernel.org \
--cc=tamird@kernel.org \
--cc=tmgross@umich.edu \
--cc=work@onurozkan.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.