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 <[email protected]> --- 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

