From 92cd07d23e6332f373c53bbce6a3daa787091f09 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Thu, 8 Oct 2015 16:33:17 -0700 Subject: [PATCH] CHROMIUM: dmaengine: tegra-adma: fix race between tasklet/terminate_all tegra_adma_terminate_all will empty the callback list that gets run in the bottom half of the interrupt handler, tegra_adma_tasklet. But if a callback is in progress, it will still run. For example, this can cause a race when pcm_dmaengine closes a stream and frees resources used by the DMA callback. We need to make sure all tasklets are done before returning from tegra_adma_terminate_all to safely close the stream. BUG=chrome-os-partner:46247 TEST=Audio playback Signed-off-by: Jon Hunter [cfreeman@nvidia.com: applied manually and added commit message] Signed-off-by: Christopher Freeman Change-Id: I5d75990e8efbc008792576bea260a5a4cbe8f2cb Reviewed-on: https://chromium-review.googlesource.com/304920 Commit-Ready: Andrew Bresticker Reviewed-by: Andrew Bresticker --- drivers/dma/tegra210-adma.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/dma/tegra210-adma.c b/drivers/dma/tegra210-adma.c index e8cf06cde7f75..22c7021fd2105 100644 --- a/drivers/dma/tegra210-adma.c +++ b/drivers/dma/tegra210-adma.c @@ -125,6 +125,7 @@ struct tegra_adma_chan { /* ISR handler and tasklet for bottom half of isr handling */ dma_isr_handler isr_handler; struct tasklet_struct tasklet; + bool tasklet_active; dma_async_tx_callback callback; void *callback_param; @@ -594,6 +595,7 @@ static void tegra_adma_tasklet(unsigned long data) int cb_count; spin_lock_irqsave(&tdc->lock, flags); + tdc->tasklet_active = true; while (!list_empty(&tdc->cb_desc)) { dma_desc = list_first_entry(&tdc->cb_desc, typeof(*dma_desc), cb_node); @@ -607,6 +609,7 @@ static void tegra_adma_tasklet(unsigned long data) callback(callback_param); spin_lock_irqsave(&tdc->lock, flags); } + tdc->tasklet_active = false; spin_unlock_irqrestore(&tdc->lock, flags); } @@ -719,7 +722,20 @@ static void tegra_adma_terminate_all(struct dma_chan *dc) sgreq->dma_desc->bytes_transferred += get_current_xferred_count(tdc, sgreq, tc); } + tegra_adma_resume(tdc); + /* + * Check for any running tasklets and kill them + */ + if (tdc->tasklet_active) { + tdc->busy = true; + spin_unlock_irqrestore(&tdc->lock, flags); + dev_warn(tdc2dev(tdc), "Killing tasklet\n"); + tasklet_kill(&tdc->tasklet); + spin_lock_irqsave(&tdc->lock, flags); + tdc->busy = false; + } + skip_dma_stop: tegra_adma_abort_all(tdc);