Message ID | 1467884151-26667-1-git-send-email-l.stach@pengutronix.de (mailing list archive) |
---|---|
State | Accepted |
Headers | show |
On Thu, Jul 07, 2016 at 11:35:51AM +0200, Lucas Stach wrote: > From: Michael Olbrich <m.olbrich@pengutronix.de> > > Currently the handler ignores the channel 0 interrupt and thus doesn't ack > it properly. This is done in order to allow sdma_run_channel0() to poll > on the irq status bit, as this function may be called in atomic context, > but needs to know when the channel has finished. > > This works mostly, as the polling happens under a spinlock, disabling IRQs > on the local CPU, leaving only a very slight race window for a spurious > IRQ to happen if the handler is executed on another CPU in an SMP system. > Still this is clearly suboptimal. > This behavior turns into a real problem on an RT system, where the spinlock > doesn't disable IRQs on the local CPU. Not acking the IRQ in the handler > in such a setup is very likely to drown the CPU in an IRQ storm, leaving > it unable to make any progress in the polling loop, leading to the IRQ > never being acked. > > Fix this by properly acknowledging the channel 0 IRQ in the handler. > As the IRQ status bit can no longer be used to poll for the channel > completion, switch over to using the SDMA_H_STATSTOP register for this > purpose, where bit 0 is cleared by the hardware when the channel is done. Applied, thanks
diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c index 0f6fd42f55ca..ce865f68a8c7 100644 --- a/drivers/dma/imx-sdma.c +++ b/drivers/dma/imx-sdma.c @@ -18,6 +18,7 @@ */ #include <linux/init.h> +#include <linux/iopoll.h> #include <linux/module.h> #include <linux/types.h> #include <linux/bitops.h> @@ -571,28 +572,20 @@ static void sdma_enable_channel(struct sdma_engine *sdma, int channel) static int sdma_run_channel0(struct sdma_engine *sdma) { int ret; - unsigned long timeout = 500; + u32 reg; sdma_enable_channel(sdma, 0); - while (!(ret = readl_relaxed(sdma->regs + SDMA_H_INTR) & 1)) { - if (timeout-- <= 0) - break; - udelay(1); - } - - if (ret) { - /* Clear the interrupt status */ - writel_relaxed(ret, sdma->regs + SDMA_H_INTR); - } else { + ret = readl_relaxed_poll_timeout_atomic(sdma->regs + SDMA_H_STATSTOP, + reg, !(reg & 1), 1, 500); + if (ret) dev_err(sdma->dev, "Timeout waiting for CH0 ready\n"); - } /* Set bits of CONFIG register with dynamic context switching */ if (readl(sdma->regs + SDMA_H_CONFIG) == 0) writel_relaxed(SDMA_H_CONFIG_CSM, sdma->regs + SDMA_H_CONFIG); - return ret ? 0 : -ETIMEDOUT; + return ret; } static int sdma_load_script(struct sdma_engine *sdma, void *buf, int size, @@ -727,9 +720,9 @@ static irqreturn_t sdma_int_handler(int irq, void *dev_id) unsigned long stat; stat = readl_relaxed(sdma->regs + SDMA_H_INTR); - /* not interested in channel 0 interrupts */ - stat &= ~1; writel_relaxed(stat, sdma->regs + SDMA_H_INTR); + /* channel 0 is special and not handled here, see run_channel0() */ + stat &= ~1; while (stat) { int channel = fls(stat) - 1;