Commit 4dffd7a2 authored by Adrian Hunter's avatar Adrian Hunter Committed by Linus Torvalds

omap_hsmmc: prevent races with irq handler

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: default avatarAdrian Hunter <adrian.hunter@nokia.com>
Acked-by: default avatarMatt 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: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 23050103
...@@ -148,6 +148,8 @@ struct mmc_omap_host { ...@@ -148,6 +148,8 @@ struct mmc_omap_host {
struct work_struct mmc_carddetect_work; struct work_struct mmc_carddetect_work;
void __iomem *base; void __iomem *base;
resource_size_t mapbase; resource_size_t mapbase;
spinlock_t irq_lock; /* Prevent races with irq handler */
unsigned long flags;
unsigned int id; unsigned int id;
unsigned int dma_len; unsigned int dma_len;
unsigned int dma_sg_idx; unsigned int dma_sg_idx;
...@@ -459,6 +461,14 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd, ...@@ -459,6 +461,14 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd,
if (host->use_dma) if (host->use_dma)
cmdreg |= DMA_EN; 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, ARG, cmd->arg);
OMAP_HSMMC_WRITE(host->base, CMD, cmdreg); OMAP_HSMMC_WRITE(host->base, CMD, cmdreg);
} }
...@@ -621,11 +631,14 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) ...@@ -621,11 +631,14 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
struct mmc_data *data; struct mmc_data *data;
int end_cmd = 0, end_trans = 0, status; int end_cmd = 0, end_trans = 0, status;
spin_lock(&host->irq_lock);
if (host->mrq == NULL) { if (host->mrq == NULL) {
OMAP_HSMMC_WRITE(host->base, STAT, OMAP_HSMMC_WRITE(host->base, STAT,
OMAP_HSMMC_READ(host->base, STAT)); OMAP_HSMMC_READ(host->base, STAT));
/* Flush posted write */ /* Flush posted write */
OMAP_HSMMC_READ(host->base, STAT); OMAP_HSMMC_READ(host->base, STAT);
spin_unlock(&host->irq_lock);
return IRQ_HANDLED; return IRQ_HANDLED;
} }
...@@ -690,6 +703,8 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) ...@@ -690,6 +703,8 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
if ((end_trans || (status & TC)) && host->mrq) if ((end_trans || (status & TC)) && host->mrq)
mmc_omap_xfer_done(host, data); mmc_omap_xfer_done(host, data);
spin_unlock(&host->irq_lock);
return IRQ_HANDLED; return IRQ_HANDLED;
} }
...@@ -1018,6 +1033,13 @@ static void omap_mmc_request(struct mmc_host *mmc, struct mmc_request *req) ...@@ -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); struct mmc_omap_host *host = mmc_priv(mmc);
int err; 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); WARN_ON(host->mrq != NULL);
host->mrq = req; host->mrq = req;
err = mmc_omap_prepare_data(host, req); err = mmc_omap_prepare_data(host, req);
...@@ -1026,6 +1048,8 @@ static void omap_mmc_request(struct mmc_host *mmc, struct mmc_request *req) ...@@ -1026,6 +1048,8 @@ static void omap_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
if (req->data) if (req->data)
req->data->error = err; req->data->error = err;
host->mrq = NULL; host->mrq = NULL;
if (!in_interrupt())
spin_unlock_irqrestore(&host->irq_lock, host->flags);
mmc_request_done(mmc, req); mmc_request_done(mmc, req);
return; return;
} }
...@@ -1580,6 +1604,7 @@ static int __init omap_mmc_probe(struct platform_device *pdev) ...@@ -1580,6 +1604,7 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
mmc->f_max = 52000000; mmc->f_max = 52000000;
sema_init(&host->sem, 1); sema_init(&host->sem, 1);
spin_lock_init(&host->irq_lock);
host->iclk = clk_get(&pdev->dev, "ick"); host->iclk = clk_get(&pdev->dev, "ick");
if (IS_ERR(host->iclk)) { if (IS_ERR(host->iclk)) {
......
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