summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Forrester <nforrester@whoi.edu>2008-11-19 15:36:21 -0800
committerGreg Kroah-Hartman <gregkh@suse.de>2008-12-05 10:55:10 -0800
commit532b2a9f6baf34ab2ce4fa501f76b97be8ab8e85 (patch)
treee6c4005536d2ebaf0055c87cc5e826f2c8414eb4
parentc53459aa96901e6d21d69acff88fb77759edd3f7 (diff)
pxa2xx_spi: bugfix full duplex dma data corruption
commit 393df744e056ba24e9531d0657d09fc3c7c0dd22 upstream. Fixes a data corruption bug in pxa2xx_spi.c when operating in full duplex mode with DMA and using buffers that overlap. SPI transmit and receive buffers are allowed to be the same or to overlap. However, this driver fails if such overlap is attempted in DMA mode because it maps the rx and tx buffers in the wrong order. By mapping DMA_FROM_DEVICE (read) before DMA_TO_DEVICE (write), it invalidates the cache before flushing it, thus discarding data which should have been transmitted. The patch corrects the order of mapping. This bug exists in all versions of pxa2xx_spi.c; similar bugs are in the drivers for two other SPI controllers (au1500, imx). A version of this patch has been tested on kernel 2.6.20 using verification of loopback data with: random transfer length, random bits-per-word, random positive offsets (both larger and smaller than transfer length) between the start of the rx and tx buffers, and varying clock rates. Signed-off-by: Ned Forrester <nforrester@whoi.edu> Cc: Vernon Sauder <vernoninhand@gmail.com> Cc: J. Scott Merritt <merrij3@rpi.edu> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--drivers/spi/pxa2xx_spi.c24
1 files changed, 12 insertions, 12 deletions
diff --git a/drivers/spi/pxa2xx_spi.c b/drivers/spi/pxa2xx_spi.c
index d47d3636227f..b5fc9788e171 100644
--- a/drivers/spi/pxa2xx_spi.c
+++ b/drivers/spi/pxa2xx_spi.c
@@ -348,21 +348,21 @@ static int map_dma_buffers(struct driver_data *drv_data)
} else
drv_data->tx_map_len = drv_data->len;
- /* Stream map the rx buffer */
- drv_data->rx_dma = dma_map_single(dev, drv_data->rx,
- drv_data->rx_map_len,
- DMA_FROM_DEVICE);
- if (dma_mapping_error(dev, drv_data->rx_dma))
- return 0;
-
- /* Stream map the tx buffer */
+ /* Stream map the tx buffer. Always do DMA_TO_DEVICE first
+ * so we flush the cache *before* invalidating it, in case
+ * the tx and rx buffers overlap.
+ */
drv_data->tx_dma = dma_map_single(dev, drv_data->tx,
- drv_data->tx_map_len,
- DMA_TO_DEVICE);
+ drv_data->tx_map_len, DMA_TO_DEVICE);
+ if (dma_mapping_error(dev, drv_data->tx_dma))
+ return 0;
- if (dma_mapping_error(dev, drv_data->tx_dma)) {
- dma_unmap_single(dev, drv_data->rx_dma,
+ /* Stream map the rx buffer */
+ drv_data->rx_dma = dma_map_single(dev, drv_data->rx,
drv_data->rx_map_len, DMA_FROM_DEVICE);
+ if (dma_mapping_error(dev, drv_data->rx_dma)) {
+ dma_unmap_single(dev, drv_data->tx_dma,
+ drv_data->tx_map_len, DMA_TO_DEVICE);
return 0;
}