[PATCH v2 1/2] iio: buffer-dmaengine: fix sg entry iteration when building dma_vecs
Nuno Sá <[email protected]>
| Newsgroups | org.kernel.vger.linux-iio |
|---|---|
| Message-ID | <20260828-iio-buffer-dmabuf-iommu-fic-v2-1-b4dd71827621@analog.com> |
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 sgt->nents, i.e. the DMA-mapped view, and stop the fill loop once bytes_used is covered - which is allowed to be smaller than the block size - passing the number of vecs actually filled to dmaengine_prep_peripheral_dma_vec(). One vec per mapped entry is enough since coalescing can only ever reduce the number of entries. A single mapped entry longer than the device's maximum segment size would need more than one, but the DMA API already assumes no single segment exceeds it [1], and splitting a vec down to the hardware descriptor size is the DMA engine driver's job - which both current .device_prep_peripheral_dma_vec() implementations do. [1]: commit ab2cbeb0ed30 ("iommu/dma: Handle SG length overflow better") 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]> --- 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, 11 insertions(+), 5 deletions(-) diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c index ecc02a427b92..376486be3f55 100644 --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -104,10 +104,13 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, if (block->sg_table) { unsigned long flags; - sgl = block->sg_table->sgl; - nents = sg_nents_for_len(sgl, block->bytes_used); - if (nents < 0) - return nents; + /* + * Only the first sgt->nents entries carry a valid + * sg_dma_address()/sg_dma_len() pair as mapping the table may + * have coalesced entries, in which case nents is smaller than + * orig_nents. + */ + nents = block->sg_table->nents; vecs = kmalloc_array(nents, sizeof(*vecs), GFP_ATOMIC); if (!vecs) @@ -115,7 +118,8 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, len_total = block->bytes_used; - for (i = 0; i < nents; i++) { + sgl = block->sg_table->sgl; + for (i = 0; i < nents && len_total; i++) { vecs[i].addr = sg_dma_address(sgl); vecs[i].len = min(sg_dma_len(sgl), len_total); len_total -= vecs[i].len; @@ -123,6 +127,8 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, sgl = sg_next(sgl); } + nents = i; + if (block->cyclic) flags = DMA_PREP_REPEAT; else -- 2.55.0