| From a04f0017c22453613d5f423326b190c61e3b4f98 Mon Sep 17 00:00:00 2001 |
| From: Alex Smith <alex.smith@imgtec.com> |
| Date: Wed, 28 Mar 2018 18:00:43 -0300 |
| Subject: mmc: jz4740: Fix race condition in IRQ mask update |
| |
| From: Alex Smith <alex.smith@imgtec.com> |
| |
| commit a04f0017c22453613d5f423326b190c61e3b4f98 upstream. |
| |
| A spinlock is held while updating the internal copy of the IRQ mask, |
| but not while writing it to the actual IMASK register. After the lock |
| is released, an IRQ can occur before the IMASK register is written. |
| If handling this IRQ causes the mask to be changed, when the handler |
| returns back to the middle of the first mask update, a stale value |
| will be written to the mask register. |
| |
| If this causes an IRQ to become unmasked that cannot have its status |
| cleared by writing a 1 to it in the IREG register, e.g. the SDIO IRQ, |
| then we can end up stuck with the same IRQ repeatedly being fired but |
| not handled. Normally the MMC IRQ handler attempts to clear any |
| unexpected IRQs by writing IREG, but for those that cannot be cleared |
| in this way then the IRQ will just repeatedly fire. |
| |
| This was resulting in lockups after a while of using Wi-Fi on the |
| CI20 (GitHub issue #19). |
| |
| Resolve by holding the spinlock until after the IMASK register has |
| been updated. |
| |
| Cc: stable@vger.kernel.org |
| Link: https://github.com/MIPS/CI20_linux/issues/19 |
| Fixes: 61bfbdb85687 ("MMC: Add support for the controller on JZ4740 SoCs.") |
| Tested-by: Mathieu Malaterre <malat@debian.org> |
| Signed-off-by: Alex Smith <alex.smith@imgtec.com> |
| Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/mmc/host/jz4740_mmc.c | 2 +- |
| 1 file changed, 1 insertion(+), 1 deletion(-) |
| |
| --- a/drivers/mmc/host/jz4740_mmc.c |
| +++ b/drivers/mmc/host/jz4740_mmc.c |
| @@ -368,9 +368,9 @@ static void jz4740_mmc_set_irq_enabled(s |
| host->irq_mask &= ~irq; |
| else |
| host->irq_mask |= irq; |
| - spin_unlock_irqrestore(&host->lock, flags); |
| |
| writew(host->irq_mask, host->base + JZ_REG_MMC_IMASK); |
| + spin_unlock_irqrestore(&host->lock, flags); |
| } |
| |
| static void jz4740_mmc_clock_enable(struct jz4740_mmc_host *host, |