[PATCH v5 03/10] dmaengine: dw: complete cookies before releasing the channel lock

From: James Hilliard

Date: Mon Nov 02 2026 - 14:35:05 EST


Completion detaches a descriptor from the active list under the channel
lock, but updates its cookie later when freeing it. Termination can take
the lock in between and complete newer descriptors. Completing the older
cookie afterwards moves completed_cookie backwards and makes an already
terminated transfer appear outstanding again.

Complete cookies while detaching descriptors in the completion, error
recovery and termination paths. Keep the active-before-queued ordering
when terminating so completed_cookie advances in submission order.
Descriptor freeing and client notification no longer update cookies.

Fixes: 3bfb1d20b547 ("dmaengine: Driver for the Synopsys DesignWare DMA controller")
Cc: stable@xxxxxxxxxxxxxxx
Reviewed-by: Frank Li <Frank.Li@xxxxxxx>
Signed-off-by: James Hilliard <james.hilliard1@xxxxxxxxx>
---
drivers/dma/dw/core.c | 24 +++++++++++++++---------
1 file changed, 15 insertions(+), 9 deletions(-)

diff --git a/drivers/dma/dw/core.c b/drivers/dma/dw/core.c
index f7ca9da5af90..2d69fab06601 100644
--- a/drivers/dma/dw/core.c
+++ b/drivers/dma/dw/core.c
@@ -231,18 +231,17 @@ static void dwc_dostart_first_queued(struct dw_dma_chan *dwc)
/*----------------------------------------------------------------------*/

static void
-dwc_descriptor_complete(struct dw_dma_chan *dwc, struct dw_desc *desc,
- bool callback_required)
+dwc_descriptor_free(struct dw_dma_chan *dwc, struct dw_desc *desc,
+ bool callback_required)
{
struct dma_async_tx_descriptor *txd = &desc->txd;
struct dw_desc *child;
unsigned long flags;
struct dmaengine_desc_callback cb;

- dev_vdbg(dmaengine_chan_dev(&dwc->chan), "descriptor %u complete\n", txd->cookie);
+ dev_vdbg(dmaengine_chan_dev(&dwc->chan), "freeing descriptor %p\n", desc);

spin_lock_irqsave(&dwc->lock, flags);
- dma_cookie_complete(txd);
if (callback_required)
dmaengine_desc_get_callback(txd, &cb);
else
@@ -329,12 +328,14 @@ static void dwc_scan_descriptors(struct dw_dma *dw, struct dw_dma_chan *dwc)

/* Detach the completed transfer before starting queued work. */
list_splice_init(&dwc->active_list, &list);
+ list_for_each_entry(desc, &list, desc_node)
+ dma_cookie_complete(&desc->txd);
dwc_dostart_first_queued(dwc);
spin_unlock_irqrestore(&dwc->lock, flags);

list_for_each_entry_safe(desc, _desc, &list, desc_node) {
list_del_init(&desc->desc_node);
- dwc_descriptor_complete(dwc, desc, true);
+ dwc_descriptor_free(dwc, desc, true);
}
return;
}
@@ -436,10 +437,11 @@ static void dwc_handle_error(struct dw_dma *dw, struct dw_dma_chan *dwc)
list_for_each_entry(child, &bad_desc->tx_list, desc_node)
dwc_dump_lli(dwc, child);

+ /* Pretend the descriptor completed successfully */
+ dma_cookie_complete(&bad_desc->txd);
spin_unlock_irqrestore(&dwc->lock, flags);

- /* Pretend the descriptor completed successfully */
- dwc_descriptor_complete(dwc, bad_desc, true);
+ dwc_descriptor_free(dwc, bad_desc, true);
return;

unlock:
@@ -940,12 +942,16 @@ static int dwc_terminate_all(struct dma_chan *chan)
/* active_list entries will end up before queued entries */
list_splice_init(&dwc->queue, &list);
list_splice_init(&dwc->active_list, &list);
+ list_for_each_entry(desc, &list, desc_node)
+ dma_cookie_complete(&desc->txd);

spin_unlock_irqrestore(&dwc->lock, flags);

/* Flush all pending and queued descriptors */
- list_for_each_entry_safe(desc, _desc, &list, desc_node)
- dwc_descriptor_complete(dwc, desc, false);
+ list_for_each_entry_safe(desc, _desc, &list, desc_node) {
+ list_del_init(&desc->desc_node);
+ dwc_descriptor_free(dwc, desc, false);
+ }

return 0;
}

--
2.53.0