[PATCH] ARM: dma: imx: Fix deadlock bug
Russell King - ARM Linux
linux at arm.linux.org.uk
Sat Jun 8 17:15:57 EDT 2013
On Sun, Jun 09, 2013 at 12:56:28AM +0400, Alexander Shiyan wrote:
> @@ -625,8 +613,9 @@ static void imxdma_tasklet(unsigned long data)
> struct imxdma_channel *imxdmac = (void *)data;
> struct imxdma_engine *imxdma = imxdmac->imxdma;
> struct imxdma_desc *desc;
> + unsigned long flags;
>
> - spin_lock(&imxdma->lock);
> + spin_lock_irqsave(&imxdma->lock, flags);
>
> if (list_empty(&imxdmac->ld_active)) {
> /* Someone might have called terminate all */
> @@ -663,7 +652,7 @@ static void imxdma_tasklet(unsigned long data)
> __func__, imxdmac->channel);
> }
> out:
> - spin_unlock(&imxdma->lock);
> + spin_unlock_irqrestore(&imxdma->lock, flags);
So, I just peeked at this driver. Who is reviewing DMA engine drivers
and why aren't they reviewing stuff properly.
static void imxdma_tasklet(unsigned long data)
{
...
spin_lock(&imxdma->lock);
...
if (desc->desc.callback)
desc->desc.callback(desc->desc.callback_param);
...
out:
spin_unlock(&imxdma->lock);
}
This stuff is well known and even documented:
For slave DMA, the subsequent transaction may not be available
for submission prior to callback function being invoked, so
slave DMA callbacks are permitted to prepare and submit a new
transaction.
For cyclic DMA, a callback function may wish to terminate the
DMA via dmaengine_terminate_all().
Therefore, it is important that DMA engine drivers drop any
locks before calling the callback function which may cause a
deadlock.
Note that callbacks will always be invoked from the DMA
engines tasklet, never from interrupt context.
Note the 3rd paragraph - and note that the IMX driver violates this
by holding that spinlock. Changing that spinlock to be an irq-saving
type just makes the problem worse.
What's more is it takes this same lock in the submit and issue_pending
functions - so this is potential deadlock territory by the mere fact
that a callback function _can_ invoke these two functions.
It also makes use of __memzero. Don't. Use memset().
Doesn't check the return value of clk_prepare_enable().
Mixes up accessors and direct accesses:
imxdmac->sg_list[i].dma_address = dma_addr;
sg_dma_len(&imxdmac->sg_list[i]) = period_len;
The first should be accessed via sg_dma_address().
Reimplements much of virt-dma.c/.h - maybe it should use this support
instead? As an added benefit you'll be able to start the next queued
request without the tasklet and get better throughput as a result -
and process all completed transfers upon tasklet invocation.
More information about the linux-arm-kernel
mailing list