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

From: Koichiro Den

Date: Sat Nov 07 2026 - 08:32:54 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 v7:
- Resample LLP and stop status if stopped to catch progress made after
the first LLP read (follow-up to Sashiko's review).

@Frank: Dropped your R-b due to the change; would appreciate another
look.

drivers/dma/dw-edma/dw-edma-core.c | 45 ++++++++++++++++++++++++++++--
1 file changed, 43 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..0c8b9545a5ba 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -146,6 +146,44 @@ 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 int dw_edma_ll_read_idx(struct dw_edma_chan *chan, bool *stopped)
+{
+ int idx;
+
+ /* Sample LLP first in case the engine starts between the reads. */
+ idx = dw_edma_core_ll_cur_idx(chan);
+ *stopped = dw_edma_ll_stopped(chan);
+ if (*stopped) {
+ /*
+ * Re-read once to catch the final LLP if the channel remains
+ * stopped. If it restarts, later interrupts will report
+ * further progress.
+ */
+ idx = dw_edma_core_ll_cur_idx(chan);
+ *stopped = dw_edma_ll_stopped(chan);
+ }
+
+ return idx;
+}
+
+static bool dw_edma_ll_drained(struct dw_edma_chan *chan)
+{
+ bool stopped;
+ int idx;
+
+ idx = dw_edma_ll_read_idx(chan, &stopped);
+ if (idx == chan->ll_max)
+ idx = 0;
+
+ return stopped && idx >= 0 && (u32)idx == chan->ll_head;
+}
+
static void dw_edma_core_ll_start(struct dw_edma_desc *desc)
{
struct dw_edma_chan *chan = desc->chan;
@@ -455,7 +493,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 +830,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