From: Sugar Zhang <sugar.zh...@rock-chips.com>

[ Upstream commit 2da254cc7908105a60a6bb219d18e8dced03dcb9 ]

This patch kill instructs the DMAC to immediately terminate
execution of a thread. and then clear the interrupt status,
at last, stop generating interrupts for DMA_SEV. to guarantee
the next dma start is clean. otherwise, one interrupt maybe leave
to next start and make some mistake.

we can reporduce the problem as follows:

DMASEV: modify the event-interrupt resource, and if the INTEN sets
function as interrupt, the DMAC will set irq<event_num> HIGH to
generate interrupt. write INTCLR to clear interrupt.

        DMA EXECUTING INSTRUCTS         DMA TERMINATE
                |                               |
                |                               |
               ...                            _stop
                |                               |
                |                       spin_lock_irqsave
             DMASEV                             |
                |                               |
                |                           mask INTEN
                |                               |
                |                            DMAKILL
                |                               |
                |                       spin_unlock_irqrestore

in above case, a interrupt was left, and if we unmask INTEN, the DMAC
will set irq<event_num> HIGH to generate interrupt.

to fix this, do as follows:

        DMA EXECUTING INSTRUCTS         DMA TERMINATE
                |                               |
                |                               |
               ...                            _stop
                |                               |
                |                       spin_lock_irqsave
             DMASEV                             |
                |                               |
                |                            DMAKILL
                |                               |
                |                          clear INTCLR
                |                           mask INTEN
                |                               |
                |                       spin_unlock_irqrestore

Signed-off-by: Sugar Zhang <sugar.zh...@rock-chips.com>
Signed-off-by: Vinod Koul <vk...@kernel.org>
Signed-off-by: Sasha Levin <sas...@kernel.org>
---
 drivers/dma/pl330.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/drivers/dma/pl330.c b/drivers/dma/pl330.c
index cff1b143fff5d..9b7a49fc76971 100644
--- a/drivers/dma/pl330.c
+++ b/drivers/dma/pl330.c
@@ -966,6 +966,7 @@ static void _stop(struct pl330_thread *thrd)
 {
        void __iomem *regs = thrd->dmac->base;
        u8 insn[6] = {0, 0, 0, 0, 0, 0};
+       u32 inten = readl(regs + INTEN);
 
        if (_state(thrd) == PL330_STATE_FAULT_COMPLETING)
                UNTIL(thrd, PL330_STATE_FAULTING | PL330_STATE_KILLING);
@@ -978,10 +979,13 @@ static void _stop(struct pl330_thread *thrd)
 
        _emit_KILL(0, insn);
 
-       /* Stop generating interrupts for SEV */
-       writel(readl(regs + INTEN) & ~(1 << thrd->ev), regs + INTEN);
-
        _execute_DBGINSN(thrd, insn, is_manager(thrd));
+
+       /* clear the event */
+       if (inten & (1 << thrd->ev))
+               writel(1 << thrd->ev, regs + INTCLR);
+       /* Stop generating interrupts for SEV */
+       writel(inten & ~(1 << thrd->ev), regs + INTEN);
 }
 
 /* Start doing req 'idx' of thread 'thrd' */
-- 
2.20.1

Reply via email to