Skip to content

Commit

Permalink
omap_hsmmc: prevent races with irq handler
Browse files Browse the repository at this point in the history
If an unexpected interrupt occurs while preparing the next request, an
oops can occur.

For example, a new request is setting up DMA for data transfer so
host->data is not NULL.  An unexpected transfer complete (TC) interrupt
comes along and the interrupt handler sets host->data to NULL.  Oops!

Prevent that by adding a spinlock.

Signed-off-by: Adrian Hunter <adrian.hunter@nokia.com>
Acked-by: Matt Fleming <matt@console-pimps.org>
Cc: Ian Molton <ian@mnementh.co.uk>
Cc: "Roberto A. Foglietta" <roberto.foglietta@gmail.com>
Cc: Jarkko Lavinen <jarkko.lavinen@nokia.com>
Cc: Denis Karpov <ext-denis.2.karpov@nokia.com>
Cc: Pierre Ossman <pierre@ossman.eu>
Cc: Philip Langdale <philipl@overt.org>
Cc: "Madhusudhan" <madhu.cr@ti.com>
Cc: <linux-mmc@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
  • Loading branch information
Adrian Hunter authored and Linus Torvalds committed Sep 23, 2009
1 parent 2305010 commit 4dffd7a
Showing 1 changed file with 25 additions and 0 deletions.
25 changes: 25 additions & 0 deletions drivers/mmc/host/omap_hsmmc.c
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ struct mmc_omap_host {
struct work_struct mmc_carddetect_work;
void __iomem *base;
resource_size_t mapbase;
spinlock_t irq_lock; /* Prevent races with irq handler */
unsigned long flags;
unsigned int id;
unsigned int dma_len;
unsigned int dma_sg_idx;
Expand Down Expand Up @@ -459,6 +461,14 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd,
if (host->use_dma)
cmdreg |= DMA_EN;

/*
* In an interrupt context (i.e. STOP command), the spinlock is unlocked
* by the interrupt handler, otherwise (i.e. for a new request) it is
* unlocked here.
*/
if (!in_interrupt())
spin_unlock_irqrestore(&host->irq_lock, host->flags);

OMAP_HSMMC_WRITE(host->base, ARG, cmd->arg);
OMAP_HSMMC_WRITE(host->base, CMD, cmdreg);
}
Expand Down Expand Up @@ -621,11 +631,14 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
struct mmc_data *data;
int end_cmd = 0, end_trans = 0, status;

spin_lock(&host->irq_lock);

if (host->mrq == NULL) {
OMAP_HSMMC_WRITE(host->base, STAT,
OMAP_HSMMC_READ(host->base, STAT));
/* Flush posted write */
OMAP_HSMMC_READ(host->base, STAT);
spin_unlock(&host->irq_lock);
return IRQ_HANDLED;
}

Expand Down Expand Up @@ -690,6 +703,8 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
if ((end_trans || (status & TC)) && host->mrq)
mmc_omap_xfer_done(host, data);

spin_unlock(&host->irq_lock);

return IRQ_HANDLED;
}

Expand Down Expand Up @@ -1018,6 +1033,13 @@ static void omap_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
struct mmc_omap_host *host = mmc_priv(mmc);
int err;

/*
* Prevent races with the interrupt handler because of unexpected
* interrupts, but not if we are already in interrupt context i.e.
* retries.
*/
if (!in_interrupt())
spin_lock_irqsave(&host->irq_lock, host->flags);
WARN_ON(host->mrq != NULL);
host->mrq = req;
err = mmc_omap_prepare_data(host, req);
Expand All @@ -1026,6 +1048,8 @@ static void omap_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
if (req->data)
req->data->error = err;
host->mrq = NULL;
if (!in_interrupt())
spin_unlock_irqrestore(&host->irq_lock, host->flags);
mmc_request_done(mmc, req);
return;
}
Expand Down Expand Up @@ -1580,6 +1604,7 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
mmc->f_max = 52000000;

sema_init(&host->sem, 1);
spin_lock_init(&host->irq_lock);

host->iclk = clk_get(&pdev->dev, "ick");
if (IS_ERR(host->iclk)) {
Expand Down

0 comments on commit 4dffd7a

Please sign in to comment.