Commit 1e72859e authored by Pierre Ossman's avatar Pierre Ossman

sdhci: handle hot-remove

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: default avatarPierre Ossman <drzeus@drzeus.cx>
parent 4489428a
...@@ -47,7 +47,7 @@ struct sdhci_pci_fixes { ...@@ -47,7 +47,7 @@ struct sdhci_pci_fixes {
int (*probe)(struct sdhci_pci_chip*); int (*probe)(struct sdhci_pci_chip*);
int (*probe_slot)(struct sdhci_pci_slot*); 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*, int (*suspend)(struct sdhci_pci_chip*,
pm_message_t); pm_message_t);
...@@ -209,8 +209,11 @@ static int jmicron_probe_slot(struct sdhci_pci_slot *slot) ...@@ -209,8 +209,11 @@ static int jmicron_probe_slot(struct sdhci_pci_slot *slot)
return 0; 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) if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC)
jmicron_enable_mmc(slot->host, 0); jmicron_enable_mmc(slot->host, 0);
} }
...@@ -540,7 +543,7 @@ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot( ...@@ -540,7 +543,7 @@ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot(
remove: remove:
if (chip->fixes && chip->fixes->remove_slot) if (chip->fixes && chip->fixes->remove_slot)
chip->fixes->remove_slot(slot); chip->fixes->remove_slot(slot, 0);
unmap: unmap:
iounmap(host->ioaddr); iounmap(host->ioaddr);
...@@ -554,10 +557,18 @@ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot( ...@@ -554,10 +557,18 @@ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot(
static void sdhci_pci_remove_slot(struct sdhci_pci_slot *slot) 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) 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); pci_release_region(slot->chip->pdev, slot->pci_bar);
......
...@@ -712,7 +712,8 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) ...@@ -712,7 +712,8 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
host->mrq = 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; host->mrq->cmd->error = -ENOMEDIUM;
tasklet_schedule(&host->finish_tasklet); tasklet_schedule(&host->finish_tasklet);
} else } else
...@@ -732,6 +733,9 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) ...@@ -732,6 +733,9 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
spin_lock_irqsave(&host->lock, flags); spin_lock_irqsave(&host->lock, flags);
if (host->flags & SDHCI_DEVICE_DEAD)
goto out;
/* /*
* Reset the chip on each power off. * Reset the chip on each power off.
* Should clear out any weird states. * Should clear out any weird states.
...@@ -770,6 +774,7 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) ...@@ -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) if(host->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS)
sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
out:
mmiowb(); mmiowb();
spin_unlock_irqrestore(&host->lock, flags); spin_unlock_irqrestore(&host->lock, flags);
} }
...@@ -784,6 +789,9 @@ static int sdhci_get_ro(struct mmc_host *mmc) ...@@ -784,6 +789,9 @@ static int sdhci_get_ro(struct mmc_host *mmc)
spin_lock_irqsave(&host->lock, flags); spin_lock_irqsave(&host->lock, flags);
if (host->flags & SDHCI_DEVICE_DEAD)
present = 0;
else
present = readl(host->ioaddr + SDHCI_PRESENT_STATE); present = readl(host->ioaddr + SDHCI_PRESENT_STATE);
spin_unlock_irqrestore(&host->lock, flags); spin_unlock_irqrestore(&host->lock, flags);
...@@ -801,6 +809,9 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) ...@@ -801,6 +809,9 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
spin_lock_irqsave(&host->lock, flags); spin_lock_irqsave(&host->lock, flags);
if (host->flags & SDHCI_DEVICE_DEAD)
goto out;
ier = readl(host->ioaddr + SDHCI_INT_ENABLE); ier = readl(host->ioaddr + SDHCI_INT_ENABLE);
ier &= ~SDHCI_INT_CARD_INT; ier &= ~SDHCI_INT_CARD_INT;
...@@ -810,6 +821,7 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) ...@@ -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_INT_ENABLE);
writel(ier, host->ioaddr + SDHCI_SIGNAL_ENABLE); writel(ier, host->ioaddr + SDHCI_SIGNAL_ENABLE);
out:
mmiowb(); mmiowb();
spin_unlock_irqrestore(&host->lock, flags); spin_unlock_irqrestore(&host->lock, flags);
...@@ -875,10 +887,11 @@ static void sdhci_tasklet_finish(unsigned long param) ...@@ -875,10 +887,11 @@ static void sdhci_tasklet_finish(unsigned long param)
* The controller needs a reset of internal state machines * The controller needs a reset of internal state machines
* upon error conditions. * upon error conditions.
*/ */
if (mrq->cmd->error || if (!(host->flags & SDHCI_DEVICE_DEAD) &&
(mrq->cmd->error ||
(mrq->data && (mrq->data->error || (mrq->data && (mrq->data->error ||
(mrq->data->stop && mrq->data->stop->error))) || (mrq->data->stop && mrq->data->stop->error))) ||
(host->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST)) { (host->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST))) {
/* Some controllers need this kick or reset won't work here */ /* Some controllers need this kick or reset won't work here */
if (host->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET) { if (host->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET) {
...@@ -1378,14 +1391,33 @@ int sdhci_add_host(struct sdhci_host *host) ...@@ -1378,14 +1391,33 @@ int sdhci_add_host(struct sdhci_host *host)
EXPORT_SYMBOL_GPL(sdhci_add_host); 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); mmc_remove_host(host->mmc);
#ifdef CONFIG_LEDS_CLASS #ifdef CONFIG_LEDS_CLASS
led_classdev_unregister(&host->led); led_classdev_unregister(&host->led);
#endif #endif
if (!dead)
sdhci_reset(host, SDHCI_RESET_ALL); sdhci_reset(host, SDHCI_RESET_ALL);
free_irq(host->irq, host); free_irq(host->irq, host);
......
...@@ -198,6 +198,7 @@ struct sdhci_host { ...@@ -198,6 +198,7 @@ struct sdhci_host {
int flags; /* Host attributes */ int flags; /* Host attributes */
#define SDHCI_USE_DMA (1<<0) /* Host is DMA capable */ #define SDHCI_USE_DMA (1<<0) /* Host is DMA capable */
#define SDHCI_REQ_USE_DMA (1<<1) /* Use DMA for this req. */ #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 max_clk; /* Max possible freq (MHz) */
unsigned int timeout_clk; /* Timeout freq (KHz) */ unsigned int timeout_clk; /* Timeout freq (KHz) */
...@@ -239,7 +240,7 @@ static inline void *sdhci_priv(struct sdhci_host *host) ...@@ -239,7 +240,7 @@ static inline void *sdhci_priv(struct sdhci_host *host)
} }
extern int sdhci_add_host(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 #ifdef CONFIG_PM
extern int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state); extern int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment