Skip to contentSkip to conversation

FAILED: patch "[PATCH] iio: buffer-dmaengine: fix sg entry iteration when building" failed to apply to 6.12-stable tree

PATCH New [email protected] 2 participants 2 messages Oct 6 – Oct 7

Subsystems (2)
lore
Share & tools
lore

[email protected]

author maintainer
#0 lore raw

The patch below does not apply to the 6.12-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.

To reproduce the conflict and resubmit, you may use the following commands:

git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.12.y
git checkout FETCH_HEAD
git cherry-pick -x 0d0ffbcc92e30a8d656ad76a6e278fa3400fed85
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026100621-baritone-yapping-1e8d@gregkh' --subject-prefix 'PATCH 6.12.y' 'HEAD^..'

Possible dependencies:

thanks,

greg k-h

------------------ original commit in Linus's tree ------------------

From 0d0ffbcc92e30a8d656ad76a6e278fa3400fed85 Mon Sep 17 00:00:00 2001
From: Michael Hennerich <[email protected]>
Date: Fri, 28 Aug 2026 11:46:55 +0100
Subject: [PATCH] iio: buffer-dmaengine: fix sg entry iteration when building
dma_vecs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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")
drivers/iio/buffer/industrialio-buffer-dmaengine.c +11 -5
index ecc02a427b92..376486be3f55 100644
@@ -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
maintainer
#1 lore raw

PATCH 1/1 iio: buffer-dmaengine: fix sg entry iteration when building dma_vecs

From: Michael Hennerich <[email protected]>

[ Upstream commit 0d0ffbcc92e30a8d656ad76a6e278fa3400fed85 ]

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")
[ sashal: adjusted context due to missing cyclic DMA flag handling ]
---
 .../iio/buffer/industrialio-buffer-dmaengine.c   | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)
drivers/iio/buffer/industrialio-buffer-dmaengine.c +11 -5
index 95222f00e5ff4..0de2784ace22f 100644
@@ -82,10 +82,13 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue,
 		dma_dir = DMA_MEM_TO_DEV;
 
 	if (block->sg_table) {
-		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)
@@ -93,7 +96,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;
@@ -101,6 +105,8 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue,
 			sgl = sg_next(sgl);
 		}
 
+		nents = i;
+
 		desc = dmaengine_prep_peripheral_dma_vec(dmaengine_buffer->chan,
 							 vecs, nents, dma_dir,
 							 DMA_PREP_INTERRUPT);
Signature · 1 line
2.53.0