[PATCH] iio: buffer-dmaengine: fix sg entry iteration when building dma_vecs
Nuno Sá <[email protected]>
| Newsgroups | org.kernel.vger.linux-iio |
|---|---|
| Message-ID | <20260818-iio-buffer-dmabuf-iommu-fic-v1-1-4ff1e44a5073@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 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]> --- 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. + */ 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); 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++) { 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á