Commit f8c58c11 authored by Doug Anderson's avatar Doug Anderson Committed by Ulf Hansson

mmc: dw_mmc: Protect read-modify-write of INTMASK with a lock

We're running into cases where our enabling of the SDIO interrupt in
dw_mmc doesn't actually take effect.  Specifically, adding patch like
this:

 +++ b/drivers/mmc/host/dw_mmc.c
 @@ -1076,6 +1076,9 @@ static void dw_mci_enable_sdio_irq(struct mmc_host *mmc, int enb)

      mci_writel(host, INTMASK,
           (int_mask | SDMMC_INT_SDIO(slot->id)));
 +    int_mask = mci_readl(host, INTMASK);
 +    if (!(int_mask & SDMMC_INT_SDIO(slot->id)))
 +      dev_err(&mmc->class_dev, "failed to enable sdio irq\n");
    } else {

...actually triggers the error message.  That's because the
dw_mci_enable_sdio_irq() unsafely does a read-modify-write of the
INTMASK register.

We can't just use the standard host->lock since that lock is not irq
safe and mmc_signal_sdio_irq() (called from interrupt context) calls
dw_mci_enable_sdio_irq().  Add a new irq-safe lock to protect INTMASK.

An alternate solution to this is to punt mmc_signal_sdio_irq() to the
tasklet and then protect INTMASK modifications by the standard host
lock.  This seemed like a bit more of a high-latency change.
Reported-by: default avatarBing Zhao <bzhao@marvell.com>
Signed-off-by: default avatarDoug Anderson <dianders@chromium.org>
Reviewed-by: default avatarJames Hogan <james.hogan@imgtec.com>
Signed-off-by: default avatarUlf Hansson <ulf.hansson@linaro.org>
parent b24c8b26
...@@ -759,6 +759,7 @@ static void dw_mci_ctrl_rd_thld(struct dw_mci *host, struct mmc_data *data) ...@@ -759,6 +759,7 @@ static void dw_mci_ctrl_rd_thld(struct dw_mci *host, struct mmc_data *data)
static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data) static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data)
{ {
unsigned long irqflags;
int sg_len; int sg_len;
u32 temp; u32 temp;
...@@ -795,9 +796,11 @@ static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data) ...@@ -795,9 +796,11 @@ static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data)
mci_writel(host, CTRL, temp); mci_writel(host, CTRL, temp);
/* Disable RX/TX IRQs, let DMA handle it */ /* Disable RX/TX IRQs, let DMA handle it */
spin_lock_irqsave(&host->irq_lock, irqflags);
temp = mci_readl(host, INTMASK); temp = mci_readl(host, INTMASK);
temp &= ~(SDMMC_INT_RXDR | SDMMC_INT_TXDR); temp &= ~(SDMMC_INT_RXDR | SDMMC_INT_TXDR);
mci_writel(host, INTMASK, temp); mci_writel(host, INTMASK, temp);
spin_unlock_irqrestore(&host->irq_lock, irqflags);
host->dma_ops->start(host, sg_len); host->dma_ops->start(host, sg_len);
...@@ -806,6 +809,7 @@ static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data) ...@@ -806,6 +809,7 @@ static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data)
static void dw_mci_submit_data(struct dw_mci *host, struct mmc_data *data) static void dw_mci_submit_data(struct dw_mci *host, struct mmc_data *data)
{ {
unsigned long irqflags;
u32 temp; u32 temp;
data->error = -EINPROGRESS; data->error = -EINPROGRESS;
...@@ -834,9 +838,12 @@ static void dw_mci_submit_data(struct dw_mci *host, struct mmc_data *data) ...@@ -834,9 +838,12 @@ static void dw_mci_submit_data(struct dw_mci *host, struct mmc_data *data)
host->part_buf_count = 0; host->part_buf_count = 0;
mci_writel(host, RINTSTS, SDMMC_INT_TXDR | SDMMC_INT_RXDR); mci_writel(host, RINTSTS, SDMMC_INT_TXDR | SDMMC_INT_RXDR);
spin_lock_irqsave(&host->irq_lock, irqflags);
temp = mci_readl(host, INTMASK); temp = mci_readl(host, INTMASK);
temp |= SDMMC_INT_TXDR | SDMMC_INT_RXDR; temp |= SDMMC_INT_TXDR | SDMMC_INT_RXDR;
mci_writel(host, INTMASK, temp); mci_writel(host, INTMASK, temp);
spin_unlock_irqrestore(&host->irq_lock, irqflags);
temp = mci_readl(host, CTRL); temp = mci_readl(host, CTRL);
temp &= ~SDMMC_CTRL_DMA_ENABLE; temp &= ~SDMMC_CTRL_DMA_ENABLE;
...@@ -1284,8 +1291,11 @@ static void dw_mci_enable_sdio_irq(struct mmc_host *mmc, int enb) ...@@ -1284,8 +1291,11 @@ static void dw_mci_enable_sdio_irq(struct mmc_host *mmc, int enb)
{ {
struct dw_mci_slot *slot = mmc_priv(mmc); struct dw_mci_slot *slot = mmc_priv(mmc);
struct dw_mci *host = slot->host; struct dw_mci *host = slot->host;
unsigned long irqflags;
u32 int_mask; u32 int_mask;
spin_lock_irqsave(&host->irq_lock, irqflags);
/* Enable/disable Slot Specific SDIO interrupt */ /* Enable/disable Slot Specific SDIO interrupt */
int_mask = mci_readl(host, INTMASK); int_mask = mci_readl(host, INTMASK);
if (enb) if (enb)
...@@ -1293,6 +1303,8 @@ static void dw_mci_enable_sdio_irq(struct mmc_host *mmc, int enb) ...@@ -1293,6 +1303,8 @@ static void dw_mci_enable_sdio_irq(struct mmc_host *mmc, int enb)
else else
int_mask &= ~SDMMC_INT_SDIO(slot->sdio_id); int_mask &= ~SDMMC_INT_SDIO(slot->sdio_id);
mci_writel(host, INTMASK, int_mask); mci_writel(host, INTMASK, int_mask);
spin_unlock_irqrestore(&host->irq_lock, irqflags);
} }
static int dw_mci_execute_tuning(struct mmc_host *mmc, u32 opcode) static int dw_mci_execute_tuning(struct mmc_host *mmc, u32 opcode)
...@@ -2661,6 +2673,7 @@ int dw_mci_probe(struct dw_mci *host) ...@@ -2661,6 +2673,7 @@ int dw_mci_probe(struct dw_mci *host)
host->quirks = host->pdata->quirks; host->quirks = host->pdata->quirks;
spin_lock_init(&host->lock); spin_lock_init(&host->lock);
spin_lock_init(&host->irq_lock);
INIT_LIST_HEAD(&host->queue); INIT_LIST_HEAD(&host->queue);
/* /*
......
...@@ -106,6 +106,11 @@ struct mmc_data; ...@@ -106,6 +106,11 @@ struct mmc_data;
* @cur_slot, @mrq and @state. These must always be updated * @cur_slot, @mrq and @state. These must always be updated
* at the same time while holding @lock. * at the same time while holding @lock.
* *
* @irq_lock is an irq-safe spinlock protecting the INTMASK register
* to allow the interrupt handler to modify it directly. Held for only long
* enough to read-modify-write INTMASK and no other locks are grabbed when
* holding this one.
*
* The @mrq field of struct dw_mci_slot is also protected by @lock, * The @mrq field of struct dw_mci_slot is also protected by @lock,
* and must always be written at the same time as the slot is added to * and must always be written at the same time as the slot is added to
* @queue. * @queue.
...@@ -125,6 +130,7 @@ struct mmc_data; ...@@ -125,6 +130,7 @@ struct mmc_data;
*/ */
struct dw_mci { struct dw_mci {
spinlock_t lock; spinlock_t lock;
spinlock_t irq_lock;
void __iomem *regs; void __iomem *regs;
struct scatterlist *sg; struct scatterlist *sg;
......
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