mmc: sh_mmcif: protect against a theoretical race
authorGuennadi Liakhovetski <g.liakhovetski@gmx.de>
Fri, 15 Apr 2011 18:30:47 +0000 (18:30 +0000)
committerChris Ball <cjb@laptop.org>
Wed, 25 May 2011 03:53:50 +0000 (23:53 -0400)
The MMC subsystem does not guarantee that host driver .request() and
.set_ios() callbacks are serialised. Such concurrent calls, however,
do not have to be meaningfully supported, drivers just have to make
sure to avoid any severe problems.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
Cc: Simon Horman <horms@verge.net.au>
Cc: Magnus Damm <damm@opensource.se>
Signed-off-by: Chris Ball <cjb@laptop.org>
drivers/mmc/host/sh_mmcif.c

index af97015..d3871b6 100644 (file)
@@ -29,6 +29,7 @@
 #include <linux/mmc/sh_mmcif.h>
 #include <linux/pagemap.h>
 #include <linux/platform_device.h>
+#include <linux/spinlock.h>
 
 #define DRIVER_NAME    "sh_mmcif"
 #define DRIVER_VERSION "2010-04-28"
 #define CLKDEV_MMC_DATA                20000000 /* 20MHz */
 #define CLKDEV_INIT            400000   /* 400 KHz */
 
+enum mmcif_state {
+       STATE_IDLE,
+       STATE_REQUEST,
+       STATE_IOS,
+};
+
 struct sh_mmcif_host {
        struct mmc_host *mmc;
        struct mmc_data *data;
@@ -164,6 +171,8 @@ struct sh_mmcif_host {
        long timeout;
        void __iomem *addr;
        struct completion intr_wait;
+       enum mmcif_state state;
+       spinlock_t lock;
 
        /* DMA support */
        struct dma_chan         *chan_rx;
@@ -798,17 +807,31 @@ static void sh_mmcif_stop_cmd(struct sh_mmcif_host *host,
 static void sh_mmcif_request(struct mmc_host *mmc, struct mmc_request *mrq)
 {
        struct sh_mmcif_host *host = mmc_priv(mmc);
+       unsigned long flags;
+
+       spin_lock_irqsave(&host->lock, flags);
+       if (host->state != STATE_IDLE) {
+               spin_unlock_irqrestore(&host->lock, flags);
+               mrq->cmd->error = -EAGAIN;
+               mmc_request_done(mmc, mrq);
+               return;
+       }
+
+       host->state = STATE_REQUEST;
+       spin_unlock_irqrestore(&host->lock, flags);
 
        switch (mrq->cmd->opcode) {
        /* MMCIF does not support SD/SDIO command */
        case SD_IO_SEND_OP_COND:
        case MMC_APP_CMD:
+               host->state = STATE_IDLE;
                mrq->cmd->error = -ETIMEDOUT;
                mmc_request_done(mmc, mrq);
                return;
        case MMC_SEND_EXT_CSD: /* = SD_SEND_IF_COND (8) */
                if (!mrq->data) {
                        /* send_if_cond cmd (not support) */
+                       host->state = STATE_IDLE;
                        mrq->cmd->error = -ETIMEDOUT;
                        mmc_request_done(mmc, mrq);
                        return;
@@ -830,12 +853,9 @@ static void sh_mmcif_request(struct mmc_host *mmc, struct mmc_request *mrq)
        sh_mmcif_start_cmd(host, mrq, mrq->cmd);
        host->data = NULL;
 
-       if (mrq->cmd->error != 0) {
-               mmc_request_done(mmc, mrq);
-               return;
-       }
-       if (mrq->stop)
+       if (!mrq->cmd->error && mrq->stop)
                sh_mmcif_stop_cmd(host, mrq, mrq->stop);
+       host->state = STATE_IDLE;
        mmc_request_done(mmc, mrq);
 }
 
@@ -843,6 +863,16 @@ static void sh_mmcif_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 {
        struct sh_mmcif_host *host = mmc_priv(mmc);
        struct sh_mmcif_plat_data *p = host->pd->dev.platform_data;
+       unsigned long flags;
+
+       spin_lock_irqsave(&host->lock, flags);
+       if (host->state != STATE_IDLE) {
+               spin_unlock_irqrestore(&host->lock, flags);
+               return;
+       }
+
+       host->state = STATE_IOS;
+       spin_unlock_irqrestore(&host->lock, flags);
 
        if (ios->power_mode == MMC_POWER_UP) {
                if (p->set_pwr)
@@ -852,6 +882,7 @@ static void sh_mmcif_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
                sh_mmcif_clock_control(host, 0);
                if (ios->power_mode == MMC_POWER_OFF && p->down_pwr)
                        p->down_pwr(host->pd);
+               host->state = STATE_IDLE;
                return;
        }
 
@@ -859,6 +890,7 @@ static void sh_mmcif_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
                sh_mmcif_clock_control(host, ios->clock);
 
        host->bus_width = ios->bus_width;
+       host->state = STATE_IDLE;
 }
 
 static int sh_mmcif_get_cd(struct mmc_host *mmc)
@@ -996,6 +1028,7 @@ static int __devinit sh_mmcif_probe(struct platform_device *pdev)
        host->pd = pdev;
 
        init_completion(&host->intr_wait);
+       spin_lock_init(&host->lock);
 
        mmc->ops = &sh_mmcif_ops;
        mmc->f_max = host->clk;
@@ -1025,6 +1058,8 @@ static int __devinit sh_mmcif_probe(struct platform_device *pdev)
 
        mmc_add_host(mmc);
 
+       sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL);
+
        ret = request_irq(irq[0], sh_mmcif_intr, 0, "sh_mmc:error", host);
        if (ret) {
                dev_err(&pdev->dev, "request_irq error (sh_mmc:error)\n");
@@ -1037,7 +1072,6 @@ static int __devinit sh_mmcif_probe(struct platform_device *pdev)
                goto clean_up2;
        }
 
-       sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL);
        sh_mmcif_detect(host->mmc);
 
        dev_info(&pdev->dev, "driver version %s\n", DRIVER_VERSION);
@@ -1063,11 +1097,11 @@ static int __devexit sh_mmcif_remove(struct platform_device *pdev)
        mmc_remove_host(host->mmc);
        sh_mmcif_release_dma(host);
 
+       sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL);
+
        if (host->addr)
                iounmap(host->addr);
 
-       sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL);
-
        irq[0] = platform_get_irq(pdev, 0);
        irq[1] = platform_get_irq(pdev, 1);