Re: [PATCH] iio: buffer-dmaengine: fix sg entry iteration when building dma_vecs

Jonathan Cameron <[email protected]>
Newsgroups org.kernel.vger.linux-iio
Message-ID <20260819014104.56bd4157@jic23-huawei>
On Tue, 18 Aug 2026 17:45:29 +0100
Nuno Sá <[email protected]> wrote:

> From: Michael Hennerich <[email protected]>
> 
> iio_dmaengine_buffer_submit_block() counts scatterlist entries with
> sg_nents_for_len(), which walks the CPU-side lengths (sg->length), but
> then consumes the DMA-side fields (sg_dma_address()/sg_dma_len()).
> After dma_map_sgtable() the two views may differ: an IOMMU can coalesce
> the mapping so that only the first sgt->nents entries carry valid DMA
> addresses, with nents < orig_nents.
> 
> On x86 with an IOMMU enabled, a DMABUF block backed by two 1 MiB
> system-heap chunks maps to a single 2 MiB IOVA range. The CPU-side
> count is 2, so the loop reads one entry past the mapped set and emits a
> garbage vec ({addr = ~0, len = 0}). The DMA engine driver rejects the
> vec array (prep returns NULL), the fence is signalled with -ENOMEM,
> which a userspace poller cannot observe, and the block is left in
> ACTIVE state so every further enqueue of it fails with -EBUSY. The
> visible symptom is a stream of zero-filled blocks followed by a wedged
> buffer.
> 
> Platforms without an IOMMU never hit this because nents == orig_nents.
> 
> Size the vec array with sg_nents_for_dma(), which walks the DMA-mapped
> view and accounts for max-length splitting, and stop the fill loop once
> bytes_used is covered - which is allowed to be smaller than the block
> size - passing the reduced count to dmaengine_prep_peripheral_dma_vec().
> 
> Assisted-by: Claude:claude-fable-5
> Fixes: 7a86d469983a ("iio: buffer-dmaengine: Support new DMABUF based userspace API")
> Signed-off-by: Michael Hennerich <[email protected]>
> Signed-off-by: Nuno Sá <[email protected]>

There are some gremlins nearby in this code...
In the else just of this context seems max_size is computed again
having been done just above the code seen here.

Unless I'm missing something that should be cleaned up as well.

Been a while since I got my head into the scatterlist
stuff, so I might have it wrong below, but I don't think what
you have here actually works if the merging of entries is
larger than the max dma entry the hardware supports.


> ---
> Note the Signed-off-by is just because I'm carrying Michael's patch!
> ---
>  drivers/iio/buffer/industrialio-buffer-dmaengine.c | 16 ++++++++++++----
>  1 file changed, 12 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> index ecc02a427b92..bece45381c8c 100644
> --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> @@ -104,10 +104,16 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue,
>  	if (block->sg_table) {
>  		unsigned long flags;
>  
> +		/*
> +		 * Use the DMA-mapped view of the sg_table: after mapping
> +		 * (e.g. through an IOMMU) the DMA entries (sgt->nents) can be
> +		 * fewer than the CPU entries, and sg_dma_address()/sg_dma_len()
> +		 * are only valid for the first sgt->nents entries. Counting
> +		 * with sg_nents_for_len() (CPU lengths) walks past them and
> +		 * hands garbage vecs to the DMA engine.

This feels like too much info after the fix is in place.  Talking about other
stuff that would be wrong is rather unusual.

> +		 */
>  		sgl = block->sg_table->sgl;
> -		nents = sg_nents_for_len(sgl, block->bytes_used);
> -		if (nents < 0)
> -			return nents;
> +		nents = sg_nents_for_dma(sgl, block->sg_table->nents, max_size);

So this fun function will generally give us the number of sgl entries, but not
quite always. It will give us how many chunks of up to max_size fit into
a particularly large entry.

>  
>  		vecs = kmalloc_array(nents, sizeof(*vecs), GFP_ATOMIC);
>  		if (!vecs)
> @@ -115,7 +121,7 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue,
>  
>  		len_total = block->bytes_used;
>  
> -		for (i = 0; i < nents; i++) {
> +		for (i = 0; i < nents && len_total; i++) {
So this needs to be more clever as we aren't just iterating entrees and filling
them in, some of them could at least in theory be too big to fit
in a single vec - hence you need to do a loop in here that sets
multiple entries if that occurs.

If that can't happen for some other reason then I think you can 
just use block->sgtable->nents instead of the more complex call above.

>  			vecs[i].addr = sg_dma_address(sgl);
>  			vecs[i].len = min(sg_dma_len(sgl), len_total);
>  			len_total -= vecs[i].len;
> @@ -133,6 +139,8 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue,
>  		 * before it can run, so always set the EOT flag.
>  		 */
>  		flags |= DMA_PREP_LOAD_EOT;
> +		nents = i;
> +
>  		desc = dmaengine_prep_peripheral_dma_vec(dmaengine_buffer->chan,
>  							 vecs, nents, dma_dir,
>  							 flags);
> 
> ---
> base-commit: b756b143e5391151e577ae645b1378a43f93c2f5
> change-id: 20260818-iio-buffer-dmabuf-iommu-fic-1b281f15e5a4
> --
> 
> Thanks!
> - Nuno Sá
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.