DMA Engine development
 help / color / mirror / Atom feed
* [PATCH] dmaengine: pl330: report a failed transfer through callback_result
@ 2026-11-11 15:21 Sungho Kim
  2026-11-11 16:17 ` sashiko-bot
  0 siblings, 1 reply; 2+ messages in thread
From: Sungho Kim @ 2026-11-11 15:21 UTC (permalink / raw)
  To: Vinod Koul; +Cc: Frank Li, dmaengine, linux-kernel, Sungho Kim

A channel fault still completes the descriptor as a success, and the
callback is invoked with a NULL result. Record the outcome and pass
DMA_TRANS_ABORTED to callback_result on a fault, an abort or a rejected
request, reporting it on the last descriptor of the transaction.

Signed-off-by: Sungho Kim <sungho.kim@furiosa.ai>
---
 drivers/dma/pl330.c | 30 +++++++++++++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)

diff --git a/drivers/dma/pl330.c b/drivers/dma/pl330.c
index c2d0b473b..d479d73cb 100644
--- a/drivers/dma/pl330.c
+++ b/drivers/dma/pl330.c
@@ -458,6 +458,12 @@ struct dma_pl330_chan {
 	/* for cyclic capability */
 	bool cyclic;
 
+	/*
+	 * First failure among the completed descriptors of a transaction,
+	 * reported once its last descriptor completes.
+	 */
+	enum dmaengine_tx_result result;
+
 	/* for runtime pm tracking */
 	bool active;
 };
@@ -534,6 +540,8 @@ struct dma_pl330_desc {
 	struct pl330_reqcfg rqcfg;
 
 	enum desc_status status;
+	/* How the transfer of this descriptor ended */
+	enum dmaengine_tx_result result;
 
 	int bytes_requested;
 	bool last;
@@ -1572,6 +1580,8 @@ static void dma_pl330_rqcb(struct dma_pl330_desc *desc, enum pl330_op_err err)
 	spin_lock_irqsave(&pch->lock, flags);
 
 	desc->status = DONE;
+	if (err != PL330_ERR_NONE)
+		desc->result = DMA_TRANS_ABORTED;
 
 	spin_unlock_irqrestore(&pch->lock, flags);
 
@@ -2059,6 +2069,7 @@ static inline void fill_queue(struct dma_pl330_chan *pch)
 		} else {
 			/* Unacceptable request */
 			desc->status = DONE;
+			desc->result = DMA_TRANS_ABORTED;
 			dev_err(pch->dmac->ddma.dev, "%s:%d Bad Desc(%d)\n",
 					__func__, __LINE__, desc->txd.cookie);
 			tasklet_schedule(&pch->task);
@@ -2101,12 +2112,26 @@ static void pl330_tasklet(struct tasklet_struct *t)
 
 	while (!list_empty(&pch->completed_list)) {
 		struct dmaengine_desc_callback cb;
+		struct dmaengine_result res = { .residue = 0 };
 
 		desc = list_first_entry(&pch->completed_list,
 					struct dma_pl330_desc, node);
 
 		dmaengine_desc_get_callback(&desc->txd, &cb);
 
+		/*
+		 * callback_result is only on the last descriptor of a transaction
+		 * (tx_submit copies just callback to the periods of a cyclic
+		 * one), so a failure of an earlier descriptor is held on the
+		 * channel until the last one completes.
+		 */
+		if (pch->result == DMA_TRANS_NOERROR)
+			pch->result = desc->result;
+		res.result = pch->result;
+		if (desc->last)
+			pch->result = DMA_TRANS_NOERROR;
+		desc->result = DMA_TRANS_NOERROR;
+
 		if (pch->cyclic) {
 			desc->status = PREP;
 			list_move_tail(&desc->node, &pch->work_list);
@@ -2126,7 +2151,7 @@ static void pl330_tasklet(struct tasklet_struct *t)
 
 		if (dmaengine_desc_callback_valid(&cb)) {
 			spin_unlock_irqrestore(&pch->lock, flags);
-			dmaengine_desc_callback_invoke(&cb, NULL);
+			dmaengine_desc_callback_invoke(&cb, &res);
 			spin_lock_irqsave(&pch->lock, flags);
 		}
 	}
@@ -2167,6 +2192,7 @@ static int pl330_alloc_chan_resources(struct dma_chan *chan)
 
 	dma_cookie_init(chan);
 	pch->cyclic = false;
+	pch->result = DMA_TRANS_NOERROR;
 
 	pch->thread = pl330_request_channel(pl330);
 	if (!pch->thread) {
@@ -2295,6 +2321,7 @@ static int pl330_terminate_all(struct dma_chan *chan)
 
 	power_down = pch->active;
 	pch->active = false;
+	pch->result = DMA_TRANS_NOERROR;
 
 	/* Mark all desc done */
 	list_for_each_entry(desc, &pch->submitted_list, node) {
@@ -2579,6 +2606,7 @@ static struct dma_pl330_desc *pluck_desc(struct list_head *pool,
 		list_del_init(&desc->node);
 
 		desc->status = PREP;
+		desc->result = DMA_TRANS_NOERROR;
 		desc->txd.callback = NULL;
 		desc->txd.callback_result = NULL;
 	}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH] dmaengine: pl330: report a failed transfer through callback_result
  2026-11-11 15:21 [PATCH] dmaengine: pl330: report a failed transfer through callback_result Sungho Kim
@ 2026-11-11 16:17 ` sashiko-bot
  0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-11-11 16:17 UTC (permalink / raw)
  To: Sungho Kim; +Cc: dmaengine, vkoul, Frank.Li

Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Medium] dmaengine: pl330: out-of-order completion in fill_queue()
  corrupts pch->result
