Skip to content

Commit

Permalink
dmaengine: tegra-apb: Improve DMA synchronization
Browse files Browse the repository at this point in the history
Boot CPU0 always handles DMA interrupts and under some rare circumstances
it could stuck in uninterruptible state for a significant time (like in a
case of KASAN + NFS root). In this case sibling CPU, which waits for DMA
transfer completion, will get a DMA transfer timeout. In order to handle
this rare condition, interrupt status needs to be polled until interrupt
is handled.

Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
Link: https://lore.kernel.org/r/20200319212321.3297-2-digetx@gmail.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
  • Loading branch information
Dmitry Osipenko authored and Vinod Koul committed Mar 23, 2020
1 parent 6de88ea commit 6697255
Showing 1 changed file with 25 additions and 0 deletions.
25 changes: 25 additions & 0 deletions drivers/dma/tegra20-apb-dma.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/wait.h>

#include "dmaengine.h"

Expand Down Expand Up @@ -202,6 +203,8 @@ struct tegra_dma_channel {
unsigned int slave_id;
struct dma_slave_config dma_sconfig;
struct tegra_dma_channel_regs channel_reg;

struct wait_queue_head wq;
};

/* tegra_dma: Tegra DMA specific information */
Expand Down Expand Up @@ -680,6 +683,7 @@ static irqreturn_t tegra_dma_isr(int irq, void *dev_id)
tdc_write(tdc, TEGRA_APBDMA_CHAN_STATUS, status);
tdc->isr_handler(tdc, false);
tasklet_schedule(&tdc->tasklet);
wake_up_all(&tdc->wq);
spin_unlock(&tdc->lock);
return IRQ_HANDLED;
}
Expand Down Expand Up @@ -785,6 +789,7 @@ static int tegra_dma_terminate_all(struct dma_chan *dc)
tegra_dma_resume(tdc);

pm_runtime_put(tdc->tdma->dev);
wake_up_all(&tdc->wq);

skip_dma_stop:
tegra_dma_abort_all(tdc);
Expand All @@ -800,10 +805,29 @@ static int tegra_dma_terminate_all(struct dma_chan *dc)
return 0;
}

static bool tegra_dma_eoc_interrupt_deasserted(struct tegra_dma_channel *tdc)
{
unsigned long flags;
u32 status;

spin_lock_irqsave(&tdc->lock, flags);
status = tdc_read(tdc, TEGRA_APBDMA_CHAN_STATUS);
spin_unlock_irqrestore(&tdc->lock, flags);

return !(status & TEGRA_APBDMA_STATUS_ISE_EOC);
}

static void tegra_dma_synchronize(struct dma_chan *dc)
{
struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc);

/*
* CPU, which handles interrupt, could be busy in
* uninterruptible state, in this case sibling CPU
* should wait until interrupt is handled.
*/
wait_event(tdc->wq, tegra_dma_eoc_interrupt_deasserted(tdc));

tasklet_kill(&tdc->tasklet);
}

Expand Down Expand Up @@ -1498,6 +1522,7 @@ static int tegra_dma_probe(struct platform_device *pdev)
tasklet_init(&tdc->tasklet, tegra_dma_tasklet,
(unsigned long)tdc);
spin_lock_init(&tdc->lock);
init_waitqueue_head(&tdc->wq);

INIT_LIST_HEAD(&tdc->pending_sg_req);
INIT_LIST_HEAD(&tdc->free_sg_req);
Expand Down

0 comments on commit 6697255

Please sign in to comment.