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


Reply via email to