DMA Engine development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Sungho Kim" <sungho.kim@furiosa.ai>
Cc: dmaengine@vger.kernel.org, vkoul@kernel.org, Frank.Li@kernel.org
Subject: Re: [PATCH] dmaengine: pl330: report a failed transfer through callback_result
Date: Wed, 11 Nov 2026 16:17:57 +0000	[thread overview]
Message-ID: <sashiko-outbox-209270@kernel.org> (raw)
In-Reply-To: <20261111152109.1551738-1-sungho.kim@furiosa.ai>

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

      reply	other threads:[~2026-11-11 16:17 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-209270@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=Frank.Li@kernel.org \
    --cc=dmaengine@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=sungho.kim@furiosa.ai \
    --cc=vkoul@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox