From: Frank Li <Frank.li@oss.nxp.com>
To: Sumit Kumar <sumit.kumar@oss.qualcomm.com>
Cc: "Vinod Koul" <vkoul@kernel.org>, "Frank Li" <Frank.Li@kernel.org>,
"Jonathan Corbet" <corbet@lwn.net>,
"Shuah Khan" <skhan@linuxfoundation.org>,
"Manivannan Sadhasivam" <mani@kernel.org>,
"Jeff Hugo" <jeff.hugo@oss.qualcomm.com>,
"Krzysztof Wilczyński" <kwilczynski@kernel.org>,
"Kishon Vijay Abraham I" <kishon@kernel.org>,
"Bjorn Helgaas" <bhelgaas@google.com>,
dmaengine@vger.kernel.org, linux-doc@vger.kernel.org,
linux-kernel@vger.kernel.org, mhi@lists.linux.dev,
linux-arm-msm@vger.kernel.org, linux-pci@vger.kernel.org
Subject: Re: [PATCH v2 2/5] dmaengine: dw-edma: Add DMA_SG support
Date: Thu, 6 Aug 2026 14:06:30 -0500 [thread overview]
Message-ID: <anTbNsOQ02V6umXf@SMW015318> (raw)
In-Reply-To: <20260803-dma_multi_sg-v2-2-c12bb05e42d6@oss.qualcomm.com>
On Mon, Aug 03, 2026 at 04:01:44PM +0530, Sumit Kumar wrote:
> [You don't often get email from sumit.kumar@oss.qualcomm.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>
> Synopsys DesignWare eDMA supports a linked-list mode where each list item
> carries independent source and destination addresses, letting multiple
> independent memory transfers be described in one linked list and submitted
> to the hardware as a single DMA transaction. The IP processes list items
> strictly in order, so paired scatter-gather entries are never reordered.
>
> Implement the DMA_SG capability by adding a new EDMA_XFER_DUAL_SG transfer
> type and a corresponding struct dw_edma_dual_sg carrying the paired source
> and destination SG lists. dw_edma_device_transfer() walks both lists in
> lockstep, building a single hardware linked-list; a per-entry length
> mismatch or premature list end fails the whole request.
It is not as simple as it. for example, if you want to transfer 9k data
from src to dest
src virtual addr 0x1004, dest 0xA0001008
when map_sg, src's sg maybe
0x1000 .. 4k offset 4,
0x9000 .. 4k offset 0
0xA000 .. 4k offset 0
or
0x1000 .. 8k offset 4
0xA000 .. 4k offset 0
which totally depend on physical address allocation although most likely
first case happen
dest sg
0xA001000 .. 4k offset 8
0xA008000 .. 4k offset 0
0xA00E000 .. 4k offset 0
descriptors
1 transfer 4k-8
2 transfer tail 4 byte
3 transfer 4k-8
4 transfer tail 4 byte
...
the start address of src and dest is highly possible differences. So it
is very hard to match your requirement, both sg's structure is the same.
Frank
>
> The transfer direction is inferred from the channel hardware polarity
> (EDMA_DIR_READ/WRITE), not from dma_slave_config.direction: for local eDMA
> (DW_EDMA_CHIP_LOCAL) read channels handle DEV_TO_MEM and write channels
> handle MEM_TO_DEV; for remote eDMA the mapping is inverted. PCIe bus
> addresses are translated via dw_edma_get_pci_address() for the remote side
> of each transfer. dmaengine_slave_config() must still be called before
> dmaengine_prep_dma_sg() because dw_edma_device_transfer() gates transfers
> on chan->configured, even though the direction field itself is unused by
> the DMA_SG path.
>
> Signed-off-by: Sumit Kumar <sumit.kumar@oss.qualcomm.com>
> ---
> drivers/dma/dw-edma/dw-edma-core.c | 87 +++++++++++++++++++++++++++++++++++---
> drivers/dma/dw-edma/dw-edma-core.h | 10 ++++-
> 2 files changed, 90 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
> index 1f893dc54c7938a9c45bafd975a4f99fdc0acb38..9fbfa5ad65b5421b164d2ab9883233225e0269e4 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-core.c
> @@ -372,6 +372,7 @@ 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 scatterlist *dst_sg = NULL;
> struct dw_edma_burst *burst;
> struct dw_edma_desc *desc;
> u64 src_addr, dst_addr;
> @@ -429,6 +430,9 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer,
> return NULL;
> if (!xfer->xfer.il->src_inc || !xfer->xfer.il->dst_inc)
> return NULL;
> + } else if (xfer->type == EDMA_XFER_DUAL_SG) {
> + if (xfer->xfer.dual_sg.len < 1)
> + return NULL;
> } else {
> return NULL;
> }
> @@ -441,16 +445,27 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer,
> dst_addr = config->dst_addr;
> }
>
> - if (dir == DMA_DEV_TO_MEM)
> - src_addr = dw_edma_get_pci_address(chan, (phys_addr_t)src_addr);
> - else
> - dst_addr = dw_edma_get_pci_address(chan, (phys_addr_t)dst_addr);
> + /*
> + * DUAL_SG translates each src/dst sg entry individually below
> + * (see the burst->sar/burst->dar assignment), so it is exempt
> + * from the single up-front translation used by other types.
> + */
> + if (xfer->type != EDMA_XFER_DUAL_SG) {
> + if (dir == DMA_DEV_TO_MEM)
> + src_addr = dw_edma_get_pci_address(chan, (phys_addr_t)src_addr);
> + else
> + dst_addr = dw_edma_get_pci_address(chan, (phys_addr_t)dst_addr);
> + }
>
> if (xfer->type == EDMA_XFER_CYCLIC) {
> cnt = xfer->xfer.cyclic.cnt;
> } else if (xfer->type == EDMA_XFER_SCATTER_GATHER) {
> cnt = xfer->xfer.sg.len;
> sg = xfer->xfer.sg.sgl;
> + } else if (xfer->type == EDMA_XFER_DUAL_SG) {
> + cnt = xfer->xfer.dual_sg.len;
> + sg = xfer->xfer.dual_sg.src_sgl;
> + dst_sg = xfer->xfer.dual_sg.dst_sgl;
> } else if (xfer->type == EDMA_XFER_INTERLEAVED) {
> cnt = xfer->xfer.il->numf * xfer->xfer.il->frame_size;
> fsz = xfer->xfer.il->frame_size;
> @@ -463,12 +478,23 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer,
> for (i = 0; i < cnt; i++) {
> if (xfer->type == EDMA_XFER_SCATTER_GATHER && !sg)
> break;
> + /*
> + * DUAL_SG walks the source and destination lists in lockstep;
> + * a premature end or a per-entry length mismatch would leave
> + * the descriptor partially built, so fail the whole request.
> + */
> + if (xfer->type == EDMA_XFER_DUAL_SG &&
> + (!sg || !dst_sg || sg_dma_len(sg) != sg_dma_len(dst_sg))) {
> + kfree(desc);
> + return NULL;
> + }
>
> burst = desc->burst + i;
>
> if (xfer->type == EDMA_XFER_CYCLIC)
> burst->sz = xfer->xfer.cyclic.len;
> - else if (xfer->type == EDMA_XFER_SCATTER_GATHER)
> + else if (xfer->type == EDMA_XFER_SCATTER_GATHER ||
> + xfer->type == EDMA_XFER_DUAL_SG)
> burst->sz = sg_dma_len(sg);
> else if (xfer->type == EDMA_XFER_INTERLEAVED)
> burst->sz = xfer->xfer.il->sgl[i % fsz].size;
> @@ -492,6 +518,9 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer,
> */
> } else if (xfer->type == EDMA_XFER_INTERLEAVED) {
> burst->dar = dst_addr;
> + } else if (xfer->type == EDMA_XFER_DUAL_SG) {
> + burst->sar = dw_edma_get_pci_address(chan, sg_dma_address(sg));
> + burst->dar = sg_dma_address(dst_sg);
> }
> } else {
> burst->dar = dst_addr;
> @@ -507,13 +536,19 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer,
> * and destination addresses are increased
> * by the same portion (data length)
> */
> - } else if (xfer->type == EDMA_XFER_INTERLEAVED) {
> + } else if (xfer->type == EDMA_XFER_INTERLEAVED) {
> burst->sar = src_addr;
> + } else if (xfer->type == EDMA_XFER_DUAL_SG) {
> + burst->sar = sg_dma_address(sg);
> + burst->dar = dw_edma_get_pci_address(chan, sg_dma_address(dst_sg));
> }
> }
>
> if (xfer->type == EDMA_XFER_SCATTER_GATHER) {
> sg = sg_next(sg);
> + } else if (xfer->type == EDMA_XFER_DUAL_SG) {
> + sg = sg_next(sg);
> + dst_sg = sg_next(dst_sg);
> } else if (xfer->type == EDMA_XFER_INTERLEAVED) {
> struct dma_interleaved_template *il = xfer->xfer.il;
> struct data_chunk *dc = &il->sgl[i % fsz];
> @@ -613,6 +648,44 @@ static void dw_hdma_set_callback_result(struct virt_dma_desc *vd,
> res->residue = residue;
> }
>
> +static struct dma_async_tx_descriptor *
> +dw_edma_device_prep_dma_sg(struct dma_chan *dchan,
> + struct scatterlist *dst_sg, unsigned int dst_nents,
> + struct scatterlist *src_sg, unsigned int src_nents,
> + unsigned long flags)
> +{
> + struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
> + struct dw_edma_transfer xfer;
> + enum dma_transfer_direction dir;
> +
> + if (src_nents != dst_nents || !src_nents)
> + return NULL;
> +
> + if (!src_sg || !dst_sg)
> + return NULL;
> +
> + /* Determine direction from channel configuration */
> + if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL)
> + dir = (chan->dir == EDMA_DIR_READ) ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV;
> + else
> + dir = (chan->dir == EDMA_DIR_WRITE) ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV;
> +
> + xfer.dchan = dchan;
> + xfer.direction = dir;
> + xfer.xfer.dual_sg.src_sgl = src_sg;
> + xfer.xfer.dual_sg.dst_sgl = dst_sg;
> + xfer.xfer.dual_sg.len = src_nents;
> + xfer.flags = flags;
> + xfer.type = EDMA_XFER_DUAL_SG;
> +
> + /*
> + * dw_edma_device_transfer() rejects unconfigured channels, so
> + * dmaengine_slave_config() must have been called on this channel
> + * beforehand even though the direction field is unused here.
> + */
> + return dw_edma_device_transfer(&xfer, dw_edma_device_get_config(dchan, NULL));
> +}
> +
> static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
> {
> struct dw_edma_desc *desc;
> @@ -997,6 +1070,7 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
> dma_cap_set(DMA_CYCLIC, dma->cap_mask);
> dma_cap_set(DMA_PRIVATE, dma->cap_mask);
> dma_cap_set(DMA_INTERLEAVE, dma->cap_mask);
> + dma_cap_set(DMA_SG, dma->cap_mask);
> dma->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
> dma->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
> dma->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
> @@ -1017,6 +1091,7 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
> dma->device_prep_config_sg = dw_edma_device_prep_config_sg;
> dma->device_prep_dma_cyclic = dw_edma_device_prep_dma_cyclic;
> dma->device_prep_interleaved_dma = dw_edma_device_prep_interleaved_dma;
> + dma->device_prep_dma_sg = dw_edma_device_prep_dma_sg;
>
> dma_set_max_seg_size(dma->dev, U32_MAX);
>
> diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
> index f6a5ad31756723e6fd72f7d9d903a1204e25c0b0..e3219ae96d84f528fb7d77dbe3a0b3d7c1264f05 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.h
> +++ b/drivers/dma/dw-edma/dw-edma-core.h
> @@ -38,7 +38,8 @@ enum dw_edma_status {
> enum dw_edma_xfer_type {
> EDMA_XFER_SCATTER_GATHER = 0,
> EDMA_XFER_CYCLIC,
> - EDMA_XFER_INTERLEAVED
> + EDMA_XFER_INTERLEAVED,
> + EDMA_XFER_DUAL_SG,
> };
>
> struct dw_edma_chan;
> @@ -151,6 +152,12 @@ struct dw_edma_sg {
> unsigned int len;
> };
>
> +struct dw_edma_dual_sg {
> + struct scatterlist *src_sgl;
> + struct scatterlist *dst_sgl;
> + unsigned int len;
> +};
> +
> struct dw_edma_cyclic {
> dma_addr_t paddr;
> size_t len;
> @@ -163,6 +170,7 @@ struct dw_edma_transfer {
> struct dw_edma_sg sg;
> struct dw_edma_cyclic cyclic;
> struct dma_interleaved_template *il;
> + struct dw_edma_dual_sg dual_sg;
> } xfer;
> enum dma_transfer_direction direction;
> unsigned long flags;
>
> --
> 2.34.1
>
next prev parent reply other threads:[~2026-08-06 19:06 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-03 10:31 [PATCH v2 0/5] dmaengine: Add batched scatter-gather DMA support Sumit Kumar
2026-08-03 10:31 ` [PATCH v2 1/5] dmaengine: Add DMA_SG support for multi-buffer scatter-gather transfers Sumit Kumar
2026-08-03 10:31 ` [PATCH v2 2/5] dmaengine: dw-edma: Add DMA_SG support Sumit Kumar
2026-08-06 19:06 ` Frank Li [this message]
2026-08-03 10:31 ` [PATCH v2 3/5] PCI: epf-mhi: Use a define for the DMA transfer timeout Sumit Kumar
2026-08-03 10:31 ` [PATCH v2 4/5] PCI: epf-mhi: Add batched DMA read support Sumit Kumar
2026-08-06 17:11 ` Frank Li
2026-08-03 10:31 ` [PATCH v2 5/5] bus: mhi: ep: Use batched read for ring caching Sumit Kumar
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=anTbNsOQ02V6umXf@SMW015318 \
--to=frank.li@oss.nxp.com \
--cc=Frank.Li@kernel.org \
--cc=bhelgaas@google.com \
--cc=corbet@lwn.net \
--cc=dmaengine@vger.kernel.org \
--cc=jeff.hugo@oss.qualcomm.com \
--cc=kishon@kernel.org \
--cc=kwilczynski@kernel.org \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pci@vger.kernel.org \
--cc=mani@kernel.org \
--cc=mhi@lists.linux.dev \
--cc=skhan@linuxfoundation.org \
--cc=sumit.kumar@oss.qualcomm.com \
--cc=vkoul@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox