[PATCHv2 2/2] dmaengine: idma64: use sg_nents_for_dma to respect hardware descriptor length limit

From: Rosen Penev

Date: Sun Jul 12 2026 - 18:00:51 EST


The iDMA 64-bit hardware has a 17-bit block transfer size field in the
CTL_HI register (IDMA64C_CTLH_BLOCK_TS_MASK = 0x1ffff). When a
scatterlist entry exceeds this limit, the driver would silently
truncate the length, transferring fewer bytes than intended.

Use sg_nents_for_dma() to compute the number of hardware descriptors
needed after splitting large SG entries into chunks that fit within
the hardware limit. Split the loop to iterate over each chunk.

Assisted-by: opencode:big-pickle
Signed-off-by: Rosen Penev <rosenp@xxxxxxxxx>
---
drivers/dma/idma64.c | 44 ++++++++++++++++++++++++++++++--------------
drivers/dma/idma64.h | 3 ++-
2 files changed, 32 insertions(+), 15 deletions(-)

diff --git a/drivers/dma/idma64.c b/drivers/dma/idma64.c
index d914f50ec309..6954ec2cdeae 100644
--- a/drivers/dma/idma64.c
+++ b/drivers/dma/idma64.c
@@ -287,27 +287,43 @@ static struct dma_async_tx_descriptor *idma64_prep_slave_sg(
struct idma64_chan *idma64c = to_idma64_chan(chan);
struct idma64_desc *desc;
struct scatterlist *sg;
- unsigned int i;
+ unsigned int i, nents;
+ int ndesc;

- desc = kzalloc_flex(*desc, hw, sg_len, GFP_NOWAIT);
+ ndesc = sg_nents_for_dma(sgl, sg_len, IDMA64C_CTLH_BLOCK_TS_MASK);
+ if (ndesc <= 0)
+ return NULL;
+
+ desc = kzalloc_flex(*desc, hw, ndesc, GFP_NOWAIT);
if (!desc)
return NULL;

- desc->ndesc = sg_len;
+ desc->ndesc = ndesc;

+ nents = 0;
for_each_sg(sgl, sg, sg_len, i) {
- struct idma64_hw_desc *hw = &desc->hw[i];
-
- /* Allocate DMA capable memory for hardware descriptor */
- hw->lli = dma_pool_alloc(idma64c->pool, GFP_NOWAIT, &hw->llp);
- if (!hw->lli) {
- desc->ndesc = i;
- idma64_desc_free(idma64c, desc);
- return NULL;
+ dma_addr_t addr = sg_dma_address(sg);
+ unsigned int len = sg_dma_len(sg);
+
+ while (len) {
+ struct idma64_hw_desc *hwdesc = &desc->hw[nents++];
+ unsigned int chunk = min(len, IDMA64C_CTLH_BLOCK_TS_MASK);
+
+ hwdesc->lli = dma_pool_alloc(idma64c->pool, GFP_NOWAIT,
+ &hwdesc->llp);
+ if (!hwdesc->lli) {
+ /* nents was already incremented by ++ above */
+ desc->ndesc = nents - 1;
+ idma64_desc_free(idma64c, desc);
+ return NULL;
+ }
+
+ hwdesc->phys = addr;
+ hwdesc->len = chunk;
+
+ addr += chunk;
+ len -= chunk;
}
-
- hw->phys = sg_dma_address(sg);
- hw->len = sg_dma_len(sg);
}

desc->direction = direction;
diff --git a/drivers/dma/idma64.h b/drivers/dma/idma64.h
index 1a67dbb24db5..297a91594b31 100644
--- a/drivers/dma/idma64.h
+++ b/drivers/dma/idma64.h
@@ -8,6 +8,7 @@
#ifndef __DMA_IDMA64_H__
#define __DMA_IDMA64_H__

+#include <linux/bits.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/spinlock.h>
@@ -51,7 +52,7 @@
#define IDMA64C_CTLL_LLP_S_EN (1 << 28) /* src block chain */

/* Bitfields in CTL_HI */
-#define IDMA64C_CTLH_BLOCK_TS_MASK ((1 << 17) - 1)
+#define IDMA64C_CTLH_BLOCK_TS_MASK GENMASK_U32(16, 0)
#define IDMA64C_CTLH_BLOCK_TS(x) ((x) & IDMA64C_CTLH_BLOCK_TS_MASK)
#define IDMA64C_CTLH_DONE (1 << 17)

--
2.55.0