[PATCH v7 2/7] dmaengine: dw-edma: Avoid resetting an LL on stale stopped status

From: Koichiro Den

Date: Wed Nov 04 2026 - 11:25:18 EST


CS can still report STOPPED briefly after a doorbell write, before the
DMA engine starts consuming the published LL entries. Finishing
termination relying solely on that value can reset the ring while a
transfer is about to start, leaving hardware and software ring positions
out of sync and stalling subsequent transfers.

Require LLP to reach the published tail, on top of the standard criteria
(ie. CS=STOPPED and zero transfer size) before
dw_edma_finish_termination(). Non-LL mode behavior is unchanged.

Cc: stable@xxxxxxxxxxxxxxx
Fixes: db79c4e264ef ("dmaengine: dw-edma: Make DMA link list work as a circular buffer")
Signed-off-by: Koichiro Den <den@xxxxxxxxxxxxx>
---
Changes from v6:
- New patch. Resolve issues found during terminate/pause test under
dma load.

drivers/dma/dw-edma/dw-edma-core.c | 24 ++++++++++++++++++++++--
1 file changed, 22 insertions(+), 2 deletions(-)

diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index b08bb31657c9..fb38d51037c5 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -146,6 +146,23 @@ static bool dw_edma_ll_pending(struct dw_edma_chan *chan)
return chan->ll_head != chan->ll_done;
}

+static bool dw_edma_ll_stopped(struct dw_edma_chan *chan)
+{
+ return dw_edma_core_ch_status(chan) == DMA_COMPLETE &&
+ !dw_edma_core_ch_transfer_size(chan);
+}
+
+static bool dw_edma_ll_drained(struct dw_edma_chan *chan)
+{
+ int idx = dw_edma_core_ll_cur_idx(chan);
+
+ if (idx == chan->ll_max)
+ idx = 0;
+
+ return idx >= 0 && (u32)idx == chan->ll_head &&
+ dw_edma_ll_stopped(chan);
+}
+
static void dw_edma_core_ll_start(struct dw_edma_desc *desc)
{
struct dw_edma_chan *chan = desc->chan;
@@ -455,7 +472,9 @@ static int dw_edma_device_terminate_all(struct dma_chan *dchan)
dw_edma_finish_termination(chan);
} else if (chan->status == EDMA_ST_IDLE) {
dw_edma_finish_termination(chan);
- } else if (dw_edma_core_ch_status(chan) == DMA_COMPLETE) {
+ } else if (chan->non_ll ?
+ dw_edma_core_ch_status(chan) == DMA_COMPLETE :
+ dw_edma_ll_drained(chan)) {
/*
* The channel is in a false BUSY state, probably didn't
* receive or lost an interrupt
@@ -790,7 +809,8 @@ static void dw_edma_done_interrupt_locked(struct dw_edma_chan *chan)
if (!vd)
break;

- dw_edma_finish_termination(chan);
+ if (chan->non_ll || dw_edma_ll_drained(chan))
+ dw_edma_finish_termination(chan);
break;

default:
--
2.54.0