mirror of
https://github.com/torvalds/linux.git
synced 2024-11-17 17:41:44 +00:00
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: Pierre Ossman <drzeus@drzeus.cx>
This commit is contained in:
parent
4489428ab5
commit
1e72859e3a
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user