DMA Engine development
 help / color / mirror / Atom feed
From: Frank Li <Frank.li@oss.nxp.com>
To: Koichiro Den <den@valinux.co.jp>
Cc: "Manivannan Sadhasivam" <mani@kernel.org>,
	"Vinod Koul" <vkoul@kernel.org>, "Frank Li" <Frank.Li@kernel.org>,
	"Gustavo Pimentel" <Gustavo.Pimentel@synopsys.com>,
	"Kees Cook" <kees@kernel.org>,
	"Krzysztof Wilczyński" <kwilczynski@kernel.org>,
	"Kishon Vijay Abraham I" <kishon@kernel.org>,
	"Bjorn Helgaas" <bhelgaas@google.com>,
	"Christoph Hellwig" <hch@lst.de>,
	"Serge Semin" <fancer.lancer@gmail.com>,
	"Cai Huoqing" <cai.huoqing@linux.dev>,
	"Niklas Cassel" <cassel@kernel.org>,
	"Devendra K Verma" <devendra.verma@amd.com>,
	dmaengine@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH v3 04/24] dmaengine: dw-edma: Make DMA link list work as a circular buffer
Date: Mon, 27 Jul 2026 15:09:17 -0400	[thread overview]
Message-ID: <ames3ZMAS4CBh0zm@lizhi-Precision-Tower-5810> (raw)
In-Reply-To: <20260727170323.2321369-5-den@valinux.co.jp>

On Tue, Jul 28, 2026 at 02:03:03AM +0900, Koichiro Den wrote:
> From: Frank Li <Frank.Li@nxp.com>
>
> The driver currently rebuilds the whole linked list for every transfer.
>
> Use it as a circular ring instead. Append entries at ll_head with the
> current cycle bit, and reserve the final entry for the link back to the
> start.
>
> Clear control words before first use so stale cycle bits cannot become
> valid entries. Reject rings without usable data slots and, until reclaim
> support lands, descriptors that exceed the usable ring capacity.
>
> Termination and abort can discard descriptors while ll_done still trails
> ll_head. Reset the ring after the channel has stopped so the next transfer
> does not inherit occupied slots.
>
> This prepares the driver for appending requests while the engine runs.
>
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
> Co-developed-by: Koichiro Den <den@valinux.co.jp>
> Signed-off-by: Koichiro Den <den@valinux.co.jp>
> ---

Reviewed-by: Frank Li <Frank.Li@nxp.com>

