[PATCH v3 23/24] dmaengine: dw-edma: Detect and recover a stalled eDMA engine

From: Koichiro Den

Date: Mon Jul 27 2026 - 13:11:28 EST


Under sustained multi-channel traffic on one eDMA integration, a write
channel occasionally stopped with pending work, no progress, and no
error. Repeated doorbells did not restart it; clearing ENGINE_EN did.

LL progress accounting already schedules a bounded recheck when a DONE
or STOP/PAUSE drain leaves published work running. Reuse it for passive
stall detection. When a check finds the channel stopped, run the normal
restart or drain path, kick it, and arm one more check. If it is still
stopped with no progress 30 ms later, queue direction recovery.

Queue every recovery request and let the workqueue coalesce duplicates,
so a request raised while the worker releases channels is not lost.

The condition uses only generic eDMA state: published work, STOPPED
status, and lack of progress. Providers opt in by supplying the recovery
operations; HDMA compatibility mode is excluded because it has no
ENGINE_EN. Normal progress cancels the check before recovery runs. The
30 ms threshold is empirical and should be validated on more hardware.

Signed-off-by: Koichiro Den <den@xxxxxxxxxxxxx>
---
Changes in v3:
- Reuse the bounded LL recheck for stall detection. (Sashiko)
- Queue a recovery request even while recovery is active, leaving
duplicate coalescing to the workqueue. (Sashiko)

drivers/dma/dw-edma/dw-edma-core.c | 55 ++++++++++++++++++++++++++++++
drivers/dma/dw-edma/dw-edma-core.h | 4 ++-
2 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 56032137a4bb..262ea893174f 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -250,6 +250,7 @@ static void dw_edma_core_reset_ll(struct dw_edma_chan *chan)

chan->ll_head = 0;
chan->ll_done = 0;
+ chan->ll_stall_valid = false;
chan->ll_recovery_pending = false;
/* Drop stale CB bits before reusing the circular LL ring. */
for (i = 0; i < chan->ll_max; i++)
@@ -291,6 +292,54 @@ static bool dw_edma_ll_pending(struct dw_edma_chan *chan)
return chan->ll_head != chan->ll_done;
}

+static bool dw_edma_engine_recovery_supported(struct dw_edma *dw)
+{
+ /*
+ * DWC PCIe Controller Databook 6.10a-lca06, "Legacy DMA and HDMA
+ * Software Compatibility":
+ *
+ * "HDMA does not implement engine enable, that is
+ * DMA_[WRITE|READ]_ENGINE_EN_OFF.DMA_[WRITE|READ]_ENGINE field."
+ */
+ if (dw->chip->mf == EDMA_MF_HDMA_COMPAT)
+ return false;
+
+ return dw->core->engine_reset && dw->core->engine_enable &&
+ dw->core->ch_transfer_size;
+}
+
+/*
+ * Called with vc.lock held for a stopped channel with LL work pending. Queue
+ * direction recovery if repeated doorbells show no progress for one recheck
+ * interval.
+ */
+static bool dw_edma_ll_stall_check(struct dw_edma_chan *chan)
+{
+ struct dw_edma_engine_recovery *rec;
+
+ if (unlikely(READ_ONCE(chan->dw->teardown)))
+ return false;
+
+ if (!dw_edma_engine_recovery_supported(chan->dw))
+ return false;
+
+ if (!chan->ll_stall_valid) {
+ chan->ll_stall_valid = true;
+ chan->ll_stall_since = jiffies;
+ return true;
+ }
+
+ if (time_before(jiffies, chan->ll_stall_since +
+ msecs_to_jiffies(DW_EDMA_LL_RECHECK_DELAY_MS)))
+ return true;
+
+ rec = &chan->dw->eng_recovery[chan->dir];
+ chan->ll_recovery_pending = true;
+ dw_edma_engine_recovery_queue(rec);
+
+ return false;
+}
+
static u32 dw_edma_core_ch_transfer_size(struct dw_edma_chan *chan)
{
if (!chan->dw->core->ch_transfer_size)
@@ -563,6 +612,7 @@ static bool dw_edma_ll_clean_pending(struct dw_edma_chan *chan, int idx)
out:
if (advanced) {
dw_edma_ll_recheck_cancel(chan);
+ chan->ll_stall_valid = false;
chan->ll_recovery_pending = false;
}

@@ -720,6 +770,8 @@ static void dw_edma_core_ch_kick(struct dw_edma_chan *chan)
*/
static bool dw_edma_core_ch_maybe_doorbell(struct dw_edma_chan *chan)
{
+ bool recheck;
+
if (chan->non_ll ||
chan->ll_recovering ||
chan->status != EDMA_ST_BUSY ||
@@ -754,7 +806,10 @@ static bool dw_edma_core_ch_maybe_doorbell(struct dw_edma_chan *chan)
}
}

+ recheck = dw_edma_ll_stall_check(chan);
dw_edma_core_ch_kick(chan);
+ if (recheck)
+ dw_edma_ll_recheck_schedule(chan);

return false;
}
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index a2699a663260..0c9c17edd835 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -127,9 +127,11 @@ struct dw_edma_chan {
bool abort_pending;
raw_spinlock_t event_lock;

+ /* Per-channel stall and recovery state. */
struct delayed_work ll_recheck_work;
unsigned long ll_recheck_at;
- /* Per-channel recovery state. */
+ unsigned long ll_stall_since;
+ bool ll_stall_valid;
bool ll_recovery_pending;
bool ll_recovering;

--
2.51.0