sdhci: handle hot-remove
authorPierre Ossman <drzeus@drzeus.cx>
Wed, 16 Apr 2008 17:13:13 +0000 (19:13 +0200)
committerPierre Ossman <drzeus@drzeus.cx>
Tue, 15 Jul 2008 12:14:40 +0000 (14:14 +0200)
Gracefully handle when the device is suddenly removed. Do a test read
and avoid any further access if that read returns -1.

Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
drivers/mmc/host/sdhci-pci.c
drivers/mmc/host/sdhci.c
drivers/mmc/host/sdhci.h

index 5dcb495..8554466 100644 (file)
@@ -47,7 +47,7 @@ struct sdhci_pci_fixes {
        int                     (*probe)(struct sdhci_pci_chip*);
 
        int                     (*probe_slot)(struct sdhci_pci_slot*);
-       void                    (*remove_slot)(struct sdhci_pci_slot*);
+       void                    (*remove_slot)(struct sdhci_pci_slot*, int);
 
        int                     (*suspend)(struct sdhci_pci_chip*,
                                        pm_message_t);
@@ -209,8 +209,11 @@ static int jmicron_probe_slot(struct sdhci_pci_slot *slot)
        return 0;
 }
 
-static void jmicron_remove_slot(struct sdhci_pci_slot *slot)
+static void jmicron_remove_slot(struct sdhci_pci_slot *slot, int dead)
 {
+       if (dead)
+               return;
+
        if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC)
                jmicron_enable_mmc(slot->host, 0);
 }
@@ -540,7 +543,7 @@ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot(
 
 remove:
        if (chip->fixes && chip->fixes->remove_slot)
-               chip->fixes->remove_slot(slot);
+               chip->fixes->remove_slot(slot, 0);
 
 unmap:
        iounmap(host->ioaddr);
@@ -554,10 +557,18 @@ release:
 
 static void sdhci_pci_remove_slot(struct sdhci_pci_slot *slot)
 {
-       sdhci_remove_host(slot->host);
+       int dead;
+       u32 scratch;
+
+       dead = 0;
+       scratch = readl(slot->host->ioaddr + SDHCI_INT_STATUS);
+       if (scratch == (u32)-1)
+               dead = 1;
+
+       sdhci_remove_host(slot->host, dead);
 
        if (slot->chip->fixes && slot->chip->fixes->remove_slot)
-               slot->chip->fixes->remove_slot(slot);
+               slot->chip->fixes->remove_slot(slot, dead);
 
        pci_release_region(slot->chip->pdev, slot->pci_bar);
 
index 95b081a..0ab582e 100644 (file)
@@ -712,7 +712,8 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
 
        host->mrq = mrq;
 
-       if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) {
+       if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)
+               || (host->flags & SDHCI_DEVICE_DEAD)) {
                host->mrq->cmd->error = -ENOMEDIUM;
                tasklet_schedule(&host->finish_tasklet);
        } else
@@ -732,6 +733,9 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 
        spin_lock_irqsave(&host->lock, flags);
 
+       if (host->flags & SDHCI_DEVICE_DEAD)
+               goto out;
+
        /*
         * Reset the chip on each power off.
         * Should clear out any weird states.
@@ -770,6 +774,7 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
        if(host->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS)
                sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
 
+out:
        mmiowb();
        spin_unlock_irqrestore(&host->lock, flags);
 }
@@ -784,7 +789,10 @@ static int sdhci_get_ro(struct mmc_host *mmc)
 
        spin_lock_irqsave(&host->lock, flags);
 
-       present = readl(host->ioaddr + SDHCI_PRESENT_STATE);
+       if (host->flags & SDHCI_DEVICE_DEAD)
+               present = 0;
+       else
+               present = readl(host->ioaddr + SDHCI_PRESENT_STATE);
 
        spin_unlock_irqrestore(&host->lock, flags);
 
@@ -801,6 +809,9 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
 
        spin_lock_irqsave(&host->lock, flags);
 
+       if (host->flags & SDHCI_DEVICE_DEAD)
+               goto out;
+
        ier = readl(host->ioaddr + SDHCI_INT_ENABLE);
 
        ier &= ~SDHCI_INT_CARD_INT;
@@ -810,6 +821,7 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
        writel(ier, host->ioaddr + SDHCI_INT_ENABLE);
        writel(ier, host->ioaddr + SDHCI_SIGNAL_ENABLE);
 
+out:
        mmiowb();
 
        spin_unlock_irqrestore(&host->lock, flags);
@@ -875,10 +887,11 @@ static void sdhci_tasklet_finish(unsigned long param)
         * The controller needs a reset of internal state machines
         * upon error conditions.
         */
-       if (mrq->cmd->error ||
-               (mrq->data && (mrq->data->error ||
-               (mrq->data->stop && mrq->data->stop->error))) ||
-               (host->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST)) {
+       if (!(host->flags & SDHCI_DEVICE_DEAD) &&
+               (mrq->cmd->error ||
+                (mrq->data && (mrq->data->error ||
+                 (mrq->data->stop && mrq->data->stop->error))) ||
+                  (host->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST))) {
 
                /* Some controllers need this kick or reset won't work here */
                if (host->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET) {
@@ -1378,15 +1391,34 @@ untasklet:
 
 EXPORT_SYMBOL_GPL(sdhci_add_host);
 
-void sdhci_remove_host(struct sdhci_host *host)
+void sdhci_remove_host(struct sdhci_host *host, int dead)
 {
+       unsigned long flags;
+
+       if (dead) {
+               spin_lock_irqsave(&host->lock, flags);
+
+               host->flags |= SDHCI_DEVICE_DEAD;
+
+               if (host->mrq) {
+                       printk(KERN_ERR "%s: Controller removed during "
+                               " transfer!\n", mmc_hostname(host->mmc));
+
+                       host->mrq->cmd->error = -ENOMEDIUM;
+                       tasklet_schedule(&host->finish_tasklet);
+               }
+
+               spin_unlock_irqrestore(&host->lock, flags);
+       }
+
        mmc_remove_host(host->mmc);
 
 #ifdef CONFIG_LEDS_CLASS
        led_classdev_unregister(&host->led);
 #endif
 
-       sdhci_reset(host, SDHCI_RESET_ALL);
+       if (!dead)
+               sdhci_reset(host, SDHCI_RESET_ALL);
 
        free_irq(host->irq, host);
 
index 7ce12f3..7c30251 100644 (file)
@@ -198,6 +198,7 @@ struct sdhci_host {
        int                     flags;          /* Host attributes */
 #define SDHCI_USE_DMA          (1<<0)          /* Host is DMA capable */
 #define SDHCI_REQ_USE_DMA      (1<<1)          /* Use DMA for this req. */
+#define SDHCI_DEVICE_DEAD      (1<<2)          /* Device unresponsive */
 
        unsigned int            max_clk;        /* Max possible freq (MHz) */
        unsigned int            timeout_clk;    /* Timeout freq (KHz) */
@@ -239,7 +240,7 @@ static inline void *sdhci_priv(struct sdhci_host *host)
 }
 
 extern int sdhci_add_host(struct sdhci_host *host);
-extern void sdhci_remove_host(struct sdhci_host *host);
+extern void sdhci_remove_host(struct sdhci_host *host, int dead);
 
 #ifdef CONFIG_PM
 extern int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state);