[PATCH v2 17/19] dmaengine: dw-edma: Detect and recover a stalled eDMA engine

From: Koichiro Den

Date: Thu Jul 23 2026 - 04:47:54 EST


Under sustained multi-channel traffic on one legacy 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.

Start a stall window when the common doorbell path sees a stopped channel
with pending entries. If a later check still finds no progress after 30
ms, reconcile the stopped LLP boundary and queue direction recovery.
Clear the stall state whenever progress advances. Another doorbell-path
check, not a timer, confirms the stall.

Only providers with the recovery operations opt in. Exclude HDMA
compatibility mode because it has no ENGINE_EN. The trigger and timeout
remain empirical and need coverage on other integrations.

Signed-off-by: Koichiro Den <den@xxxxxxxxxxxxx>
---
Changes in v2:
- New patch.

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

diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 14cd0038dbd2..1b9ba34b25c8 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -24,6 +24,7 @@
#include "../dmaengine.h"
#include "../virt-dma.h"

+#define DW_EDMA_LL_STALL_TIMEOUT_MS 30
#define DW_EDMA_ENGINE_QUIESCE_TIMEOUT_MS 250
#define DW_EDMA_ENGINE_RESET_MAX_FAILS 5
#define DW_EDMA_MAX_DIR_CH MAX(HDMA_MAX_WR_CH, HDMA_MAX_RD_CH)
@@ -129,6 +130,7 @@ static void dw_edma_core_reset_ll(struct dw_edma_chan *chan)
chan->ll_head = 0;
chan->ll_done = 0;
dw_edma_ll_irq_idx_discard(chan);
+ 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++)
@@ -183,11 +185,52 @@ static bool dw_edma_ll_advance(struct dw_edma_chan *chan, int idx, u32 *old_done

*old_done = chan->ll_done;
chan->ll_done = idx;
+ chan->ll_stall_valid = false;
chan->ll_recovery_pending = false;

return true;
}

+/*
+ * Called with vc.lock held for a stopped channel with LL work pending. Queue
+ * direction recovery if repeated doorbells show no progress for the stall
+ * timeout.
+ */
+static void dw_edma_ll_stall_check(struct dw_edma_chan *chan)
+{
+ struct dw_edma_engine_recovery *rec;
+
+ /*
+ * 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 (chan->dw->chip->mf == EDMA_MF_HDMA_COMPAT)
+ return;
+
+ if (!chan->dw->core->engine_reset ||
+ !chan->dw->core->engine_enable ||
+ !chan->dw->core->ch_transfer_size)
+ return;
+
+ if (!chan->ll_stall_valid) {
+ chan->ll_stall_valid = true;
+ chan->ll_stall_since = jiffies;
+ return;
+ }
+
+ if (!time_after(jiffies, chan->ll_stall_since +
+ msecs_to_jiffies(DW_EDMA_LL_STALL_TIMEOUT_MS)))
+ return;
+
+ rec = &chan->dw->eng_recovery[chan->dir];
+ chan->ll_recovery_pending = true;
+ if (!READ_ONCE(rec->active))
+ queue_work(chan->dw->wq, &rec->work);
+}
+
static bool dw_edma_ll_pending(struct dw_edma_chan *chan)
{
return dw_edma_core_get_used_num(chan);
@@ -492,10 +535,13 @@ static void dw_edma_core_ch_maybe_doorbell(struct dw_edma_chan *chan)
if (dw_edma_core_ch_status(chan) == DMA_IN_PROGRESS)
return;

+ /* Reconcile a lost tail completion before declaring a stall. */
dw_edma_ll_reconcile_and_refill(chan);
if (!dw_edma_ll_pending(chan))
return;

+ dw_edma_ll_stall_check(chan);
+
dw_edma_core_ch_doorbell(chan);
}

diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index 5520f49a76f1..f67232110f3c 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -104,7 +104,9 @@ struct dw_edma_chan {
bool ll_irq_stopped;
bool ll_irq_requested;

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

--
2.51.0