All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Alexandre Courbot" <acourbot@nvidia.com>
To: "Danilo Krummrich" <dakr@kernel.org>, <akpm@linux-foundation.org>,
	<ojeda@kernel.org>, <alex.gaynor@gmail.com>,
	<boqun.feng@gmail.com>, <gary@garyguo.net>,
	<bjorn3_gh@protonmail.com>, <lossin@kernel.org>,
	<a.hindborg@kernel.org>, <aliceryhl@google.com>,
	<tmgross@umich.edu>, <abdiel.janulgue@gmail.com>, <jgg@ziepe.ca>,
	<lyude@redhat.com>, <robin.murphy@arm.com>,
	<daniel.almeida@collabora.com>
Cc: <rust-for-linux@vger.kernel.org>, <linux-kernel@vger.kernel.org>
Subject: Re: [PATCH v2 3/5] rust: scatterlist: Add type-state abstraction for sg_table
Date: Sat, 23 Aug 2025 22:22:47 +0900	[thread overview]
Message-ID: <DC9U87GQ7ONZ.1489DEN1PPUAC@nvidia.com> (raw)
In-Reply-To: <20250820165431.170195-4-dakr@kernel.org>

On Thu Aug 21, 2025 at 1:52 AM JST, Danilo Krummrich wrote:
> Add a safe Rust abstraction for the kernel's scatter-gather list
> facilities (`struct scatterlist` and `struct sg_table`).
>
> This commit introduces `SGTable<T>`, a wrapper that uses a type-state
> pattern to provide compile-time guarantees about ownership and lifetime.
>
> The abstraction provides two primary states:
> - `SGTable<Owned<P>>`: Represents a table whose resources are fully
>   managed by Rust. It takes ownership of a page provider `P`, allocates
>   the underlying `struct sg_table`, maps it for DMA, and handles all
>   cleanup automatically upon drop. The DMA mapping's lifetime is tied to
>   the associated device using `Devres`, ensuring it is correctly unmapped
>   before the device is unbound.
> - `SGTable<Borrowed>` (or just `SGTable`): A zero-cost representation of
>   an externally managed `struct sg_table`. It is created from a raw
>   pointer using `SGTable::as_ref()` and provides a lifetime-bound
>   reference (`&'a SGTable`) for operations like iteration.
>
> The API exposes a safe iterator that yields `&SGEntry` references,
> allowing drivers to easily access the DMA address and length of each
> segment in the list.
>
> Co-developed-by: Abdiel Janulgue <abdiel.janulgue@gmail.com>
> Signed-off-by: Abdiel Janulgue <abdiel.janulgue@gmail.com>
> Signed-off-by: Danilo Krummrich <dakr@kernel.org>
> ---

<snip>
> +impl SGEntry {
> +    /// Convert a raw `struct scatterlist *` to a `&'a SGEntry`.
> +    ///
> +    /// # Safety
> +    ///
> +    /// Callers must ensure that the `struct scatterlist` pointed to by `ptr` is valid for the
> +    /// lifetime `'a`.
> +    #[inline]
> +    unsafe fn from_raw<'a>(ptr: *mut bindings::scatterlist) -> &'a Self {
> +        // SAFETY: The safety requirements of this function guarantee that `ptr` is a valid pointer

nit: "guarantees".

<snip>
> +impl SGTable {
> +    /// Creates a borrowed `&'a SGTable` from a raw `struct sg_table` pointer.
> +    ///
> +    /// This allows safe access to an `sg_table` that is managed elsewhere (for example, in C code).

nit: "to a".

> +    ///
> +    /// # Safety
> +    ///
> +    /// Callers must ensure that:
> +    ///
> +    /// - the `struct sg_table` pointed to by `ptr` is valid for the entire lifetime of `'a`,
> +    /// - the data behind `ptr` is not modified concurrently for the duration of `'a`.
> +    #[inline]
> +    pub unsafe fn from_raw<'a>(ptr: *mut bindings::sg_table) -> &'a Self {
> +        // SAFETY: The safety requirements of this function guarantee that `ptr` is a valid pointer
> +        // to a `struct sg_table` for the duration of `'a`.
> +        unsafe { &*ptr.cast() }
> +    }
> +
> +    #[inline]
> +    fn as_raw(&self) -> *mut bindings::sg_table {
> +        self.inner.0.get()
> +    }
> +
> +    fn as_iter(&self) -> SGTableIter<'_> {
> +        // SAFETY: `self.as_raw()` is a valid pointer to a `struct sg_table`.
> +        let ptr = unsafe { (*self.as_raw()).sgl };
> +
> +        // SAFETY: `ptr` is guaranteed to be a valid pointer to a `struct scatterlist`.
> +        let pos = Some(unsafe { SGEntry::from_raw(ptr) });
> +
> +        SGTableIter { pos }
> +    }
> +}
> +
> +/// # Invariants
> +///
> +/// - `sgt` is a valid pointer to a `struct sg_table` for the entire lifetime of an [`DmaMapSgt`].

nit: "of the".

> +/// - `sgt` is always DMA mapped.
> +struct DmaMapSgt {

Minor point: I'd call this structure `DmaMappedSgt` to highlight the
fact that it is actively mapped. Or alternatively document it and its
members so that fact is clear.

<snip>
> +impl<'a> IntoIterator for &'a SGTable {
> +    type Item = &'a SGEntry;
> +    type IntoIter = SGTableIter<'a>;
> +
> +    #[inline]
> +    fn into_iter(self) -> Self::IntoIter {
> +        self.as_iter()
> +    }
> +}

While using this for Nova, I found it a bit unnatural having to call
`into_iter` on references intead of just having an `iter` method.
`into_iter` sounds like the passed object is consumed, while it is
actually its (copied) reference that is. Why not have a regular `iter`
method on `SGTable`? Actually we do have one, but it is called `as_iter`
and is private for some reason. :)

> +
> +/// An [`Iterator`] over the [`SGEntry`] items of an [`SGTable`].
> +pub struct SGTableIter<'a> {
> +    pos: Option<&'a SGEntry>,
> +}
> +
> +impl<'a> Iterator for SGTableIter<'a> {
> +    type Item = &'a SGEntry;
> +
> +    fn next(&mut self) -> Option<Self::Item> {
> +        let entry = self.pos?;
> +
> +        // SAFETY: `entry.as_raw()` is a valid pointer to a `struct scatterlist`.
> +        let next = unsafe { bindings::sg_next(entry.as_raw()) };
> +
> +        self.pos = (!next.is_null()).then(|| {
> +            // SAFETY: If `next` is not NULL, `sg_next()` guarantees to return a valid pointer to
> +            // the next `struct scatterlist`.
> +            unsafe { SGEntry::from_raw(next) }
> +        });

This might be missing a stop condition.

For reasons I am not completely clear about, the number of mapped
segments on the device side can be smaller than the number of
scatterlists provided by the sg_table. This is highlighted by the
documentation for `dma_map_sg_attrs` [1] ("Returns the number of mapped
entries (which can be less than nents) on success") and `sg_dma_address`
[2] ("You should only work with the number of sg entries dma_map_sg
returns, or alternatively stop on the first sg_dma_len(sg) which is 0.")

`dma_map_sgtable` stores the result of `dma_map_sg_attrs` into its
`nents` member, and makes use of it in its iterator macros. See how the
"regular" iterator and the "DMA" ones differ in their stop condition:

/*
 * Loop over each sg element in the given sg_table object.
 */
#define for_each_sgtable_sg(sgt, sg, i)		\
	for_each_sg((sgt)->sgl, sg, (sgt)->orig_nents, i)

and

/*
 * Loop over each sg element in the given *DMA mapped* sg_table object.
 * Please use sg_dma_address(sg) and sg_dma_len(sg) to extract DMA addresses
 * of the each element.
 */
#define for_each_sgtable_dma_sg(sgt, sg, i)	\
	for_each_sg((sgt)->sgl, sg, (sgt)->nents, i)

The DMA variant being the only one valid for accessing the DMA address
and DMA length (the C does not enforce this however).

So only calling `sg_next` until we reach the end of the list carries the
risk that we iterate on more items than we should, with the extra ones
having their length at 0 (which is likely to be a no-op, but is still
incorrect or at the very least inefficient). We can avoid this by either
storing the value of `nents` in the iterator, or, (which might be
simpler) follow the advice given by the documentation of
`sg_dma_address` and also stop if the DMA length of the next one is
zero.

[1] https://elixir.bootlin.com/linux/v6.16/source/kernel/dma/mapping.c#L233
[2] https://elixir.bootlin.com/linux/v6.16/source/include/linux/scatterlist.h#L31

  parent reply	other threads:[~2025-08-23 13:22 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-08-20 16:52 [PATCH v2 0/5] Rust infrastructure for sg_table and scatterlist Danilo Krummrich
2025-08-20 16:52 ` [PATCH v2 1/5] rust: dma: implement DataDirection Danilo Krummrich
2025-08-22 11:38   ` Alice Ryhl
2025-08-23 11:09   ` Alexandre Courbot
2025-08-20 16:52 ` [PATCH v2 2/5] rust: dma: add type alias for bindings::dma_addr_t Danilo Krummrich
2025-08-22 11:38   ` Alice Ryhl
2025-08-23 11:10   ` Alexandre Courbot
2025-08-20 16:52 ` [PATCH v2 3/5] rust: scatterlist: Add type-state abstraction for sg_table Danilo Krummrich
2025-08-20 17:14   ` Daniel Almeida
2025-08-22 11:44   ` Alice Ryhl
2025-08-22 11:48     ` Danilo Krummrich
2025-08-22 11:52       ` Alice Ryhl
2025-08-22 11:54         ` Danilo Krummrich
2025-08-23 12:44           ` Alexandre Courbot
2025-08-23 13:22   ` Alexandre Courbot [this message]
2025-08-23 13:48     ` Danilo Krummrich
2025-08-23 14:12       ` Alexandre Courbot
2025-08-23 14:32     ` Jason Gunthorpe
2025-08-23 14:57       ` Danilo Krummrich
2025-08-23 13:47   ` Alexandre Courbot
2025-08-23 13:57     ` Danilo Krummrich
2025-08-23 14:16       ` Alexandre Courbot
2025-08-23 14:20         ` Danilo Krummrich
2025-08-23 14:29           ` Alexandre Courbot
2025-08-20 16:52 ` [PATCH v2 4/5] samples: rust: dma: add sample code for SGTable Danilo Krummrich
2025-08-20 16:52 ` [PATCH v2 5/5] MAINTAINERS: rust: dma: add scatterlist files Danilo Krummrich
2025-08-22 11:45 ` [PATCH v2 0/5] Rust infrastructure for sg_table and scatterlist Alice Ryhl
2025-08-22 13:31 ` Alexandre Courbot

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=DC9U87GQ7ONZ.1489DEN1PPUAC@nvidia.com \
    --to=acourbot@nvidia.com \
    --cc=a.hindborg@kernel.org \
    --cc=abdiel.janulgue@gmail.com \
    --cc=akpm@linux-foundation.org \
    --cc=alex.gaynor@gmail.com \
    --cc=aliceryhl@google.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun.feng@gmail.com \
    --cc=dakr@kernel.org \
    --cc=daniel.almeida@collabora.com \
    --cc=gary@garyguo.net \
    --cc=jgg@ziepe.ca \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lossin@kernel.org \
    --cc=lyude@redhat.com \
    --cc=ojeda@kernel.org \
    --cc=robin.murphy@arm.com \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=tmgross@umich.edu \
    /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.