* [PATCH v6 01/10] dmaengine: dw-edma: Add dw_edma_core_ll_cur_idx() to get current LL entry index
2026-08-20 17:34 [PATCH v6 00/10] dmaengine: dw-edma: Prepare for dynamic LL appends Koichiro Den
@ 2026-08-20 17:34 ` Koichiro Den
2026-08-20 17:44 ` sashiko-bot
2026-08-20 17:34 ` [PATCH v6 02/10] dmaengine: dw-edma: Add dw_edma_core_ll_clear() to clear LL control-word Koichiro Den
` (8 subsequent siblings)
9 siblings, 1 reply; 13+ messages in thread
From: Koichiro Den @ 2026-08-20 17:34 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Manivannan Sadhasivam, Gustavo Pimentel,
Kees Cook, Krzysztof Wilczyński, Kishon Vijay Abraham I,
Bjorn Helgaas, Christoph Hellwig, Serge Semin, Cai Huoqing,
Niklas Cassel
Cc: Devendra K Verma, dmaengine, linux-kernel
From: Frank Li <Frank.Li@nxp.com>
Add dw_edma_core_ll_cur_idx() to get the current LL entry index and
prepare for dynamic addition of DMA requests while the DMA engine is
running.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
Reviewed-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>
---
drivers/dma/dw-edma/dw-edma-core.h | 10 ++++++++++
drivers/dma/dw-edma/dw-edma-v0-core.c | 20 ++++++++++++++++++++
drivers/dma/dw-edma/dw-hdma-v0-core.c | 20 ++++++++++++++++++++
include/linux/dma/edma.h | 2 +-
4 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index f6a5ad317567..e50fa7965778 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -138,6 +138,7 @@ struct dw_edma_core_ops {
void (*ll_data)(struct dw_edma_chan *chan, struct dw_edma_burst *burst,
u32 idx, bool cb, bool irq);
void (*ll_link)(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr);
+ int (*ll_cur_idx)(struct dw_edma_chan *chan);
void (*ch_doorbell)(struct dw_edma_chan *chan);
void (*ch_enable)(struct dw_edma_chan *chan);
void (*ch_config)(struct dw_edma_chan *chan);
@@ -181,6 +182,15 @@ struct dw_edma_chan *dchan2dw_edma_chan(struct dma_chan *dchan)
return vc2dw_edma_chan(to_virt_chan(dchan));
}
+/*
+ * Return the current LL entry index. A negative value means that the channel
+ * context is not initialized or was lost after a link reset.
+ */
+static inline int dw_edma_core_ll_cur_idx(struct dw_edma_chan *chan)
+{
+ return chan->dw->core->ll_cur_idx(chan);
+}
+
static inline u64 dw_edma_core_get_ll_paddr(struct dw_edma_chan *chan)
{
if (chan->dir == EDMA_DIR_WRITE)
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index abc1bd4c0be2..cc8059182940 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -616,6 +616,25 @@ static void dw_edma_v0_core_ch_doorbell(struct dw_edma_chan *chan)
FIELD_PREP(EDMA_V0_DOORBELL_CH_MASK, chan->id));
}
+static int dw_edma_v0_core_ll_cur_idx(struct dw_edma_chan *chan)
+{
+ u32 base, val;
+
+ val = GET_CH_32(chan->dw, chan->dir, chan->id, llp.lsb);
+ base = lower_32_bits(dw_edma_core_get_ll_paddr(chan));
+
+ /*
+ * LL regions stay within one 4 GiB address window. Reject an all-ones
+ * MMIO value. If the low word is zero, use the high word to distinguish
+ * a nonzero boundary address from an unprogrammed all-zero context.
+ */
+ if (val == U32_MAX ||
+ (!val && !GET_CH_32(chan->dw, chan->dir, chan->id, llp.msb)))
+ return -EINVAL;
+
+ return (val - base) / EDMA_LL_SZ;
+}
+
/* eDMA debugfs callbacks */
static void dw_edma_v0_core_debugfs_on(struct dw_edma *dw)
{
@@ -650,6 +669,7 @@ static const struct dw_edma_core_ops dw_edma_v0_core = {
.handle_int = dw_edma_v0_core_handle_int,
.ll_data = dw_edma_v0_core_ll_data,
.ll_link = dw_edma_v0_core_ll_link,
+ .ll_cur_idx = dw_edma_v0_core_ll_cur_idx,
.ch_doorbell = dw_edma_v0_core_ch_doorbell,
.ch_enable = dw_edma_v0_core_ch_enable,
.ch_config = dw_edma_v0_core_ch_config,
diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
index 36ee72efcd31..19da1c8fd615 100644
--- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
@@ -384,6 +384,25 @@ static void dw_hdma_v0_core_ch_doorbell(struct dw_edma_chan *chan)
SET_CH_32(dw, chan->dir, chan->id, doorbell, HDMA_V0_DOORBELL_START);
}
+static int dw_hdma_v0_core_ll_cur_idx(struct dw_edma_chan *chan)
+{
+ u32 base, val;
+
+ val = GET_CH_32(chan->dw, chan->dir, chan->id, llp.lsb);
+ base = lower_32_bits(dw_edma_core_get_ll_paddr(chan));
+
+ /*
+ * LL regions stay within one 4 GiB address window. Reject an all-ones
+ * MMIO value. If the low word is zero, use the high word to distinguish
+ * a nonzero boundary address from an unprogrammed all-zero context.
+ */
+ if (val == U32_MAX ||
+ (!val && !GET_CH_32(chan->dw, chan->dir, chan->id, llp.msb)))
+ return -EINVAL;
+
+ return (val - base) / EDMA_LL_SZ;
+}
+
/* HDMA debugfs callbacks */
static void dw_hdma_v0_core_debugfs_on(struct dw_edma *dw)
{
@@ -406,6 +425,7 @@ static const struct dw_edma_core_ops dw_hdma_v0_core = {
.non_ll_start = dw_hdma_v0_core_non_ll_start,
.ll_data = dw_hdma_v0_core_ll_data,
.ll_link = dw_hdma_v0_core_ll_link,
+ .ll_cur_idx = dw_hdma_v0_core_ll_cur_idx,
.ch_doorbell = dw_hdma_v0_core_ch_doorbell,
.ch_enable = dw_hdma_v0_core_ch_enable,
.ch_config = dw_hdma_v0_core_ch_config,
diff --git a/include/linux/dma/edma.h b/include/linux/dma/edma.h
index 3c8e2ef9dee0..a4e5aab8fddc 100644
--- a/include/linux/dma/edma.h
+++ b/include/linux/dma/edma.h
@@ -130,7 +130,7 @@ struct dw_edma_chip {
u16 ll_wr_cnt;
u16 ll_rd_cnt;
- /* link list address */
+ /* Linked-list regions must not cross a 4 GiB address boundary. */
struct dw_edma_region ll_region_wr[HDMA_MAX_WR_CH];
struct dw_edma_region ll_region_rd[HDMA_MAX_RD_CH];
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH v6 01/10] dmaengine: dw-edma: Add dw_edma_core_ll_cur_idx() to get current LL entry index
2026-08-20 17:34 ` [PATCH v6 01/10] dmaengine: dw-edma: Add dw_edma_core_ll_cur_idx() to get current LL entry index Koichiro Den
@ 2026-08-20 17:44 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-08-20 17:44 UTC (permalink / raw)
To: Koichiro Den; +Cc: Frank.Li, dmaengine, vkoul
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Unsigned subtraction underflow causes out-of-bounds hardware pointer values to return a large positive index instead of a negative error code.
--
--- Patch [1]: [PATCH v6 01/10] dmaengine: dw-edma: Add dw_edma_core_ll_cur_idx() to get current LL entry index ---
commit a5cf77298fa28da8c44ef71a7e3da99c79085cf7
Author: Frank Li <Frank.Li@nxp.com>
dmaengine: dw-edma: Add dw_edma_core_ll_cur_idx() to get current LL entry index
This commit introduces the dw_edma_core_ll_cur_idx() function to retrieve
the current linked list entry index. This prepares the driver for the
dynamic addition of DMA requests while the DMA engine is running.
> diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
> index abc1bd4c0be2e..cc80591829403 100644
> --- a/drivers/dma/dw-edma/dw-edma-v0-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
> @@ -616,6 +616,25 @@ static void dw_edma_v0_core_ch_doorbell(struct dw_edma_chan *chan)
> FIELD_PREP(EDMA_V0_DOORBELL_CH_MASK, chan->id));
> }
>
> +static int dw_edma_v0_core_ll_cur_idx(struct dw_edma_chan *chan)
> +{
> + u32 base, val;
> +
> + val = GET_CH_32(chan->dw, chan->dir, chan->id, llp.lsb);
> + base = lower_32_bits(dw_edma_core_get_ll_paddr(chan));
> +
> + /*
> + * LL regions stay within one 4 GiB address window. Reject an all-ones
> + * MMIO value. If the low word is zero, use the high word to distinguish
> + * a nonzero boundary address from an unprogrammed all-zero context.
> + */
> + if (val == U32_MAX ||
> + (!val && !GET_CH_32(chan->dw, chan->dir, chan->id, llp.msb)))
> + return -EINVAL;
> +
> + return (val - base) / EDMA_LL_SZ;
[Severity: Medium]
Is it possible for val to be less than base if the channel context is
uninitialized and holds a stale linked-list pointer from a previous
allocation at a lower physical address?
Since val and base are unsigned 32-bit integers, if val < base, the operation
val - base underflows to a large unsigned integer. Dividing this by
EDMA_LL_SZ yields a massive positive index that fits into the signed int
return type, rather than returning a negative error code as intended.
Would it make sense to verify that val is >= base, or to bounds check the
resulting index against chan->ll_max to prevent potential out-of-bounds
reads or writes?
> +}
> +
[ ... ]
> diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
> index 36ee72efcd317..19da1c8fd6159 100644
> --- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
> +++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
> @@ -384,6 +384,25 @@ static void dw_hdma_v0_core_ch_doorbell(struct dw_edma_chan *chan)
> SET_CH_32(dw, chan->dir, chan->id, doorbell, HDMA_V0_DOORBELL_START);
> }
>
> +static int dw_hdma_v0_core_ll_cur_idx(struct dw_edma_chan *chan)
> +{
> + u32 base, val;
> +
> + val = GET_CH_32(chan->dw, chan->dir, chan->id, llp.lsb);
> + base = lower_32_bits(dw_edma_core_get_ll_paddr(chan));
> +
> + /*
> + * LL regions stay within one 4 GiB address window. Reject an all-ones
> + * MMIO value. If the low word is zero, use the high word to distinguish
> + * a nonzero boundary address from an unprogrammed all-zero context.
> + */
> + if (val == U32_MAX ||
> + (!val && !GET_CH_32(chan->dw, chan->dir, chan->id, llp.msb)))
> + return -EINVAL;
> +
> + return (val - base) / EDMA_LL_SZ;
[Severity: Medium]
Could this suffer from the same underflow issue as the v0 core implementation
if val is less than base?
> +}
> +
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260820173439.2004068-1-den@valinux.co.jp?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v6 02/10] dmaengine: dw-edma: Add dw_edma_core_ll_clear() to clear LL control-word
2026-08-20 17:34 [PATCH v6 00/10] dmaengine: dw-edma: Prepare for dynamic LL appends Koichiro Den
2026-08-20 17:34 ` [PATCH v6 01/10] dmaengine: dw-edma: Add dw_edma_core_ll_cur_idx() to get current LL entry index Koichiro Den
@ 2026-08-20 17:34 ` Koichiro Den
2026-08-20 17:34 ` [PATCH v6 03/10] dmaengine: dw-edma: Factor out linked-list transfer start Koichiro Den
` (7 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Koichiro Den @ 2026-08-20 17:34 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Manivannan Sadhasivam, Gustavo Pimentel,
Kees Cook, Krzysztof Wilczyński, Kishon Vijay Abraham I,
Bjorn Helgaas, Christoph Hellwig, Serge Semin, Cai Huoqing,
Niklas Cassel
Cc: Devendra K Verma, dmaengine, linux-kernel
Add a core operation for clearing the control word of one linked-list
entry. A later patch uses the operation to invalidate stale CB when
resetting a circular LL ring.
No functional change.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
drivers/dma/dw-edma/dw-edma-core.h | 6 ++++++
drivers/dma/dw-edma/dw-edma-v0-core.c | 16 ++++++++++++++++
drivers/dma/dw-edma/dw-hdma-v0-core.c | 16 ++++++++++++++++
3 files changed, 38 insertions(+)
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index e50fa7965778..089f913fd247 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -138,6 +138,7 @@ struct dw_edma_core_ops {
void (*ll_data)(struct dw_edma_chan *chan, struct dw_edma_burst *burst,
u32 idx, bool cb, bool irq);
void (*ll_link)(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr);
+ void (*ll_clear)(struct dw_edma_chan *chan, u32 idx);
int (*ll_cur_idx)(struct dw_edma_chan *chan);
void (*ch_doorbell)(struct dw_edma_chan *chan);
void (*ch_enable)(struct dw_edma_chan *chan);
@@ -255,6 +256,11 @@ dw_edma_core_ll_link(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr)
chan->dw->core->ll_link(chan, idx, cb, addr);
}
+static inline void dw_edma_core_ll_clear(struct dw_edma_chan *chan, u32 idx)
+{
+ chan->dw->core->ll_clear(chan, idx);
+}
+
static inline void dw_edma_core_ch_doorbell(struct dw_edma_chan *chan)
{
chan->dw->core->ch_doorbell(chan);
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index cc8059182940..bb86d2f60ab5 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -605,6 +605,21 @@ dw_edma_v0_core_ll_link(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr)
dw_edma_v0_write_ll_link(chan, idx, control, addr);
}
+static void dw_edma_v0_core_ll_clear(struct dw_edma_chan *chan, u32 idx)
+{
+ ptrdiff_t ofs = idx * sizeof(struct dw_edma_v0_lli);
+
+ if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
+ struct dw_edma_v0_lli *lli = chan->ll_region.vaddr.mem + ofs;
+
+ lli->control = 0;
+ } else {
+ struct dw_edma_v0_lli __iomem *lli = chan->ll_region.vaddr.io + ofs;
+
+ writel(0, &lli->control);
+ }
+}
+
static void dw_edma_v0_core_ch_doorbell(struct dw_edma_chan *chan)
{
struct dw_edma *dw = chan->dw;
@@ -669,6 +684,7 @@ static const struct dw_edma_core_ops dw_edma_v0_core = {
.handle_int = dw_edma_v0_core_handle_int,
.ll_data = dw_edma_v0_core_ll_data,
.ll_link = dw_edma_v0_core_ll_link,
+ .ll_clear = dw_edma_v0_core_ll_clear,
.ll_cur_idx = dw_edma_v0_core_ll_cur_idx,
.ch_doorbell = dw_edma_v0_core_ch_doorbell,
.ch_enable = dw_edma_v0_core_ch_enable,
diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
index 19da1c8fd615..47d7a01658eb 100644
--- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
@@ -374,6 +374,21 @@ dw_hdma_v0_core_ll_link(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr)
dw_hdma_v0_write_ll_link(chan, idx, control, addr);
}
+static void dw_hdma_v0_core_ll_clear(struct dw_edma_chan *chan, u32 idx)
+{
+ ptrdiff_t ofs = idx * sizeof(struct dw_hdma_v0_lli);
+
+ if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
+ struct dw_hdma_v0_lli *lli = chan->ll_region.vaddr.mem + ofs;
+
+ lli->control = 0;
+ } else {
+ struct dw_hdma_v0_lli __iomem *lli = chan->ll_region.vaddr.io + ofs;
+
+ writel(0, &lli->control);
+ }
+}
+
static void dw_hdma_v0_core_ch_doorbell(struct dw_edma_chan *chan)
{
struct dw_edma *dw = chan->dw;
@@ -425,6 +440,7 @@ static const struct dw_edma_core_ops dw_hdma_v0_core = {
.non_ll_start = dw_hdma_v0_core_non_ll_start,
.ll_data = dw_hdma_v0_core_ll_data,
.ll_link = dw_hdma_v0_core_ll_link,
+ .ll_clear = dw_hdma_v0_core_ll_clear,
.ll_cur_idx = dw_hdma_v0_core_ll_cur_idx,
.ch_doorbell = dw_hdma_v0_core_ch_doorbell,
.ch_enable = dw_hdma_v0_core_ch_enable,
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v6 03/10] dmaengine: dw-edma: Factor out linked-list transfer start
2026-08-20 17:34 [PATCH v6 00/10] dmaengine: dw-edma: Prepare for dynamic LL appends Koichiro Den
2026-08-20 17:34 ` [PATCH v6 01/10] dmaengine: dw-edma: Add dw_edma_core_ll_cur_idx() to get current LL entry index Koichiro Den
2026-08-20 17:34 ` [PATCH v6 02/10] dmaengine: dw-edma: Add dw_edma_core_ll_clear() to clear LL control-word Koichiro Den
@ 2026-08-20 17:34 ` Koichiro Den
2026-08-20 17:34 ` [PATCH v6 04/10] dmaengine: dw-edma: Make DMA link list work as a circular buffer Koichiro Den
` (6 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Koichiro Den @ 2026-08-20 17:34 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Manivannan Sadhasivam, Gustavo Pimentel,
Kees Cook, Krzysztof Wilczyński, Kishon Vijay Abraham I,
Bjorn Helgaas, Christoph Hellwig, Serge Semin, Cai Huoqing,
Niklas Cassel
Cc: Devendra K Verma, dmaengine, linux-kernel
Move linked-list programming into dw_edma_core_ll_start(). It can derive
the first-batch state from desc->start_burst, so dw_edma_core_start() no
longer needs a pass-through argument.
No functional change.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
drivers/dma/dw-edma/dw-edma-core.c | 29 +++++++++++++++++++----------
1 file changed, 19 insertions(+), 10 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 03b2c2188351..a6bb68ffdfe2 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -67,17 +67,11 @@ static void vchan_free_desc(struct virt_dma_desc *vdesc)
kfree(vd2dw_edma_desc(vdesc));
}
-static void dw_edma_core_start(struct dw_edma_desc *desc, bool first)
+static void dw_edma_core_ll_start(struct dw_edma_desc *desc)
{
struct dw_edma_chan *chan = desc->chan;
- size_t i = 0;
-
- if (chan->non_ll) {
- chan->dw->core->non_ll_start(chan, &desc->burst[desc->start_burst]);
- desc->done_burst = desc->start_burst;
- desc->start_burst += 1;
- return;
- }
+ size_t i;
+ bool first = !desc->start_burst;
for (i = 0; i + desc->start_burst < desc->nburst; i++) {
u32 idx = i + desc->start_burst;
@@ -101,6 +95,21 @@ static void dw_edma_core_start(struct dw_edma_desc *desc, bool first)
dw_edma_core_ch_doorbell(chan);
}
+static void dw_edma_core_start(struct dw_edma_desc *desc)
+{
+ struct dw_edma_chan *chan = desc->chan;
+
+ if (chan->non_ll) {
+ chan->dw->core->non_ll_start(chan,
+ &desc->burst[desc->start_burst]);
+ desc->done_burst = desc->start_burst;
+ desc->start_burst += 1;
+ return;
+ }
+
+ dw_edma_core_ll_start(desc);
+}
+
static int dw_edma_start_transfer(struct dw_edma_chan *chan)
{
struct dw_edma_desc *desc;
@@ -114,7 +123,7 @@ static int dw_edma_start_transfer(struct dw_edma_chan *chan)
if (!desc)
return 0;
- dw_edma_core_start(desc, !desc->start_burst);
+ dw_edma_core_start(desc);
desc->cb = !desc->cb;
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v6 04/10] dmaengine: dw-edma: Make DMA link list work as a circular buffer
2026-08-20 17:34 [PATCH v6 00/10] dmaengine: dw-edma: Prepare for dynamic LL appends Koichiro Den
` (2 preceding siblings ...)
2026-08-20 17:34 ` [PATCH v6 03/10] dmaengine: dw-edma: Factor out linked-list transfer start Koichiro Den
@ 2026-08-20 17:34 ` Koichiro Den
2026-08-20 17:50 ` sashiko-bot
2026-08-20 17:34 ` [PATCH v6 05/10] dmaengine: dw-edma: Move callback result helper before LL helpers Koichiro Den
` (5 subsequent siblings)
9 siblings, 1 reply; 13+ messages in thread
From: Koichiro Den @ 2026-08-20 17:34 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Manivannan Sadhasivam, Gustavo Pimentel,
Kees Cook, Krzysztof Wilczyński, Kishon Vijay Abraham I,
Bjorn Helgaas, Christoph Hellwig, Serge Semin, Cai Huoqing,
Niklas Cassel
Cc: Devendra K Verma, dmaengine, linux-kernel
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>
Reviewed-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>
---
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 a6bb68ffdfe2..1af262b6e881 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);
@@ -872,6 +937,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;
}
@@ -963,6 +1031,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
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH v6 04/10] dmaengine: dw-edma: Make DMA link list work as a circular buffer
2026-08-20 17:34 ` [PATCH v6 04/10] dmaengine: dw-edma: Make DMA link list work as a circular buffer Koichiro Den
@ 2026-08-20 17:50 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-08-20 17:50 UTC (permalink / raw)
To: Koichiro Den; +Cc: vkoul, Frank.Li, dmaengine
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The driver explicitly rejects linked-list DMA descriptors larger than the ring size, breaking support for large DMA transfers.
--
--- Patch [4]: [PATCH v6 04/10] dmaengine: dw-edma: Make DMA link list work as a circular buffer ---
commit afa3e356ba74dbd5874af4d32f4fd3abdf7cbf7c
Author: Frank Li <Frank.Li@nxp.com>
dmaengine: dw-edma: Make DMA link list work as a circular buffer
This patch converts the DMA link list into a circular buffer instead of
rebuilding it for every transfer. It clears control words before first use,
rejects descriptors that exceed the usable ring capacity, and resets the
ring after termination or abort.
> diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
> index a6bb68ffdfe26..1af262b6e881e 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;
> +
[Severity: High]
Does this change break support for large DMA transfers?
By explicitly rejecting linked-list DMA descriptors larger than the ring
size, any client driver (such as storage or networking) that submits a
scatter-gather transfer larger than chan->ll_max - 1 will fail
unconditionally.
While the commit message acknowledges this as a limitation "until reclaim
support lands", that support appears to be completely missing from this
patch series.
Could this cause functional regressions for subsystems relying on this DMA
engine to submit moderately large transfers?
[ ... ]
> @@ -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) {
[Severity: High]
Since ring space is only reclaimed here when an entire descriptor completes,
does this structural change prevent incrementally chunking large descriptors?
Without the ability to reclaim space as partial transfers complete, this
seems to be the reason why large transfers can no longer be supported.
Could partial completions be handled here to prevent the functional regression
before the full reclaim support lands?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260820173439.2004068-1-den@valinux.co.jp?part=4
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v6 05/10] dmaengine: dw-edma: Move callback result helper before LL helpers
2026-08-20 17:34 [PATCH v6 00/10] dmaengine: dw-edma: Prepare for dynamic LL appends Koichiro Den
` (3 preceding siblings ...)
2026-08-20 17:34 ` [PATCH v6 04/10] dmaengine: dw-edma: Make DMA link list work as a circular buffer Koichiro Den
@ 2026-08-20 17:34 ` Koichiro Den
2026-08-20 17:34 ` [PATCH v6 06/10] dmaengine: dw-edma: Dispatch DONE interrupts by channel request Koichiro Den
` (4 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Koichiro Den @ 2026-08-20 17:34 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Manivannan Sadhasivam, Gustavo Pimentel,
Kees Cook, Krzysztof Wilczyński, Kishon Vijay Abraham I,
Bjorn Helgaas, Christoph Hellwig, Serge Semin, Cai Huoqing,
Niklas Cassel
Cc: Devendra K Verma, dmaengine, linux-kernel
From: Frank Li <Frank.Li@nxp.com>
Move dw_hdma_set_callback_result() next to the descriptor helpers so
linked-list completion and reset code can use it without a forward
declaration.
No functional change.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
drivers/dma/dw-edma/dw-edma-core.c | 50 +++++++++++++++---------------
1 file changed, 25 insertions(+), 25 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 1af262b6e881..ab8e343f0196 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -73,6 +73,31 @@ static void vchan_free_desc(struct virt_dma_desc *vdesc)
kfree(vd2dw_edma_desc(vdesc));
}
+static void dw_hdma_set_callback_result(struct virt_dma_desc *vd,
+ enum dmaengine_tx_result result)
+{
+ u32 residue = 0;
+ struct dw_edma_desc *desc;
+ struct dmaengine_result *res;
+
+ if (!vd->tx.callback_result)
+ return;
+
+ desc = vd2dw_edma_desc(vd);
+ if (desc) {
+ residue = desc->alloc_sz;
+
+ if (result == DMA_TRANS_NOERROR)
+ residue -= desc->burst[desc->start_burst - 1].xfer_sz;
+ else if (desc->done_burst)
+ residue -= desc->burst[desc->done_burst - 1].xfer_sz;
+ }
+
+ res = &vd->tx_result;
+ res->result = result;
+ res->residue = residue;
+}
+
static void dw_edma_core_reset_ll(struct dw_edma_chan *chan)
{
u32 i;
@@ -660,31 +685,6 @@ dw_edma_device_prep_interleaved_dma(struct dma_chan *dchan,
return dw_edma_device_transfer(&xfer, dw_edma_device_get_config(dchan, NULL));
}
-static void dw_hdma_set_callback_result(struct virt_dma_desc *vd,
- enum dmaengine_tx_result result)
-{
- u32 residue = 0;
- struct dw_edma_desc *desc;
- struct dmaengine_result *res;
-
- if (!vd->tx.callback_result)
- return;
-
- desc = vd2dw_edma_desc(vd);
- if (desc) {
- residue = desc->alloc_sz;
-
- if (result == DMA_TRANS_NOERROR)
- residue -= desc->burst[desc->start_burst - 1].xfer_sz;
- else if (desc->done_burst)
- residue -= desc->burst[desc->done_burst - 1].xfer_sz;
- }
-
- res = &vd->tx_result;
- res->result = result;
- res->residue = residue;
-}
-
static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
{
struct dw_edma_desc *desc;
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v6 06/10] dmaengine: dw-edma: Dispatch DONE interrupts by channel request
2026-08-20 17:34 [PATCH v6 00/10] dmaengine: dw-edma: Prepare for dynamic LL appends Koichiro Den
` (4 preceding siblings ...)
2026-08-20 17:34 ` [PATCH v6 05/10] dmaengine: dw-edma: Move callback result helper before LL helpers Koichiro Den
@ 2026-08-20 17:34 ` Koichiro Den
2026-08-20 17:34 ` [PATCH v6 07/10] dmaengine: dw-edma: Centralize LL doorbell decisions Koichiro Den
` (3 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Koichiro Den @ 2026-08-20 17:34 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Manivannan Sadhasivam, Gustavo Pimentel,
Kees Cook, Krzysztof Wilczyński, Kishon Vijay Abraham I,
Bjorn Helgaas, Christoph Hellwig, Serge Semin, Cai Huoqing,
Niklas Cassel
Cc: Devendra K Verma, dmaengine, linux-kernel
Prepare for a later patch that reclaims LL progress before request handling
and can remove the last issued descriptor. Move the request switch outside
the common descriptor check.
Keep the existing per-request descriptor checks for now. That later patch
removes them when STOP/PAUSE handling becomes independent of the descriptor
list.
No functional change.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
drivers/dma/dw-edma/dw-edma-core.c | 56 ++++++++++++++++--------------
1 file changed, 29 insertions(+), 27 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index ab8e343f0196..3c1f3f728875 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -697,39 +697,41 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
return;
}
- vd = vchan_next_desc(&chan->vc);
- if (vd) {
- switch (chan->request) {
- case EDMA_REQ_NONE:
- case EDMA_REQ_PAUSE:
- desc = vd2dw_edma_desc(vd);
- if (desc->start_burst >= desc->nburst) {
- dw_hdma_set_callback_result(vd,
- DMA_TRANS_NOERROR);
- list_del(&vd->node);
- vchan_cookie_complete(vd);
- if (!chan->non_ll)
- chan->ll_done = chan->ll_head;
- }
+ switch (chan->request) {
+ case EDMA_REQ_NONE:
+ case EDMA_REQ_PAUSE:
+ vd = vchan_next_desc(&chan->vc);
+ if (!vd)
+ break;
- if (chan->request == EDMA_REQ_PAUSE) {
- chan->request = EDMA_REQ_NONE;
- chan->status = EDMA_ST_PAUSE;
- break;
- }
+ desc = vd2dw_edma_desc(vd);
+ if (desc->start_burst >= desc->nburst) {
+ dw_hdma_set_callback_result(vd, DMA_TRANS_NOERROR);
+ list_del(&vd->node);
+ vchan_cookie_complete(vd);
+ if (!chan->non_ll)
+ chan->ll_done = chan->ll_head;
+ }
- /* Continue transferring if there are remaining chunks or issued requests.
- */
- chan->status = dw_edma_start_transfer(chan) ? EDMA_ST_BUSY : EDMA_ST_IDLE;
+ if (chan->request == EDMA_REQ_PAUSE) {
+ chan->request = EDMA_REQ_NONE;
+ chan->status = EDMA_ST_PAUSE;
break;
+ }
- case EDMA_REQ_STOP:
- dw_edma_finish_termination(chan);
- break;
+ chan->status = dw_edma_start_transfer(chan) ? EDMA_ST_BUSY : EDMA_ST_IDLE;
+ break;
- default:
+ case EDMA_REQ_STOP:
+ vd = vchan_next_desc(&chan->vc);
+ if (!vd)
break;
- }
+
+ dw_edma_finish_termination(chan);
+ break;
+
+ default:
+ break;
}
spin_unlock_irqrestore(&chan->vc.lock, flags);
}
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v6 07/10] dmaengine: dw-edma: Centralize LL doorbell decisions
2026-08-20 17:34 [PATCH v6 00/10] dmaengine: dw-edma: Prepare for dynamic LL appends Koichiro Den
` (5 preceding siblings ...)
2026-08-20 17:34 ` [PATCH v6 06/10] dmaengine: dw-edma: Dispatch DONE interrupts by channel request Koichiro Den
@ 2026-08-20 17:34 ` Koichiro Den
2026-08-20 17:34 ` [PATCH v6 08/10] dmaengine: dw-edma: Prepare LL progress event handling Koichiro Den
` (2 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Koichiro Den @ 2026-08-20 17:34 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Manivannan Sadhasivam, Gustavo Pimentel,
Kees Cook, Krzysztof Wilczyński, Kishon Vijay Abraham I,
Bjorn Helgaas, Christoph Hellwig, Serge Semin, Cai Huoqing,
Niklas Cassel
Cc: Devendra K Verma, dmaengine, linux-kernel
LL fill currently rings the doorbell every time. A running eDMA or HDMA
channel follows entries whose CB matches CCS without another kick. A
channel stopped at a CB mismatch needs one.
Move that decision to dw_edma_core_ch_maybe_doorbell(). It rings only
for a BUSY LL channel with pending entries, no EDMA_REQ_STOP or
EDMA_REQ_PAUSE. The BUSY check prevents EDMA_REQ_STOP completion from
restarting terminated work.
issue_pending() still publishes LL entries only from IDLE. A later patch
allows live appends. Non-LL channels are unchanged.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
drivers/dma/dw-edma/dw-edma-core.c | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 3c1f3f728875..5a8407ca68a2 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -167,8 +167,6 @@ static void dw_edma_core_ll_start(struct dw_edma_desc *desc)
desc->done_burst = desc->start_burst;
desc->start_burst = i;
-
- dw_edma_core_ch_doorbell(chan);
}
static void dw_edma_core_start(struct dw_edma_desc *desc)
@@ -249,6 +247,16 @@ static void dw_edma_finish_termination(struct dw_edma_chan *chan)
chan->status = EDMA_ST_IDLE;
}
+/* Must be called with vc.lock held. */
+static void dw_edma_core_ch_maybe_doorbell(struct dw_edma_chan *chan)
+{
+ if (chan->non_ll || chan->request != EDMA_REQ_NONE ||
+ chan->status != EDMA_ST_BUSY || !dw_edma_ll_pending(chan))
+ return;
+
+ dw_edma_core_ch_doorbell(chan);
+}
+
static void dw_edma_device_caps(struct dma_chan *dchan,
struct dma_slave_caps *caps)
{
@@ -374,6 +382,7 @@ static int dw_edma_device_resume(struct dma_chan *dchan)
chan->status = EDMA_ST_BUSY;
if (!dw_edma_start_transfer(chan))
chan->status = EDMA_ST_IDLE;
+ dw_edma_core_ch_maybe_doorbell(chan);
}
return err;
@@ -420,6 +429,7 @@ static void dw_edma_device_issue_pending(struct dma_chan *dchan)
chan->status == EDMA_ST_IDLE) {
chan->status = EDMA_ST_BUSY;
dw_edma_start_transfer(chan);
+ dw_edma_core_ch_maybe_doorbell(chan);
}
spin_unlock_irqrestore(&chan->vc.lock, flags);
}
@@ -733,6 +743,8 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
default:
break;
}
+ dw_edma_core_ch_maybe_doorbell(chan);
+
spin_unlock_irqrestore(&chan->vc.lock, flags);
}
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v6 08/10] dmaengine: dw-edma: Prepare LL progress event handling
2026-08-20 17:34 [PATCH v6 00/10] dmaengine: dw-edma: Prepare for dynamic LL appends Koichiro Den
` (6 preceding siblings ...)
2026-08-20 17:34 ` [PATCH v6 07/10] dmaengine: dw-edma: Centralize LL doorbell decisions Koichiro Den
@ 2026-08-20 17:34 ` Koichiro Den
2026-08-20 17:34 ` [PATCH v6 09/10] dmaengine: dw-edma: Prepare deferred IRQ reporting for LL events Koichiro Den
2026-08-20 17:34 ` [PATCH v6 10/10] dmaengine: dw-edma: Prepare LL kicks for event serialization Koichiro Den
9 siblings, 0 replies; 13+ messages in thread
From: Koichiro Den @ 2026-08-20 17:34 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Manivannan Sadhasivam, Gustavo Pimentel,
Kees Cook, Krzysztof Wilczyński, Kishon Vijay Abraham I,
Bjorn Helgaas, Christoph Hellwig, Serge Semin, Cai Huoqing,
Niklas Cassel
Cc: Devendra K Verma, dmaengine, linux-kernel
A later patch records each observed LL event (STOP or PROGRESS) together
with the LLP sample taken in the same interrupt-handler pass. It needs a
provider operation to clear stale LL interrupt status before a channel
restart. Add it.
Centralize runtime request updates in dw_edma_set_request(). A later
patch uses it to cancel a pending LL recheck when STOP or PAUSE changes
the channel policy. Rename the existing workqueue event bits to
distinguish them from provider IRQ events.
No functional change.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
drivers/dma/dw-edma/dw-edma-core.c | 35 ++++++++++++++++-----------
drivers/dma/dw-edma/dw-edma-core.h | 6 +++++
drivers/dma/dw-edma/dw-edma-v0-core.c | 1 +
drivers/dma/dw-edma/dw-hdma-v0-core.c | 1 +
4 files changed, 29 insertions(+), 14 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 5a8407ca68a2..bbfd82b12ab5 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -30,9 +30,9 @@ struct dw_edma_desc *vd2dw_edma_desc(struct virt_dma_desc *vd)
return container_of(vd, struct dw_edma_desc, vd);
}
-enum dw_edma_irq_event {
- DW_EDMA_IRQ_DONE = BIT(0),
- DW_EDMA_IRQ_ABORT = BIT(1),
+enum dw_edma_deferred_event {
+ DW_EDMA_DEFERRED_DONE = BIT(0),
+ DW_EDMA_DEFERRED_ABORT = BIT(1),
};
static inline
@@ -73,6 +73,13 @@ static void vchan_free_desc(struct virt_dma_desc *vdesc)
kfree(vd2dw_edma_desc(vdesc));
}
+/* Must be called with vc.lock held. */
+static void
+dw_edma_set_request(struct dw_edma_chan *chan, enum dw_edma_request request)
+{
+ chan->request = request;
+}
+
static void dw_hdma_set_callback_result(struct virt_dma_desc *vd,
enum dmaengine_tx_result result)
{
@@ -243,7 +250,7 @@ static void dw_edma_finish_termination(struct dw_edma_chan *chan)
if (!chan->non_ll && dw_edma_ll_pending(chan))
dw_edma_core_reset_ll(chan);
- chan->request = EDMA_REQ_NONE;
+ dw_edma_set_request(chan, EDMA_REQ_NONE);
chan->status = EDMA_ST_IDLE;
}
@@ -360,7 +367,7 @@ static int dw_edma_device_pause(struct dma_chan *dchan)
else if (chan->request != EDMA_REQ_NONE)
err = -EPERM;
else
- chan->request = EDMA_REQ_PAUSE;
+ dw_edma_set_request(chan, EDMA_REQ_PAUSE);
return err;
}
@@ -410,10 +417,10 @@ static int dw_edma_device_terminate_all(struct dma_chan *dchan)
} else if (chan->request > EDMA_REQ_PAUSE) {
err = -EPERM;
} else {
- chan->request = EDMA_REQ_STOP;
+ dw_edma_set_request(chan, EDMA_REQ_STOP);
}
if (chan->status == EDMA_ST_IDLE)
- chan->request = EDMA_REQ_NONE;
+ dw_edma_set_request(chan, EDMA_REQ_NONE);
return err;
}
@@ -724,7 +731,7 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
}
if (chan->request == EDMA_REQ_PAUSE) {
- chan->request = EDMA_REQ_NONE;
+ dw_edma_set_request(chan, EDMA_REQ_NONE);
chan->status = EDMA_ST_PAUSE;
break;
}
@@ -764,7 +771,7 @@ static void dw_edma_abort_interrupt(struct dw_edma_chan *chan)
}
if (!chan->non_ll)
dw_edma_core_reset_ll(chan);
- chan->request = EDMA_REQ_NONE;
+ dw_edma_set_request(chan, EDMA_REQ_NONE);
chan->status = EDMA_ST_IDLE;
spin_unlock_irqrestore(&chan->vc.lock, flags);
}
@@ -778,15 +785,15 @@ static void dw_edma_irq_work(struct work_struct *work)
do {
events = atomic_xchg(&chan->irq_pending, 0);
- if (events & DW_EDMA_IRQ_DONE)
+ if (events & DW_EDMA_DEFERRED_DONE)
dw_edma_done_interrupt(chan);
- if (events & DW_EDMA_IRQ_ABORT)
+ if (events & DW_EDMA_DEFERRED_ABORT)
dw_edma_abort_interrupt(chan);
} while (atomic_read(&chan->irq_pending));
}
static void dw_edma_queue_irq_work(struct dw_edma_chan *chan,
- enum dw_edma_irq_event event)
+ enum dw_edma_deferred_event event)
{
atomic_or(event, &chan->irq_pending);
queue_work(chan->dw->wq, &chan->irq_work);
@@ -794,12 +801,12 @@ static void dw_edma_queue_irq_work(struct dw_edma_chan *chan,
static void dw_edma_done_interrupt_deferred(struct dw_edma_chan *chan)
{
- dw_edma_queue_irq_work(chan, DW_EDMA_IRQ_DONE);
+ dw_edma_queue_irq_work(chan, DW_EDMA_DEFERRED_DONE);
}
static void dw_edma_abort_interrupt_deferred(struct dw_edma_chan *chan)
{
- dw_edma_queue_irq_work(chan, DW_EDMA_IRQ_ABORT);
+ dw_edma_queue_irq_work(chan, DW_EDMA_DEFERRED_ABORT);
}
static void dw_edma_emul_irq_ack(struct irq_data *d)
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index 761a5ab4bbb5..8d993e8cccbb 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -163,6 +163,7 @@ struct dw_edma_core_ops {
void (*ll_link)(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr);
void (*ll_clear)(struct dw_edma_chan *chan, u32 idx);
int (*ll_cur_idx)(struct dw_edma_chan *chan);
+ void (*ll_irq_clear)(struct dw_edma_chan *chan);
void (*ch_doorbell)(struct dw_edma_chan *chan);
void (*ch_enable)(struct dw_edma_chan *chan);
void (*ch_config)(struct dw_edma_chan *chan);
@@ -284,6 +285,11 @@ static inline void dw_edma_core_ll_clear(struct dw_edma_chan *chan, u32 idx)
chan->dw->core->ll_clear(chan, idx);
}
+static inline void dw_edma_core_ll_irq_clear(struct dw_edma_chan *chan)
+{
+ chan->dw->core->ll_irq_clear(chan);
+}
+
static inline void dw_edma_core_ch_doorbell(struct dw_edma_chan *chan)
{
chan->dw->core->ch_doorbell(chan);
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index bb86d2f60ab5..9053354eda67 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -686,6 +686,7 @@ static const struct dw_edma_core_ops dw_edma_v0_core = {
.ll_link = dw_edma_v0_core_ll_link,
.ll_clear = dw_edma_v0_core_ll_clear,
.ll_cur_idx = dw_edma_v0_core_ll_cur_idx,
+ .ll_irq_clear = dw_edma_v0_core_clear_done_int,
.ch_doorbell = dw_edma_v0_core_ch_doorbell,
.ch_enable = dw_edma_v0_core_ch_enable,
.ch_config = dw_edma_v0_core_ch_config,
diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
index 47d7a01658eb..4acbace43519 100644
--- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
@@ -442,6 +442,7 @@ static const struct dw_edma_core_ops dw_hdma_v0_core = {
.ll_link = dw_hdma_v0_core_ll_link,
.ll_clear = dw_hdma_v0_core_ll_clear,
.ll_cur_idx = dw_hdma_v0_core_ll_cur_idx,
+ .ll_irq_clear = dw_hdma_v0_core_clear_done_int,
.ch_doorbell = dw_hdma_v0_core_ch_doorbell,
.ch_enable = dw_hdma_v0_core_ch_enable,
.ch_config = dw_hdma_v0_core_ch_config,
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v6 09/10] dmaengine: dw-edma: Prepare deferred IRQ reporting for LL events
2026-08-20 17:34 [PATCH v6 00/10] dmaengine: dw-edma: Prepare for dynamic LL appends Koichiro Den
` (7 preceding siblings ...)
2026-08-20 17:34 ` [PATCH v6 08/10] dmaengine: dw-edma: Prepare LL progress event handling Koichiro Den
@ 2026-08-20 17:34 ` Koichiro Den
2026-08-20 17:34 ` [PATCH v6 10/10] dmaengine: dw-edma: Prepare LL kicks for event serialization Koichiro Den
9 siblings, 0 replies; 13+ messages in thread
From: Koichiro Den @ 2026-08-20 17:34 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Manivannan Sadhasivam, Gustavo Pimentel,
Kees Cook, Krzysztof Wilczyński, Kishon Vijay Abraham I,
Bjorn Helgaas, Christoph Hellwig, Serge Semin, Cai Huoqing,
Niklas Cassel
Cc: Devendra K Verma, dmaengine, linux-kernel
A later change serializes status capture, interrupt clear, and event
recording against channel restart. It must also publish all causes
observed for a channel in that critical section through one callback.
Otherwise, the deferred worker can consume one cause before another from
the same status pass is recorded.
Replace the DONE and ABORT callback pair with one callback carrying an
event bitmap. For now, map the bitmap back to the existing deferred DONE
and ABORT work, so behavior remains unchanged.
Also split DONE handling into a lock-taking wrapper and a vc.lock-held
body. The later LL event path needs to validate its recorded event under
vc.lock before running the existing DONE state machine.
No functional change.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
drivers/dma/dw-edma/dw-edma-core.c | 44 ++++++++++++++++-----------
drivers/dma/dw-edma/dw-edma-core.h | 16 +++++++---
drivers/dma/dw-edma/dw-edma-v0-core.c | 6 ++--
drivers/dma/dw-edma/dw-hdma-v0-core.c | 6 ++--
4 files changed, 44 insertions(+), 28 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index bbfd82b12ab5..97de0ad4489a 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -702,17 +702,16 @@ dw_edma_device_prep_interleaved_dma(struct dma_chan *dchan,
return dw_edma_device_transfer(&xfer, dw_edma_device_get_config(dchan, NULL));
}
-static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
+/* Must be called with vc.lock held. */
+static void dw_edma_done_interrupt_locked(struct dw_edma_chan *chan)
{
struct dw_edma_desc *desc;
struct virt_dma_desc *vd;
- unsigned long flags;
- spin_lock_irqsave(&chan->vc.lock, flags);
- if (chan->status == EDMA_ST_PAUSE) {
- spin_unlock_irqrestore(&chan->vc.lock, flags);
+ lockdep_assert_held(&chan->vc.lock);
+
+ if (chan->status == EDMA_ST_PAUSE)
return;
- }
switch (chan->request) {
case EDMA_REQ_NONE:
@@ -751,7 +750,14 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
break;
}
dw_edma_core_ch_maybe_doorbell(chan);
+}
+static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&chan->vc.lock, flags);
+ dw_edma_done_interrupt_locked(chan);
spin_unlock_irqrestore(&chan->vc.lock, flags);
}
@@ -793,20 +799,24 @@ static void dw_edma_irq_work(struct work_struct *work)
}
static void dw_edma_queue_irq_work(struct dw_edma_chan *chan,
- enum dw_edma_deferred_event event)
+ unsigned int events)
{
- atomic_or(event, &chan->irq_pending);
+ atomic_or(events, &chan->irq_pending);
queue_work(chan->dw->wq, &chan->irq_work);
}
-static void dw_edma_done_interrupt_deferred(struct dw_edma_chan *chan)
+static void dw_edma_record_irq(struct dw_edma_chan *chan, unsigned int events)
{
- dw_edma_queue_irq_work(chan, DW_EDMA_DEFERRED_DONE);
-}
+ unsigned int pending = 0;
-static void dw_edma_abort_interrupt_deferred(struct dw_edma_chan *chan)
-{
- dw_edma_queue_irq_work(chan, DW_EDMA_DEFERRED_ABORT);
+ if (events & (DW_EDMA_IRQ_DONE | DW_EDMA_IRQ_PROGRESS |
+ DW_EDMA_IRQ_STOP))
+ pending |= DW_EDMA_DEFERRED_DONE;
+ if (events & DW_EDMA_IRQ_ABORT)
+ pending |= DW_EDMA_DEFERRED_ABORT;
+
+ if (pending)
+ dw_edma_queue_irq_work(chan, pending);
}
static void dw_edma_emul_irq_ack(struct irq_data *d)
@@ -907,8 +917,7 @@ static inline irqreturn_t dw_edma_interrupt_write_inner(int irq, void *data)
struct dw_edma_irq *dw_irq = data;
return dw_edma_core_handle_int(dw_irq, EDMA_DIR_WRITE,
- dw_edma_done_interrupt_deferred,
- dw_edma_abort_interrupt_deferred);
+ dw_edma_record_irq);
}
static inline irqreturn_t dw_edma_interrupt_read_inner(int irq, void *data)
@@ -916,8 +925,7 @@ static inline irqreturn_t dw_edma_interrupt_read_inner(int irq, void *data)
struct dw_edma_irq *dw_irq = data;
return dw_edma_core_handle_int(dw_irq, EDMA_DIR_READ,
- dw_edma_done_interrupt_deferred,
- dw_edma_abort_interrupt_deferred);
+ dw_edma_record_irq);
}
static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data)
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index 8d993e8cccbb..30b508eb3e3d 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -41,6 +41,13 @@ enum dw_edma_xfer_type {
EDMA_XFER_INTERLEAVED
};
+enum dw_edma_irq_event {
+ DW_EDMA_IRQ_DONE = BIT(0),
+ DW_EDMA_IRQ_PROGRESS = BIT(1),
+ DW_EDMA_IRQ_STOP = BIT(2),
+ DW_EDMA_IRQ_ABORT = BIT(3),
+};
+
struct dw_edma_chan;
struct dw_edma_chunk;
@@ -147,7 +154,8 @@ struct dw_edma {
const struct dw_edma_core_ops *core;
};
-typedef void (*dw_edma_handler_t)(struct dw_edma_chan *);
+typedef void (*dw_edma_handler_t)(struct dw_edma_chan *chan,
+ unsigned int events);
struct dw_edma_core_ops {
void (*off)(struct dw_edma *dw);
@@ -156,7 +164,7 @@ struct dw_edma_core_ops {
u16 (*ch_count)(struct dw_edma *dw, enum dw_edma_dir dir);
enum dma_status (*ch_status)(struct dw_edma_chan *chan);
irqreturn_t (*handle_int)(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
- dw_edma_handler_t done, dw_edma_handler_t abort);
+ dw_edma_handler_t handler);
void (*non_ll_start)(struct dw_edma_chan *chan, struct dw_edma_burst *child);
void (*ll_data)(struct dw_edma_chan *chan, struct dw_edma_burst *burst,
u32 idx, bool cb, bool irq);
@@ -256,9 +264,9 @@ enum dma_status dw_edma_core_ch_status(struct dw_edma_chan *chan)
static inline irqreturn_t
dw_edma_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
- dw_edma_handler_t done, dw_edma_handler_t abort)
+ dw_edma_handler_t handler)
{
- return dw_irq->dw->core->handle_int(dw_irq, dir, done, abort);
+ return dw_irq->dw->core->handle_int(dw_irq, dir, handler);
}
static inline
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index 9053354eda67..4a1bb128fa00 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -336,7 +336,7 @@ static void dw_edma_v0_core_clear_abort_int(struct dw_edma_chan *chan)
static irqreturn_t
dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
- dw_edma_handler_t done, dw_edma_handler_t abort)
+ dw_edma_handler_t handler)
{
struct dw_edma *dw = dw_irq->dw;
unsigned long total, pos, val;
@@ -375,7 +375,7 @@ dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
continue;
dw_edma_v0_core_clear_done_int(chan);
- done(chan);
+ handler(chan, DW_EDMA_IRQ_DONE);
ret = IRQ_HANDLED;
}
@@ -389,7 +389,7 @@ dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
continue;
dw_edma_v0_core_clear_abort_int(chan);
- abort(chan);
+ handler(chan, DW_EDMA_IRQ_ABORT);
ret = IRQ_HANDLED;
}
diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
index 4acbace43519..c6d7f9885aa8 100644
--- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
@@ -174,7 +174,7 @@ static u32 dw_hdma_v0_core_status_int(struct dw_edma_chan *chan)
static irqreturn_t
dw_hdma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
- dw_edma_handler_t done, dw_edma_handler_t abort)
+ dw_edma_handler_t handler)
{
struct dw_edma *dw = dw_irq->dw;
unsigned long total, pos, val;
@@ -200,14 +200,14 @@ dw_hdma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
val = dw_hdma_v0_core_status_int(chan);
if (FIELD_GET(HDMA_V0_STOP_INT_MASK, val)) {
dw_hdma_v0_core_clear_done_int(chan);
- done(chan);
+ handler(chan, DW_EDMA_IRQ_STOP);
ret = IRQ_HANDLED;
}
if (FIELD_GET(HDMA_V0_ABORT_INT_MASK, val)) {
dw_hdma_v0_core_clear_abort_int(chan);
- abort(chan);
+ handler(chan, DW_EDMA_IRQ_ABORT);
ret = IRQ_HANDLED;
}
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v6 10/10] dmaengine: dw-edma: Prepare LL kicks for event serialization
2026-08-20 17:34 [PATCH v6 00/10] dmaengine: dw-edma: Prepare for dynamic LL appends Koichiro Den
` (8 preceding siblings ...)
2026-08-20 17:34 ` [PATCH v6 09/10] dmaengine: dw-edma: Prepare deferred IRQ reporting for LL events Koichiro Den
@ 2026-08-20 17:34 ` Koichiro Den
9 siblings, 0 replies; 13+ messages in thread
From: Koichiro Den @ 2026-08-20 17:34 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Manivannan Sadhasivam, Gustavo Pimentel,
Kees Cook, Krzysztof Wilczyński, Kishon Vijay Abraham I,
Bjorn Helgaas, Christoph Hellwig, Serge Semin, Cai Huoqing,
Niklas Cassel
Cc: Devendra K Verma, dmaengine, linux-kernel
Both eDMA and HDMA perform the same remote LL read-back immediately
before writing the doorbell register.
A later patch serializes each raw doorbell write against IRQ event
capture. Keeping the read-back in the provider callback would hold
event_lock across a remote read. Move the common read-back into the
high-level dw_edma_core_ch_doorbell(), where it can run before the
serialized section. Name the provider write wrapper
dw_edma_core_do_ch_doorbell() to distinguish the raw write, and leave
dw_edma_core_ch_maybe_doorbell() to decide whether a kick is needed.
No functional change.
Suggested-by: Frank Li <Frank.li@oss.nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
drivers/dma/dw-edma/dw-edma-core.c | 18 ++++++++++++++++++
drivers/dma/dw-edma/dw-edma-core.h | 2 +-
drivers/dma/dw-edma/dw-edma-v0-core.c | 16 ----------------
drivers/dma/dw-edma/dw-hdma-v0-core.c | 16 ----------------
4 files changed, 19 insertions(+), 33 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 97de0ad4489a..e2eba6d72b53 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -13,6 +13,7 @@
#include <linux/dmaengine.h>
#include <linux/err.h>
#include <linux/interrupt.h>
+#include <linux/io.h>
#include <linux/irq.h>
#include <linux/dma/edma.h>
#include <linux/dma-mapping.h>
@@ -254,6 +255,23 @@ static void dw_edma_finish_termination(struct dw_edma_chan *chan)
chan->status = EDMA_ST_IDLE;
}
+static void dw_edma_core_ll_sync(struct dw_edma_chan *chan)
+{
+ /*
+ * Remote controller registers and LL memory may be reached through
+ * different paths. Complete posted LL writes before the doorbell.
+ */
+ if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
+ readl(chan->ll_region.vaddr.io);
+}
+
+/* Must be called with vc.lock held for an LL channel. */
+static void dw_edma_core_ch_doorbell(struct dw_edma_chan *chan)
+{
+ dw_edma_core_ll_sync(chan);
+ dw_edma_core_do_ch_doorbell(chan);
+}
+
/* Must be called with vc.lock held. */
static void dw_edma_core_ch_maybe_doorbell(struct dw_edma_chan *chan)
{
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index 30b508eb3e3d..76fd5a41a707 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -298,7 +298,7 @@ static inline void dw_edma_core_ll_irq_clear(struct dw_edma_chan *chan)
chan->dw->core->ll_irq_clear(chan);
}
-static inline void dw_edma_core_ch_doorbell(struct dw_edma_chan *chan)
+static inline void dw_edma_core_do_ch_doorbell(struct dw_edma_chan *chan)
{
chan->dw->core->ch_doorbell(chan);
}
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index 4a1bb128fa00..c053cb4c2844 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -480,20 +480,6 @@ static void dw_edma_v0_core_ch_enable(struct dw_edma_chan *chan)
upper_32_bits(chan->ll_region.paddr));
}
-static void dw_edma_v0_sync_ll_data(struct dw_edma_chan *chan)
-{
- /*
- * In case of remote eDMA engine setup, the DW PCIe RP/EP internal
- * configuration registers and application memory are normally accessed
- * over different buses. Ensure LL-data reaches the memory before the
- * doorbell register is toggled by issuing the dummy-read from the remote
- * LL memory in a hope that the MRd TLP will return only after the
- * last MWr TLP is completed
- */
- if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
- readl(chan->ll_region.vaddr.io);
-}
-
static void dw_edma_v0_core_ch_config(struct dw_edma_chan *chan)
{
struct dw_edma *dw = chan->dw;
@@ -624,8 +610,6 @@ static void dw_edma_v0_core_ch_doorbell(struct dw_edma_chan *chan)
{
struct dw_edma *dw = chan->dw;
- dw_edma_v0_sync_ll_data(chan);
-
/* Doorbell */
SET_RW_32(dw, chan->dir, doorbell,
FIELD_PREP(EDMA_V0_DOORBELL_CH_MASK, chan->id));
diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
index c6d7f9885aa8..0b3c8496b0ad 100644
--- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
@@ -282,20 +282,6 @@ static void dw_hdma_v0_core_ch_enable(struct dw_edma_chan *chan)
HDMA_V0_CONSUMER_CYCLE_STAT | HDMA_V0_CONSUMER_CYCLE_BIT);
}
-static void dw_hdma_v0_sync_ll_data(struct dw_edma_chan *chan)
-{
- /*
- * In case of remote HDMA engine setup, the DW PCIe RP/EP internal
- * configuration registers and application memory are normally accessed
- * over different buses. Ensure LL-data reaches the memory before the
- * doorbell register is toggled by issuing the dummy-read from the remote
- * LL memory in a hope that the MRd TLP will return only after the
- * last MWr TLP is completed
- */
- if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
- readl(chan->ll_region.vaddr.io);
-}
-
static void dw_hdma_v0_core_non_ll_start(struct dw_edma_chan *chan,
struct dw_edma_burst *child)
{
@@ -393,8 +379,6 @@ static void dw_hdma_v0_core_ch_doorbell(struct dw_edma_chan *chan)
{
struct dw_edma *dw = chan->dw;
- dw_hdma_v0_sync_ll_data(chan);
-
/* Doorbell */
SET_CH_32(dw, chan->dir, chan->id, doorbell, HDMA_V0_DOORBELL_START);
}
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread