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
>
next prev 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