* [PATCH v3 01/10] dmaengine: dw-edma: Move control field update of DMA link to the last step
2026-07-02 21:21 [PATCH v3 00/10] dmaengine: dw-edma: flatten desc structions and simple code Frank.Li
@ 2026-07-02 21:21 ` Frank.Li
2026-07-02 21:31 ` sashiko-bot
2026-07-02 21:21 ` [PATCH v3 02/10] dmaengine: dw-edma: Add xfer_sz field to struct dw_edma_chunk Frank.Li
` (8 subsequent siblings)
9 siblings, 1 reply; 21+ messages in thread
From: Frank.Li @ 2026-07-02 21:21 UTC (permalink / raw)
To: Manivannan Sadhasivam, Vinod Koul, Gustavo Pimentel, Kees Cook,
Gustavo A. R. Silva, Manivannan Sadhasivam,
Krzysztof Wilczyński, Kishon Vijay Abraham I, Bjorn Helgaas,
Christoph Hellwig, Niklas Cassel
Cc: dmaengine, linux-kernel, linux-hardening, linux-pci, linux-nvme,
Koichiro Den, imx, Verma, Devendra, Frank Li
From: Frank Li <Frank.Li@nxp.com>
The control field in a DMA link list entry must be updated as the final
step because it includes the CB bit, which indicates whether the entry is
ready. Add dma_wmb() to ensure the correct memory write ordering.
Currently the driver does not update DMA link entries while the DMA is
running, so no visible failure occurs. However, fixing the ordering now
prepares the driver for supporting link entry updates during DMA operation.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
drivers/dma/dw-edma/dw-edma-v0-core.c | 10 ++++++----
drivers/dma/dw-edma/dw-hdma-v0-core.c | 10 ++++++----
2 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index cfdd6463252e6..ee5c3c317557b 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -284,17 +284,18 @@ static void dw_edma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
struct dw_edma_v0_lli *lli = chunk->ll_region.vaddr.mem + ofs;
- lli->control = control;
lli->transfer_size = size;
lli->sar.reg = sar;
lli->dar.reg = dar;
+ dma_wmb();
+ lli->control = control;
} else {
struct dw_edma_v0_lli __iomem *lli = chunk->ll_region.vaddr.io + ofs;
- writel(control, &lli->control);
writel(size, &lli->transfer_size);
writeq(sar, &lli->sar.reg);
writeq(dar, &lli->dar.reg);
+ writel(control, &lli->control);
}
}
@@ -306,13 +307,14 @@ static void dw_edma_v0_write_ll_link(struct dw_edma_chunk *chunk,
if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
struct dw_edma_v0_llp *llp = chunk->ll_region.vaddr.mem + ofs;
- llp->control = control;
llp->llp.reg = pointer;
+ dma_wmb();
+ llp->control = control;
} else {
struct dw_edma_v0_llp __iomem *llp = chunk->ll_region.vaddr.io + ofs;
- writel(control, &llp->control);
writeq(pointer, &llp->llp.reg);
+ writel(control, &llp->control);
}
}
diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
index 632abb8b481cf..1201f1ab5f359 100644
--- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
@@ -160,17 +160,18 @@ static void dw_hdma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
struct dw_hdma_v0_lli *lli = chunk->ll_region.vaddr.mem + ofs;
- lli->control = control;
lli->transfer_size = size;
lli->sar.reg = sar;
lli->dar.reg = dar;
+ dma_wmb();
+ lli->control = control;
} else {
struct dw_hdma_v0_lli __iomem *lli = chunk->ll_region.vaddr.io + ofs;
- writel(control, &lli->control);
writel(size, &lli->transfer_size);
writeq(sar, &lli->sar.reg);
writeq(dar, &lli->dar.reg);
+ writel(control, &lli->control);
}
}
@@ -182,13 +183,14 @@ static void dw_hdma_v0_write_ll_link(struct dw_edma_chunk *chunk,
if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
struct dw_hdma_v0_llp *llp = chunk->ll_region.vaddr.mem + ofs;
- llp->control = control;
llp->llp.reg = pointer;
+ dma_wmb();
+ llp->control = control;
} else {
struct dw_hdma_v0_llp __iomem *llp = chunk->ll_region.vaddr.io + ofs;
- writel(control, &llp->control);
writeq(pointer, &llp->llp.reg);
+ writel(control, &llp->control);
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH v3 01/10] dmaengine: dw-edma: Move control field update of DMA link to the last step
2026-07-02 21:21 ` [PATCH v3 01/10] dmaengine: dw-edma: Move control field update of DMA link to the last step Frank.Li
@ 2026-07-02 21:31 ` sashiko-bot
0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-02 21:31 UTC (permalink / raw)
To: Frank.Li; +Cc: dmaengine, vkoul, imx, linux-pci, Frank.Li
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The DMA descriptor's `control` field is updated locklessly via plain assignment, which can cause compiler store tearing and violate the memory write ordering guarantees intended by the patch.
--
commit f34c073c93dcf4643e941a0c9907083868d8f064
Author: Frank Li <Frank.Li@nxp.com>
dmaengine: dw-edma: Move control field update of DMA link to the last step
This commit moves the control field update to the end of the descriptor
programming sequence and adds a memory barrier. The goal is to ensure the
descriptor is fully written before the ready bit in the control field is
set, preparing the driver for concurrent descriptor updates while the DMA
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 cfdd6463252e6..ee5c3c317557b 100644
> --- a/drivers/dma/dw-edma/dw-edma-v0-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
> @@ -284,17 +284,18 @@ static void dw_edma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
> if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
> struct dw_edma_v0_lli *lli = chunk->ll_region.vaddr.mem + ofs;
>
> - lli->control = control;
> lli->transfer_size = size;
> lli->sar.reg = sar;
> lli->dar.reg = dar;
> + dma_wmb();
> + lli->control = control;
[Severity: Medium]
Could this plain assignment cause compiler store tearing and violate the
intended memory ordering?
Because struct dw_edma_v0_lli is __packed and lacks an explicit __aligned
attribute, the compiler assumes a 1-byte alignment. On architectures
compiled with strict alignment, this 32-bit assignment might be split into
four separate byte-sized stores.
If byte 0 is written first, the hardware could see the ready bit set
before the remaining control bits are written, bypassing the safety of the
preceding dma_wmb(). Would it be safer to use WRITE_ONCE() or a proper
accessor here to guarantee a single 32-bit store?
[ ... ]
> @@ -306,13 +307,14 @@ static void dw_edma_v0_write_ll_link(struct dw_edma_chunk *chunk,
> if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
> struct dw_edma_v0_llp *llp = chunk->ll_region.vaddr.mem + ofs;
>
> - llp->control = control;
> llp->llp.reg = pointer;
> + dma_wmb();
> + llp->control = control;
[Severity: Medium]
Does this assignment suffer from the same store tearing issue as the link
data descriptor above, given struct dw_edma_v0_llp is also __packed?
> diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
> index 632abb8b481cf..1201f1ab5f359 100644
> --- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
> +++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
> @@ -160,17 +160,18 @@ static void dw_hdma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
> if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
> struct dw_hdma_v0_lli *lli = chunk->ll_region.vaddr.mem + ofs;
>
> - lli->control = control;
> lli->transfer_size = size;
> lli->sar.reg = sar;
> lli->dar.reg = dar;
> + dma_wmb();
> + lli->control = control;
[Severity: Medium]
Could the same store tearing occur here in dw_hdma_v0_write_ll_data,
since struct dw_hdma_v0_lli is also __packed?
[ ... ]
> @@ -182,13 +183,14 @@ static void dw_hdma_v0_write_ll_link(struct dw_edma_chunk *chunk,
> if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
> struct dw_hdma_v0_llp *llp = chunk->ll_region.vaddr.mem + ofs;
>
> - llp->control = control;
> llp->llp.reg = pointer;
> + dma_wmb();
> + llp->control = control;
[Severity: Medium]
Could the same store tearing occur here in dw_hdma_v0_write_ll_link,
since struct dw_hdma_v0_llp is also __packed?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260702-edma_ll-v3-0-877aa463740c@nxp.com?part=1
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v3 02/10] dmaengine: dw-edma: Add xfer_sz field to struct dw_edma_chunk
2026-07-02 21:21 [PATCH v3 00/10] dmaengine: dw-edma: flatten desc structions and simple code Frank.Li
2026-07-02 21:21 ` [PATCH v3 01/10] dmaengine: dw-edma: Move control field update of DMA link to the last step Frank.Li
@ 2026-07-02 21:21 ` Frank.Li
2026-07-02 21:39 ` sashiko-bot
2026-07-02 21:21 ` [PATCH v3 03/10] dmaengine: dw-edma: Move ll_region from struct dw_edma_chunk to struct dw_edma_chan Frank.Li
` (7 subsequent siblings)
9 siblings, 1 reply; 21+ messages in thread
From: Frank.Li @ 2026-07-02 21:21 UTC (permalink / raw)
To: Manivannan Sadhasivam, Vinod Koul, Gustavo Pimentel, Kees Cook,
Gustavo A. R. Silva, Manivannan Sadhasivam,
Krzysztof Wilczyński, Kishon Vijay Abraham I, Bjorn Helgaas,
Christoph Hellwig, Niklas Cassel
Cc: dmaengine, linux-kernel, linux-hardening, linux-pci, linux-nvme,
Koichiro Den, imx, Verma, Devendra, Frank Li
From: Frank Li <Frank.Li@nxp.com>
Reusing ll_region.sz as the transfer size is misleading because
ll_region.sz represents the memory size of the EDMA link list, not the
amount of data to be transferred.
Add a new xfer_sz field to explicitly indicate the total transfer size
of a chunk.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
drivers/dma/dw-edma/dw-edma-core.c | 4 ++--
drivers/dma/dw-edma/dw-edma-core.h | 1 +
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 1fec1b52e3d47..53469c8c8b82e 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -192,7 +192,7 @@ static int dw_edma_start_transfer(struct dw_edma_chan *chan)
return 0;
dw_edma_core_start(dw, child, !desc->xfer_sz);
- desc->xfer_sz += child->ll_region.sz;
+ desc->xfer_sz += child->xfer_sz;
dw_edma_free_burst(child);
list_del(&child->list);
kfree(child);
@@ -527,7 +527,7 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer,
else if (xfer->type == EDMA_XFER_INTERLEAVED)
burst->sz = xfer->xfer.il->sgl[i % fsz].size;
- chunk->ll_region.sz += burst->sz;
+ chunk->xfer_sz += burst->sz;
desc->alloc_sz += burst->sz;
if (dir == DMA_DEV_TO_MEM) {
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index 6474cacf71953..db5f45bf048c3 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -57,6 +57,7 @@ struct dw_edma_chunk {
u32 bursts_alloc;
u8 cb;
+ u32 xfer_sz;
struct dw_edma_region ll_region; /* Linked list */
};
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH v3 02/10] dmaengine: dw-edma: Add xfer_sz field to struct dw_edma_chunk
2026-07-02 21:21 ` [PATCH v3 02/10] dmaengine: dw-edma: Add xfer_sz field to struct dw_edma_chunk Frank.Li
@ 2026-07-02 21:39 ` sashiko-bot
0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-02 21:39 UTC (permalink / raw)
To: Frank.Li; +Cc: imx, vkoul, Frank.Li, linux-pci, dmaengine
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing `vc.lock` acquisition in `dw_edma_device_resume`, `dw_edma_device_pause`, and `dw_edma_device_terminate_all` leads to severe race conditions with the interrupt handler.
- [High] Missing endianness conversions for DMA descriptors on DW_EDMA_CHIP_LOCAL configurations.
--
commit 4cc66a09b65be2e8496de1853db67b385be4c5bb
Author: Frank Li <Frank.Li@nxp.com>
dmaengine: dw-edma: Add xfer_sz field to struct dw_edma_chunk
Reusing ll_region.sz as the transfer size is misleading because
ll_region.sz represents the memory size of the EDMA link list, not the
amount of data to be transferred.
Add a new xfer_sz field to explicitly indicate the total transfer size
of a chunk.
> diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
> index 1fec1b52e3d47..53469c8c8b82e 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-core.c
> @@ -192,7 +192,7 @@ static int dw_edma_start_transfer(struct dw_edma_chan *chan)
> return 0;
>
> dw_edma_core_start(dw, child, !desc->xfer_sz);
> - desc->xfer_sz += child->ll_region.sz;
> + desc->xfer_sz += child->xfer_sz;
[Severity: High]
This is a pre-existing issue, but I noticed a potential race condition
involving dw_edma_start_transfer() and the DMA control functions.
Does dw_edma_device_resume() safely handle concurrent accesses with the
interrupt handler?
It appears dw_edma_device_resume() calls dw_edma_start_transfer()
locklessly, which eventually invokes vchan_next_desc(). However,
vchan_next_desc() explicitly requires vc.lock to be held.
Similar unlocked modifications occur in dw_edma_device_pause(), where
chan->request is modified without locks:
drivers/dma/dw-edma/dw-edma-core.c:dw_edma_device_pause() {
...
else
chan->request = EDMA_REQ_PAUSE;
}
Can this race with dw_edma_done_interrupt() (which accesses these fields
under vc.lock) and lead to list corruption or lockdep warnings?
> dw_edma_free_burst(child);
> list_del(&child->list);
> kfree(child);
[ ... ]
[Severity: High]
This is a pre-existing issue, but are there missing endianness conversions
for DMA descriptors on DW_EDMA_CHIP_LOCAL configurations?
In drivers/dma/dw-edma/dw-edma-v0-core.c:dw_edma_v0_write_ll_data():
lli->transfer_size = size;
lli->sar.reg = sar;
lli->dar.reg = dar;
dma_wmb();
lli->control = control;
These native assignments write host-endian values. On Big-Endian systems
using local eDMA controllers, will the Little-Endian eDMA hardware
misinterpret addresses, lengths, and control bits?
Similar native assignments also appear in dw_edma_v0_write_ll_link() and
dw_hdma_v0_write_ll_data().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260702-edma_ll-v3-0-877aa463740c@nxp.com?part=2
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v3 03/10] dmaengine: dw-edma: Move ll_region from struct dw_edma_chunk to struct dw_edma_chan
2026-07-02 21:21 [PATCH v3 00/10] dmaengine: dw-edma: flatten desc structions and simple code Frank.Li
2026-07-02 21:21 ` [PATCH v3 01/10] dmaengine: dw-edma: Move control field update of DMA link to the last step Frank.Li
2026-07-02 21:21 ` [PATCH v3 02/10] dmaengine: dw-edma: Add xfer_sz field to struct dw_edma_chunk Frank.Li
@ 2026-07-02 21:21 ` Frank.Li
2026-07-02 21:31 ` sashiko-bot
2026-07-02 21:21 ` [PATCH v3 04/10] dmaengine: dw-edma: Pass down dw_edma_chan to reduce one level of indirection Frank.Li
` (6 subsequent siblings)
9 siblings, 1 reply; 21+ messages in thread
From: Frank.Li @ 2026-07-02 21:21 UTC (permalink / raw)
To: Manivannan Sadhasivam, Vinod Koul, Gustavo Pimentel, Kees Cook,
Gustavo A. R. Silva, Manivannan Sadhasivam,
Krzysztof Wilczyński, Kishon Vijay Abraham I, Bjorn Helgaas,
Christoph Hellwig, Niklas Cassel
Cc: dmaengine, linux-kernel, linux-hardening, linux-pci, linux-nvme,
Koichiro Den, imx, Verma, Devendra, Frank Li
From: Frank Li <Frank.Li@nxp.com>
ll_region is identical for all chunks belonging to the same DMA channel,
so there is no need to copy it into each chunk. Move ll_region to
struct dw_edma_chan to avoid redundant copies.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
drivers/dma/dw-edma/dw-edma-core.c | 15 ++++-----------
drivers/dma/dw-edma/dw-edma-core.h | 2 +-
drivers/dma/dw-edma/dw-edma-v0-core.c | 18 ++++++++++--------
drivers/dma/dw-edma/dw-hdma-v0-core.c | 18 ++++++++++--------
4 files changed, 25 insertions(+), 28 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 53469c8c8b82e..2652ad8e7a8f6 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -64,7 +64,6 @@ static struct dw_edma_burst *dw_edma_alloc_burst(struct dw_edma_chunk *chunk)
static struct dw_edma_chunk *dw_edma_alloc_chunk(struct dw_edma_desc *desc)
{
- struct dw_edma_chip *chip = desc->chan->dw->chip;
struct dw_edma_chan *chan = desc->chan;
struct dw_edma_chunk *chunk;
@@ -81,13 +80,6 @@ static struct dw_edma_chunk *dw_edma_alloc_chunk(struct dw_edma_desc *desc)
* - Even chunks originate CB equal to 1
*/
chunk->cb = !(desc->chunks_alloc % 2);
- if (chan->dir == EDMA_DIR_WRITE) {
- chunk->ll_region.paddr = chip->ll_region_wr[chan->id].paddr;
- chunk->ll_region.vaddr = chip->ll_region_wr[chan->id].vaddr;
- } else {
- chunk->ll_region.paddr = chip->ll_region_rd[chan->id].paddr;
- chunk->ll_region.vaddr = chip->ll_region_rd[chan->id].vaddr;
- }
if (desc->chunk) {
/* Create and add new element into the linked list */
@@ -925,10 +917,11 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
chan->status = EDMA_ST_IDLE;
if (chan->dir == EDMA_DIR_WRITE)
- chan->ll_max = (chip->ll_region_wr[chan->id].sz / EDMA_LL_SZ);
+ chan->ll_region = chip->ll_region_wr[chan->id];
else
- chan->ll_max = (chip->ll_region_rd[chan->id].sz / EDMA_LL_SZ);
- chan->ll_max -= 1;
+ chan->ll_region = chip->ll_region_rd[chan->id];
+
+ chan->ll_max = chan->ll_region.sz / EDMA_LL_SZ - 1;
dev_vdbg(dev, "L. List:\tChannel %s[%u] max_cnt=%u\n",
str_write_read(chan->dir == EDMA_DIR_WRITE),
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index db5f45bf048c3..b96089baf0f9c 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -58,7 +58,6 @@ struct dw_edma_chunk {
u8 cb;
u32 xfer_sz;
- struct dw_edma_region ll_region; /* Linked list */
};
struct dw_edma_desc {
@@ -79,6 +78,7 @@ struct dw_edma_chan {
enum dw_edma_dir dir;
u32 ll_max;
+ struct dw_edma_region ll_region; /* Linked list */
struct msi_msg msi;
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index ee5c3c317557b..51e50f1fdcac4 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -280,9 +280,10 @@ static void dw_edma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
u32 control, u32 size, u64 sar, u64 dar)
{
ptrdiff_t ofs = i * sizeof(struct dw_edma_v0_lli);
+ struct dw_edma_chan *chan = chunk->chan;
if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
- struct dw_edma_v0_lli *lli = chunk->ll_region.vaddr.mem + ofs;
+ struct dw_edma_v0_lli *lli = chan->ll_region.vaddr.mem + ofs;
lli->transfer_size = size;
lli->sar.reg = sar;
@@ -290,7 +291,7 @@ static void dw_edma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
dma_wmb();
lli->control = control;
} else {
- struct dw_edma_v0_lli __iomem *lli = chunk->ll_region.vaddr.io + ofs;
+ struct dw_edma_v0_lli __iomem *lli = chan->ll_region.vaddr.io + ofs;
writel(size, &lli->transfer_size);
writeq(sar, &lli->sar.reg);
@@ -303,15 +304,16 @@ static void dw_edma_v0_write_ll_link(struct dw_edma_chunk *chunk,
int i, u32 control, u64 pointer)
{
ptrdiff_t ofs = i * sizeof(struct dw_edma_v0_lli);
+ struct dw_edma_chan *chan = chunk->chan;
if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
- struct dw_edma_v0_llp *llp = chunk->ll_region.vaddr.mem + ofs;
+ struct dw_edma_v0_llp *llp = chan->ll_region.vaddr.mem + ofs;
llp->llp.reg = pointer;
dma_wmb();
llp->control = control;
} else {
- struct dw_edma_v0_llp __iomem *llp = chunk->ll_region.vaddr.io + ofs;
+ struct dw_edma_v0_llp __iomem *llp = chan->ll_region.vaddr.io + ofs;
writeq(pointer, &llp->llp.reg);
writel(control, &llp->control);
@@ -345,7 +347,7 @@ static void dw_edma_v0_core_write_chunk(struct dw_edma_chunk *chunk)
if (!chunk->cb)
control |= DW_EDMA_V0_CB;
- dw_edma_v0_write_ll_link(chunk, i, control, chunk->ll_region.paddr);
+ dw_edma_v0_write_ll_link(chunk, i, control, chan->ll_region.paddr);
}
static void dw_edma_v0_sync_ll_data(struct dw_edma_chunk *chunk)
@@ -359,7 +361,7 @@ static void dw_edma_v0_sync_ll_data(struct dw_edma_chunk *chunk)
* last MWr TLP is completed
*/
if (!(chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
- readl(chunk->ll_region.vaddr.io);
+ readl(chunk->chan->ll_region.vaddr.io);
}
static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
@@ -430,9 +432,9 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
/* Linked list */
/* llp is not aligned on 64bit -> keep 32bit accesses */
SET_CH_32(dw, chan->dir, chan->id, llp.lsb,
- lower_32_bits(chunk->ll_region.paddr));
+ lower_32_bits(chan->ll_region.paddr));
SET_CH_32(dw, chan->dir, chan->id, llp.msb,
- upper_32_bits(chunk->ll_region.paddr));
+ upper_32_bits(chan->ll_region.paddr));
}
dw_edma_v0_sync_ll_data(chunk);
diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
index 1201f1ab5f359..20089d57f8ab0 100644
--- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
@@ -156,9 +156,10 @@ static void dw_hdma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
u32 control, u32 size, u64 sar, u64 dar)
{
ptrdiff_t ofs = i * sizeof(struct dw_hdma_v0_lli);
+ struct dw_edma_chan *chan = chunk->chan;
if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
- struct dw_hdma_v0_lli *lli = chunk->ll_region.vaddr.mem + ofs;
+ struct dw_hdma_v0_lli *lli = chan->ll_region.vaddr.mem + ofs;
lli->transfer_size = size;
lli->sar.reg = sar;
@@ -166,7 +167,7 @@ static void dw_hdma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
dma_wmb();
lli->control = control;
} else {
- struct dw_hdma_v0_lli __iomem *lli = chunk->ll_region.vaddr.io + ofs;
+ struct dw_hdma_v0_lli __iomem *lli = chan->ll_region.vaddr.io + ofs;
writel(size, &lli->transfer_size);
writeq(sar, &lli->sar.reg);
@@ -179,15 +180,16 @@ static void dw_hdma_v0_write_ll_link(struct dw_edma_chunk *chunk,
int i, u32 control, u64 pointer)
{
ptrdiff_t ofs = i * sizeof(struct dw_hdma_v0_lli);
+ struct dw_edma_chan *chan = chunk->chan;
if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
- struct dw_hdma_v0_llp *llp = chunk->ll_region.vaddr.mem + ofs;
+ struct dw_hdma_v0_llp *llp = chan->ll_region.vaddr.mem + ofs;
llp->llp.reg = pointer;
dma_wmb();
llp->control = control;
} else {
- struct dw_hdma_v0_llp __iomem *llp = chunk->ll_region.vaddr.io + ofs;
+ struct dw_hdma_v0_llp __iomem *llp = chan->ll_region.vaddr.io + ofs;
writeq(pointer, &llp->llp.reg);
writel(control, &llp->control);
@@ -210,7 +212,7 @@ static void dw_hdma_v0_core_write_chunk(struct dw_edma_chunk *chunk)
if (!chunk->cb)
control |= DW_HDMA_V0_CB;
- dw_hdma_v0_write_ll_link(chunk, i, control, chunk->ll_region.paddr);
+ dw_hdma_v0_write_ll_link(chunk, i, control, chunk->chan->ll_region.paddr);
}
static void dw_hdma_v0_sync_ll_data(struct dw_edma_chunk *chunk)
@@ -224,7 +226,7 @@ static void dw_hdma_v0_sync_ll_data(struct dw_edma_chunk *chunk)
* last MWr TLP is completed
*/
if (!(chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
- readl(chunk->ll_region.vaddr.io);
+ readl(chunk->chan->ll_region.vaddr.io);
}
static void dw_hdma_v0_core_ll_start(struct dw_edma_chunk *chunk, bool first)
@@ -251,9 +253,9 @@ static void dw_hdma_v0_core_ll_start(struct dw_edma_chunk *chunk, bool first)
/* Linked list */
/* llp is not aligned on 64bit -> keep 32bit accesses */
SET_CH_32(dw, chan->dir, chan->id, llp.lsb,
- lower_32_bits(chunk->ll_region.paddr));
+ lower_32_bits(chan->ll_region.paddr));
SET_CH_32(dw, chan->dir, chan->id, llp.msb,
- upper_32_bits(chunk->ll_region.paddr));
+ upper_32_bits(chan->ll_region.paddr));
/* Set consumer cycle */
SET_CH_32(dw, chan->dir, chan->id, cycle_sync,
HDMA_V0_CONSUMER_CYCLE_STAT | HDMA_V0_CONSUMER_CYCLE_BIT);
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH v3 03/10] dmaengine: dw-edma: Move ll_region from struct dw_edma_chunk to struct dw_edma_chan
2026-07-02 21:21 ` [PATCH v3 03/10] dmaengine: dw-edma: Move ll_region from struct dw_edma_chunk to struct dw_edma_chan Frank.Li
@ 2026-07-02 21:31 ` sashiko-bot
0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-02 21:31 UTC (permalink / raw)
To: Frank.Li; +Cc: imx, vkoul, dmaengine, linux-pci, Frank.Li
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing lock acquisition in dw_edma_device_resume() when accessing vchan descriptors.
--
commit 80df5f151f3cb77451f6a2e4ec10cbaad5b50565
Author: Frank Li <Frank.Li@nxp.com>
dmaengine: dw-edma: Move ll_region from struct dw_edma_chunk to struct dw_edma_chan
This commit moves the ll_region field from struct dw_edma_chunk to struct
dw_edma_chan to avoid redundant copies. Since the ll_region is identical
for all chunks belonging to the same DMA channel, it can be shared.
> diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
> index 53469c8c8b82e..2652ad8e7a8f6 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-core.c
[ ... ]
> static int dw_edma_device_resume(struct dma_chan *dchan)
> {
> ...
> dw_edma_start_transfer(chan);
> ...
> }
[Severity: High]
This is a pre-existing issue, but can this sequence cause a data race on
the descriptor lists?
Looking at dw_edma_device_resume(), dw_edma_start_transfer() is called
without holding the chan->vc.lock. This invokes vchan_next_desc(), which
reads from the desc_issued list.
If another CPU concurrently calls dw_edma_device_issue_pending(), it uses
vchan_issue_pending() which runs list_splice_tail_init() on desc_issued
while holding the lock.
Could this concurrent access corrupt the list pointers when a client driver
dynamically pauses and resumes channels?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260702-edma_ll-v3-0-877aa463740c@nxp.com?part=3
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v3 04/10] dmaengine: dw-edma: Pass down dw_edma_chan to reduce one level of indirection
2026-07-02 21:21 [PATCH v3 00/10] dmaengine: dw-edma: flatten desc structions and simple code Frank.Li
` (2 preceding siblings ...)
2026-07-02 21:21 ` [PATCH v3 03/10] dmaengine: dw-edma: Move ll_region from struct dw_edma_chunk to struct dw_edma_chan Frank.Li
@ 2026-07-02 21:21 ` Frank.Li
2026-07-02 21:28 ` sashiko-bot
2026-07-02 21:21 ` [PATCH v3 05/10] dmaengine: dw-edma: Add helper dw_(edma|hdma)_v0_core_ch_enable() Frank.Li
` (5 subsequent siblings)
9 siblings, 1 reply; 21+ messages in thread
From: Frank.Li @ 2026-07-02 21:21 UTC (permalink / raw)
To: Manivannan Sadhasivam, Vinod Koul, Gustavo Pimentel, Kees Cook,
Gustavo A. R. Silva, Manivannan Sadhasivam,
Krzysztof Wilczyński, Kishon Vijay Abraham I, Bjorn Helgaas,
Christoph Hellwig, Niklas Cassel
Cc: dmaengine, linux-kernel, linux-hardening, linux-pci, linux-nvme,
Koichiro Den, imx, Verma, Devendra, Frank Li
From: Frank Li <Frank.Li@nxp.com>
Some helper functions do not use any information from dw_edma_chunk, so
passing a dw_edma_chan pointer directly avoids an unnecessary level of
pointer dereferencing and simplifies data access.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
drivers/dma/dw-edma/dw-edma-v0-core.c | 22 ++++++++++------------
drivers/dma/dw-edma/dw-hdma-v0-core.c | 23 +++++++++++------------
2 files changed, 21 insertions(+), 24 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index 51e50f1fdcac4..c341aa5343417 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -276,13 +276,12 @@ dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
return ret;
}
-static void dw_edma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
+static void dw_edma_v0_write_ll_data(struct dw_edma_chan *chan, int i,
u32 control, u32 size, u64 sar, u64 dar)
{
ptrdiff_t ofs = i * sizeof(struct dw_edma_v0_lli);
- struct dw_edma_chan *chan = chunk->chan;
- if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
+ if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
struct dw_edma_v0_lli *lli = chan->ll_region.vaddr.mem + ofs;
lli->transfer_size = size;
@@ -300,13 +299,12 @@ static void dw_edma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
}
}
-static void dw_edma_v0_write_ll_link(struct dw_edma_chunk *chunk,
+static void dw_edma_v0_write_ll_link(struct dw_edma_chan *chan,
int i, u32 control, u64 pointer)
{
ptrdiff_t ofs = i * sizeof(struct dw_edma_v0_lli);
- struct dw_edma_chan *chan = chunk->chan;
- if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
+ if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
struct dw_edma_v0_llp *llp = chan->ll_region.vaddr.mem + ofs;
llp->llp.reg = pointer;
@@ -339,7 +337,7 @@ static void dw_edma_v0_core_write_chunk(struct dw_edma_chunk *chunk)
control |= DW_EDMA_V0_RIE;
}
- dw_edma_v0_write_ll_data(chunk, i++, control, child->sz,
+ dw_edma_v0_write_ll_data(chan, i++, control, child->sz,
child->sar, child->dar);
}
@@ -347,10 +345,10 @@ static void dw_edma_v0_core_write_chunk(struct dw_edma_chunk *chunk)
if (!chunk->cb)
control |= DW_EDMA_V0_CB;
- dw_edma_v0_write_ll_link(chunk, i, control, chan->ll_region.paddr);
+ dw_edma_v0_write_ll_link(chan, i, control, chan->ll_region.paddr);
}
-static void dw_edma_v0_sync_ll_data(struct dw_edma_chunk *chunk)
+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
@@ -360,8 +358,8 @@ static void dw_edma_v0_sync_ll_data(struct dw_edma_chunk *chunk)
* LL memory in a hope that the MRd TLP will return only after the
* last MWr TLP is completed
*/
- if (!(chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
- readl(chunk->chan->ll_region.vaddr.io);
+ if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
+ readl(chan->ll_region.vaddr.io);
}
static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
@@ -437,7 +435,7 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
upper_32_bits(chan->ll_region.paddr));
}
- dw_edma_v0_sync_ll_data(chunk);
+ dw_edma_v0_sync_ll_data(chan);
/* Doorbell */
SET_RW_32(dw, chan->dir, doorbell,
diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
index 20089d57f8ab0..156b1cc225091 100644
--- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
@@ -152,13 +152,12 @@ dw_hdma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
return ret;
}
-static void dw_hdma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
+static void dw_hdma_v0_write_ll_data(struct dw_edma_chan *chan, int i,
u32 control, u32 size, u64 sar, u64 dar)
{
ptrdiff_t ofs = i * sizeof(struct dw_hdma_v0_lli);
- struct dw_edma_chan *chan = chunk->chan;
- if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
+ if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
struct dw_hdma_v0_lli *lli = chan->ll_region.vaddr.mem + ofs;
lli->transfer_size = size;
@@ -176,13 +175,12 @@ static void dw_hdma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
}
}
-static void dw_hdma_v0_write_ll_link(struct dw_edma_chunk *chunk,
+static void dw_hdma_v0_write_ll_link(struct dw_edma_chan *chan,
int i, u32 control, u64 pointer)
{
ptrdiff_t ofs = i * sizeof(struct dw_hdma_v0_lli);
- struct dw_edma_chan *chan = chunk->chan;
- if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
+ if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
struct dw_hdma_v0_llp *llp = chan->ll_region.vaddr.mem + ofs;
llp->llp.reg = pointer;
@@ -198,6 +196,7 @@ static void dw_hdma_v0_write_ll_link(struct dw_edma_chunk *chunk,
static void dw_hdma_v0_core_write_chunk(struct dw_edma_chunk *chunk)
{
+ struct dw_edma_chan *chan = chunk->chan;
struct dw_edma_burst *child;
u32 control = 0, i = 0;
@@ -205,17 +204,17 @@ static void dw_hdma_v0_core_write_chunk(struct dw_edma_chunk *chunk)
control = DW_HDMA_V0_CB;
list_for_each_entry(child, &chunk->burst->list, list)
- dw_hdma_v0_write_ll_data(chunk, i++, control, child->sz,
+ dw_hdma_v0_write_ll_data(chan, i++, control, child->sz,
child->sar, child->dar);
control = DW_HDMA_V0_LLP | DW_HDMA_V0_TCB;
if (!chunk->cb)
control |= DW_HDMA_V0_CB;
- dw_hdma_v0_write_ll_link(chunk, i, control, chunk->chan->ll_region.paddr);
+ dw_hdma_v0_write_ll_link(chan, i, control, chunk->chan->ll_region.paddr);
}
-static void dw_hdma_v0_sync_ll_data(struct dw_edma_chunk *chunk)
+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
@@ -225,8 +224,8 @@ static void dw_hdma_v0_sync_ll_data(struct dw_edma_chunk *chunk)
* LL memory in a hope that the MRd TLP will return only after the
* last MWr TLP is completed
*/
- if (!(chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
- readl(chunk->chan->ll_region.vaddr.io);
+ if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
+ readl(chan->ll_region.vaddr.io);
}
static void dw_hdma_v0_core_ll_start(struct dw_edma_chunk *chunk, bool first)
@@ -261,7 +260,7 @@ static void dw_hdma_v0_core_ll_start(struct dw_edma_chunk *chunk, bool first)
HDMA_V0_CONSUMER_CYCLE_STAT | HDMA_V0_CONSUMER_CYCLE_BIT);
}
- dw_hdma_v0_sync_ll_data(chunk);
+ dw_hdma_v0_sync_ll_data(chan);
/* Doorbell */
SET_CH_32(dw, chan->dir, chan->id, doorbell, HDMA_V0_DOORBELL_START);
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH v3 05/10] dmaengine: dw-edma: Add helper dw_(edma|hdma)_v0_core_ch_enable()
2026-07-02 21:21 [PATCH v3 00/10] dmaengine: dw-edma: flatten desc structions and simple code Frank.Li
` (3 preceding siblings ...)
2026-07-02 21:21 ` [PATCH v3 04/10] dmaengine: dw-edma: Pass down dw_edma_chan to reduce one level of indirection Frank.Li
@ 2026-07-02 21:21 ` Frank.Li
2026-07-02 21:29 ` sashiko-bot
2026-07-02 21:21 ` [PATCH v3 06/10] dmaengine: dw-edma: Add callbacks to fill link list entries Frank.Li
` (4 subsequent siblings)
9 siblings, 1 reply; 21+ messages in thread
From: Frank.Li @ 2026-07-02 21:21 UTC (permalink / raw)
To: Manivannan Sadhasivam, Vinod Koul, Gustavo Pimentel, Kees Cook,
Gustavo A. R. Silva, Manivannan Sadhasivam,
Krzysztof Wilczyński, Kishon Vijay Abraham I, Bjorn Helgaas,
Christoph Hellwig, Niklas Cassel
Cc: dmaengine, linux-kernel, linux-hardening, linux-pci, linux-nvme,
Koichiro Den, imx, Verma, Devendra, Frank Li
From: Frank Li <Frank.Li@nxp.com>
Move the channel-enable logic into a new helper function,
dw_(edma|hdma)_v0_core_ch_enable(), in preparation for supporting dynamic
link entry additions.
No functional changes.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
drivers/dma/dw-edma/dw-edma-v0-core.c | 128 +++++++++++++++++-----------------
drivers/dma/dw-edma/dw-hdma-v0-core.c | 54 +++++++-------
2 files changed, 93 insertions(+), 89 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index c341aa5343417..8d38867cd9983 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -318,6 +318,67 @@ static void dw_edma_v0_write_ll_link(struct dw_edma_chan *chan,
}
}
+static void dw_edma_v0_core_ch_enable(struct dw_edma_chan *chan)
+{
+ struct dw_edma *dw = chan->dw;
+ unsigned long flags;
+ u32 tmp;
+
+ /* Enable engine */
+ SET_RW_32(dw, chan->dir, engine_en, BIT(0));
+ if (dw->chip->mf == EDMA_MF_HDMA_COMPAT) {
+ switch (chan->id) {
+ case 0:
+ SET_RW_COMPAT(dw, chan->dir, ch0_pwr_en, BIT(0));
+ break;
+ case 1:
+ SET_RW_COMPAT(dw, chan->dir, ch1_pwr_en, BIT(0));
+ break;
+ case 2:
+ SET_RW_COMPAT(dw, chan->dir, ch2_pwr_en, BIT(0));
+ break;
+ case 3:
+ SET_RW_COMPAT(dw, chan->dir, ch3_pwr_en, BIT(0));
+ break;
+ case 4:
+ SET_RW_COMPAT(dw, chan->dir, ch4_pwr_en, BIT(0));
+ break;
+ case 5:
+ SET_RW_COMPAT(dw, chan->dir, ch5_pwr_en, BIT(0));
+ break;
+ case 6:
+ SET_RW_COMPAT(dw, chan->dir, ch6_pwr_en, BIT(0));
+ break;
+ case 7:
+ SET_RW_COMPAT(dw, chan->dir, ch7_pwr_en, BIT(0));
+ break;
+ }
+ }
+ /* Interrupt unmask - done, abort */
+ raw_spin_lock_irqsave(&dw->lock, flags);
+
+ tmp = GET_RW_32(dw, chan->dir, int_mask);
+ tmp &= ~FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id));
+ tmp &= ~FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id));
+ SET_RW_32(dw, chan->dir, int_mask, tmp);
+ /* Linked list error */
+ tmp = GET_RW_32(dw, chan->dir, linked_list_err_en);
+ tmp |= FIELD_PREP(EDMA_V0_LINKED_LIST_ERR_MASK, BIT(chan->id));
+ SET_RW_32(dw, chan->dir, linked_list_err_en, tmp);
+
+ raw_spin_unlock_irqrestore(&dw->lock, flags);
+
+ /* Channel control */
+ SET_CH_32(dw, chan->dir, chan->id, ch_control1,
+ (DW_EDMA_V0_CCS | DW_EDMA_V0_LLE));
+ /* Linked list */
+ /* llp is not aligned on 64bit -> keep 32bit accesses */
+ SET_CH_32(dw, chan->dir, chan->id, llp.lsb,
+ lower_32_bits(chan->ll_region.paddr));
+ SET_CH_32(dw, chan->dir, chan->id, llp.msb,
+ upper_32_bits(chan->ll_region.paddr));
+}
+
static void dw_edma_v0_core_write_chunk(struct dw_edma_chunk *chunk)
{
struct dw_edma_burst *child;
@@ -366,74 +427,11 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
{
struct dw_edma_chan *chan = chunk->chan;
struct dw_edma *dw = chan->dw;
- unsigned long flags;
- u32 tmp;
dw_edma_v0_core_write_chunk(chunk);
- if (first) {
- /* Enable engine */
- SET_RW_32(dw, chan->dir, engine_en, BIT(0));
- if (dw->chip->mf == EDMA_MF_HDMA_COMPAT) {
- switch (chan->id) {
- case 0:
- SET_RW_COMPAT(dw, chan->dir, ch0_pwr_en,
- BIT(0));
- break;
- case 1:
- SET_RW_COMPAT(dw, chan->dir, ch1_pwr_en,
- BIT(0));
- break;
- case 2:
- SET_RW_COMPAT(dw, chan->dir, ch2_pwr_en,
- BIT(0));
- break;
- case 3:
- SET_RW_COMPAT(dw, chan->dir, ch3_pwr_en,
- BIT(0));
- break;
- case 4:
- SET_RW_COMPAT(dw, chan->dir, ch4_pwr_en,
- BIT(0));
- break;
- case 5:
- SET_RW_COMPAT(dw, chan->dir, ch5_pwr_en,
- BIT(0));
- break;
- case 6:
- SET_RW_COMPAT(dw, chan->dir, ch6_pwr_en,
- BIT(0));
- break;
- case 7:
- SET_RW_COMPAT(dw, chan->dir, ch7_pwr_en,
- BIT(0));
- break;
- }
- }
- /* Interrupt unmask - done, abort */
- raw_spin_lock_irqsave(&dw->lock, flags);
-
- tmp = GET_RW_32(dw, chan->dir, int_mask);
- tmp &= ~FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id));
- tmp &= ~FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id));
- SET_RW_32(dw, chan->dir, int_mask, tmp);
- /* Linked list error */
- tmp = GET_RW_32(dw, chan->dir, linked_list_err_en);
- tmp |= FIELD_PREP(EDMA_V0_LINKED_LIST_ERR_MASK, BIT(chan->id));
- SET_RW_32(dw, chan->dir, linked_list_err_en, tmp);
-
- raw_spin_unlock_irqrestore(&dw->lock, flags);
-
- /* Channel control */
- SET_CH_32(dw, chan->dir, chan->id, ch_control1,
- (DW_EDMA_V0_CCS | DW_EDMA_V0_LLE));
- /* Linked list */
- /* llp is not aligned on 64bit -> keep 32bit accesses */
- SET_CH_32(dw, chan->dir, chan->id, llp.lsb,
- lower_32_bits(chan->ll_region.paddr));
- SET_CH_32(dw, chan->dir, chan->id, llp.msb,
- upper_32_bits(chan->ll_region.paddr));
- }
+ if (first)
+ dw_edma_v0_core_ch_enable(chan);
dw_edma_v0_sync_ll_data(chan);
diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
index 156b1cc225091..31bbdc6a40642 100644
--- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
@@ -194,6 +194,34 @@ static void dw_hdma_v0_write_ll_link(struct dw_edma_chan *chan,
}
}
+static void dw_hdma_v0_core_ch_enable(struct dw_edma_chan *chan)
+{
+ struct dw_edma *dw = chan->dw;
+ u32 tmp;
+
+ /* Enable engine */
+ SET_CH_32(dw, chan->dir, chan->id, ch_en, BIT(0));
+ /* Interrupt unmask - stop, abort */
+ tmp = GET_CH_32(dw, chan->dir, chan->id, int_setup);
+ tmp &= ~(HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK);
+ /* Interrupt enable - stop, abort */
+ tmp |= HDMA_V0_LOCAL_STOP_INT_EN | HDMA_V0_LOCAL_ABORT_INT_EN;
+ if (!(dw->chip->flags & DW_EDMA_CHIP_LOCAL))
+ tmp |= HDMA_V0_REMOTE_STOP_INT_EN | HDMA_V0_REMOTE_ABORT_INT_EN;
+ SET_CH_32(dw, chan->dir, chan->id, int_setup, tmp);
+ /* Channel control */
+ SET_CH_32(dw, chan->dir, chan->id, control1, HDMA_V0_LINKLIST_EN);
+ /* Linked list */
+ /* llp is not aligned on 64bit -> keep 32bit accesses */
+ SET_CH_32(dw, chan->dir, chan->id, llp.lsb,
+ lower_32_bits(chan->ll_region.paddr));
+ SET_CH_32(dw, chan->dir, chan->id, llp.msb,
+ upper_32_bits(chan->ll_region.paddr));
+ /* Set consumer cycle */
+ SET_CH_32(dw, chan->dir, chan->id, cycle_sync,
+ HDMA_V0_CONSUMER_CYCLE_STAT | HDMA_V0_CONSUMER_CYCLE_BIT);
+}
+
static void dw_hdma_v0_core_write_chunk(struct dw_edma_chunk *chunk)
{
struct dw_edma_chan *chan = chunk->chan;
@@ -232,33 +260,11 @@ static void dw_hdma_v0_core_ll_start(struct dw_edma_chunk *chunk, bool first)
{
struct dw_edma_chan *chan = chunk->chan;
struct dw_edma *dw = chan->dw;
- u32 tmp;
dw_hdma_v0_core_write_chunk(chunk);
- if (first) {
- /* Enable engine */
- SET_CH_32(dw, chan->dir, chan->id, ch_en, BIT(0));
- /* Interrupt unmask - stop, abort */
- tmp = GET_CH_32(dw, chan->dir, chan->id, int_setup);
- tmp &= ~(HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK);
- /* Interrupt enable - stop, abort */
- tmp |= HDMA_V0_LOCAL_STOP_INT_EN | HDMA_V0_LOCAL_ABORT_INT_EN;
- if (!(dw->chip->flags & DW_EDMA_CHIP_LOCAL))
- tmp |= HDMA_V0_REMOTE_STOP_INT_EN | HDMA_V0_REMOTE_ABORT_INT_EN;
- SET_CH_32(dw, chan->dir, chan->id, int_setup, tmp);
- /* Channel control */
- SET_CH_32(dw, chan->dir, chan->id, control1, HDMA_V0_LINKLIST_EN);
- /* Linked list */
- /* llp is not aligned on 64bit -> keep 32bit accesses */
- SET_CH_32(dw, chan->dir, chan->id, llp.lsb,
- lower_32_bits(chan->ll_region.paddr));
- SET_CH_32(dw, chan->dir, chan->id, llp.msb,
- upper_32_bits(chan->ll_region.paddr));
- /* Set consumer cycle */
- SET_CH_32(dw, chan->dir, chan->id, cycle_sync,
- HDMA_V0_CONSUMER_CYCLE_STAT | HDMA_V0_CONSUMER_CYCLE_BIT);
- }
+ if (first)
+ dw_hdma_v0_core_ch_enable(chan);
dw_hdma_v0_sync_ll_data(chan);
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH v3 06/10] dmaengine: dw-edma: Add callbacks to fill link list entries
2026-07-02 21:21 [PATCH v3 00/10] dmaengine: dw-edma: flatten desc structions and simple code Frank.Li
` (4 preceding siblings ...)
2026-07-02 21:21 ` [PATCH v3 05/10] dmaengine: dw-edma: Add helper dw_(edma|hdma)_v0_core_ch_enable() Frank.Li
@ 2026-07-02 21:21 ` Frank.Li
2026-07-02 21:31 ` sashiko-bot
2026-07-02 21:21 ` [PATCH v3 07/10] dmaengine: dw-edma: Add non_ll_start() callback Frank.Li
` (3 subsequent siblings)
9 siblings, 1 reply; 21+ messages in thread
From: Frank.Li @ 2026-07-02 21:21 UTC (permalink / raw)
To: Manivannan Sadhasivam, Vinod Koul, Gustavo Pimentel, Kees Cook,
Gustavo A. R. Silva, Manivannan Sadhasivam,
Krzysztof Wilczyński, Kishon Vijay Abraham I, Bjorn Helgaas,
Christoph Hellwig, Niklas Cassel
Cc: dmaengine, linux-kernel, linux-hardening, linux-pci, linux-nvme,
Koichiro Den, imx, Verma, Devendra, Frank Li
From: Frank Li <Frank.Li@nxp.com>
Introduce four new callbacks to fill link list entries in preparation for
replacing dw_(edma|hdma)_v0_core_start().
Filling link list entries is expected to become more complex, and without
this abstraction both eDMA and HDMA paths would need to duplicate the same
logic. Add fill-entry callbacks so the code can be shared cleanly between
eDMA and HDMA implementations.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
change in v2
- update commit message
- use eDMA and HDMI
- keep inline to avoid build warnings. dw-edma-v0-core.c also include
dw-edma-core.h
---
drivers/dma/dw-edma/dw-edma-core.h | 29 ++++++++++++++++++++++
drivers/dma/dw-edma/dw-edma-v0-core.c | 46 +++++++++++++++++++++++++++++++++++
drivers/dma/dw-edma/dw-hdma-v0-core.c | 38 +++++++++++++++++++++++++++++
3 files changed, 113 insertions(+)
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index b96089baf0f9c..bab4d49c92feb 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -126,6 +126,12 @@ struct dw_edma_core_ops {
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);
void (*start)(struct dw_edma_chunk *chunk, bool first);
+ 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 (*ch_doorbell)(struct dw_edma_chan *chan);
+ void (*ch_enable)(struct dw_edma_chan *chan);
+
void (*ch_config)(struct dw_edma_chan *chan);
void (*debugfs_on)(struct dw_edma *dw);
void (*ack_emulated_irq)(struct dw_edma *dw);
@@ -204,6 +210,29 @@ void dw_edma_core_ch_config(struct dw_edma_chan *chan)
chan->dw->core->ch_config(chan);
}
+static inline void
+dw_edma_core_ll_data(struct dw_edma_chan *chan, struct dw_edma_burst *burst,
+ u32 idx, bool cb, bool irq)
+{
+ chan->dw->core->ll_data(chan, burst, idx, cb, irq);
+}
+
+static inline void
+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_ch_doorbell(struct dw_edma_chan *chan)
+{
+ chan->dw->core->ch_doorbell(chan);
+}
+
+static inline void dw_edma_core_ch_enable(struct dw_edma_chan *chan)
+{
+ chan->dw->core->ch_enable(chan);
+}
+
static inline
void dw_edma_core_debugfs_on(struct dw_edma *dw)
{
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index 8d38867cd9983..10ad63d7e6016 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -509,6 +509,48 @@ static void dw_edma_v0_core_ch_config(struct dw_edma_chan *chan)
}
}
+static void
+dw_edma_v0_core_ll_data(struct dw_edma_chan *chan, struct dw_edma_burst *burst,
+ u32 idx, bool cb, bool irq)
+{
+ u32 control = 0;
+
+ if (cb)
+ control |= DW_EDMA_V0_CB;
+
+ if (irq) {
+ control |= DW_EDMA_V0_LIE;
+
+ if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
+ control |= DW_EDMA_V0_RIE;
+ }
+
+ dw_edma_v0_write_ll_data(chan, idx, control, burst->sz, burst->sar,
+ burst->dar);
+}
+
+static void
+dw_edma_v0_core_ll_link(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr)
+{
+ u32 control = DW_EDMA_V0_LLP | DW_EDMA_V0_TCB;
+
+ if (!cb)
+ control |= DW_EDMA_V0_CB;
+
+ dw_edma_v0_write_ll_link(chan, idx, control, chan->ll_region.paddr);
+}
+
+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));
+}
+
/* eDMA debugfs callbacks */
static void dw_edma_v0_core_debugfs_on(struct dw_edma *dw)
{
@@ -540,6 +582,10 @@ static const struct dw_edma_core_ops dw_edma_v0_core = {
.ch_status = dw_edma_v0_core_ch_status,
.handle_int = dw_edma_v0_core_handle_int,
.start = dw_edma_v0_core_start,
+ .ll_data = dw_edma_v0_core_ll_data,
+ .ll_link = dw_edma_v0_core_ll_link,
+ .ch_doorbell = dw_edma_v0_core_ch_doorbell,
+ .ch_enable = dw_edma_v0_core_ch_enable,
.ch_config = dw_edma_v0_core_ch_config,
.debugfs_on = dw_edma_v0_core_debugfs_on,
.ack_emulated_irq = dw_edma_v0_core_ack_emulated_irq,
diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
index 31bbdc6a40642..52c6ea09fcab5 100644
--- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
@@ -348,6 +348,40 @@ static void dw_hdma_v0_core_ch_config(struct dw_edma_chan *chan)
SET_CH_32(dw, chan->dir, chan->id, msi_msgdata, chan->msi.data);
}
+static void
+dw_hdma_v0_core_ll_data(struct dw_edma_chan *chan, struct dw_edma_burst *burst,
+ u32 idx, bool cb, bool irq)
+{
+ u32 control = 0;
+
+ if (cb)
+ control |= DW_HDMA_V0_CB;
+
+ dw_hdma_v0_write_ll_data(chan, idx, control, burst->sz, burst->sar,
+ burst->dar);
+}
+
+static void
+dw_hdma_v0_core_ll_link(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr)
+{
+ u32 control = DW_HDMA_V0_LLP | DW_HDMA_V0_TCB;
+
+ if (!cb)
+ control |= DW_HDMA_V0_CB;
+
+ dw_hdma_v0_write_ll_link(chan, idx, control, chan->ll_region.paddr);
+}
+
+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);
+}
+
/* HDMA debugfs callbacks */
static void dw_hdma_v0_core_debugfs_on(struct dw_edma *dw)
{
@@ -366,6 +400,10 @@ static const struct dw_edma_core_ops dw_hdma_v0_core = {
.ch_status = dw_hdma_v0_core_ch_status,
.handle_int = dw_hdma_v0_core_handle_int,
.start = dw_hdma_v0_core_start,
+ .ll_data = dw_hdma_v0_core_ll_data,
+ .ll_link = dw_hdma_v0_core_ll_link,
+ .ch_doorbell = dw_hdma_v0_core_ch_doorbell,
+ .ch_enable = dw_hdma_v0_core_ch_enable,
.ch_config = dw_hdma_v0_core_ch_config,
.debugfs_on = dw_hdma_v0_core_debugfs_on,
.db_offset = dw_hdma_v0_core_db_offset,
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH v3 06/10] dmaengine: dw-edma: Add callbacks to fill link list entries
2026-07-02 21:21 ` [PATCH v3 06/10] dmaengine: dw-edma: Add callbacks to fill link list entries Frank.Li
@ 2026-07-02 21:31 ` sashiko-bot
0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-02 21:31 UTC (permalink / raw)
To: Frank.Li; +Cc: dmaengine, Frank.Li, imx, vkoul, linux-pci
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The `addr` parameter is completely ignored in both the `dw_edma_v0_core_ll_link` and `dw_hdma_v0_core_ll_link` callbacks, with the code incorrectly hardcoding `chan->ll_region.paddr` instead.
--
commit b095560e132d78a5d96555e8baeab1d49da1b0c1
Author: Frank Li <Frank.Li@nxp.com>
dmaengine: dw-edma: Add callbacks to fill link list entries
This commit introduces four new callbacks to fill link list entries and handle
channel operations. This abstraction allows sharing logic between the eDMA and
HDMA paths to avoid duplicating code.
> diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
> index 8d38867cd9983..10ad63d7e6016 100644
> --- a/drivers/dma/dw-edma/dw-edma-v0-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
[ ... ]
> @@ -509,6 +509,48 @@ static void dw_edma_v0_core_ch_config(struct dw_edma_chan *chan)
> }
> }
>
[ ... ]
> +static void
> +dw_edma_v0_core_ll_link(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr)
> +{
> + u32 control = DW_EDMA_V0_LLP | DW_EDMA_V0_TCB;
> +
> + if (!cb)
> + control |= DW_EDMA_V0_CB;
> +
> + dw_edma_v0_write_ll_link(chan, idx, control, chan->ll_region.paddr);
[Severity: Low]
Could this omission cause a regression if the caller expects the provided addr
to be used? The callback signature explicitly takes a u64 addr argument, but
the implementation ignores it and hardcodes chan->ll_region.paddr instead.
> +}
> +
[ ... ]
> diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
> index 31bbdc6a40642..52c6ea09fcab5 100644
> --- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
> +++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
[ ... ]
> @@ -348,6 +348,40 @@ static void dw_hdma_v0_core_ch_config(struct dw_edma_chan *chan)
> SET_CH_32(dw, chan->dir, chan->id, msi_msgdata, chan->msi.data);
> }
>
[ ... ]
> +static void
> +dw_hdma_v0_core_ll_link(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr)
> +{
> + u32 control = DW_HDMA_V0_LLP | DW_HDMA_V0_TCB;
> +
> + if (!cb)
> + control |= DW_HDMA_V0_CB;
> +
> + dw_hdma_v0_write_ll_link(chan, idx, control, chan->ll_region.paddr);
[Severity: Low]
What about the HDMA implementation? The addr argument passed to
dw_hdma_v0_core_ll_link() is also completely ignored in favor of hardcoding
chan->ll_region.paddr.
> +}
> +
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260702-edma_ll-v3-0-877aa463740c@nxp.com?part=6
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v3 07/10] dmaengine: dw-edma: Add non_ll_start() callback
2026-07-02 21:21 [PATCH v3 00/10] dmaengine: dw-edma: flatten desc structions and simple code Frank.Li
` (5 preceding siblings ...)
2026-07-02 21:21 ` [PATCH v3 06/10] dmaengine: dw-edma: Add callbacks to fill link list entries Frank.Li
@ 2026-07-02 21:21 ` Frank.Li
2026-07-02 21:36 ` sashiko-bot
2026-07-02 21:21 ` [PATCH v3 08/10] dmaengine: dw-edma: Use common dw_edma_core_start() for both eDMA and HDMA Frank.Li
` (2 subsequent siblings)
9 siblings, 1 reply; 21+ messages in thread
From: Frank.Li @ 2026-07-02 21:21 UTC (permalink / raw)
To: Manivannan Sadhasivam, Vinod Koul, Gustavo Pimentel, Kees Cook,
Gustavo A. R. Silva, Manivannan Sadhasivam,
Krzysztof Wilczyński, Kishon Vijay Abraham I, Bjorn Helgaas,
Christoph Hellwig, Niklas Cassel
Cc: dmaengine, linux-kernel, linux-hardening, linux-pci, linux-nvme,
Koichiro Den, imx, Verma, Devendra, Frank Li
From: Frank Li <Frank.Li@nxp.com>
Add a non_ll_start() callback and move the common non-linked-list channel
handling into the EDMA core so it can be shared by both the EDMA and HDMA.
Prepare for the upcoming reorganization of the burst and chunk structures.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
drivers/dma/dw-edma/dw-edma-core.h | 12 +++++++++++-
drivers/dma/dw-edma/dw-hdma-v0-core.c | 23 ++++-------------------
2 files changed, 15 insertions(+), 20 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index bab4d49c92feb..e18d6e827c2c9 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -126,6 +126,7 @@ struct dw_edma_core_ops {
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);
void (*start)(struct dw_edma_chunk *chunk, bool first);
+ 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);
void (*ll_link)(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr);
@@ -201,7 +202,16 @@ dw_edma_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
static inline
void dw_edma_core_start(struct dw_edma *dw, struct dw_edma_chunk *chunk, bool first)
{
- dw->core->start(chunk, first);
+ if (chunk->chan->non_ll) {
+ struct dw_edma_burst *child;
+
+ child = list_first_entry_or_null(&chunk->burst->list,
+ struct dw_edma_burst, list);
+ if (child)
+ dw->core->non_ll_start(chunk->chan, child);
+ } else {
+ dw->core->start(chunk, first);
+ }
}
static inline
diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
index 52c6ea09fcab5..4cff839022213 100644
--- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
@@ -272,18 +272,12 @@ static void dw_hdma_v0_core_ll_start(struct dw_edma_chunk *chunk, bool first)
SET_CH_32(dw, chan->dir, chan->id, doorbell, HDMA_V0_DOORBELL_START);
}
-static void dw_hdma_v0_core_non_ll_start(struct dw_edma_chunk *chunk)
+static void dw_hdma_v0_core_non_ll_start(struct dw_edma_chan *chan,
+ struct dw_edma_burst *child)
{
- struct dw_edma_chan *chan = chunk->chan;
struct dw_edma *dw = chan->dw;
- struct dw_edma_burst *child;
u32 val;
- child = list_first_entry_or_null(&chunk->burst->list,
- struct dw_edma_burst, list);
- if (!child)
- return;
-
SET_CH_32(dw, chan->dir, chan->id, ch_en, HDMA_V0_CH_EN);
/* Source address */
@@ -324,16 +318,6 @@ static void dw_hdma_v0_core_non_ll_start(struct dw_edma_chunk *chunk)
HDMA_V0_DOORBELL_START);
}
-static void dw_hdma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
-{
- struct dw_edma_chan *chan = chunk->chan;
-
- if (chan->non_ll)
- dw_hdma_v0_core_non_ll_start(chunk);
- else
- dw_hdma_v0_core_ll_start(chunk, first);
-}
-
static void dw_hdma_v0_core_ch_config(struct dw_edma_chan *chan)
{
struct dw_edma *dw = chan->dw;
@@ -399,7 +383,8 @@ static const struct dw_edma_core_ops dw_hdma_v0_core = {
.ch_count = dw_hdma_v0_core_ch_count,
.ch_status = dw_hdma_v0_core_ch_status,
.handle_int = dw_hdma_v0_core_handle_int,
- .start = dw_hdma_v0_core_start,
+ .start = dw_hdma_v0_core_ll_start,
+ .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,
.ch_doorbell = dw_hdma_v0_core_ch_doorbell,
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH v3 07/10] dmaengine: dw-edma: Add non_ll_start() callback
2026-07-02 21:21 ` [PATCH v3 07/10] dmaengine: dw-edma: Add non_ll_start() callback Frank.Li
@ 2026-07-02 21:36 ` sashiko-bot
0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-02 21:36 UTC (permalink / raw)
To: Frank.Li; +Cc: imx, vkoul, linux-pci, dmaengine, Frank.Li
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Lockless channel state modification and list manipulation in dmaengine callbacks.
--
commit 962243519d7014f0b711fe65a08901734b5c7e22
Author: Frank Li <Frank.Li@nxp.com>
dmaengine: dw-edma: Add non_ll_start() callback
This commit adds a non_ll_start() callback and moves the common
non-linked-list channel handling into the EDMA core so it can be shared
by both the EDMA and HDMA.
> diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
> index bab4d49c92feb..e18d6e827c2c9 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.h
> +++ b/drivers/dma/dw-edma/dw-edma-core.h
[ ... ]
> @@ -201,7 +202,16 @@ dw_edma_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
> static inline
> void dw_edma_core_start(struct dw_edma *dw, struct dw_edma_chunk *chunk, bool first)
> {
> - dw->core->start(chunk, first);
> + if (chunk->chan->non_ll) {
> + struct dw_edma_burst *child;
> +
> + child = list_first_entry_or_null(&chunk->burst->list,
> + struct dw_edma_burst, list);
> + if (child)
> + dw->core->non_ll_start(chunk->chan, child);
> + } else {
> + dw->core->start(chunk, first);
> + }
> }
[Severity: High]
This is a pre-existing issue, but does dw_edma_device_resume() modify
channel state and invoke dw_edma_start_transfer() locklessly?
Looking at drivers/dma/dw-edma/dw-edma-core.c, if a client driver invokes
dmaengine_resume() (which calls dw_edma_device_resume()) concurrently with
the channel's interrupt handler dw_edma_done_interrupt() or dmaengine_submit(),
dw_edma_device_resume() modifies chan->status and calls
dw_edma_start_transfer() without acquiring chan->vc.lock:
drivers/dma/dw-edma/dw-edma-core.c:dw_edma_device_resume() {
...
chan->status = EDMA_ST_BUSY;
dw_edma_start_transfer(chan);
...
}
Meanwhile, dw_edma_done_interrupt() and dw_edma_device_issue_pending()
execute dw_edma_start_transfer() with chan->vc.lock held.
If dw_edma_device_resume() and dw_edma_done_interrupt() run concurrently,
both can invoke dw_edma_start_transfer() on the same channel, read the same
descriptor via vchan_next_desc(&chan->vc), and perform state modifications on
it:
drivers/dma/dw-edma/dw-edma-core.c:dw_edma_start_transfer() {
...
vd = vchan_next_desc(&chan->vc);
...
list_del(&child->list);
kfree(child);
...
}
Could this sequence lead to a double-free and list corruption?
Other functions like dw_edma_device_pause() and dw_edma_device_terminate_all()
appear to similarly manipulate channel state locklessly:
drivers/dma/dw-edma/dw-edma-core.c:dw_edma_device_pause() {
...
chan->request = EDMA_REQ_PAUSE;
...
}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260702-edma_ll-v3-0-877aa463740c@nxp.com?part=7
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v3 08/10] dmaengine: dw-edma: Use common dw_edma_core_start() for both eDMA and HDMA
2026-07-02 21:21 [PATCH v3 00/10] dmaengine: dw-edma: flatten desc structions and simple code Frank.Li
` (6 preceding siblings ...)
2026-07-02 21:21 ` [PATCH v3 07/10] dmaengine: dw-edma: Add non_ll_start() callback Frank.Li
@ 2026-07-02 21:21 ` Frank.Li
2026-07-02 21:38 ` sashiko-bot
2026-07-02 21:21 ` [PATCH v3 09/10] dmaengine: dw-edma: Use burst array instead of linked list Frank.Li
2026-07-02 21:21 ` [PATCH v3 10/10] dmaengine: dw-edma: Remove struct dw_edma_chunk Frank.Li
9 siblings, 1 reply; 21+ messages in thread
From: Frank.Li @ 2026-07-02 21:21 UTC (permalink / raw)
To: Manivannan Sadhasivam, Vinod Koul, Gustavo Pimentel, Kees Cook,
Gustavo A. R. Silva, Manivannan Sadhasivam,
Krzysztof Wilczyński, Kishon Vijay Abraham I, Bjorn Helgaas,
Christoph Hellwig, Niklas Cassel
Cc: dmaengine, linux-kernel, linux-hardening, linux-pci, linux-nvme,
Koichiro Den, imx, Verma, Devendra, Frank Li
From: Frank Li <Frank.Li@nxp.com>
Use common dw_edma_core_start() for both eDMA and HDMA. Remove .start()
callback functions at eDMA and HDMA.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
change in v2
- use eDMA and HDMA
---
drivers/dma/dw-edma/dw-edma-core.c | 32 +++++++++++++++++++++--
drivers/dma/dw-edma/dw-edma-core.h | 16 ------------
drivers/dma/dw-edma/dw-edma-v0-core.c | 48 -----------------------------------
drivers/dma/dw-edma/dw-hdma-v0-core.c | 37 ---------------------------
4 files changed, 30 insertions(+), 103 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 2652ad8e7a8f6..f52d9fd18e573 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -163,9 +163,37 @@ static void vchan_free_desc(struct virt_dma_desc *vdesc)
dw_edma_free_desc(vd2dw_edma_desc(vdesc));
}
+static void dw_edma_core_start(struct dw_edma_chunk *chunk, bool first)
+{
+ struct dw_edma_chan *chan = chunk->chan;
+ struct dw_edma_burst *child;
+ u32 i = 0;
+ int j;
+
+ if (chan->non_ll) {
+ child = list_first_entry_or_null(&chunk->burst->list,
+ struct dw_edma_burst, list);
+ if (child)
+ chan->dw->core->non_ll_start(chunk->chan, child);
+ return;
+ }
+
+ j = chunk->bursts_alloc;
+ list_for_each_entry(child, &chunk->burst->list, list) {
+ j--;
+ dw_edma_core_ll_data(chan, child, i++, chunk->cb, !j);
+ }
+
+ dw_edma_core_ll_link(chan, i, chunk->cb, chan->ll_region.paddr);
+
+ if (first)
+ dw_edma_core_ch_enable(chan);
+
+ dw_edma_core_ch_doorbell(chan);
+}
+
static int dw_edma_start_transfer(struct dw_edma_chan *chan)
{
- struct dw_edma *dw = chan->dw;
struct dw_edma_chunk *child;
struct dw_edma_desc *desc;
struct virt_dma_desc *vd;
@@ -183,7 +211,7 @@ static int dw_edma_start_transfer(struct dw_edma_chan *chan)
if (!child)
return 0;
- dw_edma_core_start(dw, child, !desc->xfer_sz);
+ dw_edma_core_start(child, !desc->xfer_sz);
desc->xfer_sz += child->xfer_sz;
dw_edma_free_burst(child);
list_del(&child->list);
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index e18d6e827c2c9..27415f3a2d04b 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -125,7 +125,6 @@ struct dw_edma_core_ops {
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);
- void (*start)(struct dw_edma_chunk *chunk, bool first);
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);
@@ -199,21 +198,6 @@ dw_edma_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
return dw_irq->dw->core->handle_int(dw_irq, dir, done, abort);
}
-static inline
-void dw_edma_core_start(struct dw_edma *dw, struct dw_edma_chunk *chunk, bool first)
-{
- if (chunk->chan->non_ll) {
- struct dw_edma_burst *child;
-
- child = list_first_entry_or_null(&chunk->burst->list,
- struct dw_edma_burst, list);
- if (child)
- dw->core->non_ll_start(chunk->chan, child);
- } else {
- dw->core->start(chunk, first);
- }
-}
-
static inline
void dw_edma_core_ch_config(struct dw_edma_chan *chan)
{
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index 10ad63d7e6016..8ee2db0b3739f 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -379,36 +379,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_core_write_chunk(struct dw_edma_chunk *chunk)
-{
- struct dw_edma_burst *child;
- struct dw_edma_chan *chan = chunk->chan;
- u32 control = 0, i = 0;
- int j;
-
- if (chunk->cb)
- control = DW_EDMA_V0_CB;
-
- j = chunk->bursts_alloc;
- list_for_each_entry(child, &chunk->burst->list, list) {
- j--;
- if (!j) {
- control |= DW_EDMA_V0_LIE;
- if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
- control |= DW_EDMA_V0_RIE;
- }
-
- dw_edma_v0_write_ll_data(chan, i++, control, child->sz,
- child->sar, child->dar);
- }
-
- control = DW_EDMA_V0_LLP | DW_EDMA_V0_TCB;
- if (!chunk->cb)
- control |= DW_EDMA_V0_CB;
-
- dw_edma_v0_write_ll_link(chan, i, control, chan->ll_region.paddr);
-}
-
static void dw_edma_v0_sync_ll_data(struct dw_edma_chan *chan)
{
/*
@@ -423,23 +393,6 @@ static void dw_edma_v0_sync_ll_data(struct dw_edma_chan *chan)
readl(chan->ll_region.vaddr.io);
}
-static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
-{
- struct dw_edma_chan *chan = chunk->chan;
- struct dw_edma *dw = chan->dw;
-
- dw_edma_v0_core_write_chunk(chunk);
-
- if (first)
- dw_edma_v0_core_ch_enable(chan);
-
- dw_edma_v0_sync_ll_data(chan);
-
- /* Doorbell */
- SET_RW_32(dw, chan->dir, doorbell,
- FIELD_PREP(EDMA_V0_DOORBELL_CH_MASK, chan->id));
-}
-
static void dw_edma_v0_core_ch_config(struct dw_edma_chan *chan)
{
struct dw_edma *dw = chan->dw;
@@ -581,7 +534,6 @@ static const struct dw_edma_core_ops dw_edma_v0_core = {
.ch_count = dw_edma_v0_core_ch_count,
.ch_status = dw_edma_v0_core_ch_status,
.handle_int = dw_edma_v0_core_handle_int,
- .start = dw_edma_v0_core_start,
.ll_data = dw_edma_v0_core_ll_data,
.ll_link = dw_edma_v0_core_ll_link,
.ch_doorbell = dw_edma_v0_core_ch_doorbell,
diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
index 4cff839022213..ad0ed28c928f8 100644
--- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
@@ -222,26 +222,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_core_write_chunk(struct dw_edma_chunk *chunk)
-{
- struct dw_edma_chan *chan = chunk->chan;
- struct dw_edma_burst *child;
- u32 control = 0, i = 0;
-
- if (chunk->cb)
- control = DW_HDMA_V0_CB;
-
- list_for_each_entry(child, &chunk->burst->list, list)
- dw_hdma_v0_write_ll_data(chan, i++, control, child->sz,
- child->sar, child->dar);
-
- control = DW_HDMA_V0_LLP | DW_HDMA_V0_TCB;
- if (!chunk->cb)
- control |= DW_HDMA_V0_CB;
-
- dw_hdma_v0_write_ll_link(chan, i, control, chunk->chan->ll_region.paddr);
-}
-
static void dw_hdma_v0_sync_ll_data(struct dw_edma_chan *chan)
{
/*
@@ -256,22 +236,6 @@ static void dw_hdma_v0_sync_ll_data(struct dw_edma_chan *chan)
readl(chan->ll_region.vaddr.io);
}
-static void dw_hdma_v0_core_ll_start(struct dw_edma_chunk *chunk, bool first)
-{
- struct dw_edma_chan *chan = chunk->chan;
- struct dw_edma *dw = chan->dw;
-
- dw_hdma_v0_core_write_chunk(chunk);
-
- if (first)
- dw_hdma_v0_core_ch_enable(chan);
-
- dw_hdma_v0_sync_ll_data(chan);
-
- /* Doorbell */
- SET_CH_32(dw, chan->dir, chan->id, doorbell, HDMA_V0_DOORBELL_START);
-}
-
static void dw_hdma_v0_core_non_ll_start(struct dw_edma_chan *chan,
struct dw_edma_burst *child)
{
@@ -383,7 +347,6 @@ static const struct dw_edma_core_ops dw_hdma_v0_core = {
.ch_count = dw_hdma_v0_core_ch_count,
.ch_status = dw_hdma_v0_core_ch_status,
.handle_int = dw_hdma_v0_core_handle_int,
- .start = dw_hdma_v0_core_ll_start,
.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,
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH v3 08/10] dmaengine: dw-edma: Use common dw_edma_core_start() for both eDMA and HDMA
2026-07-02 21:21 ` [PATCH v3 08/10] dmaengine: dw-edma: Use common dw_edma_core_start() for both eDMA and HDMA Frank.Li
@ 2026-07-02 21:38 ` sashiko-bot
0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-02 21:38 UTC (permalink / raw)
To: Frank.Li; +Cc: dmaengine, linux-pci, imx, vkoul, Frank.Li
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Low] The implementations of the `.ll_link` callback silently ignore the `addr` parameter.
Pre-existing issues:
- [High] dw_edma_device_resume calls dw_edma_start_transfer without holding the virtual channel lock, leading to a data race.
--
commit a71d48f7b78a779048479e64e69c2bb3e5bf67b9
Author: Frank Li <Frank.Li@nxp.com>
dmaengine: dw-edma: Use common dw_edma_core_start() for both eDMA and HDMA
The commit removes the .start() callback functions for eDMA and HDMA
and consolidates them by using a common dw_edma_core_start() function
for both engine types.
> diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
> index 2652ad8e7a8f6..f52d9fd18e573 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-core.c
> @@ -163,9 +163,37 @@ static void vchan_free_desc(struct virt_dma_desc *vdesc)
> dw_edma_free_desc(vd2dw_edma_desc(vdesc));
> }
>
> +static void dw_edma_core_start(struct dw_edma_chunk *chunk, bool first)
> +{
> + struct dw_edma_chan *chan = chunk->chan;
> + struct dw_edma_burst *child;
> + u32 i = 0;
> + int j;
> +
> + if (chan->non_ll) {
> + child = list_first_entry_or_null(&chunk->burst->list,
> + struct dw_edma_burst, list);
> + if (child)
> + chan->dw->core->non_ll_start(chunk->chan, child);
> + return;
> + }
> +
> + j = chunk->bursts_alloc;
> + list_for_each_entry(child, &chunk->burst->list, list) {
> + j--;
> + dw_edma_core_ll_data(chan, child, i++, chunk->cb, !j);
> + }
> +
> + dw_edma_core_ll_link(chan, i, chunk->cb, chan->ll_region.paddr);
[Severity: Low]
Does the underlying implementation of .ll_link actually use the address
parameter passed here?
Looking at dw_edma_v0_core_ll_link() in dw-edma-v0-core.c, it appears to
ignore the addr parameter entirely and hardcodes chan->ll_region.paddr:
static void
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, chan->ll_region.paddr);
}
The same seems to be true for dw_hdma_v0_core_ll_link(). While it currently
works because chan->ll_region.paddr is explicitly passed, would this cause
silent failures for any future callers attempting to pass a different address?
> +
> + if (first)
> + dw_edma_core_ch_enable(chan);
> +
> + dw_edma_core_ch_doorbell(chan);
> +}
> +
> static int dw_edma_start_transfer(struct dw_edma_chan *chan)
> {
[Severity: High]
This is a pre-existing issue, but does dw_edma_start_transfer() race with
other contexts when called from dw_edma_device_resume()?
In dw_edma_device_resume(), dw_edma_start_transfer() is called without holding
the virtual channel lock:
static int dw_edma_device_resume(struct dma_chan *dchan)
{
...
} else {
chan->status = EDMA_ST_BUSY;
dw_edma_start_transfer(chan);
}
return err;
}
However, dw_edma_start_transfer() subsequently accesses the virtual channel
list through vchan_next_desc(), which expects chan->vc.lock to be held. Can
this lead to list corruption or data races if another thread concurrently
calls dw_edma_device_issue_pending() or if an interrupt is being processed?
> - struct dw_edma *dw = chan->dw;
> struct dw_edma_chunk *child;
> struct dw_edma_desc *desc;
> struct virt_dma_desc *vd;
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260702-edma_ll-v3-0-877aa463740c@nxp.com?part=8
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v3 09/10] dmaengine: dw-edma: Use burst array instead of linked list
2026-07-02 21:21 [PATCH v3 00/10] dmaengine: dw-edma: flatten desc structions and simple code Frank.Li
` (7 preceding siblings ...)
2026-07-02 21:21 ` [PATCH v3 08/10] dmaengine: dw-edma: Use common dw_edma_core_start() for both eDMA and HDMA Frank.Li
@ 2026-07-02 21:21 ` Frank.Li
2026-07-02 21:40 ` sashiko-bot
2026-07-02 21:21 ` [PATCH v3 10/10] dmaengine: dw-edma: Remove struct dw_edma_chunk Frank.Li
9 siblings, 1 reply; 21+ messages in thread
From: Frank.Li @ 2026-07-02 21:21 UTC (permalink / raw)
To: Manivannan Sadhasivam, Vinod Koul, Gustavo Pimentel, Kees Cook,
Gustavo A. R. Silva, Manivannan Sadhasivam,
Krzysztof Wilczyński, Kishon Vijay Abraham I, Bjorn Helgaas,
Christoph Hellwig, Niklas Cassel
Cc: dmaengine, linux-kernel, linux-hardening, linux-pci, linux-nvme,
Koichiro Den, imx, Verma, Devendra, Frank Li
From: Frank Li <Frank.Li@nxp.com>
The current descriptor layout is:
struct dw_edma_desc *desc
└─ chunk list
└─ burst list
Creating a DMA descriptor requires at least three kzalloc() calls because
each burst is allocated as a linked-list node. Since the number of bursts
is already known when the descriptor is created, a linked list is not
necessary.
Allocate a burst array when creating each chunk to simplify the code and
eliminate one kzalloc() call.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
drivers/dma/dw-edma/dw-edma-core.c | 120 +++++++------------------------------
drivers/dma/dw-edma/dw-edma-core.h | 9 +--
2 files changed, 26 insertions(+), 103 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index f52d9fd18e573..01bee22fe3b3e 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -40,38 +40,15 @@ u64 dw_edma_get_pci_address(struct dw_edma_chan *chan, phys_addr_t cpu_addr)
return cpu_addr;
}
-static struct dw_edma_burst *dw_edma_alloc_burst(struct dw_edma_chunk *chunk)
-{
- struct dw_edma_burst *burst;
-
- burst = kzalloc_obj(*burst, GFP_NOWAIT);
- if (unlikely(!burst))
- return NULL;
-
- INIT_LIST_HEAD(&burst->list);
- if (chunk->burst) {
- /* Create and add new element into the linked list */
- chunk->bursts_alloc++;
- list_add_tail(&burst->list, &chunk->burst->list);
- } else {
- /* List head */
- chunk->bursts_alloc = 0;
- chunk->burst = burst;
- }
-
- return burst;
-}
-
-static struct dw_edma_chunk *dw_edma_alloc_chunk(struct dw_edma_desc *desc)
+static struct dw_edma_chunk *dw_edma_alloc_chunk(struct dw_edma_desc *desc, u32 nburst)
{
struct dw_edma_chan *chan = desc->chan;
struct dw_edma_chunk *chunk;
- chunk = kzalloc_obj(*chunk, GFP_NOWAIT);
+ chunk = kzalloc_flex(*chunk, burst, nburst, GFP_NOWAIT);
if (unlikely(!chunk))
return NULL;
- INIT_LIST_HEAD(&chunk->list);
chunk->chan = chan;
/* Toggling change bit (CB) in each chunk, this is a mechanism to
* inform the eDMA HW block that this is a new linked list ready
@@ -81,20 +58,10 @@ static struct dw_edma_chunk *dw_edma_alloc_chunk(struct dw_edma_desc *desc)
*/
chunk->cb = !(desc->chunks_alloc % 2);
- if (desc->chunk) {
- /* Create and add new element into the linked list */
- if (!dw_edma_alloc_burst(chunk)) {
- kfree(chunk);
- return NULL;
- }
- desc->chunks_alloc++;
- list_add_tail(&chunk->list, &desc->chunk->list);
- } else {
- /* List head */
- chunk->burst = NULL;
- desc->chunks_alloc = 0;
- desc->chunk = chunk;
- }
+ chunk->nburst = nburst;
+
+ list_add_tail(&chunk->list, &desc->chunk_list);
+ desc->chunks_alloc++;
return chunk;
}
@@ -108,53 +75,23 @@ static struct dw_edma_desc *dw_edma_alloc_desc(struct dw_edma_chan *chan)
return NULL;
desc->chan = chan;
- if (!dw_edma_alloc_chunk(desc)) {
- kfree(desc);
- return NULL;
- }
- return desc;
-}
+ INIT_LIST_HEAD(&desc->chunk_list);
-static void dw_edma_free_burst(struct dw_edma_chunk *chunk)
-{
- struct dw_edma_burst *child, *_next;
-
- /* Remove all the list elements */
- list_for_each_entry_safe(child, _next, &chunk->burst->list, list) {
- list_del(&child->list);
- kfree(child);
- chunk->bursts_alloc--;
- }
-
- /* Remove the list head */
- kfree(child);
- chunk->burst = NULL;
+ return desc;
}
-static void dw_edma_free_chunk(struct dw_edma_desc *desc)
+static void dw_edma_free_desc(struct dw_edma_desc *desc)
{
struct dw_edma_chunk *child, *_next;
- if (!desc->chunk)
- return;
-
/* Remove all the list elements */
- list_for_each_entry_safe(child, _next, &desc->chunk->list, list) {
- dw_edma_free_burst(child);
+ list_for_each_entry_safe(child, _next, &desc->chunk_list, list) {
list_del(&child->list);
kfree(child);
desc->chunks_alloc--;
}
- /* Remove the list head */
- kfree(child);
- desc->chunk = NULL;
-}
-
-static void dw_edma_free_desc(struct dw_edma_desc *desc)
-{
- dw_edma_free_chunk(desc);
kfree(desc);
}
@@ -166,23 +103,17 @@ static void vchan_free_desc(struct virt_dma_desc *vdesc)
static void dw_edma_core_start(struct dw_edma_chunk *chunk, bool first)
{
struct dw_edma_chan *chan = chunk->chan;
- struct dw_edma_burst *child;
u32 i = 0;
- int j;
if (chan->non_ll) {
- child = list_first_entry_or_null(&chunk->burst->list,
- struct dw_edma_burst, list);
- if (child)
- chan->dw->core->non_ll_start(chunk->chan, child);
+ if (chunk->nburst == 1)
+ chan->dw->core->non_ll_start(chunk->chan, &chunk->burst[0]);
return;
}
- j = chunk->bursts_alloc;
- list_for_each_entry(child, &chunk->burst->list, list) {
- j--;
- dw_edma_core_ll_data(chan, child, i++, chunk->cb, !j);
- }
+ for (i = 0; i < chunk->nburst; i++)
+ dw_edma_core_ll_data(chan, &chunk->burst[i], i, chunk->cb,
+ i == chunk->nburst - 1);
dw_edma_core_ll_link(chan, i, chunk->cb, chan->ll_region.paddr);
@@ -206,14 +137,13 @@ static int dw_edma_start_transfer(struct dw_edma_chan *chan)
if (!desc)
return 0;
- child = list_first_entry_or_null(&desc->chunk->list,
+ child = list_first_entry_or_null(&desc->chunk_list,
struct dw_edma_chunk, list);
if (!child)
return 0;
dw_edma_core_start(child, !desc->xfer_sz);
desc->xfer_sz += child->xfer_sz;
- dw_edma_free_burst(child);
list_del(&child->list);
kfree(child);
desc->chunks_alloc--;
@@ -425,14 +355,14 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer,
struct dw_edma_chan *chan = dchan2dw_edma_chan(xfer->dchan);
enum dma_transfer_direction dir = xfer->direction;
struct scatterlist *sg = NULL;
- struct dw_edma_chunk *chunk;
+ struct dw_edma_chunk *chunk = NULL;
struct dw_edma_burst *burst;
struct dw_edma_desc *desc;
u64 src_addr, dst_addr;
size_t fsz = 0;
u32 bursts_max;
u32 cnt = 0;
- int i;
+ u32 i;
if (!chan->configured)
return NULL;
@@ -499,10 +429,6 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer,
if (unlikely(!desc))
goto err_alloc;
- chunk = dw_edma_alloc_chunk(desc);
- if (unlikely(!chunk))
- goto err_alloc;
-
if (xfer->type == EDMA_XFER_INTERLEAVED) {
src_addr = xfer->xfer.il->src_start;
dst_addr = xfer->xfer.il->dst_start;
@@ -530,15 +456,15 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer,
if (xfer->type == EDMA_XFER_SCATTER_GATHER && !sg)
break;
- if (chunk->bursts_alloc == bursts_max) {
- chunk = dw_edma_alloc_chunk(desc);
+ if (!(i % chan->ll_max)) {
+ u32 n = min(cnt - i, chan->ll_max);
+
+ chunk = dw_edma_alloc_chunk(desc, n);
if (unlikely(!chunk))
goto err_alloc;
}
- burst = dw_edma_alloc_burst(chunk);
- if (unlikely(!burst))
- goto err_alloc;
+ burst = chunk->burst + (i % chan->ll_max);
if (xfer->type == EDMA_XFER_CYCLIC)
burst->sz = xfer->xfer.cyclic.len;
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index 27415f3a2d04b..4950c57fca34f 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -43,7 +43,6 @@ struct dw_edma_chan;
struct dw_edma_chunk;
struct dw_edma_burst {
- struct list_head list;
u64 sar;
u64 dar;
u32 sz;
@@ -52,18 +51,16 @@ struct dw_edma_burst {
struct dw_edma_chunk {
struct list_head list;
struct dw_edma_chan *chan;
- struct dw_edma_burst *burst;
-
- u32 bursts_alloc;
-
u8 cb;
u32 xfer_sz;
+ u32 nburst;
+ struct dw_edma_burst burst[] __counted_by(nburst);
};
struct dw_edma_desc {
struct virt_dma_desc vd;
struct dw_edma_chan *chan;
- struct dw_edma_chunk *chunk;
+ struct list_head chunk_list;
u32 chunks_alloc;
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH v3 09/10] dmaengine: dw-edma: Use burst array instead of linked list
2026-07-02 21:21 ` [PATCH v3 09/10] dmaengine: dw-edma: Use burst array instead of linked list Frank.Li
@ 2026-07-02 21:40 ` sashiko-bot
0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-02 21:40 UTC (permalink / raw)
To: Frank.Li; +Cc: vkoul, imx, Frank.Li, linux-pci, dmaengine
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Early loop termination during scatter-gather mapping leaves trailing bursts uninitialized, leading to invalid DMA hardware configuration and silent hangs.
Pre-existing issues:
- [High] Missing spinlock in dw_edma_device_resume() leads to list corruption, data races, and lockdep warnings.
--
commit a2364f066e8539a3a2c9d2afab52db8336748992
Author: Frank Li <Frank.Li@nxp.com>
dmaengine: dw-edma: Use burst array instead of linked list
Creating a DMA descriptor requires at least three kzalloc() calls because
each burst is allocated as a linked-list node. Allocate a burst array when
creating each chunk to simplify the code and eliminate one kzalloc() call.
> diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
> index f52d9fd18e573..01bee22fe3b3e 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-core.c
[ ... ]
> @@ -166,23 +103,17 @@ static void vchan_free_desc(struct virt_dma_desc *vdesc)
> static void dw_edma_core_start(struct dw_edma_chunk *chunk, bool first)
> {
> struct dw_edma_chan *chan = chunk->chan;
> - struct dw_edma_burst *child;
> u32 i = 0;
> - int j;
>
> if (chan->non_ll) {
> - child = list_first_entry_or_null(&chunk->burst->list,
> - struct dw_edma_burst, list);
> - if (child)
> - chan->dw->core->non_ll_start(chunk->chan, child);
> + if (chunk->nburst == 1)
> + chan->dw->core->non_ll_start(chunk->chan, &chunk->burst[0]);
> return;
> }
>
> - j = chunk->bursts_alloc;
> - list_for_each_entry(child, &chunk->burst->list, list) {
> - j--;
> - dw_edma_core_ll_data(chan, child, i++, chunk->cb, !j);
> - }
> + for (i = 0; i < chunk->nburst; i++)
> + dw_edma_core_ll_data(chan, &chunk->burst[i], i, chunk->cb,
> + i == chunk->nburst - 1);
[Severity: High]
Does this loop iterate over uninitialized bursts if the scatter-gather
loop in dw_edma_device_transfer() terminates early?
In dw_edma_device_transfer(), if the loop breaks early because !sg,
chunk->nburst remains at its originally allocated count rather than the
actual number of initialized elements.
Since the irq flag is only set on the very last element
(i == chunk->nburst - 1), and that element might be uninitialized, could
this program the DMA hardware with zero-sized transfers and cause a
silent hang?
>
> dw_edma_core_ll_link(chan, i, chunk->cb, chan->ll_region.paddr);
>
[ ... ]
> @@ -530,15 +456,15 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer,
> if (xfer->type == EDMA_XFER_SCATTER_GATHER && !sg)
> break;
>
> - if (chunk->bursts_alloc == bursts_max) {
> - chunk = dw_edma_alloc_chunk(desc);
> + if (!(i % chan->ll_max)) {
> + u32 n = min(cnt - i, chan->ll_max);
> +
> + chunk = dw_edma_alloc_chunk(desc, n);
> if (unlikely(!chunk))
> goto err_alloc;
> }
[Severity: High]
This is a pre-existing issue, but looking at dw_edma_device_resume(),
it appears to modify state and access lists without taking the channel's
virtual spinlock:
drivers/dma/dw-edma/dw-edma-core.c:dw_edma_device_resume() {
...
} else {
chan->status = EDMA_ST_BUSY;
dw_edma_start_transfer(chan);
}
...
}
Since dw_edma_start_transfer() eventually calls vchan_next_desc() which
expects lockdep_assert_held(&vc->lock), and hardware interrupts could fire
simultaneously calling dw_edma_done_interrupt(), can this lead to list
corruption and data races?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260702-edma_ll-v3-0-877aa463740c@nxp.com?part=9
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v3 10/10] dmaengine: dw-edma: Remove struct dw_edma_chunk
2026-07-02 21:21 [PATCH v3 00/10] dmaengine: dw-edma: flatten desc structions and simple code Frank.Li
` (8 preceding siblings ...)
2026-07-02 21:21 ` [PATCH v3 09/10] dmaengine: dw-edma: Use burst array instead of linked list Frank.Li
@ 2026-07-02 21:21 ` Frank.Li
2026-07-02 21:38 ` sashiko-bot
9 siblings, 1 reply; 21+ messages in thread
From: Frank.Li @ 2026-07-02 21:21 UTC (permalink / raw)
To: Manivannan Sadhasivam, Vinod Koul, Gustavo Pimentel, Kees Cook,
Gustavo A. R. Silva, Manivannan Sadhasivam,
Krzysztof Wilczyński, Kishon Vijay Abraham I, Bjorn Helgaas,
Christoph Hellwig, Niklas Cassel
Cc: dmaengine, linux-kernel, linux-hardening, linux-pci, linux-nvme,
Koichiro Den, imx, Verma, Devendra, Frank Li
From: Frank Li <Frank.Li@nxp.com>
The current descriptor layout is:
struct dw_edma_desc *desc
└─ chunk list
└─ burst[]
Creating a DMA descriptor requires at least two kzalloc() calls because
each chunk is allocated as a linked-list node. Since the number of bursts
is already known when the descriptor is created, this linked-list layer is
unnecessary.
Move the burst array directly into struct dw_edma_desc and remove the
struct dw_edma_chunk layer entirely.
Use start_burst and done_burst to track the current bursts, which current
are in the DMA link list.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
change in v2
- remove debug code
- move "residue = desc->alloc_sz;" in if(desc) check
- keep inline to avoid build warning
---
drivers/dma/dw-edma/dw-edma-core.c | 141 ++++++++++++-------------------------
drivers/dma/dw-edma/dw-edma-core.h | 24 ++++---
2 files changed, 59 insertions(+), 106 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 01bee22fe3b3e..eead38897c42d 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -40,82 +40,52 @@ u64 dw_edma_get_pci_address(struct dw_edma_chan *chan, phys_addr_t cpu_addr)
return cpu_addr;
}
-static struct dw_edma_chunk *dw_edma_alloc_chunk(struct dw_edma_desc *desc, u32 nburst)
-{
- struct dw_edma_chan *chan = desc->chan;
- struct dw_edma_chunk *chunk;
-
- chunk = kzalloc_flex(*chunk, burst, nburst, GFP_NOWAIT);
- if (unlikely(!chunk))
- return NULL;
-
- chunk->chan = chan;
- /* Toggling change bit (CB) in each chunk, this is a mechanism to
- * inform the eDMA HW block that this is a new linked list ready
- * to be consumed.
- * - Odd chunks originate CB equal to 0
- * - Even chunks originate CB equal to 1
- */
- chunk->cb = !(desc->chunks_alloc % 2);
-
- chunk->nburst = nburst;
-
- list_add_tail(&chunk->list, &desc->chunk_list);
- desc->chunks_alloc++;
-
- return chunk;
-}
-
-static struct dw_edma_desc *dw_edma_alloc_desc(struct dw_edma_chan *chan)
+static struct dw_edma_desc *
+dw_edma_alloc_desc(struct dw_edma_chan *chan, u32 nburst)
{
struct dw_edma_desc *desc;
- desc = kzalloc_obj(*desc, GFP_NOWAIT);
+ desc = kzalloc_flex(*desc, burst, nburst, GFP_NOWAIT);
if (unlikely(!desc))
return NULL;
desc->chan = chan;
-
- INIT_LIST_HEAD(&desc->chunk_list);
+ desc->nburst = nburst;
+ desc->cb = true;
return desc;
}
-static void dw_edma_free_desc(struct dw_edma_desc *desc)
-{
- struct dw_edma_chunk *child, *_next;
-
- /* Remove all the list elements */
- list_for_each_entry_safe(child, _next, &desc->chunk_list, list) {
- list_del(&child->list);
- kfree(child);
- desc->chunks_alloc--;
- }
-
- kfree(desc);
-}
-
static void vchan_free_desc(struct virt_dma_desc *vdesc)
{
- dw_edma_free_desc(vd2dw_edma_desc(vdesc));
+ kfree(vd2dw_edma_desc(vdesc));
}
-static void dw_edma_core_start(struct dw_edma_chunk *chunk, bool first)
+static void dw_edma_core_start(struct dw_edma_desc *desc, bool first)
{
- struct dw_edma_chan *chan = chunk->chan;
+ struct dw_edma_chan *chan = desc->chan;
u32 i = 0;
if (chan->non_ll) {
- if (chunk->nburst == 1)
- chan->dw->core->non_ll_start(chunk->chan, &chunk->burst[0]);
+ chan->dw->core->non_ll_start(chan, &desc->burst[desc->start_burst]);
+ desc->done_burst = desc->start_burst;
+ desc->start_burst += 1;
return;
}
- for (i = 0; i < chunk->nburst; i++)
- dw_edma_core_ll_data(chan, &chunk->burst[i], i, chunk->cb,
- i == chunk->nburst - 1);
+ for (i = 0; i < desc->nburst; i++) {
+ if (i == chan->ll_max - 1)
+ break;
+
+ dw_edma_core_ll_data(chan, &desc->burst[i + desc->start_burst],
+ i, desc->cb,
+ i == desc->nburst - 1 || i == chan->ll_max - 2);
+ }
+
+ desc->done_burst = desc->start_burst;
+ desc->start_burst += i;
- dw_edma_core_ll_link(chan, i, chunk->cb, chan->ll_region.paddr);
+ dw_edma_core_ll_link(chan, i, desc->cb, chan->ll_region.paddr);
if (first)
dw_edma_core_ch_enable(chan);
@@ -125,7 +95,6 @@ static void dw_edma_core_start(struct dw_edma_chunk *chunk, bool first)
static int dw_edma_start_transfer(struct dw_edma_chan *chan)
{
- struct dw_edma_chunk *child;
struct dw_edma_desc *desc;
struct virt_dma_desc *vd;
@@ -137,16 +106,9 @@ static int dw_edma_start_transfer(struct dw_edma_chan *chan)
if (!desc)
return 0;
- child = list_first_entry_or_null(&desc->chunk_list,
- struct dw_edma_chunk, list);
- if (!child)
- return 0;
+ dw_edma_core_start(desc, !desc->start_burst);
- dw_edma_core_start(child, !desc->xfer_sz);
- desc->xfer_sz += child->xfer_sz;
- list_del(&child->list);
- kfree(child);
- desc->chunks_alloc--;
+ desc->cb = !desc->cb;
return 1;
}
@@ -337,8 +299,10 @@ dw_edma_device_tx_status(struct dma_chan *dchan, dma_cookie_t cookie,
vd = vchan_find_desc(&chan->vc, cookie);
if (vd) {
desc = vd2dw_edma_desc(vd);
- if (desc)
- residue = desc->alloc_sz - desc->xfer_sz;
+
+ residue = desc->alloc_sz;
+ if (desc && desc->done_burst)
+ residue -= desc->burst[desc->done_burst].xfer_sz;
}
spin_unlock_irqrestore(&chan->vc.lock, flags);
@@ -355,12 +319,10 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer,
struct dw_edma_chan *chan = dchan2dw_edma_chan(xfer->dchan);
enum dma_transfer_direction dir = xfer->direction;
struct scatterlist *sg = NULL;
- struct dw_edma_chunk *chunk = NULL;
struct dw_edma_burst *burst;
struct dw_edma_desc *desc;
u64 src_addr, dst_addr;
size_t fsz = 0;
- u32 bursts_max;
u32 cnt = 0;
u32 i;
@@ -418,17 +380,6 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer,
return NULL;
}
- /*
- * For non-LL mode, only a single burst can be handled
- * in a single chunk unlike LL mode where multiple bursts
- * can be configured in a single chunk.
- */
- bursts_max = chan->non_ll ? 1 : chan->ll_max;
-
- desc = dw_edma_alloc_desc(chan);
- if (unlikely(!desc))
- goto err_alloc;
-
if (xfer->type == EDMA_XFER_INTERLEAVED) {
src_addr = xfer->xfer.il->src_start;
dst_addr = xfer->xfer.il->dst_start;
@@ -452,19 +403,15 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer,
fsz = xfer->xfer.il->frame_size;
}
+ desc = dw_edma_alloc_desc(chan, cnt);
+ if (unlikely(!desc))
+ return NULL;
+
for (i = 0; i < cnt; i++) {
if (xfer->type == EDMA_XFER_SCATTER_GATHER && !sg)
break;
- if (!(i % chan->ll_max)) {
- u32 n = min(cnt - i, chan->ll_max);
-
- chunk = dw_edma_alloc_chunk(desc, n);
- if (unlikely(!chunk))
- goto err_alloc;
- }
-
- burst = chunk->burst + (i % chan->ll_max);
+ burst = desc->burst + i;
if (xfer->type == EDMA_XFER_CYCLIC)
burst->sz = xfer->xfer.cyclic.len;
@@ -473,8 +420,8 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer,
else if (xfer->type == EDMA_XFER_INTERLEAVED)
burst->sz = xfer->xfer.il->sgl[i % fsz].size;
- chunk->xfer_sz += burst->sz;
desc->alloc_sz += burst->sz;
+ burst->xfer_sz = desc->alloc_sz;
if (dir == DMA_DEV_TO_MEM) {
burst->sar = src_addr;
@@ -529,12 +476,6 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer,
}
return vchan_tx_prep(&chan->vc, &desc->vd, xfer->flags);
-
-err_alloc:
- if (desc)
- dw_edma_free_desc(desc);
-
- return NULL;
}
static struct dma_async_tx_descriptor *
@@ -605,8 +546,14 @@ static void dw_hdma_set_callback_result(struct virt_dma_desc *vd,
return;
desc = vd2dw_edma_desc(vd);
- if (desc)
- residue = desc->alloc_sz - desc->xfer_sz;
+ 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;
@@ -625,7 +572,7 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
switch (chan->request) {
case EDMA_REQ_NONE:
desc = vd2dw_edma_desc(vd);
- if (!desc->chunks_alloc) {
+ if (desc->start_burst >= desc->nburst) {
dw_hdma_set_callback_result(vd,
DMA_TRANS_NOERROR);
list_del(&vd->node);
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index 4950c57fca34f..7f2ec871f5bd5 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -46,15 +46,8 @@ struct dw_edma_burst {
u64 sar;
u64 dar;
u32 sz;
-};
-
-struct dw_edma_chunk {
- struct list_head list;
- struct dw_edma_chan *chan;
- u8 cb;
+ /* precalulate summary of previous burst total size */
u32 xfer_sz;
- u32 nburst;
- struct dw_edma_burst burst[] __counted_by(nburst);
};
struct dw_edma_desc {
@@ -66,6 +59,12 @@ struct dw_edma_desc {
u32 alloc_sz;
u32 xfer_sz;
+
+ u32 done_burst;
+ u32 start_burst;
+ u8 cb;
+ u32 nburst;
+ struct dw_edma_burst burst[] __counted_by(nburst);
};
struct dw_edma_chan {
@@ -128,7 +127,6 @@ struct dw_edma_core_ops {
void (*ll_link)(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr);
void (*ch_doorbell)(struct dw_edma_chan *chan);
void (*ch_enable)(struct dw_edma_chan *chan);
-
void (*ch_config)(struct dw_edma_chan *chan);
void (*debugfs_on)(struct dw_edma *dw);
void (*ack_emulated_irq)(struct dw_edma *dw);
@@ -170,6 +168,14 @@ struct dw_edma_chan *dchan2dw_edma_chan(struct dma_chan *dchan)
return vc2dw_edma_chan(to_virt_chan(dchan));
}
+static inline u64 dw_edma_core_get_ll_paddr(struct dw_edma_chan *chan)
+{
+ if (chan->dir == EDMA_DIR_WRITE)
+ return chan->dw->chip->ll_region_wr[chan->id].paddr;
+
+ return chan->dw->chip->ll_region_rd[chan->id].paddr;
+}
+
static inline
void dw_edma_core_off(struct dw_edma *dw)
{
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH v3 10/10] dmaengine: dw-edma: Remove struct dw_edma_chunk
2026-07-02 21:21 ` [PATCH v3 10/10] dmaengine: dw-edma: Remove struct dw_edma_chunk Frank.Li
@ 2026-07-02 21:38 ` sashiko-bot
0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-02 21:38 UTC (permalink / raw)
To: Frank.Li; +Cc: dmaengine, Frank.Li, imx, vkoul, linux-pci
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] The loop in `dw_edma_core_start` uses an incorrect upper bound for multi-chunk DMA transfers, causing out-of-bounds heap slab reads, invalid DMA programming, and missing completion interrupts.
- [Medium] Incorrect DMA residue calculation in `dw_edma_device_tx_status` under-reports remaining bytes due to an off-by-one array index.
- [Low] Dead fields remain in `struct dw_edma_desc` after the removal of the chunk layer.
--
commit bff8cdb94c387e71cb6eb50f2573846d0ceb45ec
Author: Frank Li <Frank.Li@nxp.com>
dmaengine: dw-edma: Remove struct dw_edma_chunk
This commit removes the intermediate linked-list layer (struct dw_edma_chunk)
from the DMA descriptor layout. It moves the burst array directly into
struct dw_edma_desc and uses start_burst and done_burst to track progress.
> diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
> --- a/drivers/dma/dw-edma/dw-edma-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-core.c
[ ... ]
> -static void dw_edma_core_start(struct dw_edma_chunk *chunk, bool first)
> +static void dw_edma_core_start(struct dw_edma_desc *desc, bool first)
> {
> - struct dw_edma_chan *chan = chunk->chan;
> + struct dw_edma_chan *chan = desc->chan;
> u32 i = 0;
>
> if (chan->non_ll) {
> - if (chunk->nburst == 1)
> - chan->dw->core->non_ll_start(chunk->chan, &chunk->burst[0]);
> + chan->dw->core->non_ll_start(chan, &desc->burst[desc->start_burst]);
> + desc->done_burst = desc->start_burst;
> + desc->start_burst += 1;
> return;
> }
>
> - for (i = 0; i < chunk->nburst; i++)
> - dw_edma_core_ll_data(chan, &chunk->burst[i], i, chunk->cb,
> - i == chunk->nburst - 1);
> + for (i = 0; i < desc->nburst; i++) {
> + if (i == chan->ll_max - 1)
> + break;
> +
> + dw_edma_core_ll_data(chan, &desc->burst[i + desc->start_burst],
> + i, desc->cb,
> + i == desc->nburst - 1 || i == chan->ll_max - 2);
> + }
[Severity: Critical]
Is the loop bound desc->nburst correct when resuming a partially completed
transfer?
For a DMA transfer where the total bursts exceed the hardware chunk limit
(chan->ll_max - 1), desc->start_burst advances after the first chunk. On
subsequent chunks, the loop limit doesn't account for the bursts already
processed. This allows the index calculation i + desc->start_burst to
exceed the size of the allocated desc->burst array, resulting in an
out-of-bounds slab read.
Additionally, does the terminal IRQ condition i == desc->nburst - 1 ever
evaluate to true on the final chunk? Since i is a relative chunk index and
desc->nburst is the absolute total, this could prevent the final completion
interrupt from firing.
> +
> + desc->done_burst = desc->start_burst;
> + desc->start_burst += i;
>
> - dw_edma_core_ll_link(chan, i, chunk->cb, chan->ll_region.paddr);
> + dw_edma_core_ll_link(chan, i, desc->cb, chan->ll_region.paddr);
>
> if (first)
> dw_edma_core_ch_enable(chan);
[ ... ]
> @@ -337,8 +299,10 @@ dw_edma_device_tx_status(struct dma_chan *dchan, dma_cookie_t cookie,
> vd = vchan_find_desc(&chan->vc, cookie);
> if (vd) {
> desc = vd2dw_edma_desc(vd);
> - if (desc)
> - residue = desc->alloc_sz - desc->xfer_sz;
> +
> + residue = desc->alloc_sz;
> + if (desc && desc->done_burst)
> + residue -= desc->burst[desc->done_burst].xfer_sz;
[Severity: Medium]
Could this residue calculation under-report the remaining bytes?
When querying the status of a partially complete multi-chunk transfer
(desc->done_burst > 0), this subtracts the cumulative transfer size at
desc->burst[desc->done_burst].xfer_sz. This array index actually holds the
transfer size including the first burst of the currently executing chunk.
Should this use desc->done_burst - 1 instead, to subtract only the bytes
from fully completed chunks?
> }
> spin_unlock_irqrestore(&chan->vc.lock, flags);
>
[ ... ]
> diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
> --- a/drivers/dma/dw-edma/dw-edma-core.h
> +++ b/drivers/dma/dw-edma/dw-edma-core.h
> @@ -66,6 +59,12 @@ struct dw_edma_desc {
>
> u32 alloc_sz;
> u32 xfer_sz;
> +
> + u32 done_burst;
> + u32 start_burst;
> + u8 cb;
> + u32 nburst;
> + struct dw_edma_burst burst[] __counted_by(nburst);
> };
[Severity: Low]
Since the chunk linked-list layer was removed in this commit, are the
chunk_list and chunks_alloc fields still necessary in struct dw_edma_desc?
It appears xfer_sz might also be unused now that the previous transfer size
tracking was removed from dw_edma_start_transfer().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260702-edma_ll-v3-0-877aa463740c@nxp.com?part=10
^ permalink raw reply [flat|nested] 21+ messages in thread