Commit 2d80667f authored by Tomasz Wlostowski's avatar Tomasz Wlostowski

kernel: fix race condition in DMA irq

parent 4dbf3185
......@@ -208,6 +208,8 @@ struct fmctdc_dev {
struct zio_dma_sgt *zdma;
int dma_chan_mask;
/* interrupt lock to prevent triggering a DMA access while the previous one is still running */
spinlock_t dma_lock;
};
static inline u32 ft_ioread(struct fmctdc_dev *ft, unsigned long addr)
......
......@@ -284,6 +284,19 @@ static void ft_dma_work(struct work_struct *work)
int i, err, transfer, n_block;
unsigned long *loop;
/* The code below prevents a race condition between the ft_dma_work interrupt and
the DMA complete handler. On some systems (in particular multicore) and some patterns
of input timestamps, an IRQ for one of the TDC channels can be triggered while the DMA
transfer is still in progress. In the worst case, the call of zio_dma_alloc_sg few lines below
this comment will overwrite a pointer being used (in parallel CPU core) by ft_irq_handler_dma_complete().
Sometimes this would drop a kernel crash or produce incorrect timestamps.
Since DMA xfers in GN4124 cannot be queued, we prevent this by
a simple, crude spinlock (David, Fede, fixme please ;-) */
if (!spin_trylock(&ft->dma_lock))
return;
irq_stat = ft_ioread(ft, ft->ft_irq_base + TDC_EIC_REG_EIC_ISR);
irq_stat &= TDC_EIC_EIC_IMR_TDC_DMA_MASK;
......@@ -343,6 +356,7 @@ err_alloc:
zio_trigger_abort_disable(&ft->zdev->cset[i], 0);
}
spin_unlock(&ft->dma_lock);
ft_irq_enable_restore(ft);
}
......@@ -453,7 +467,10 @@ static irqreturn_t ft_irq_handler_dma_complete(int irq, void *dev_id)
irq_stat = ft_ioread(ft, ft->ft_dma_eic_base + DMA_EIC_REG_EIC_ISR);
if (!irq_stat)
{
spin_unlock(&ft->dma_lock);
return IRQ_NONE;
}
ft_iowrite(ft, irq_stat, ft->ft_dma_eic_base + TDC_EIC_REG_EIC_ISR);
loop = (unsigned long *) &ft->dma_chan_mask;
......@@ -486,6 +503,8 @@ static irqreturn_t ft_irq_handler_dma_complete(int irq, void *dev_id)
out:
fmc_irq_ack(fmc);
spin_unlock(&ft->dma_lock);
/* Re-Enable interrupts that were disabled in the IRQ handler */
ft_irq_enable_restore(ft);
......@@ -644,6 +663,8 @@ int ft_irq_init(struct fmctdc_dev *ft)
{
int ret;
spin_lock_init(&ft->dma_lock);
ft_irq_coalescing_timeout_set(ft, -1, irq_timeout_ms_default);
ft_irq_coalescing_size_set(ft, -1, 40);
......
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