aboutsummaryrefslogtreecommitdiff
path: root/drivers/mmc
diff options
context:
space:
mode:
authorBen Dooks <ben-linux@fluff.org>2008-11-03 20:19:01 +0000
committerBen Dooks <ben-linux@fluff.org>2008-11-03 20:30:50 +0000
commit6d26f8260a3c06933310d9641deae782182eba24 (patch)
tree6a4ba910b831e971bb022ddd30c4a96ff53ff9cb /drivers/mmc
parente8b704d34687aa65ccc4f064b29786305f8427bc (diff)
SDHCI: Check DMA for overruns at end of transfer
At the end of a transfer, check that the DMA engine in the SDHCI controller actually did what it was meant to and didn't overrun the end of the buffer. This seems to be triggered by a timeout during an CMD25 (multiple block write) to a card. The mmc_block module then issues a command to find out how much data was moved and this seems to end up triggering this DMA check. The result is the card's queue generates an OOPS as the stack has been trampled on due to the extra data transfered. Signed-off-by: Ben Dooks <ben-linux@fluff.org>
Diffstat (limited to 'drivers/mmc')
-rw-r--r--drivers/mmc/host/sdhci.c19
1 files changed, 19 insertions, 0 deletions
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index f86f30058c1..59c890e7b4c 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -736,6 +736,23 @@ static void sdhci_set_transfer_mode(struct sdhci_host *host,
writew(mode, host->ioaddr + SDHCI_TRANSFER_MODE);
}
+static void shdci_check_dma_overrun(struct sdhci_host *host, struct mmc_data *data)
+{
+ u32 dma_pos = readl(host->ioaddr + SDHCI_DMA_ADDRESS);
+ u32 dma_start = sg_dma_address(data->sg);
+ u32 dma_end = dma_start + data->sg->length;
+
+ /* Test whether we ended up moving more data than
+ * was originally requested. */
+
+ if (dma_pos <= dma_end)
+ return;
+
+ printk(KERN_ERR "%s: dma overrun, dma %08x, req %08x..%08x\n",
+ mmc_hostname(host->mmc), dma_pos,
+ dma_start, dma_end);
+}
+
static void sdhci_finish_data(struct sdhci_host *host)
{
struct mmc_data *data;
@@ -749,6 +766,8 @@ static void sdhci_finish_data(struct sdhci_host *host)
if (host->flags & SDHCI_USE_ADMA)
sdhci_adma_table_post(host, data);
else {
+ shdci_check_dma_overrun(host, data);
+
dma_unmap_sg(mmc_dev(host->mmc), data->sg,
data->sg_len, (data->flags & MMC_DATA_READ) ?
DMA_FROM_DEVICE : DMA_TO_DEVICE);