Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+16 −5
Original line number Diff line number Diff line
@@ -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 @@ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_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)
		slot->chip->fixes->remove_slot(slot);
		slot->chip->fixes->remove_slot(slot, dead);

	pci_release_region(slot->chip->pdev, slot->pci_bar);

+40 −8
Original line number Diff line number Diff line
@@ -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,6 +789,9 @@ static int sdhci_get_ro(struct mmc_host *mmc)

	spin_lock_irqsave(&host->lock, flags);

	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 ||
	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)) {
		   (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,14 +1391,33 @@ int sdhci_add_host(struct sdhci_host *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);

#ifdef CONFIG_LEDS_CLASS
	led_classdev_unregister(&host->led);
#endif

	if (!dead)
		sdhci_reset(host, SDHCI_RESET_ALL);

	free_irq(host->irq, host);
+2 −1
Original line number Diff line number Diff line
@@ -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);