mmc: dw_mmc: fix race with request and removal
authorJames Hogan <james.hogan@imgtec.com>
Fri, 24 Jun 2011 12:55:10 +0000 (13:55 +0100)
committerChris Ball <cjb@laptop.org>
Wed, 20 Jul 2011 21:20:58 +0000 (17:20 -0400)
When a request is made, the card presence is checked and the request is
queued. These two parts must be atomic with respect to card removal, or
a card removal could be handled in between, and the new request wouldn't
get cancelled until another card was inserted. Therefore move the
spinlock protection from dw_mci_queue_request() up into dw_mci_request()
to cover the presence check.

Note that the test_bit() used for the presence check isn't atomic
itself, so should have been protected by a spinlock anyway.

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Acked-by: Will Newton <will.newton@imgtec.com>
Signed-off-by: Chris Ball <cjb@laptop.org>
drivers/mmc/host/dw_mmc.c

index 08c0592ed9bcd2b249c7486748b9c29b5915cddb..c4bddf6a5f1f3c99ab977fb5c9189334f199a18d 100644 (file)
@@ -625,13 +625,13 @@ static void dw_mci_start_request(struct dw_mci *host,
                host->stop_cmdr = dw_mci_prepare_command(slot->mmc, mrq->stop);
 }
 
+/* must be called with host->lock held */
 static void dw_mci_queue_request(struct dw_mci *host, struct dw_mci_slot *slot,
                                 struct mmc_request *mrq)
 {
        dev_vdbg(&slot->mmc->class_dev, "queue request: state=%d\n",
                 host->state);
 
-       spin_lock_bh(&host->lock);
        slot->mrq = mrq;
 
        if (host->state == STATE_IDLE) {
@@ -640,8 +640,6 @@ static void dw_mci_queue_request(struct dw_mci *host, struct dw_mci_slot *slot,
        } else {
                list_add_tail(&slot->queue_node, &host->queue);
        }
-
-       spin_unlock_bh(&host->lock);
 }
 
 static void dw_mci_request(struct mmc_host *mmc, struct mmc_request *mrq)
@@ -651,14 +649,23 @@ static void dw_mci_request(struct mmc_host *mmc, struct mmc_request *mrq)
 
        WARN_ON(slot->mrq);
 
+       /*
+        * The check for card presence and queueing of the request must be
+        * atomic, otherwise the card could be removed in between and the
+        * request wouldn't fail until another card was inserted.
+        */
+       spin_lock_bh(&host->lock);
+
        if (!test_bit(DW_MMC_CARD_PRESENT, &slot->flags)) {
+               spin_unlock_bh(&host->lock);
                mrq->cmd->error = -ENOMEDIUM;
                mmc_request_done(mmc, mrq);
                return;
        }
 
-       /* We don't support multiple blocks of weird lengths. */
        dw_mci_queue_request(host, slot, mrq);
+
+       spin_unlock_bh(&host->lock);
 }
 
 static void dw_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)