> Changes in v3:
>   - Reset ring state after termination or abort. (Frank, Sashiko)
>   - Use ll_done as the consumer boundary from the beginning and move the
>     ring accounting helpers here, so the later progress-reclamation
>     patch can focus on consuming IRQ-paired progress.
>   - Calculate free space once per ring-fill pass.
>
>  drivers/dma/dw-edma/dw-edma-core.c | 125 +++++++++++++++++++++++------
>  drivers/dma/dw-edma/dw-edma-core.h |  27 ++++++-
>  2 files changed, 125 insertions(+), 27 deletions(-)
>
> diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
> index 6a25a050b89c..d7a8a43b71d6 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-core.c
> @@ -51,13 +51,19 @@ dw_edma_alloc_desc(struct dw_edma_chan *chan, size_t nburst)
>  {
>  	struct dw_edma_desc *desc;
>
> +	/*
> +	 * For now, a descriptor that does not fit would stall the channel
> +	 * forever: reject it up front.
> +	 */
> +	if (!chan->non_ll && nburst > chan->ll_max - 1)
> +		return NULL;
> +
>  	desc = kzalloc_flex(*desc, burst, nburst, GFP_NOWAIT);
>  	if (unlikely(!desc))
>  		return NULL;
>
>  	desc->chan = chan;
>  	desc->nburst = nburst;
> -	desc->cb = true;
>
>  	return desc;
>  }
> @@ -67,30 +73,75 @@ static void vchan_free_desc(struct virt_dma_desc *vdesc)
>  	kfree(vd2dw_edma_desc(vdesc));
>  }
>
> +static void dw_edma_core_reset_ll(struct dw_edma_chan *chan)
> +{
> +	u32 i;
> +
> +	chan->ll_head = 0;
> +	chan->ll_done = 0;
> +	/* Drop stale CB bits before reusing the circular LL ring. */
> +	for (i = 0; i < chan->ll_max; i++)
> +		dw_edma_core_ll_clear(chan, i);
> +	chan->cb = true;
> +
> +	dw_edma_core_ll_link(chan, chan->ll_max, chan->cb,
> +			     chan->ll_region.paddr);
> +
> +	dw_edma_core_ch_enable(chan);
> +	chan->ll_valid = true;
> +}
> +
> +static u32 dw_edma_core_get_ll_dist(struct dw_edma_chan *chan, u32 from, u32 to)
> +{
> +	return (to + chan->ll_max - from) % chan->ll_max;
> +}
> +
> +static u32 dw_edma_core_get_used_num(struct dw_edma_chan *chan)
> +{
> +	return dw_edma_core_get_ll_dist(chan, chan->ll_done, chan->ll_head);
> +}
> +
> +static u32 dw_edma_core_get_free_num(struct dw_edma_chan *chan)
> +{
> +	/* Keep one data entry free so equal indices mean an empty ring. */
> +	return chan->ll_max - 1 - dw_edma_core_get_used_num(chan);
> +}
> +
> +static bool dw_edma_ll_pending(struct dw_edma_chan *chan)
> +{
> +	return chan->ll_head != chan->ll_done;
> +}
> +
>  static void dw_edma_core_ll_start(struct dw_edma_desc *desc)
>  {
>  	struct dw_edma_chan *chan = desc->chan;
>  	size_t i;
> -	bool first = !desc->start_burst;
> +	u32 free;
> +
> +	free = dw_edma_core_get_free_num(chan);
> +	for (i = desc->start_burst; i < desc->nburst && free; i++, free--) {
> +		/*
> +		 * Refresh the link element before filling the last data slot so
> +		 * the next lap has the updated CB value.
> +		 */
> +		if (chan->ll_head == chan->ll_max - 1)
> +			dw_edma_core_ll_link(chan, chan->ll_max, chan->cb,
> +					     chan->ll_region.paddr);
>
> -	for (i = 0; i + desc->start_burst < desc->nburst; i++) {
> -		u32 idx = i + desc->start_burst;
> +		dw_edma_core_ll_data(chan, &desc->burst[i],
> +				     chan->ll_head, chan->cb,
> +				     i == desc->nburst - 1 || free == 1);
>
> -		if (i == chan->ll_max)
> -			break;
> +		chan->ll_head++;
>
> -		dw_edma_core_ll_data(chan, &desc->burst[idx],
> -				     i, desc->cb,
> -				     idx == desc->nburst - 1 || i == chan->ll_max - 1);
> +		if (chan->ll_head == chan->ll_max) {
> +			chan->cb = !chan->cb;
> +			chan->ll_head = 0;
> +		}
>  	}
>
>  	desc->done_burst = desc->start_burst;
> -	desc->start_burst += i;
> -
> -	dw_edma_core_ll_link(chan, i, desc->cb, chan->ll_region.paddr);
> -
> -	if (first)
> -		dw_edma_core_ch_enable(chan);
> +	desc->start_burst = i;
>
>  	dw_edma_core_ch_doorbell(chan);
>  }
> @@ -123,9 +174,10 @@ static int dw_edma_start_transfer(struct dw_edma_chan *chan)
>  	if (!desc)
>  		return 0;
>
> -	dw_edma_core_start(desc);
> +	if (!chan->non_ll && !chan->ll_valid)
> +		dw_edma_core_reset_ll(chan);
>
> -	desc->cb = !desc->cb;
> +	dw_edma_core_start(desc);
>
>  	return 1;
>  }
> @@ -159,6 +211,19 @@ static void dw_edma_terminate_all_descs(struct dw_edma_chan *chan)
>  	dw_edma_terminate_vdesc_list(&chan->vc.desc_submitted);
>  }
>
> +/* Must be called with vc.lock held after the channel has stopped. */
> +static void dw_edma_finish_termination(struct dw_edma_chan *chan)
> +{
> +	dw_edma_terminate_all_descs(chan);
> +
> +	/* Preserve a clean ring; resync only if entries remain published. */
> +	if (!chan->non_ll && dw_edma_ll_pending(chan))
> +		dw_edma_core_reset_ll(chan);
> +
> +	chan->request = EDMA_REQ_NONE;
> +	chan->status = EDMA_ST_IDLE;
> +}
> +
>  static void dw_edma_device_caps(struct dma_chan *dchan,
>  				struct dma_slave_caps *caps)
>  {
> @@ -299,17 +364,15 @@ static int dw_edma_device_terminate_all(struct dma_chan *dchan)
>  	if (!chan->configured) {
>  		dw_edma_terminate_all_descs(chan);
>  	} else if (chan->status == EDMA_ST_PAUSE) {
> -		dw_edma_terminate_all_descs(chan);
> -		chan->status = EDMA_ST_IDLE;
> +		dw_edma_finish_termination(chan);
>  	} else if (chan->status == EDMA_ST_IDLE) {
> -		dw_edma_terminate_all_descs(chan);
> +		dw_edma_finish_termination(chan);
>  	} 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;
> +		dw_edma_finish_termination(chan);
>  	} else if (chan->request > EDMA_REQ_PAUSE) {
>  		err = -EPERM;
>  	} else {
> @@ -645,6 +708,8 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
>  							    DMA_TRANS_NOERROR);
>  				list_del(&vd->node);
>  				vchan_cookie_complete(vd);
> +				if (!chan->non_ll)
> +					chan->ll_done = chan->ll_head;
>  			}
>
>  			if (chan->request == EDMA_REQ_PAUSE) {
> @@ -659,9 +724,7 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
>  			break;
>
>  		case EDMA_REQ_STOP:
> -			dw_edma_terminate_all_descs(chan);
> -			chan->request = EDMA_REQ_NONE;
> -			chan->status = EDMA_ST_IDLE;
> +			dw_edma_finish_termination(chan);
>  			break;
>
>  		default:
> @@ -685,6 +748,8 @@ static void dw_edma_abort_interrupt(struct dw_edma_chan *chan)
>  		list_del(&vd->node);
>  		vchan_cookie_complete(vd);
>  	}
> +	if (!chan->non_ll)
> +		dw_edma_core_reset_ll(chan);
>  	chan->request = EDMA_REQ_NONE;
>  	chan->status = EDMA_ST_IDLE;
>  	spin_unlock_irqrestore(&chan->vc.lock, flags);
> @@ -871,6 +936,9 @@ static int dw_edma_alloc_chan_resources(struct dma_chan *dchan)
>  	if (chan->status != EDMA_ST_IDLE)
>  		return -EBUSY;
>
> +	/* The hardware context may have been invalidated while unowned. */
> +	chan->ll_valid = false;
> +
>  	return 0;
>  }
>
> @@ -962,6 +1030,13 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
>  		else
>  			chan->ll_region = chip->ll_region_rd[chan->id];
>
> +		if (!chip->cfg_non_ll && chan->ll_region.sz < 3 * EDMA_LL_SZ) {
> +			dev_err(dev,
> +				"channel %s[%u]: LL region has fewer than 2 data entries\n",
> +				str_write_read(chan->dir == EDMA_DIR_WRITE),
> +				chan->id);
> +			return -EINVAL;
> +		}
>  		chan->ll_max = chan->ll_region.sz / EDMA_LL_SZ - 1;
>
>  		dev_vdbg(dev, "L. List:\tChannel %s[%u] max_cnt=%u\n",
> diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
> index 089f913fd247..761a5ab4bbb5 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.h
> +++ b/drivers/dma/dw-edma/dw-edma-core.h
> @@ -60,7 +60,6 @@ struct dw_edma_desc {
>
>  	size_t				done_burst;
>  	size_t				start_burst;
> -	u8				cb;
>  	size_t				nburst;
>  	struct dw_edma_burst            burst[] __counted_by(nburst);
>  };
> @@ -72,8 +71,32 @@ struct dw_edma_chan {
>  	enum dw_edma_dir		dir;
>  	u8				func_no;
>
> -	u32				ll_max;
> +	/*
> +	 * New LL entries are appended at ll_head. Entries between ll_done
> +	 * and ll_head, modulo the LL ring, are owned by DMA; the rest are
> +	 * owned by software.
> +	 *
> +	 *   software-owned      DMA-owned       software-owned
> +	 * +---------------+-------------------+---------------+
> +	 * ^               ^                   ^
> +	 * 0             ll_done             ll_head
> +	 *
> +	 * The link entry points back to the region start. ll_head == ll_done
> +	 * means all entries are software-owned and previous DMA work is
> +	 * done.
> +	 *
> +	 * Software always keeps at least one free entry, so the ring is
> +	 * never completely DMA-owned. That keeps a hardware-reported physical
> +	 * LL index unique within the current ll_done..ll_head producer window.
> +	 */
> +	u32				ll_head;
> +	u32				ll_done;
> +
> +	u32				ll_max;		/* Data entries */
>  	struct dw_edma_region		ll_region;	/* Linked list */
> +	bool				ll_valid;	/* LL context programmed */
> +
> +	bool				cb;
>
>  	struct msi_msg			msi;
>
> --
> 2.51.0
>

  parent reply	other threads:[~2026-07-27 19:09 UTC|newest]

Thread overview: 43+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-27 17:02 [PATCH v3 00/24] dmaengine: dw-edma: Support dynamic LL appends Koichiro Den
2026-07-27 17:03 ` [PATCH v3 01/24] dmaengine: dw-edma: Add dw_edma_core_ll_cur_idx() to get current LL entry index Koichiro Den
2026-07-27 17:17   ` sashiko-bot
2026-07-27 17:03 ` [PATCH v3 02/24] dmaengine: dw-edma: Add dw_edma_core_ll_clear() to clear LL control-word Koichiro Den
2026-07-27 17:03 ` [PATCH v3 03/24] dmaengine: dw-edma: Factor out linked-list transfer start Koichiro Den
2026-07-27 17:03 ` [PATCH v3 04/24] dmaengine: dw-edma: Make DMA link list work as a circular buffer Koichiro Den
2026-07-27 17:24   ` sashiko-bot
2026-07-27 19:09   ` Frank Li [this message]
2026-07-27 17:03 ` [PATCH v3 05/24] dmaengine: dw-edma: Move callback result helper before LL helpers Koichiro Den
2026-07-27 17:03 ` [PATCH v3 06/24] dmaengine: dw-edma: Dispatch DONE interrupts by channel request Koichiro Den
2026-07-27 17:13   ` sashiko-bot
2026-07-27 19:11   ` Frank Li
2026-07-27 17:03 ` [PATCH v3 07/24] dmaengine: dw-edma: Centralize LL doorbell decisions Koichiro Den
2026-07-27 17:23   ` sashiko-bot
2026-07-27 19:14     ` Frank Li
2026-07-27 17:03 ` [PATCH v3 08/24] dmaengine: dw-edma: Prepare LL progress event handling Koichiro Den
2026-07-27 19:47   ` Frank Li
2026-07-27 20:09   ` Frank Li
2026-07-27 17:03 ` [PATCH v3 09/24] dmaengine: dw-edma: Prepare deferred IRQ reporting for LL events Koichiro Den
2026-07-27 20:12   ` Frank Li
2026-07-27 17:03 ` [PATCH v3 10/24] dmaengine: dw-edma: Prepare LL kicks for event serialization Koichiro Den
2026-07-27 20:18   ` Frank Li
2026-07-27 17:03 ` [PATCH v3 11/24] dmaengine: dw-edma: Serialize LL event capture with channel kicks Koichiro Den
2026-07-27 20:35   ` Frank Li
2026-07-27 17:03 ` [PATCH v3 12/24] dmaengine: dw-edma: Keep channels stopped while ABORT is pending Koichiro Den
2026-07-27 17:19   ` sashiko-bot
2026-07-27 17:03 ` [PATCH v3 13/24] dmaengine: dw-edma: Reclaim issued descriptors from IRQ-paired LL progress Koichiro Den
2026-07-27 17:03 ` [PATCH v3 14/24] dmaengine: dw-edma: Add LL interrupt placement policy Koichiro Den
2026-07-27 17:21   ` sashiko-bot
2026-07-27 17:03 ` [PATCH v3 15/24] dmaengine: dw-edma: Recheck stopped LL channels before restart Koichiro Den
2026-07-27 17:21   ` sashiko-bot
2026-07-27 17:03 ` [PATCH v3 16/24] dmaengine: dw-edma: Use HDMA watermarks as progress events Koichiro Den
2026-07-27 17:03 ` [PATCH v3 17/24] dmaengine: dw-edma: Recover stopped channels from tx_status() Koichiro Den
2026-07-27 17:03 ` [PATCH v3 18/24] dmaengine: dw-edma: Make the LL ring reset a full channel resync Koichiro Den
2026-07-27 17:24   ` sashiko-bot
2026-07-27 17:03 ` [PATCH v3 19/24] dmaengine: dw-edma: Drain LL entries for STOP and PAUSE Koichiro Den
2026-07-27 17:30   ` sashiko-bot
2026-07-27 17:03 ` [PATCH v3 20/24] dmaengine: dw-edma: Dynamically append requests while running Koichiro Den
2026-07-27 17:03 ` [PATCH v3 21/24] dmaengine: dw-edma: Add engine reset and enable operations Koichiro Den
2026-07-27 17:03 ` [PATCH v3 22/24] dmaengine: dw-edma: Add engine recovery infrastructure Koichiro Den
2026-07-27 17:03 ` [PATCH v3 23/24] dmaengine: dw-edma: Detect and recover a stalled eDMA engine Koichiro Den
2026-07-27 17:37   ` sashiko-bot
2026-07-27 17:03 ` [PATCH v3 24/24] dmaengine: dw-edma: Add trace support Koichiro Den

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=ames3ZMAS4CBh0zm@lizhi-Precision-Tower-5810 \
    --to=frank.li@oss.nxp.com \
    --cc=Frank.Li@kernel.org \
    --cc=Gustavo.Pimentel@synopsys.com \
    --cc=bhelgaas@google.com \
    --cc=cai.huoqing@linux.dev \
    --cc=cassel@kernel.org \
    --cc=den@valinux.co.jp \
    --cc=devendra.verma@amd.com \
    --cc=dmaengine@vger.kernel.org \
    --cc=fancer.lancer@gmail.com \
    --cc=hch@lst.de \
    --cc=kees@kernel.org \
    --cc=kishon@kernel.org \
    --cc=kwilczynski@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mani@kernel.org \
    --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