Re: [PATCH v2 23/64] dmaengine: timb_dma: route callbacks via channel BH

[email protected] Mon, 27 Jul 2026 21:09:14 +0000
Newsgroups org.kernel.vger.dmaengine
Message-ID <[email protected]>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [High] Data race and out-of-bounds read in __td_ier_mask due to lockless traversal of active_list racing with asynchronous workqueue.

Pre-existing issues:
- [High] Premature completion of active transfers due to a race between td_issue_pending and timb_dma_chan_bh.
--

--- Patch [23]: [PATCH v2 23/64] dmaengine: timb_dma: route callbacks via channel BH ---
commit 2a07cb9b339578f9caa75d42037da515bda38de2
Author: Allen Pais <[email protected]>

dmaengine: timb_dma: route callbacks via channel BH

This patch schedules per-channel BH work for completions so callbacks run
through the dma_chan deferral mechanism while keeping the controller
tasklet for IRQ handling.

> diff --git a/drivers/dma/timb_dma.c b/drivers/dma/timb_dma.c
> --- a/drivers/dma/timb_dma.c
> +++ b/drivers/dma/timb_dma.c

[ ... ]

> @@ -580,17 +582,25 @@ static void td_tasklet(struct tasklet_struct *t)
>  	for (i = 0; i < td->dma.chancnt; i++)
>  		if (ipr & (1 << i)) {
>  			struct timb_dma_chan *td_chan = td->channels + i;
> -			spin_lock(&td_chan->lock);
> -			__td_finish(td_chan);
> -			if (!list_empty(&td_chan->queue))
> -				__td_start_next(td_chan);
> -			spin_unlock(&td_chan->lock);
> +			dma_chan_schedule_bh(&td_chan->chan);
>  		}
>  
>  	ier = __td_ier_mask(td);

[Severity: High]
Can this lead to an out-of-bounds memory read?

With completion processing moved to an asynchronous workqueue via
dma_chan_schedule_bh(), td_tasklet() and timb_dma_chan_bh() can now run
concurrently. The tasklet calls __td_ier_mask() right here, which
locklessly evaluates td_chan->ongoing and traverses td_chan->active_list.

If __td_ier_mask() reads ongoing as true right before the workqueue removes
the final descriptor from active_list in __td_finish(), wouldn't list_entry()
calculate a bogus pointer relative to the empty list head? This could cause a
read of unmapped memory or garbage when accessing td_desc->interrupt.

>  	iowrite32(ier, td->membase + TIMBDMA_IER);
>  }
>  
> +static void timb_dma_chan_bh(struct dma_chan *chan)
> +{
> +	struct timb_dma_chan *td_chan = container_of(chan, struct timb_dma_chan,
> +						     chan);
> +
> +	spin_lock(&td_chan->lock);
> +	__td_finish(td_chan);

[Severity: High]
This is a pre-existing issue, but does moving to an asynchronous workqueue
widen a race condition here?

Currently, if td_issue_pending() races with a hardware interrupt, it can
acknowledge the interrupt and complete the transfer before the completion
path runs.

If td_issue_pending() also started the next transfer, this newly scheduled
timb_dma_chan_bh() workqueue will blindly call __td_finish() and complete
whatever is currently in the active_list. By changing the completion from a
tasklet (which had a very short race window) to a scheduled workqueue, the
delay is much longer. Will this result in erroneously aborting and completing
the newly started transfer?

> +	if (!list_empty(&td_chan->queue))
> +		__td_start_next(td_chan);
> +	spin_unlock(&td_chan->lock);
> +}

-- 
Sashiko AI review ยท https://sashiko.dev/#/patchset/[email protected]?part=23