[PATCH] dmaengine: dw: Fix data corruption in large device to memory transfers

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



When transferring more data than the maximum block size supported by the
HW multiplied by source width the transfer is split into smaller chunks.
Currently code calculates the memory width and thus aligment before
splitting for both memory to device and device to memory transfers.

For memory to device transfers this work fine since alignment is preserved
through the splitting and split blocks are still memory width aligned.
However in device to memory transfers aligment breaks when maximum block
size multiplied by register width doesn't have the same alignment than the
buffer. For instance when transferring from an 8-bit register 4100 bytes
(32-bit aligned) on a DW DMA that has maximum block size of 4095 elements.
An attempt to do such transfers caused data corruption.

Fix this by calculating and setting the destination memory width after
splitting by using the split block aligment and length.

Signed-off-by: Jarkko Nikula <jarkko.nikula@xxxxxxxxxxxxxxx>
---
I'm not sure is this stable material or not. I'm a bit on not stable
side so I didn't Cc it. I noticed the issue by tweaking the spidev to
allow bigger than 4 KiB buffer. There were RX corruption when doing 8-bit
transfers with even sized buffers of >= 4098 bytes on an HW where max
block size is 4095.
---
 drivers/dma/dw/core.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/dma/dw/core.c b/drivers/dma/dw/core.c
index edf053f73a49..878e2bf58233 100644
--- a/drivers/dma/dw/core.c
+++ b/drivers/dma/dw/core.c
@@ -831,8 +831,6 @@ slave_sg_todev_fill_desc:
 			mem = sg_dma_address(sg);
 			len = sg_dma_len(sg);
 
-			mem_width = __ffs(data_width | mem | len);
-
 slave_sg_fromdev_fill_desc:
 			desc = dwc_desc_get(dwc);
 			if (!desc)
@@ -840,15 +838,17 @@ slave_sg_fromdev_fill_desc:
 
 			lli_write(desc, sar, reg);
 			lli_write(desc, dar, mem);
-			lli_write(desc, ctllo, ctllo | DWC_CTLL_DST_WIDTH(mem_width));
 			if ((len >> reg_width) > dwc->block_size) {
 				dlen = dwc->block_size << reg_width;
-				mem += dlen;
 				len -= dlen;
 			} else {
 				dlen = len;
 				len = 0;
 			}
+			mem_width = __ffs(data_width | mem | dlen);
+			mem += dlen;
+			lli_write(desc, ctllo,
+				  ctllo | DWC_CTLL_DST_WIDTH(mem_width));
 			lli_write(desc, ctlhi, dlen >> reg_width);
 			desc->len = dlen;
 
-- 
2.8.1

--
To unsubscribe from this list: send the line "unsubscribe dmaengine" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Kernel]     [Linux ARM (vger)]     [Linux ARM MSM]     [Linux Omap]     [Linux Arm]     [Linux Tegra]     [Fedora ARM]     [Linux for Samsung SOC]     [eCos]     [Linux PCI]     [Linux Fastboot]     [Gcc Help]     [Git]     [DCCP]     [IETF Announce]     [Security]     [Linux MIPS]     [Yosemite Campsites]

  Powered by Linux