bcm2835-mmc: Prevent DMA race condition
authorPhil Elwell <phil@raspberrypi.org>
Tue, 18 Jul 2017 14:30:48 +0000 (15:30 +0100)
committerRaspbian kernel package updater <root@raspbian.org>
Sun, 23 Jul 2017 03:09:35 +0000 (03:09 +0000)
The end of a read operation is triggered by the completion of the DMA
transfer, but writes are complete when the data IRQ is raised. The
bcm2835-mmc driver contains a race between the handling of the DMA
completion interrupt and the submission of the next request. Fix the
race by deferring the completion of the request until the DMA
transfer finishes.

Signed-off-by: Phil Elwell <phil@raspberrypi.org>
drivers/mmc/host/bcm2835-mmc.c

index 981db05de1ff52a83550e41ab362eecf99cafa29..c4a5e992c6fb4a40b933239350ed4bfc8fb40155 100644 (file)
@@ -115,6 +115,7 @@ struct bcm2835_host {
 
        bool                                    have_dma;
        bool                                    use_dma;
+       bool                                    wait_for_dma;
        /*end of DMA part*/
 
        int                                             max_delay;      /* maximum length of time spent waiting */
@@ -341,6 +342,8 @@ static void bcm2835_mmc_dma_complete(void *param)
 
        spin_lock_irqsave(&host->lock, flags);
 
+       host->use_dma = false;
+
        if (host->data && !(host->data->flags & MMC_DATA_WRITE)) {
                /* otherwise handled in SDHCI IRQ */
                dma_chan = host->dma_chan_rxtx;
@@ -351,6 +354,9 @@ static void bcm2835_mmc_dma_complete(void *param)
                     dir_data);
 
                bcm2835_mmc_finish_data(host);
+       } else if (host->wait_for_dma) {
+               host->wait_for_dma = false;
+               tasklet_schedule(&host->finish_tasklet);
        }
 
        spin_unlock_irqrestore(&host->lock, flags);
@@ -690,6 +696,7 @@ void bcm2835_mmc_send_command(struct bcm2835_host *host, struct mmc_command *cmd
        mod_timer(&host->timer, timeout);
 
        host->cmd = cmd;
+       host->use_dma = false;
 
        bcm2835_mmc_prepare_data(host, cmd);
 
@@ -759,8 +766,11 @@ static void bcm2835_mmc_finish_data(struct bcm2835_host *host)
                }
 
                bcm2835_mmc_send_command(host, data->stop);
-       } else
+       } else if (host->use_dma) {
+               host->wait_for_dma = true;
+       } else {
                tasklet_schedule(&host->finish_tasklet);
+       }
 }
 
 static void bcm2835_mmc_finish_command(struct bcm2835_host *host)