It also noticed 2 related pre-existing issues this patch does not
introduce:
- [High] dmaengine: pl330: synchronous pl330_tasklet() call in
  pl330_issue_pending() [1]
- [Critical] dmaengine: pl330: desc_pool list corruption in
  pl330_terminate_all() [2]
--

commit 5e7b59c03f80710bfb7ee7db9e4f312f02ac2fb2
Author: Sungho Kim <sungho.kim@furiosa.ai>

dmaengine: pl330: report a failed transfer through callback_result

This commit records descriptor transfer failures in the PL330 driver and
reports DMA_TRANS_ABORTED through callback_result when the last descriptor
of a transaction completes.

> diff --git a/drivers/dma/pl330.c b/drivers/dma/pl330.c
> index 977a9fde80adb..72e83dfdcecbb 100644
> --- a/drivers/dma/pl330.c
> +++ b/drivers/dma/pl330.c

[ ... ]

> @@ -2059,6 +2069,7 @@ static inline void fill_queue(struct dma_pl330_chan *pch)
>  		} else {
>  			/* Unacceptable request */
>  			desc->status = DONE;
> +			desc->result = DMA_TRANS_ABORTED;
>  			dev_err(pch->dmac->ddma.dev, "%s:%d Bad Desc(%d)\n",
>  					__func__, __LINE__, desc->txd.cookie);
>  			tasklet_schedule(&pch->task);
> @@ -2101,12 +2112,26 @@ static void pl330_tasklet(struct tasklet_struct *t)
>  
>  	while (!list_empty(&pch->completed_list)) {
>  		struct dmaengine_desc_callback cb;
> +		struct dmaengine_result res = { .residue = 0 };
>  
>  		desc = list_first_entry(&pch->completed_list,
>  					struct dma_pl330_desc, node);
>  
>  		dmaengine_desc_get_callback(&desc->txd, &cb);
>  
> +		/*
> +		 * callback_result is only on the last descriptor of a transaction
> +		 * (tx_submit copies just callback to the periods of a cyclic
> +		 * one), so a failure of an earlier descriptor is held on the
> +		 * channel until the last one completes.
> +		 */
> +		if (pch->result == DMA_TRANS_NOERROR)
> +			pch->result = desc->result;
> +		res.result = pch->result;
> +		if (desc->last)
> +			pch->result = DMA_TRANS_NOERROR;
> +		desc->result = DMA_TRANS_NOERROR;

[Severity: Medium]
Can an out-of-order failure in fill_queue() misattribute pch->result across
descriptors?

When pl330_submit_req() fails with an error other than -EAGAIN (for example
-ENOMEM when a request exceeds the microcode buffer), fill_queue() marks
that descriptor DONE with DMA_TRANS_ABORTED while earlier descriptors on
pch->work_list are still BUSY in the hardware thread:

drivers/dma/pl330.c:pl330_tasklet() {
	list_for_each_entry_safe(desc, _dt, &pch->work_list, node)
		if (desc->status == DONE) {
			if (!pch->cyclic)
				dma_cookie_complete(&desc->txd);
			list_move_tail(&desc->node, &pch->completed_list);
		}
}

pl330_tasklet() moves DONE descriptors to pch->completed_list ahead of
earlier BUSY descriptors.  If an earlier transaction's last descriptor is
still BUSY when the first descriptor of the next transaction is rejected by
fill_queue(), the rejected descriptor completes first and sets
pch->result = DMA_TRANS_ABORTED, which is then reported and cleared when
the earlier transaction finishes without error.  Similarly, within a
multi-descriptor transaction, if the last descriptor is rejected while an
earlier descriptor is BUSY, callback_result runs before the hardware stops
and resets pch->result to DMA_TRANS_NOERROR; if the in-flight descriptor
later faults, pch->result is left stuck at DMA_TRANS_ABORTED for the next
transaction.

>  		if (pch->cyclic) {
>  			desc->status = PREP;
>  			list_move_tail(&desc->node, &pch->work_list);
> @@ -2126,7 +2151,7 @@ static void pl330_tasklet(struct tasklet_struct *t)
>  
>  		if (dmaengine_desc_callback_valid(&cb)) {
>  			spin_unlock_irqrestore(&pch->lock, flags);
> -			dmaengine_desc_callback_invoke(&cb, NULL);
> +			dmaengine_desc_callback_invoke(&cb, &res);
>  			spin_lock_irqsave(&pch->lock, flags);
>  		}
>  	}

[Severity: High]
This problem wasn't introduced by this patch, but pl330_issue_pending()
calls pl330_tasklet() directly in the caller's context [1], running
completion callbacks synchronously inside dma_async_issue_pending():

drivers/dma/pl330.c:pl330_issue_pending() {
	...
	list_splice_tail_init(&pch->submitted_list, &pch->work_list);
	spin_unlock_irqrestore(&pch->lock, flags);

	pl330_tasklet(&pch->task);
}

If a descriptor on pch->work_list was marked DONE by dma_pl330_rqcb() or
rejected by fill_queue(), a client calling dma_async_issue_pending() under
its own lock deadlocks when its completion callback re-acquires that lock:

  spin_lock(&client->lock)
  <hardirq>
    dma_pl330_rqcb()
      desc->status = DONE
      tasklet_schedule(&pch->task)
  dma_async_issue_pending()
    pl330_issue_pending()
      pl330_tasklet(&pch->task)
        dmaengine_desc_callback_invoke()
          client_complete_cb()
            spin_lock(&client->lock)  <-- deadlock

The direct pl330_tasklet() call also races with the scheduled tasklet when
pch->work_list becomes empty, allowing both contexts to set
power_down = true and call pm_runtime_put_autosuspend(), underflowing the
controller's runtime PM usage counter.

[ ... ]

> @@ -2295,6 +2321,7 @@ static int pl330_terminate_all(struct dma_chan *chan)
>  
>  	power_down = pch->active;
>  	pch->active = false;
> +	pch->result = DMA_TRANS_NOERROR;
>  
>  	/* Mark all desc done */
>  	list_for_each_entry(desc, &pch->submitted_list, node) {

[Severity: Critical]
This problem wasn't introduced by this patch, but pl330_terminate_all() and
pl330_free_chan_resources() splice descriptors back into pl330->desc_pool
without holding pl330->pool_lock [2]:

drivers/dma/pl330.c:pl330_terminate_all() {
	...
	list_splice_tail_init(&pch->submitted_list, &pl330->desc_pool);
	list_splice_tail_init(&pch->work_list, &pl330->desc_pool);
	list_splice_tail_init(&pch->completed_list, &pl330->desc_pool);
	spin_unlock_irqrestore(&pch->lock, flags);
	...
}

drivers/dma/pl330.c:pl330_free_chan_resources() {
	...
	spin_lock_irqsave(&pl330->lock, flags);
	...
	if (pch->cyclic)
		list_splice_tail_init(&pch->work_list, &pch->dmac->desc_pool);

	spin_unlock_irqrestore(&pl330->lock, flags);
	...
}

Can this corrupt pl330->desc_pool when another channel concurrently
allocates or frees descriptors under pl330->pool_lock via pluck_desc() or
add_desc()?

CPU0                              CPU1
pl330_terminate_all()             pl330_get_desc()
  spin_lock_irqsave(&pch->lock)     pluck_desc(&pl330->desc_pool,
                                               &pl330->pool_lock)
                                      spin_lock_irqsave(&pl330->pool_lock)
  list_splice_tail_init(...,          list_del_init(&desc->node)
    &pl330->desc_pool)                  <-- concurrent list mutation
                                      spin_unlock_irqrestore(&pl330->pool_lock)
  spin_unlock_irqrestore(&pch->lock)

[1] https://sashiko.dev/#/bug/linux-130356b0-b34d-4ec8-b462-9a2ed651bcf4
[2] https://sashiko.dev/#/bug/linux-ecddac4a-242e-4dfb-895d-8ed9e5e87844

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261111152109.1551738-1-sungho.kim@furiosa.ai?part=1

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-11-11 16:17 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-11 15:21 [PATCH] dmaengine: pl330: report a failed transfer through callback_result Sungho Kim
2026-11-11 16:17 ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox