diff options
author | Pierre Ossman <drzeus@drzeus.cx> | 2008-04-16 13:13:13 -0400 |
---|---|---|
committer | Pierre Ossman <drzeus@drzeus.cx> | 2008-07-15 08:14:40 -0400 |
commit | 1e72859e3ae16346d4007024b20d2d4ef387dcc3 (patch) | |
tree | 5fc8319ce14b0770546bbbf9a72c90abaf019317 | |
parent | 4489428ab5a49a6f443d9aa17f1d891417787d7b (diff) |
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>
-rw-r--r-- | drivers/mmc/host/sdhci-pci.c | 21 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.c | 48 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.h | 3 |
3 files changed, 58 insertions, 14 deletions
diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c index 5dcb4958e47b..8554466e0f4e 100644 --- a/drivers/mmc/host/sdhci-pci.c +++ b/drivers/mmc/host/sdhci-pci.c | |||
@@ -47,7 +47,7 @@ struct sdhci_pci_fixes { | |||
47 | int (*probe)(struct sdhci_pci_chip*); | 47 | int (*probe)(struct sdhci_pci_chip*); |
48 | 48 | ||
49 | int (*probe_slot)(struct sdhci_pci_slot*); | 49 | int (*probe_slot)(struct sdhci_pci_slot*); |
50 | void (*remove_slot)(struct sdhci_pci_slot*); | 50 | void (*remove_slot)(struct sdhci_pci_slot*, int); |
51 | 51 | ||
52 | int (*suspend)(struct sdhci_pci_chip*, | 52 | int (*suspend)(struct sdhci_pci_chip*, |
53 | pm_message_t); | 53 | pm_message_t); |
@@ -209,8 +209,11 @@ static int jmicron_probe_slot(struct sdhci_pci_slot *slot) | |||
209 | return 0; | 209 | return 0; |
210 | } | 210 | } |
211 | 211 | ||
212 | static void jmicron_remove_slot(struct sdhci_pci_slot *slot) | 212 | static void jmicron_remove_slot(struct sdhci_pci_slot *slot, int dead) |
213 | { | 213 | { |
214 | if (dead) | ||
215 | return; | ||
216 | |||
214 | if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC) | 217 | if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC) |
215 | jmicron_enable_mmc(slot->host, 0); | 218 | jmicron_enable_mmc(slot->host, 0); |
216 | } | 219 | } |
@@ -540,7 +543,7 @@ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot( | |||
540 | 543 | ||
541 | remove: | 544 | remove: |
542 | if (chip->fixes && chip->fixes->remove_slot) | 545 | if (chip->fixes && chip->fixes->remove_slot) |
543 | chip->fixes->remove_slot(slot); | 546 | chip->fixes->remove_slot(slot, 0); |
544 | 547 | ||
545 | unmap: | 548 | unmap: |
546 | iounmap(host->ioaddr); | 549 | iounmap(host->ioaddr); |
@@ -554,10 +557,18 @@ release: | |||
554 | 557 | ||
555 | static void sdhci_pci_remove_slot(struct sdhci_pci_slot *slot) | 558 | static void sdhci_pci_remove_slot(struct sdhci_pci_slot *slot) |
556 | { | 559 | { |
557 | sdhci_remove_host(slot->host); | 560 | int dead; |
561 | u32 scratch; | ||
562 | |||
563 | dead = 0; | ||
564 | scratch = readl(slot->host->ioaddr + SDHCI_INT_STATUS); | ||
565 | if (scratch == (u32)-1) | ||
566 | dead = 1; | ||
567 | |||
568 | sdhci_remove_host(slot->host, dead); | ||
558 | 569 | ||
559 | if (slot->chip->fixes && slot->chip->fixes->remove_slot) | 570 | if (slot->chip->fixes && slot->chip->fixes->remove_slot) |
560 | slot->chip->fixes->remove_slot(slot); | 571 | slot->chip->fixes->remove_slot(slot, dead); |
561 | 572 | ||
562 | pci_release_region(slot->chip->pdev, slot->pci_bar); | 573 | pci_release_region(slot->chip->pdev, slot->pci_bar); |
563 | 574 | ||
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 95b081a9967b..0ab582e77ac2 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c | |||
@@ -712,7 +712,8 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) | |||
712 | 712 | ||
713 | host->mrq = mrq; | 713 | host->mrq = mrq; |
714 | 714 | ||
715 | if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) { | 715 | if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT) |
716 | || (host->flags & SDHCI_DEVICE_DEAD)) { | ||
716 | host->mrq->cmd->error = -ENOMEDIUM; | 717 | host->mrq->cmd->error = -ENOMEDIUM; |
717 | tasklet_schedule(&host->finish_tasklet); | 718 | tasklet_schedule(&host->finish_tasklet); |
718 | } else | 719 | } else |
@@ -732,6 +733,9 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) | |||
732 | 733 | ||
733 | spin_lock_irqsave(&host->lock, flags); | 734 | spin_lock_irqsave(&host->lock, flags); |
734 | 735 | ||
736 | if (host->flags & SDHCI_DEVICE_DEAD) | ||
737 | goto out; | ||
738 | |||
735 | /* | 739 | /* |
736 | * Reset the chip on each power off. | 740 | * Reset the chip on each power off. |
737 | * Should clear out any weird states. | 741 | * Should clear out any weird states. |
@@ -770,6 +774,7 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) | |||
770 | if(host->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS) | 774 | if(host->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS) |
771 | sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); | 775 | sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); |
772 | 776 | ||
777 | out: | ||
773 | mmiowb(); | 778 | mmiowb(); |
774 | spin_unlock_irqrestore(&host->lock, flags); | 779 | spin_unlock_irqrestore(&host->lock, flags); |
775 | } | 780 | } |
@@ -784,7 +789,10 @@ static int sdhci_get_ro(struct mmc_host *mmc) | |||
784 | 789 | ||
785 | spin_lock_irqsave(&host->lock, flags); | 790 | spin_lock_irqsave(&host->lock, flags); |
786 | 791 | ||
787 | present = readl(host->ioaddr + SDHCI_PRESENT_STATE); | 792 | if (host->flags & SDHCI_DEVICE_DEAD) |
793 | present = 0; | ||
794 | else | ||
795 | present = readl(host->ioaddr + SDHCI_PRESENT_STATE); | ||
788 | 796 | ||
789 | spin_unlock_irqrestore(&host->lock, flags); | 797 | spin_unlock_irqrestore(&host->lock, flags); |
790 | 798 | ||
@@ -801,6 +809,9 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) | |||
801 | 809 | ||
802 | spin_lock_irqsave(&host->lock, flags); | 810 | spin_lock_irqsave(&host->lock, flags); |
803 | 811 | ||
812 | if (host->flags & SDHCI_DEVICE_DEAD) | ||
813 | goto out; | ||
814 | |||
804 | ier = readl(host->ioaddr + SDHCI_INT_ENABLE); | 815 | ier = readl(host->ioaddr + SDHCI_INT_ENABLE); |
805 | 816 | ||
806 | ier &= ~SDHCI_INT_CARD_INT; | 817 | ier &= ~SDHCI_INT_CARD_INT; |
@@ -810,6 +821,7 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) | |||
810 | writel(ier, host->ioaddr + SDHCI_INT_ENABLE); | 821 | writel(ier, host->ioaddr + SDHCI_INT_ENABLE); |
811 | writel(ier, host->ioaddr + SDHCI_SIGNAL_ENABLE); | 822 | writel(ier, host->ioaddr + SDHCI_SIGNAL_ENABLE); |
812 | 823 | ||
824 | out: | ||
813 | mmiowb(); | 825 | mmiowb(); |
814 | 826 | ||
815 | spin_unlock_irqrestore(&host->lock, flags); | 827 | spin_unlock_irqrestore(&host->lock, flags); |
@@ -875,10 +887,11 @@ static void sdhci_tasklet_finish(unsigned long param) | |||
875 | * The controller needs a reset of internal state machines | 887 | * The controller needs a reset of internal state machines |
876 | * upon error conditions. | 888 | * upon error conditions. |
877 | */ | 889 | */ |
878 | if (mrq->cmd->error || | 890 | if (!(host->flags & SDHCI_DEVICE_DEAD) && |
879 | (mrq->data && (mrq->data->error || | 891 | (mrq->cmd->error || |
880 | (mrq->data->stop && mrq->data->stop->error))) || | 892 | (mrq->data && (mrq->data->error || |
881 | (host->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST)) { | 893 | (mrq->data->stop && mrq->data->stop->error))) || |
894 | (host->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST))) { | ||
882 | 895 | ||
883 | /* Some controllers need this kick or reset won't work here */ | 896 | /* Some controllers need this kick or reset won't work here */ |
884 | if (host->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET) { | 897 | if (host->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET) { |
@@ -1378,15 +1391,34 @@ untasklet: | |||
1378 | 1391 | ||
1379 | EXPORT_SYMBOL_GPL(sdhci_add_host); | 1392 | EXPORT_SYMBOL_GPL(sdhci_add_host); |
1380 | 1393 | ||
1381 | void sdhci_remove_host(struct sdhci_host *host) | 1394 | void sdhci_remove_host(struct sdhci_host *host, int dead) |
1382 | { | 1395 | { |
1396 | unsigned long flags; | ||
1397 | |||
1398 | if (dead) { | ||
1399 | spin_lock_irqsave(&host->lock, flags); | ||
1400 | |||
1401 | host->flags |= SDHCI_DEVICE_DEAD; | ||
1402 | |||
1403 | if (host->mrq) { | ||
1404 | printk(KERN_ERR "%s: Controller removed during " | ||
1405 | " transfer!\n", mmc_hostname(host->mmc)); | ||
1406 | |||
1407 | host->mrq->cmd->error = -ENOMEDIUM; | ||
1408 | tasklet_schedule(&host->finish_tasklet); | ||
1409 | } | ||
1410 | |||
1411 | spin_unlock_irqrestore(&host->lock, flags); | ||
1412 | } | ||
1413 | |||
1383 | mmc_remove_host(host->mmc); | 1414 | mmc_remove_host(host->mmc); |
1384 | 1415 | ||
1385 | #ifdef CONFIG_LEDS_CLASS | 1416 | #ifdef CONFIG_LEDS_CLASS |
1386 | led_classdev_unregister(&host->led); | 1417 | led_classdev_unregister(&host->led); |
1387 | #endif | 1418 | #endif |
1388 | 1419 | ||
1389 | sdhci_reset(host, SDHCI_RESET_ALL); | 1420 | if (!dead) |
1421 | sdhci_reset(host, SDHCI_RESET_ALL); | ||
1390 | 1422 | ||
1391 | free_irq(host->irq, host); | 1423 | free_irq(host->irq, host); |
1392 | 1424 | ||
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 7ce12f3e7452..7c302515a6a5 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h | |||
@@ -198,6 +198,7 @@ struct sdhci_host { | |||
198 | int flags; /* Host attributes */ | 198 | int flags; /* Host attributes */ |
199 | #define SDHCI_USE_DMA (1<<0) /* Host is DMA capable */ | 199 | #define SDHCI_USE_DMA (1<<0) /* Host is DMA capable */ |
200 | #define SDHCI_REQ_USE_DMA (1<<1) /* Use DMA for this req. */ | 200 | #define SDHCI_REQ_USE_DMA (1<<1) /* Use DMA for this req. */ |
201 | #define SDHCI_DEVICE_DEAD (1<<2) /* Device unresponsive */ | ||
201 | 202 | ||
202 | unsigned int max_clk; /* Max possible freq (MHz) */ | 203 | unsigned int max_clk; /* Max possible freq (MHz) */ |
203 | unsigned int timeout_clk; /* Timeout freq (KHz) */ | 204 | unsigned int timeout_clk; /* Timeout freq (KHz) */ |
@@ -239,7 +240,7 @@ static inline void *sdhci_priv(struct sdhci_host *host) | |||
239 | } | 240 | } |
240 | 241 | ||
241 | extern int sdhci_add_host(struct sdhci_host *host); | 242 | extern int sdhci_add_host(struct sdhci_host *host); |
242 | extern void sdhci_remove_host(struct sdhci_host *host); | 243 | extern void sdhci_remove_host(struct sdhci_host *host, int dead); |
243 | 244 | ||
244 | #ifdef CONFIG_PM | 245 | #ifdef CONFIG_PM |
245 | extern int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state); | 246 | extern int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state); |