From: Sumit Kumar <sumit.kumar@oss.qualcomm.com>
To: "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>
Cc: 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,
Sumit Kumar <sumit.kumar@oss.qualcomm.com>
Subject: [PATCH v2 2/5] dmaengine: dw-edma: Add DMA_SG support
Date: Mon, 03 Aug 2026 16:01:44 +0530 [thread overview]
Message-ID: <20260803-dma_multi_sg-v2-2-c12bb05e42d6@oss.qualcomm.com> (raw)
In-Reply-To: <20260803-dma_multi_sg-v2-0-c12bb05e42d6@oss.qualcomm.com>
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.
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-03 10:32 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 ` Sumit Kumar [this message]
2026-08-06 19:06 ` [PATCH v2 2/5] dmaengine: dw-edma: Add DMA_SG support Frank Li
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=20260803-dma_multi_sg-v2-2-c12bb05e42d6@oss.qualcomm.com \
--to=sumit.kumar@oss.qualcomm.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=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