[PATCH v3 2/9] dmaengine: dw-edma: Terminate all descriptors without callbacks
From: Koichiro Den
Date: Wed Jul 15 2026 - 14:03:38 EST
The DMA Engine client documentation says in the "Terminate APIs" section
of Documentation/driver-api/dmaengine/client.rst:
"No callback functions will be called for any incomplete transfers."
dw-edma instead calls vchan_cookie_complete() when a deferred STOP reaches
the interrupt handler. This schedules a callback for the active descriptor
and leaves other issued or submitted descriptors queued. A late callback
after dmaengine_terminate_sync() can dereference client state that has
already been freed, while leftover descriptors may later restart into
reused buffers or leak.
Move all issued and submitted descriptors to the terminated list whenever
termination completes. For a pending STOP, do this from both the DONE and
ABORT paths. Complete their cookies in order without scheduling callbacks.
A STOP can remain pending until the running transfer raises an interrupt.
Make device_synchronize() wait until the channel has stopped and
terminate_all() deconfigures it before releasing terminated descriptors.
Reuse it from free_chan_resources(), then release the remaining virt-dma
resources. Sleep instead of busy-polling while waiting, and warn if the
existing timeout expires.
Fixes: e63d79d1ffcd ("dmaengine: Add Synopsys eDMA IP core driver")
Signed-off-by: Koichiro Den <den@xxxxxxxxxxxxx>
---
Changes in v3:
- Merge the descriptor cleanup patch so device_synchronize() is safe
when introduced. (Sashiko)
- Handle pending STOP requests from the ABORT path as well.
- Drop a redundant cond_resched() after usleep_range().
- Drop Frank's Reviewed-by tag due to these changes.
- Polish a source comment.
drivers/dma/dw-edma/dw-edma-core.c | 87 ++++++++++++++++++++++++++----
1 file changed, 76 insertions(+), 11 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 89a4c498a17b..44ef5fbe3fd4 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -7,6 +7,7 @@
*/
#include <linux/module.h>
+#include <linux/delay.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/dmaengine.h>
@@ -201,6 +202,35 @@ static int dw_edma_start_transfer(struct dw_edma_chan *chan)
return 1;
}
+static void dw_edma_terminate_vdesc(struct virt_dma_desc *vd)
+{
+ list_del(&vd->node);
+ dma_cookie_complete(&vd->tx);
+ vchan_terminate_vdesc(vd);
+}
+
+static void dw_edma_terminate_vdesc_list(struct list_head *head)
+{
+ struct virt_dma_desc *vd, *_vd;
+
+ list_for_each_entry_safe(vd, _vd, head, node)
+ dw_edma_terminate_vdesc(vd);
+}
+
+/* Must be called with vc.lock held. */
+static void dw_edma_terminate_all_descs(struct dw_edma_chan *chan)
+{
+ /*
+ * This order must not be reversed. Cookies are assigned when
+ * descriptors are submitted, so desc_issued contains older cookies
+ * than desc_submitted. Completing desc_submitted first could move
+ * chan->vc.chan.completed_cookie backwards when desc_issued is
+ * terminated afterwards.
+ */
+ dw_edma_terminate_vdesc_list(&chan->vc.desc_issued);
+ dw_edma_terminate_vdesc_list(&chan->vc.desc_submitted);
+}
+
static void dw_edma_device_caps(struct dma_chan *dchan,
struct dma_slave_caps *caps)
{
@@ -306,20 +336,25 @@ static int dw_edma_device_resume(struct dma_chan *dchan)
static int dw_edma_device_terminate_all(struct dma_chan *dchan)
{
struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ unsigned long flags;
int err = 0;
+ spin_lock_irqsave(&chan->vc.lock, flags);
if (!chan->configured) {
- /* Do nothing */
+ dw_edma_terminate_all_descs(chan);
} else if (chan->status == EDMA_ST_PAUSE) {
+ dw_edma_terminate_all_descs(chan);
chan->status = EDMA_ST_IDLE;
chan->configured = false;
} else if (chan->status == EDMA_ST_IDLE) {
+ dw_edma_terminate_all_descs(chan);
chan->configured = false;
} else if (dw_edma_core_ch_status(chan) == DMA_COMPLETE) {
/*
* The channel is in a false BUSY state, probably didn't
* receive or lost an interrupt
*/
+ dw_edma_terminate_all_descs(chan);
chan->status = EDMA_ST_IDLE;
chan->configured = false;
} else if (chan->request > EDMA_REQ_PAUSE) {
@@ -327,6 +362,7 @@ static int dw_edma_device_terminate_all(struct dma_chan *dchan)
} else {
chan->request = EDMA_REQ_STOP;
}
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
return err;
}
@@ -673,8 +709,7 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
break;
case EDMA_REQ_STOP:
- list_del(&vd->node);
- vchan_cookie_complete(vd);
+ dw_edma_terminate_all_descs(chan);
chan->request = EDMA_REQ_NONE;
chan->status = EDMA_ST_IDLE;
break;
@@ -698,7 +733,9 @@ static void dw_edma_abort_interrupt(struct dw_edma_chan *chan)
spin_lock_irqsave(&chan->vc.lock, flags);
vd = vchan_next_desc(&chan->vc);
- if (vd) {
+ if (vd && chan->request == EDMA_REQ_STOP) {
+ dw_edma_terminate_all_descs(chan);
+ } else if (vd) {
dw_hdma_set_callback_result(vd, DMA_TRANS_ABORTED);
list_del(&vd->node);
vchan_cookie_complete(vd);
@@ -856,21 +893,48 @@ static int dw_edma_alloc_chan_resources(struct dma_chan *dchan)
return 0;
}
-static void dw_edma_free_chan_resources(struct dma_chan *dchan)
+static void dw_edma_wait_termination(struct dma_chan *dchan)
{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
unsigned long timeout = jiffies + msecs_to_jiffies(5000);
- int ret;
+ unsigned long flags;
+ bool configured = true;
+ /*
+ * dw_edma_device_terminate_all() may defer cleanup to a later interrupt
+ * while the channel is still running. Retry until the channel is
+ * deconfigured, which means termination is complete.
+ */
while (time_before(jiffies, timeout)) {
- ret = dw_edma_device_terminate_all(dchan);
- if (!ret)
- break;
+ dw_edma_device_terminate_all(dchan);
- if (time_after_eq(jiffies, timeout))
+ spin_lock_irqsave(&chan->vc.lock, flags);
+ configured = chan->configured;
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
+ if (!configured)
return;
- cpu_relax();
+ usleep_range(1000, 2000);
}
+
+ dev_warn(chan->dw->chip->dev,
+ "timeout waiting for channel termination\n");
+}
+
+static void dw_edma_device_synchronize(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+
+ dw_edma_wait_termination(dchan);
+ vchan_synchronize(&chan->vc);
+}
+
+static void dw_edma_free_chan_resources(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+
+ dw_edma_device_synchronize(dchan);
+ vchan_free_chan_resources(&chan->vc);
}
static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
@@ -968,6 +1032,7 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
dma->device_pause = dw_edma_device_pause;
dma->device_resume = dw_edma_device_resume;
dma->device_terminate_all = dw_edma_device_terminate_all;
+ dma->device_synchronize = dw_edma_device_synchronize;
dma->device_issue_pending = dw_edma_device_issue_pending;
dma->device_tx_status = dw_edma_device_tx_status;
dma->device_prep_slave_sg = dw_edma_device_prep_slave_sg;
--
2.51.0