* [PATCH v2 0/5] PCI: endpoint: Remote DMA support via vNTB
@ 2026-08-28 17:09 Koichiro Den
2026-08-28 17:09 ` [PATCH v2 1/5] PCI: endpoint: Add DMA auxiliary resource metadata Koichiro Den
` (4 more replies)
0 siblings, 5 replies; 14+ messages in thread
From: Koichiro Den @ 2026-08-28 17:09 UTC (permalink / raw)
To: Manivannan Sadhasivam, Krzysztof Wilczyński,
Kishon Vijay Abraham I, Frank Li, Jon Mason, Dave Jiang,
Allen Hubbe, Niklas Cassel
Cc: Bjorn Helgaas, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Jingoo Han, Lorenzo Pieralisi, Rob Herring, Jerome Brunet,
linux-pci, linux-doc, linux-kernel, ntb
Hi,
This series lets an RC use endpoint-local DesignWare eDMA read channels
through vNTB.
It is intended to supersede the separate PCI DMA EPF v7 approach (see
the link below). Since no consumer outside the vNTB/ntb_hw_epf path is
currently known, this rework keeps the DMA export there instead of
maintaining a generic ABI for one use case. It also removes the need for
a second PCI function, allowing this setup on EPCs without
multi-function support.
On the endpoint, pci-epf-vntb reserves the complete eDMA read direction
and exposes those channels as a group. HDMA is not yet supported,
keeping the series minimal.
The DMA feature is opt-in through the pci-epf-vntb configfs attribute
'use_dma'. Without it, no DMA extension is exported. pci-epf-ntb is
unchanged.
Base and dependency
===================
This series is currently based on next-20260827 (3d83758432b5) plus:
[PATCH v2 0/3] dmaengine: dw-edma: Prepare channels for remote use
https://lore.kernel.org/r/20260828163611.2691264-1-den@valinux.co.jp/
The prerequisite is for the dmaengine tree and is not part of
pci/endpoint. linux-next provides a common buildable base for both
series. Once the prerequisite lands, this series can be rebased onto
pci/endpoint.
Best regards,
Koichiro
---
Changes in v2:
- Rewrite most of v1 and move the dw-edma preparation into the
companion series. Drop the generic pci-ep-dma API, auxiliary-bus
child/frontend, test ntb client, and preparatory MW-state
refactoring.
See individual patch trailers for provenance.
- Replace v1's post-registration channel-ID cache with driver-supplied
static IDs. (Frank)
https://lore.kernel.org/r/abMcgQOHDD55Yv0e@lizhi-Precision-Tower-5810/
- Let pci-epf-vntb reserve channels with dma_request_channel() and
configure interrupt routing with dmaengine_slave_config(). This
removes the PCI EPC DMA delegation API. (Frank)
https://lore.kernel.org/r/ao2nHoCwfTEEiFSr@SMW015318/
- Drop v1's dma_offset and dma_num_chans attributes. use_dma now enables
export of the complete eDMA read direction as a group.
v1:
https://lore.kernel.org/r/20260312165005.1148676-1-den@valinux.co.jp/
Related PCI DMA EPF v7:
https://lore.kernel.org/r/20260813063757.3131865-1-den@valinux.co.jp/
Koichiro Den (5):
PCI: endpoint: Add DMA auxiliary resource metadata
PCI: dwc: Expose endpoint DMA resources
PCI: endpoint: pci-epf-vntb: Move epf_ntb_is_bar_used() up
PCI: endpoint: pci-epf-vntb: Export endpoint DMA channels
NTB: ntb_hw_epf: Discover vNTB-embedded DMA
Documentation/PCI/endpoint/pci-vntb-howto.rst | 23 +-
drivers/ntb/hw/epf/ntb_hw_epf.c | 360 ++++++++-
.../pci/controller/dwc/pcie-designware-ep.c | 107 ++-
drivers/pci/endpoint/functions/pci-epf-vntb.c | 692 +++++++++++++++++-
include/linux/pci-epc.h | 34 +-
5 files changed, 1154 insertions(+), 62 deletions(-)
--
2.51.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v2 1/5] PCI: endpoint: Add DMA auxiliary resource metadata
2026-08-28 17:09 [PATCH v2 0/5] PCI: endpoint: Remote DMA support via vNTB Koichiro Den
@ 2026-08-28 17:09 ` Koichiro Den
2026-08-28 17:15 ` sashiko-bot
2026-08-28 18:48 ` Frank Li
2026-08-28 17:09 ` [PATCH v2 2/5] PCI: dwc: Expose endpoint DMA resources Koichiro Den
` (3 subsequent siblings)
4 siblings, 2 replies; 14+ messages in thread
From: Koichiro Den @ 2026-08-28 17:09 UTC (permalink / raw)
To: Manivannan Sadhasivam, Krzysztof Wilczyński,
Kishon Vijay Abraham I, Frank Li, Jon Mason, Dave Jiang,
Allen Hubbe, Niklas Cassel
Cc: Bjorn Helgaas, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Jingoo Han, Lorenzo Pieralisi, Rob Herring, Jerome Brunet,
linux-pci, linux-doc, linux-kernel, ntb
Endpoint functions that export controller-local DMA channels need the
controller register and descriptor-memory layout.
Add DMA control and descriptor-memory resource types. The control resource
carries the register layout and channel counts. Each descriptor region is
associated with the static channel ID assigned by the local DMA driver.
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
Changes in v2:
- Rework PCI DMA EPF v7 patch 3 with only the metadata needed by vNTB.
https://lore.kernel.org/r/20260813063757.3131865-4-den@valinux.co.jp/
- Keep descriptor metadata keyed only by static channel IDs. (Frank)
https://lore.kernel.org/r/lm7tadnxsyrypu4mypptlkx5qkytex4qxsijdr5ydud2n3anvf@yakjfjg5ng4u/
include/linux/pci-epc.h | 34 +++++++++++++++++++++++++++++++---
1 file changed, 31 insertions(+), 3 deletions(-)
diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h
index f247cf9bcf1a..e0348e7bc534 100644
--- a/include/linux/pci-epc.h
+++ b/include/linux/pci-epc.h
@@ -65,6 +65,8 @@ struct pci_epc_map {
* enum pci_epc_aux_resource_type - auxiliary resource type identifiers
* @PCI_EPC_AUX_DOORBELL_MMIO: Doorbell MMIO, that might be outside the DMA
* controller register window
+ * @PCI_EPC_AUX_DMA_CTRL_MMIO: DMA controller MMIO register window
+ * @PCI_EPC_AUX_DMA_DESC_MEM: DMA descriptor memory
*
* EPC backends may expose auxiliary blocks (e.g. DMA engines) by mapping their
* register windows and descriptor memories into BAR space. This enum
@@ -72,13 +74,25 @@ struct pci_epc_map {
*/
enum pci_epc_aux_resource_type {
PCI_EPC_AUX_DOORBELL_MMIO,
+ PCI_EPC_AUX_DMA_CTRL_MMIO,
+ PCI_EPC_AUX_DMA_DESC_MEM,
};
/**
- * struct pci_epc_aux_resource - a physical auxiliary resource that may be
- * exposed for peer use
+ * enum pci_epc_aux_dma_reg_layout - DMA controller register layout
+ * @PCI_EPC_AUX_DMA_REG_LAYOUT_UNKNOWN: unknown or uninitialized layout
+ * @PCI_EPC_AUX_DMA_REG_LAYOUT_DW_EDMA: Synopsys DesignWare eDMA/HDMA layout
+ */
+enum pci_epc_aux_dma_reg_layout {
+ PCI_EPC_AUX_DMA_REG_LAYOUT_UNKNOWN = 0,
+ PCI_EPC_AUX_DMA_REG_LAYOUT_DW_EDMA,
+};
+
+/**
+ * struct pci_epc_aux_resource - an auxiliary resource that may be exposed for
+ * peer use
* @type: resource type, see enum pci_epc_aux_resource_type
- * @phys_addr: physical base address of the resource
+ * @phys_addr: CPU physical base address of an MMIO resource
* @size: size of the resource in bytes
* @bar: BAR number where this resource is already exposed to the RC
* (NO_BAR if not)
@@ -99,6 +113,20 @@ struct pci_epc_aux_resource {
int irq; /* IRQ number for the doorbell handler */
u32 data; /* write value to ring the doorbell */
} db_mmio;
+
+ /* PCI_EPC_AUX_DMA_CTRL_MMIO */
+ struct {
+ enum pci_epc_aux_dma_reg_layout reg_layout;
+ u32 reg_layout_data;
+ u16 ep_to_rc_ch_cnt;
+ u16 rc_to_ep_ch_cnt;
+ } dma_ctrl;
+
+ /* PCI_EPC_AUX_DMA_DESC_MEM */
+ struct {
+ dma_addr_t dma_addr; /* Endpoint-local DMA address */
+ u16 chan_id;
+ } dma_desc;
} u;
};
--
2.51.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 2/5] PCI: dwc: Expose endpoint DMA resources
2026-08-28 17:09 [PATCH v2 0/5] PCI: endpoint: Remote DMA support via vNTB Koichiro Den
2026-08-28 17:09 ` [PATCH v2 1/5] PCI: endpoint: Add DMA auxiliary resource metadata Koichiro Den
@ 2026-08-28 17:09 ` Koichiro Den
2026-08-28 17:16 ` sashiko-bot
2026-08-28 18:59 ` Frank Li
2026-08-28 17:09 ` [PATCH v2 3/5] PCI: endpoint: pci-epf-vntb: Move epf_ntb_is_bar_used() up Koichiro Den
` (2 subsequent siblings)
4 siblings, 2 replies; 14+ messages in thread
From: Koichiro Den @ 2026-08-28 17:09 UTC (permalink / raw)
To: Manivannan Sadhasivam, Krzysztof Wilczyński,
Kishon Vijay Abraham I, Frank Li, Jon Mason, Dave Jiang,
Allen Hubbe, Niklas Cassel
Cc: Bjorn Helgaas, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Jingoo Han, Lorenzo Pieralisi, Rob Herring, Jerome Brunet,
linux-pci, linux-doc, linux-kernel, ntb
Expose the endpoint-integrated eDMA register window and linked-list memory
through the EPC auxiliary resource API. Endpoint functions can then choose
which channels to export and map the required windows.
Associate each linked-list region with the direction-flattened static
channel ID used by the local dw-edma device. Report these resources only
after that device is registered and only for channels with linked-list
memory.
When the register window is already part of a reserved BAR, report its BAR
and offset. Otherwise report its CPU physical address.
While at it, harden the existing doorbell resource query. Report it
only after the local dw-edma device is registered, and reject VF
queries because DWC cannot assign DMA/HDMA registers to VFs.
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
Changes in v2:
- Rework PCI DMA EPF v7 patch 7 around static channel IDs and the
reduced metadata.
https://lore.kernel.org/r/20260813063757.3131865-8-den@valinux.co.jp/
- Use cfg_non_ll instead of carrying over its LL-region scan.
- Leave the peer-visible layout to pci-epf-vntb.
.../pci/controller/dwc/pcie-designware-ep.c | 107 ++++++++++++++++--
1 file changed, 99 insertions(+), 8 deletions(-)
diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c
index de8ee3db4360..c5e07edb52b1 100644
--- a/drivers/pci/controller/dwc/pcie-designware-ep.c
+++ b/drivers/pci/controller/dwc/pcie-designware-ep.c
@@ -858,6 +858,15 @@ dw_pcie_ep_find_bar_rsvd_region(struct dw_pcie_ep *ep,
return NULL;
}
+static int dw_pcie_ep_check_edma_vfunc(u8 vfunc_no)
+{
+ /*
+ * The DWC endpoint databook says it is not possible to assign the
+ * DMA/HDMA registers to any Virtual Function.
+ */
+ return vfunc_no ? -EOPNOTSUPP : 0;
+}
+
static int
dw_pcie_ep_get_aux_resources_count(struct pci_epc *epc, u8 func_no,
u8 vfunc_no)
@@ -865,14 +874,30 @@ dw_pcie_ep_get_aux_resources_count(struct pci_epc *epc, u8 func_no,
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
struct dw_edma_chip *edma = &pci->edma;
+ u16 ll_wr_cnt, ll_rd_cnt;
+ int count = 0;
+ int ret;
if (!pci->edma_reg_size)
return 0;
- if (edma->db_offset == ~0)
+ ret = dw_pcie_ep_check_edma_vfunc(vfunc_no);
+ if (ret)
+ return ret;
+
+ if (!edma->dw)
return 0;
- return 1;
+ ll_wr_cnt = edma->ll_wr_cnt;
+ ll_rd_cnt = edma->ll_rd_cnt;
+
+ if (!edma->cfg_non_ll)
+ count += 1 + ll_wr_cnt + ll_rd_cnt;
+
+ if (edma->db_offset != ~0)
+ count++;
+
+ return count;
}
static int
@@ -885,14 +910,32 @@ dw_pcie_ep_get_aux_resources(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
const struct pci_epc_bar_rsvd_region *rsvd;
struct dw_edma_chip *edma = &pci->edma;
enum pci_barno dma_ctrl_bar = NO_BAR;
- resource_size_t db_offset = edma->db_offset;
+ resource_size_t db_offset;
resource_size_t dma_ctrl_bar_offset = 0;
resource_size_t dma_reg_size;
- int count;
+ u16 ll_wr_cnt, ll_rd_cnt;
+ bool has_ll;
+ unsigned int i;
+ int count, ret;
- count = dw_pcie_ep_get_aux_resources_count(epc, func_no, vfunc_no);
- if (count < 0)
- return count;
+ if (!pci->edma_reg_size)
+ return 0;
+
+ ret = dw_pcie_ep_check_edma_vfunc(vfunc_no);
+ if (ret)
+ return ret;
+
+ if (!edma->dw)
+ return 0;
+
+ ll_wr_cnt = edma->ll_wr_cnt;
+ ll_rd_cnt = edma->ll_rd_cnt;
+ db_offset = edma->db_offset;
+ has_ll = !edma->cfg_non_ll;
+
+ count = db_offset != ~0;
+ if (has_ll)
+ count += 1 + ll_wr_cnt + ll_rd_cnt;
if (num_resources < count)
return -ENOSPC;
@@ -909,6 +952,54 @@ dw_pcie_ep_get_aux_resources(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
if (rsvd && rsvd->size < dma_reg_size)
dma_reg_size = rsvd->size;
+ count = 0;
+ if (has_ll) {
+ resources[count++] = (struct pci_epc_aux_resource) {
+ .type = PCI_EPC_AUX_DMA_CTRL_MMIO,
+ .phys_addr = pci->edma_reg_phys,
+ .size = dma_reg_size,
+ .bar = dma_ctrl_bar,
+ .bar_offset = dma_ctrl_bar_offset,
+ .u.dma_ctrl = {
+ .reg_layout = PCI_EPC_AUX_DMA_REG_LAYOUT_DW_EDMA,
+ .reg_layout_data = edma->mf,
+ .ep_to_rc_ch_cnt = ll_wr_cnt,
+ .rc_to_ep_ch_cnt = ll_rd_cnt,
+ },
+ };
+
+ for (i = 0; i < ll_wr_cnt; i++) {
+ struct dw_edma_region *ll = &edma->ll_region_wr[i];
+
+ resources[count++] = (struct pci_epc_aux_resource) {
+ .type = PCI_EPC_AUX_DMA_DESC_MEM,
+ .size = ll->sz,
+ .bar = NO_BAR,
+ .u.dma_desc = {
+ .dma_addr = ll->paddr,
+ .chan_id = i,
+ },
+ };
+ }
+
+ for (i = 0; i < ll_rd_cnt; i++) {
+ struct dw_edma_region *ll = &edma->ll_region_rd[i];
+
+ resources[count++] = (struct pci_epc_aux_resource) {
+ .type = PCI_EPC_AUX_DMA_DESC_MEM,
+ .size = ll->sz,
+ .bar = NO_BAR,
+ .u.dma_desc = {
+ .dma_addr = ll->paddr,
+ .chan_id = ll_wr_cnt + i,
+ },
+ };
+ }
+ }
+
+ if (db_offset == ~0)
+ return 0;
+
/*
* For interrupt-emulation doorbells, report a standalone resource
* instead of bundling it into the DMA controller MMIO resource.
@@ -917,7 +1008,7 @@ dw_pcie_ep_get_aux_resources(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
sizeof(u32), dma_reg_size))
return -EINVAL;
- resources[0] = (struct pci_epc_aux_resource) {
+ resources[count] = (struct pci_epc_aux_resource) {
.type = PCI_EPC_AUX_DOORBELL_MMIO,
.phys_addr = pci->edma_reg_phys + db_offset,
.size = sizeof(u32),
--
2.51.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 3/5] PCI: endpoint: pci-epf-vntb: Move epf_ntb_is_bar_used() up
2026-08-28 17:09 [PATCH v2 0/5] PCI: endpoint: Remote DMA support via vNTB Koichiro Den
2026-08-28 17:09 ` [PATCH v2 1/5] PCI: endpoint: Add DMA auxiliary resource metadata Koichiro Den
2026-08-28 17:09 ` [PATCH v2 2/5] PCI: dwc: Expose endpoint DMA resources Koichiro Den
@ 2026-08-28 17:09 ` Koichiro Den
2026-08-28 17:14 ` sashiko-bot
2026-08-28 18:59 ` Frank Li
2026-08-28 17:09 ` [PATCH v2 4/5] PCI: endpoint: pci-epf-vntb: Export endpoint DMA channels Koichiro Den
2026-08-28 17:09 ` [PATCH v2 5/5] NTB: ntb_hw_epf: Discover vNTB-embedded DMA Koichiro Den
4 siblings, 2 replies; 14+ messages in thread
From: Koichiro Den @ 2026-08-28 17:09 UTC (permalink / raw)
To: Manivannan Sadhasivam, Krzysztof Wilczyński,
Kishon Vijay Abraham I, Frank Li, Jon Mason, Dave Jiang,
Allen Hubbe, Niklas Cassel
Cc: Bjorn Helgaas, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Jingoo Han, Lorenzo Pieralisi, Rob Herring, Jerome Brunet,
linux-pci, linux-doc, linux-kernel, ntb
Move epf_ntb_is_bar_used() up so later BAR selection code can call it
without a forward declaration. No functional change.
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
drivers/pci/endpoint/functions/pci-epf-vntb.c | 40 +++++++++----------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c
index c3caec927d74..d12d134ce553 100644
--- a/drivers/pci/endpoint/functions/pci-epf-vntb.c
+++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c
@@ -199,6 +199,26 @@ static int epf_ntb_link_up(struct epf_ntb *ntb, bool link_up)
return 0;
}
+/**
+ * epf_ntb_is_bar_used() - Check if a bar is used in the ntb configuration
+ * @ntb: NTB device that facilitates communication between HOST and VHOST
+ * @barno: Checked bar number
+ *
+ * Returns: true if used, false if free.
+ */
+static bool epf_ntb_is_bar_used(struct epf_ntb *ntb,
+ enum pci_barno barno)
+{
+ int i;
+
+ for (i = 0; i < VNTB_BAR_NUM; i++) {
+ if (ntb->epf_ntb_bar[i] == barno)
+ return true;
+ }
+
+ return false;
+}
+
/**
* epf_ntb_configure_mw() - Configure the Outbound Address Space for VHOST
* to access the memory window of HOST
@@ -830,26 +850,6 @@ static void epf_ntb_mw_bar_clear(struct epf_ntb *ntb, int num_mws)
}
}
-/**
- * epf_ntb_is_bar_used() - Check if a bar is used in the ntb configuration
- * @ntb: NTB device that facilitates communication between HOST and VHOST
- * @barno: Checked bar number
- *
- * Returns: true if used, false if free.
- */
-static bool epf_ntb_is_bar_used(struct epf_ntb *ntb,
- enum pci_barno barno)
-{
- int i;
-
- for (i = 0; i < VNTB_BAR_NUM; i++) {
- if (ntb->epf_ntb_bar[i] == barno)
- return true;
- }
-
- return false;
-}
-
/**
* epf_ntb_find_bar() - Assign BAR number when no configuration is provided
* @ntb: NTB device that facilitates communication between HOST and VHOST
--
2.51.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 4/5] PCI: endpoint: pci-epf-vntb: Export endpoint DMA channels
2026-08-28 17:09 [PATCH v2 0/5] PCI: endpoint: Remote DMA support via vNTB Koichiro Den
` (2 preceding siblings ...)
2026-08-28 17:09 ` [PATCH v2 3/5] PCI: endpoint: pci-epf-vntb: Move epf_ntb_is_bar_used() up Koichiro Den
@ 2026-08-28 17:09 ` Koichiro Den
2026-08-28 17:24 ` sashiko-bot
2026-08-28 17:09 ` [PATCH v2 5/5] NTB: ntb_hw_epf: Discover vNTB-embedded DMA Koichiro Den
4 siblings, 1 reply; 14+ messages in thread
From: Koichiro Den @ 2026-08-28 17:09 UTC (permalink / raw)
To: Manivannan Sadhasivam, Krzysztof Wilczyński,
Kishon Vijay Abraham I, Frank Li, Jon Mason, Dave Jiang,
Allen Hubbe, Niklas Cassel
Cc: Bjorn Helgaas, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Jingoo Han, Lorenzo Pieralisi, Rob Herring, Jerome Brunet,
linux-pci, linux-doc, linux-kernel, ntb
An RC may use endpoint-local DMA read channels to transfer data directly
to an endpoint DMA address once both sides agree to use them. Unrolled
eDMA has direction-wide control, so reserve the complete read direction
and route its interrupts to the RC when use_dma is enabled.
Describe the controller and per-channel descriptor memory in a private
control-region extension.
Two configfs attributes 'use_dma' and 'dma_bar' (corresponding to
BAR_DMA) are added. The DMA export remains disabled unless 'use_dma' is
explicitly set to 1.
Keep resources already assigned to a BAR in place, and map the rest
through 'dma_bar'. It uses an unused BAR by default, but can instead
follow an existing MW range. Control and doorbell BARs cannot be shared.
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
Changes in v2:
- Completely rework v1 patches 8-10: drop the EPC delegation hooks and
generic pci-ep-dma helper, and keep the export within pci-epf-vntb.
https://lore.kernel.org/r/20260312165005.1148676-1-den@valinux.co.jp/
- Rework PCI DMA EPF v7 patch 9 into this implementation.
https://lore.kernel.org/r/20260813063757.3131865-10-den@valinux.co.jp/
- Keep BAR-backed channel descriptors in place instead of remapping them
through dma_bar.
- Reserve channels by static IDs and configure their interrupt routing
through dma_slave_config. (Frank)
https://lore.kernel.org/r/ao2nHoCwfTEEiFSr@SMW015318/
Documentation/PCI/endpoint/pci-vntb-howto.rst | 23 +-
drivers/pci/endpoint/functions/pci-epf-vntb.c | 660 +++++++++++++++++-
2 files changed, 660 insertions(+), 23 deletions(-)
diff --git a/Documentation/PCI/endpoint/pci-vntb-howto.rst b/Documentation/PCI/endpoint/pci-vntb-howto.rst
index 3679f5c30254..90e0cdea9a1d 100644
--- a/Documentation/PCI/endpoint/pci-vntb-howto.rst
+++ b/Documentation/PCI/endpoint/pci-vntb-howto.rst
@@ -90,9 +90,9 @@ of the function device and is populated with the following NTB specific
attributes that can be configured by the user::
# ls functions/pci_epf_vntb/func1/pci_epf_vntb.0/
- ctrl_bar db_count mw1_bar mw2_bar mw3_bar mw4_bar spad_count
- db_bar mw1 mw2 mw3 mw4 num_mws vbus_number
- vntb_vid vntb_pid
+ ctrl_bar dma_bar mw2 mw3_bar num_mws vbus_number
+ db_bar mw1 mw2_bar mw4 spad_count vntb_pid
+ db_count mw1_bar mw3 mw4_bar use_dma vntb_vid
A sample configuration for NTB function is given below::
@@ -105,6 +105,23 @@ By default, each construct is assigned a BAR, as needed and in order.
Should a specific BAR setup be required by the platform, BAR may be assigned
to each construct using the related ``XYZ_bar`` entry.
+To export DMA channels, enable the feature before binding the function::
+
+ # echo 1 > functions/pci_epf_vntb/func1/pci_epf_vntb.0/use_dma
+
+This currently supports the unrolled DesignWare eDMA layout. If any DMA
+resource has no fixed BAR assignment, the EPC must support subrange and
+dynamic inbound mappings.
+
+When ``use_dma`` is enabled, ``dma_bar`` selects the BAR used for such DMA
+resources. It defaults to an unused BAR.
+With a separate DMA BAR, an older ``ntb_hw_epf`` peer can ignore the extension
+and continue using legacy NTB. A peer that understands the extension treats the
+advertised DMA type as required and fails probe if its support is unavailable.
+Setting ``dma_bar`` to an MW BAR appends the DMA resources after the MW range.
+This requires an updated peer because older peers treat the whole BAR as an MW.
+The control and doorbell BARs cannot be shared.
+
A sample configuration for virtual NTB driver for virtual PCI bus::
# echo 0x1957 > functions/pci_epf_vntb/func1/pci_epf_vntb.0/vntb_vid
diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c
index d12d134ce553..142799f64fe1 100644
--- a/drivers/pci/endpoint/functions/pci-epf-vntb.c
+++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c
@@ -39,8 +39,12 @@
#include <linux/atomic.h>
#include <linux/bitops.h>
#include <linux/delay.h>
+#include <linux/dma/edma.h>
+#include <linux/dmaengine.h>
#include <linux/io.h>
#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/overflow.h>
#include <linux/slab.h>
#include <linux/pci-ep-msi.h>
@@ -56,6 +60,8 @@ static struct workqueue_struct *kpcintb_workqueue;
#define COMMAND_TEARDOWN_MW 4
#define COMMAND_LINK_UP 5
#define COMMAND_LINK_DOWN 6
+#define COMMAND_CONFIGURE_DMA 7
+#define COMMAND_TEARDOWN_DMA 8
#define COMMAND_STATUS_OK 1
#define COMMAND_STATUS_ERROR 2
@@ -69,6 +75,10 @@ static struct workqueue_struct *kpcintb_workqueue;
#define MSIX_ENABLE BIT(16)
#define MAX_MW 4
+#define EPF_NTB_DMA_MAGIC 0x414d444e /* "NDMA": NTB DMA */
+#define EPF_NTB_DMA_REVISION 1
+#define EPF_NTB_DMA_TYPE_DW_EDMA 1
+
/* Limit per-work execution to avoid monopolizing kworker on doorbell storms. */
#define VNTB_PEER_DB_WORK_BUDGET 5
@@ -79,6 +89,7 @@ enum epf_ntb_bar {
BAR_MW2,
BAR_MW3,
BAR_MW4,
+ BAR_DMA,
VNTB_BAR_NUM,
};
@@ -91,6 +102,30 @@ enum epf_irq_slot {
#define MIN_DB_COUNT (EPF_IRQ_DB_START + 1)
#define MAX_DB_COUNT 32
+/* Private wire extension consumed by ntb_hw_epf. */
+struct epf_ntb_dma_region_ctrl {
+ u32 bar;
+ u32 offset;
+ u32 size;
+} __packed;
+
+struct epf_ntb_dma_chan_ctrl {
+ struct epf_ntb_dma_region_ctrl desc;
+ u32 desc_addr_lo;
+ u32 desc_addr_hi;
+} __packed;
+
+struct epf_ntb_dma_ctrl {
+ u32 magic;
+ u16 revision;
+ u16 length;
+ u32 type;
+ /* BAR range occupied by resources without a fixed BAR assignment. */
+ struct epf_ntb_dma_region_ctrl submap;
+ struct epf_ntb_dma_region_ctrl reg;
+ struct epf_ntb_dma_chan_ctrl chan[EDMA_MAX_RD_CH];
+} __packed;
+
/*
* +--------------------------------------------------+ Base
* | |
@@ -129,8 +164,19 @@ struct epf_ntb_ctrl {
u32 db_entry_size;
u32 db_data[MAX_DB_COUNT];
u32 db_offset[MAX_DB_COUNT];
+ struct epf_ntb_dma_ctrl dma;
} __packed;
+struct epf_ntb_dma {
+ struct mutex lock; /* Serialize DMA/MW BAR updates */
+ struct epf_ntb_dma_ctrl ctrl;
+ struct dma_chan *dchan[EDMA_MAX_RD_CH];
+ struct pci_epf_bar_submap submap[EDMA_MAX_RD_CH + 3];
+ struct pci_epf_bar_submap *reg_submap;
+ unsigned int num_submap;
+ u16 rd_ch_cnt;
+};
+
struct epf_ntb {
struct ntb_dev ntb;
struct pci_epf *epf;
@@ -148,6 +194,7 @@ struct epf_ntb {
u16 vntb_vid;
bool linkup;
+ bool use_dma;
/*
* True when doorbells are interrupt-driven (MSI or embedded), false
@@ -159,6 +206,7 @@ struct epf_ntb {
enum pci_barno epf_ntb_bar[VNTB_BAR_NUM];
struct epf_ntb_ctrl *reg;
+ struct epf_ntb_dma *dma;
u32 *epf_db;
@@ -211,7 +259,8 @@ static bool epf_ntb_is_bar_used(struct epf_ntb *ntb,
{
int i;
- for (i = 0; i < VNTB_BAR_NUM; i++) {
+ /* BAR_DMA is optional and may share an MW BAR. */
+ for (i = 0; i < BAR_DMA; i++) {
if (ntb->epf_ntb_bar[i] == barno)
return true;
}
@@ -219,6 +268,445 @@ static bool epf_ntb_is_bar_used(struct epf_ntb *ntb,
return false;
}
+static u64 epf_ntb_dma_bar_offset(struct epf_ntb *ntb,
+ enum pci_barno barno)
+{
+ unsigned int i;
+
+ for (i = 0; i < ntb->num_mws; i++)
+ if (ntb->epf_ntb_bar[BAR_MW1 + i] == barno)
+ return ntb->mws_size[i];
+
+ return 0;
+}
+
+static bool epf_ntb_dma_shares_bar(struct epf_ntb *ntb,
+ enum pci_barno barno)
+{
+ return ntb->dma && ntb->dma->num_submap &&
+ ntb->epf_ntb_bar[BAR_DMA] == barno;
+}
+
+static int
+epf_ntb_dma_resolve_bar(struct epf_ntb *ntb,
+ const struct pci_epc_features *features)
+{
+ enum pci_barno barno;
+
+ if (ntb->epf_ntb_bar[BAR_DMA] != NO_BAR) {
+ barno = ntb->epf_ntb_bar[BAR_DMA];
+ if (barno == ntb->epf_ntb_bar[BAR_CONFIG] ||
+ barno == ntb->epf_ntb_bar[BAR_DB])
+ return -EINVAL;
+ if (epf_ntb_dma_bar_offset(ntb, barno))
+ return 0;
+ if (epf_ntb_is_bar_used(ntb, barno) ||
+ pci_epc_get_next_free_bar(features, barno) != barno)
+ return -EINVAL;
+ return 0;
+ }
+
+ barno = BAR_0;
+ while ((barno = pci_epc_get_next_free_bar(features, barno)) != NO_BAR) {
+ if (!epf_ntb_is_bar_used(ntb, barno)) {
+ ntb->epf_ntb_bar[BAR_DMA] = barno;
+ return 0;
+ }
+ barno++;
+ }
+
+ return -ENOENT;
+}
+
+struct epf_ntb_dma_filter {
+ struct device *dev;
+ int chan_id;
+};
+
+static bool epf_ntb_dma_filter(struct dma_chan *chan, void *data)
+{
+ struct epf_ntb_dma_filter *filter = data;
+
+ return chan->device->dev == filter->dev &&
+ chan->chan_id == filter->chan_id;
+}
+
+static int epf_ntb_dma_add_region(struct epf_ntb_dma *dma,
+ const struct pci_epc_aux_resource *resource,
+ dma_addr_t target_addr,
+ enum pci_barno barno, size_t align, u32 *next,
+ struct epf_ntb_dma_region_ctrl *region,
+ struct pci_epf_bar_submap **submap_out)
+{
+ struct pci_epf_bar_submap *submap;
+ resource_size_t delta, map_size, size;
+ dma_addr_t base;
+
+ if (!resource->size || resource->size > U32_MAX)
+ return -EINVAL;
+
+ region->size = resource->size;
+ if (resource->bar != NO_BAR) {
+ if (resource->bar < BAR_0 || resource->bar > BAR_5 ||
+ resource->bar_offset > U32_MAX)
+ return -EINVAL;
+
+ region->bar = resource->bar;
+ region->offset = resource->bar_offset;
+ return 0;
+ }
+ submap = &dma->submap[dma->num_submap++];
+
+ base = ALIGN_DOWN(target_addr, align);
+ delta = target_addr - base;
+ if (check_add_overflow(delta, resource->size, &size))
+ return -EOVERFLOW;
+ map_size = ALIGN(size, align);
+ if (map_size < size)
+ return -EOVERFLOW;
+ if (map_size > U32_MAX - *next)
+ return -EOVERFLOW;
+
+ submap->phys_addr = base;
+ submap->size = map_size;
+ region->bar = barno;
+ region->offset = *next + delta;
+ *next += map_size;
+ if (submap_out)
+ *submap_out = submap;
+
+ return 0;
+}
+
+/* DW eDMA */
+
+static int epf_ntb_dw_edma_config_irq_mode(struct dma_chan *chan, enum dw_edma_ch_irq_mode mode)
+{
+ struct dma_slave_config config = {
+ .peripheral_config = &mode,
+ .peripheral_size = sizeof(mode),
+ };
+
+ return dmaengine_slave_config(chan, &config);
+}
+
+static int epf_ntb_dw_edma_claim(struct device *dev, int chan_id,
+ struct dma_chan **dchan)
+{
+ struct epf_ntb_dma_filter filter = {
+ .dev = dev,
+ .chan_id = chan_id,
+ };
+ dma_cap_mask_t mask;
+ struct dma_chan *chan;
+ int ret;
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ chan = dma_request_channel(mask, epf_ntb_dma_filter, &filter);
+ if (!chan)
+ return -EBUSY;
+
+ ret = epf_ntb_dw_edma_config_irq_mode(chan, DW_EDMA_CH_IRQ_REMOTE);
+ if (ret) {
+ dma_release_channel(chan);
+ return ret;
+ }
+
+ *dchan = chan;
+
+ return 0;
+}
+
+static void epf_ntb_dw_edma_release_channels(struct epf_ntb *ntb,
+ struct epf_ntb_dma *dma,
+ bool quiesce)
+{
+ unsigned int i;
+ int ret;
+
+ if (quiesce) {
+ /*
+ * RC programming has stopped and this EPF owns the complete read
+ * direction, so one termination quiesces the direction.
+ */
+ ret = dmaengine_terminate_sync(dma->dchan[0]);
+ if (ret)
+ dev_warn(&ntb->epf->dev,
+ "failed to terminate remote DMA: %d\n", ret);
+ }
+
+ for (i = 0; i < dma->rd_ch_cnt; i++) {
+ if (!dma->dchan[i])
+ continue;
+
+ dma_release_channel(dma->dchan[i]);
+ }
+}
+
+static const struct pci_epc_aux_resource *
+epf_ntb_dw_edma_find_desc(const struct pci_epc_aux_resource *resources,
+ unsigned int count, u16 chan_id)
+{
+ unsigned int i;
+
+ for (i = 0; i < count; i++)
+ if (resources[i].type == PCI_EPC_AUX_DMA_DESC_MEM &&
+ resources[i].u.dma_desc.chan_id == chan_id)
+ return &resources[i];
+
+ return NULL;
+}
+
+static int
+epf_ntb_dw_edma_collect(struct epf_ntb *ntb,
+ const struct pci_epc_aux_resource *ctrl,
+ const struct pci_epc_aux_resource *resources,
+ unsigned int count)
+{
+ const struct pci_epc_features *features;
+ const struct pci_epc_aux_resource *desc[EDMA_MAX_RD_CH];
+ struct device *dma_dev;
+ bool needs_submap;
+ u64 offset;
+ unsigned int i;
+ size_t align;
+ u32 next = 0;
+ int ret;
+
+ if (ctrl->u.dma_ctrl.reg_layout_data != EDMA_MF_EDMA_UNROLL)
+ return -EOPNOTSUPP;
+ if (ctrl->u.dma_ctrl.ep_to_rc_ch_cnt > EDMA_MAX_WR_CH ||
+ !ctrl->u.dma_ctrl.rc_to_ep_ch_cnt ||
+ ctrl->u.dma_ctrl.rc_to_ep_ch_cnt > EDMA_MAX_RD_CH)
+ return -EINVAL;
+
+ struct epf_ntb_dma *dma __free(kfree) =
+ kzalloc(sizeof(*dma), GFP_KERNEL);
+ if (!dma)
+ return -ENOMEM;
+
+ mutex_init(&dma->lock);
+ dma->rd_ch_cnt = ctrl->u.dma_ctrl.rc_to_ep_ch_cnt;
+
+ features = pci_epc_get_features(ntb->epf->epc, ntb->epf->func_no,
+ ntb->epf->vfunc_no);
+ if (!features)
+ return -EOPNOTSUPP;
+
+ align = features->align ?: 1;
+ if (!is_power_of_2(align))
+ return -EINVAL;
+
+ needs_submap = ctrl->bar == NO_BAR;
+ for (i = 0; i < dma->rd_ch_cnt; i++) {
+ u16 chan_id = ctrl->u.dma_ctrl.ep_to_rc_ch_cnt + i;
+
+ desc[i] = epf_ntb_dw_edma_find_desc(resources, count, chan_id);
+ if (!desc[i])
+ return -EINVAL;
+ needs_submap |= desc[i]->bar == NO_BAR;
+ }
+ if (needs_submap) {
+ if (!features->subrange_mapping ||
+ !features->dynamic_inbound_mapping)
+ return -EOPNOTSUPP;
+ ret = epf_ntb_dma_resolve_bar(ntb, features);
+ if (ret)
+ return ret;
+
+ offset = epf_ntb_dma_bar_offset(ntb,
+ ntb->epf_ntb_bar[BAR_DMA]);
+ if (offset > U32_MAX)
+ return -EOVERFLOW;
+ dma->ctrl.submap.offset = offset;
+ next = offset;
+ if (next) {
+ dma->submap[0].size = next;
+ dma->num_submap = 1;
+ }
+ }
+
+ dma->ctrl.magic = EPF_NTB_DMA_MAGIC;
+ dma->ctrl.revision = EPF_NTB_DMA_REVISION;
+ dma->ctrl.type = EPF_NTB_DMA_TYPE_DW_EDMA;
+ dma->ctrl.submap.bar = U32_MAX;
+ dma->ctrl.length = offsetof(struct epf_ntb_dma_ctrl,
+ chan[dma->rd_ch_cnt]);
+
+ ret = epf_ntb_dma_add_region(dma, ctrl, ctrl->phys_addr,
+ ntb->epf_ntb_bar[BAR_DMA],
+ align, &next, &dma->ctrl.reg,
+ &dma->reg_submap);
+ if (ret)
+ return ret;
+ for (i = 0; i < dma->rd_ch_cnt; i++) {
+ struct epf_ntb_dma_chan_ctrl *chan = &dma->ctrl.chan[i];
+ dma_addr_t dma_addr = desc[i]->u.dma_desc.dma_addr;
+
+ ret = epf_ntb_dma_add_region(dma, desc[i], dma_addr,
+ ntb->epf_ntb_bar[BAR_DMA],
+ align, &next,
+ &chan->desc, NULL);
+ if (ret)
+ return ret;
+ chan->desc_addr_lo = lower_32_bits(dma_addr);
+ chan->desc_addr_hi = upper_32_bits(dma_addr);
+ }
+ if (dma->num_submap) {
+ dma->ctrl.submap.bar = ntb->epf_ntb_bar[BAR_DMA];
+ dma->ctrl.submap.size = next - dma->ctrl.submap.offset;
+ }
+
+ dma_dev = ntb->epf->epc->dev.parent;
+ for (i = 0; i < dma->rd_ch_cnt; i++) {
+ u16 chan_id = ctrl->u.dma_ctrl.ep_to_rc_ch_cnt + i;
+
+ ret = epf_ntb_dw_edma_claim(dma_dev, chan_id, &dma->dchan[i]);
+ if (ret)
+ goto err_release;
+ }
+
+ if (dma->reg_submap) {
+ dma->reg_submap->phys_addr =
+ dma_map_resource(dma_dev, dma->reg_submap->phys_addr,
+ dma->reg_submap->size,
+ DMA_BIDIRECTIONAL, 0);
+ if (dma_mapping_error(dma_dev, dma->reg_submap->phys_addr)) {
+ ret = -EIO;
+ goto err_release;
+ }
+ }
+
+ ntb->dma = no_free_ptr(dma);
+ return 0;
+
+err_release:
+ epf_ntb_dw_edma_release_channels(ntb, dma, false);
+ return ret;
+}
+
+/* Common endpoint DMA */
+
+static int epf_ntb_dma_collect(struct epf_ntb *ntb)
+{
+ const struct pci_epc_aux_resource *ctrl = NULL;
+ unsigned int i;
+ int count, ret;
+
+ if (!ntb->use_dma)
+ return 0;
+
+ count = pci_epc_get_aux_resources_count(ntb->epf->epc,
+ ntb->epf->func_no,
+ ntb->epf->vfunc_no);
+ if (count <= 0)
+ return count ?: -ENODEV;
+
+ struct pci_epc_aux_resource *resources __free(kfree) =
+ kcalloc(count, sizeof(*resources), GFP_KERNEL);
+ if (!resources)
+ return -ENOMEM;
+
+ ret = pci_epc_get_aux_resources(ntb->epf->epc, ntb->epf->func_no,
+ ntb->epf->vfunc_no, resources, count);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < count; i++) {
+ if (resources[i].type != PCI_EPC_AUX_DMA_CTRL_MMIO)
+ continue;
+ if (ctrl)
+ return -EINVAL;
+ ctrl = &resources[i];
+ }
+ if (!ctrl)
+ return -ENODEV;
+
+ switch (ctrl->u.dma_ctrl.reg_layout) {
+ case PCI_EPC_AUX_DMA_REG_LAYOUT_DW_EDMA:
+ return epf_ntb_dw_edma_collect(ntb, ctrl, resources, count);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static void epf_ntb_dma_release(struct epf_ntb *ntb, bool quiesce)
+{
+ struct epf_ntb_dma *dma = ntb->dma;
+ struct device *dev;
+
+ if (!dma)
+ return;
+
+ switch (dma->ctrl.type) {
+ case EPF_NTB_DMA_TYPE_DW_EDMA:
+ epf_ntb_dw_edma_release_channels(ntb, dma, quiesce);
+ break;
+ }
+ dev = ntb->epf->epc->dev.parent;
+ if (dma->reg_submap)
+ dma_unmap_resource(dev, dma->reg_submap->phys_addr,
+ dma->reg_submap->size, DMA_BIDIRECTIONAL, 0);
+ kfree(dma);
+ ntb->dma = NULL;
+}
+
+static int epf_ntb_dma_set_bar_locked(struct epf_ntb *ntb, dma_addr_t addr,
+ bool submapped)
+{
+ struct epf_ntb_dma *dma = ntb->dma;
+ struct pci_epf_bar *bar;
+ dma_addr_t old_addr;
+ bool old_submapped;
+ int restore, ret;
+
+ lockdep_assert_held(&dma->lock);
+
+ bar = &ntb->epf->bar[ntb->epf_ntb_bar[BAR_DMA]];
+ old_submapped = bar->num_submap;
+ if (dma->ctrl.submap.offset) {
+ old_addr = dma->submap[0].phys_addr;
+ dma->submap[0].phys_addr = addr;
+ }
+ bar->submap = submapped ? dma->submap : NULL;
+ bar->num_submap = submapped ? dma->num_submap : 0;
+
+ ret = pci_epc_set_bar(ntb->epf->epc, ntb->epf->func_no,
+ ntb->epf->vfunc_no, bar);
+ if (!ret)
+ return 0;
+
+ if (dma->ctrl.submap.offset)
+ dma->submap[0].phys_addr = old_addr;
+ bar->submap = old_submapped ? dma->submap : NULL;
+ bar->num_submap = old_submapped ? dma->num_submap : 0;
+ restore = pci_epc_set_bar(ntb->epf->epc, ntb->epf->func_no,
+ ntb->epf->vfunc_no, bar);
+ if (restore)
+ dev_warn(&ntb->epf->dev,
+ "failed to restore DMA/MW BAR mapping: %d\n", restore);
+
+ return ret;
+}
+
+static int epf_ntb_dma_set_active(struct epf_ntb *ntb, bool active)
+{
+ struct epf_ntb_dma *dma = ntb->dma;
+ struct pci_epf_bar *bar;
+
+ if (!dma || !dma->num_submap)
+ return 0;
+
+ guard(mutex)(&dma->lock);
+
+ bar = &ntb->epf->bar[ntb->epf_ntb_bar[BAR_DMA]];
+ if (!!bar->num_submap == active)
+ return 0;
+
+ return epf_ntb_dma_set_bar_locked(ntb, bar->phys_addr, active);
+}
+
/**
* epf_ntb_configure_mw() - Configure the Outbound Address Space for VHOST
* to access the memory window of HOST
@@ -339,6 +827,14 @@ static void epf_ntb_cmd_handler(struct work_struct *work)
epf_ntb_teardown_mw(ntb, argument);
ctrl->command_status = COMMAND_STATUS_OK;
break;
+ case COMMAND_CONFIGURE_DMA:
+ ret = epf_ntb_dma_set_active(ntb, true);
+ ctrl->command_status = ret ? COMMAND_STATUS_ERROR : COMMAND_STATUS_OK;
+ break;
+ case COMMAND_TEARDOWN_DMA:
+ ret = epf_ntb_dma_set_active(ntb, false);
+ ctrl->command_status = ret ? COMMAND_STATUS_ERROR : COMMAND_STATUS_OK;
+ break;
case COMMAND_LINK_UP:
ntb->linkup = true;
ret = epf_ntb_link_up(ntb, true);
@@ -459,9 +955,8 @@ static void epf_ntb_config_spad_bar_free(struct epf_ntb *ntb)
* region
* @ntb: NTB device that facilitates communication between HOST and VHOST
*
- * Allocate the Local Memory mentioned in the above diagram. The size of
- * CONFIG REGION is sizeof(struct epf_ntb_ctrl) and size of SCRATCHPAD REGION
- * is obtained from "spad-count" configfs entry.
+ * Allocate the control and scratchpad regions, omitting the optional DMA
+ * extension when no channels are exported.
*
* Returns: Zero for success, or an error code in case of failure
*/
@@ -481,7 +976,9 @@ static int epf_ntb_config_spad_bar_alloc(struct epf_ntb *ntb)
barno = ntb->epf_ntb_bar[BAR_CONFIG];
spad_count = ntb->spad_count;
- ctrl_size = ALIGN(sizeof(struct epf_ntb_ctrl), sizeof(u32));
+ ctrl_size = ntb->dma ? sizeof(struct epf_ntb_ctrl) :
+ offsetof(struct epf_ntb_ctrl, dma);
+ ctrl_size = ALIGN(ctrl_size, sizeof(u32));
spad_size = 2 * spad_count * sizeof(u32);
base = pci_epf_alloc_space(epf, ctrl_size + spad_size,
@@ -507,6 +1004,9 @@ static int epf_ntb_config_spad_bar_alloc(struct epf_ntb *ntb)
ntb->reg->db_offset[i] = 0;
}
+ if (ntb->dma)
+ ctrl->dma = ntb->dma->ctrl;
+
return 0;
}
@@ -738,6 +1238,46 @@ static int epf_ntb_db_bar_init(struct epf_ntb *ntb)
static void epf_ntb_mw_bar_clear(struct epf_ntb *ntb, int num_mws);
+static int epf_ntb_dma_bar_init(struct epf_ntb *ntb)
+{
+ const struct pci_epc_features *features;
+ struct epf_ntb_dma *dma = ntb->dma;
+ struct pci_epf_bar *bar;
+ enum pci_barno barno;
+ u32 mapped_size;
+ int ret;
+
+ features = pci_epc_get_features(ntb->epf->epc, ntb->epf->func_no,
+ ntb->epf->vfunc_no);
+ if (!features)
+ return -EOPNOTSUPP;
+
+ barno = ntb->epf_ntb_bar[BAR_DMA];
+ mapped_size = dma->ctrl.submap.offset + dma->ctrl.submap.size;
+ if (!pci_epf_alloc_space(ntb->epf, mapped_size, barno, features,
+ PRIMARY_INTERFACE))
+ return -ENOMEM;
+
+ bar = &ntb->epf->bar[barno];
+ if (bar->size > U32_MAX)
+ return -EOVERFLOW;
+
+ if (dma->ctrl.submap.offset)
+ dma->submap[0].phys_addr = bar->phys_addr;
+ if (mapped_size < bar->size)
+ dma->submap[dma->num_submap++] = (struct pci_epf_bar_submap) {
+ .phys_addr = bar->phys_addr + mapped_size,
+ .size = bar->size - mapped_size,
+ };
+
+ ret = pci_epc_set_bar(ntb->epf->epc, ntb->epf->func_no,
+ ntb->epf->vfunc_no, bar);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
/**
* epf_ntb_db_bar_clear() - Clear doorbell BAR and free memory
* allocated in peer's outbound address space
@@ -776,16 +1316,27 @@ static void epf_ntb_db_bar_clear(struct epf_ntb *ntb)
*/
static int epf_ntb_mw_bar_init(struct epf_ntb *ntb)
{
+ struct device *dev = &ntb->epf->dev;
+ bool dma_bar_set = false;
+ enum pci_barno barno;
+ u64 size;
int ret = 0;
int i;
- u64 size;
- enum pci_barno barno;
- struct device *dev = &ntb->epf->dev;
for (i = 0; i < ntb->num_mws; i++) {
size = ntb->mws_size[i];
barno = ntb->epf_ntb_bar[BAR_MW1 + i];
+ if (epf_ntb_dma_shares_bar(ntb, barno)) {
+ ret = epf_ntb_dma_bar_init(ntb);
+ if (ret) {
+ dev_err(dev, "DMA/MW BAR set failed\n");
+ goto err_alloc_mem;
+ }
+ dma_bar_set = true;
+ goto alloc_vpci_mw;
+ }
+
ntb->epf->bar[barno].barno = barno;
ntb->epf->bar[barno].size = size;
ntb->epf->bar[barno].addr = NULL;
@@ -803,6 +1354,7 @@ static int epf_ntb_mw_bar_init(struct epf_ntb *ntb)
goto err_alloc_mem;
}
+alloc_vpci_mw:
/* Allocate EPC outbound memory windows to vpci vntb device */
ntb->vpci_mw_addr[i] = pci_epc_mem_alloc_addr(ntb->epf->epc,
&ntb->vpci_mw_phy[i],
@@ -810,17 +1362,20 @@ static int epf_ntb_mw_bar_init(struct epf_ntb *ntb)
if (!ntb->vpci_mw_addr[i]) {
ret = -ENOMEM;
dev_err(dev, "Failed to allocate source address\n");
- goto err_set_bar;
+ i++;
+ goto err_alloc_mem;
+ }
+ }
+ if (ntb->dma && ntb->dma->num_submap && !dma_bar_set) {
+ ret = epf_ntb_dma_bar_init(ntb);
+ if (ret) {
+ dev_err(dev, "DMA BAR set failed\n");
+ goto err_alloc_mem;
}
}
- return ret;
+ return 0;
-err_set_bar:
- pci_epc_clear_bar(ntb->epf->epc,
- ntb->epf->func_no,
- ntb->epf->vfunc_no,
- &ntb->epf->bar[barno]);
err_alloc_mem:
epf_ntb_mw_bar_clear(ntb, i);
return ret;
@@ -833,20 +1388,43 @@ static int epf_ntb_mw_bar_init(struct epf_ntb *ntb)
*/
static void epf_ntb_mw_bar_clear(struct epf_ntb *ntb, int num_mws)
{
+ bool bar_cleared[BAR_5 + 1] = {};
enum pci_barno barno;
int i;
for (i = 0; i < num_mws; i++) {
barno = ntb->epf_ntb_bar[BAR_MW1 + i];
- pci_epc_clear_bar(ntb->epf->epc,
- ntb->epf->func_no,
- ntb->epf->vfunc_no,
- &ntb->epf->bar[barno]);
+ if (!bar_cleared[barno]) {
+ pci_epc_clear_bar(ntb->epf->epc,
+ ntb->epf->func_no,
+ ntb->epf->vfunc_no,
+ &ntb->epf->bar[barno]);
+ bar_cleared[barno] = true;
+ }
+
+ if (!ntb->vpci_mw_addr[i])
+ continue;
pci_epc_mem_free_addr(ntb->epf->epc,
ntb->vpci_mw_phy[i],
ntb->vpci_mw_addr[i],
ntb->mws_size[i]);
+ ntb->vpci_mw_addr[i] = NULL;
+ }
+
+ if (ntb->dma && ntb->dma->num_submap) {
+ barno = ntb->epf_ntb_bar[BAR_DMA];
+ if (!ntb->epf->bar[barno].addr)
+ return;
+
+ ntb->epf->bar[barno].submap = NULL;
+ ntb->epf->bar[barno].num_submap = 0;
+ if (!bar_cleared[barno])
+ pci_epc_clear_bar(ntb->epf->epc, ntb->epf->func_no,
+ ntb->epf->vfunc_no,
+ &ntb->epf->bar[barno]);
+ pci_epf_free_space(ntb->epf, ntb->epf->bar[barno].addr, barno,
+ PRIMARY_INTERFACE);
}
}
@@ -877,7 +1455,8 @@ static int epf_ntb_find_bar(struct epf_ntb *ntb,
* Verify if the BAR found is not already assigned
* through the provided configuration
*/
- if (!epf_ntb_is_bar_used(ntb, barno))
+ if ((!ntb->use_dma || ntb->epf_ntb_bar[BAR_DMA] != barno) &&
+ !epf_ntb_is_bar_used(ntb, barno))
ntb->epf_ntb_bar[bar] = barno;
barno += 1;
@@ -1191,10 +1770,28 @@ static ssize_t epf_ntb_db_count_store(struct config_item *item,
return len;
}
+static ssize_t epf_ntb_use_dma_store(struct config_item *item,
+ const char *page, size_t len)
+{
+ struct config_group *group = to_config_group(item);
+ struct epf_ntb *ntb = to_epf_ntb(group);
+ int ret;
+
+ if (epf_ntb_epc_attached(ntb))
+ return -EOPNOTSUPP;
+
+ ret = kstrtobool(page, &ntb->use_dma);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
EPF_NTB_R(spad_count)
EPF_NTB_W(spad_count)
EPF_NTB_R(db_count)
EPF_NTB_R(num_mws)
+EPF_NTB_R(use_dma)
EPF_NTB_R(vbus_number)
EPF_NTB_W(vbus_number)
EPF_NTB_R(vntb_pid)
@@ -1221,10 +1818,14 @@ EPF_NTB_BAR_R(mw3_bar, BAR_MW3)
EPF_NTB_BAR_W(mw3_bar, BAR_MW3)
EPF_NTB_BAR_R(mw4_bar, BAR_MW4)
EPF_NTB_BAR_W(mw4_bar, BAR_MW4)
+EPF_NTB_BAR_R(dma_bar, BAR_DMA)
+EPF_NTB_BAR_W(dma_bar, BAR_DMA)
CONFIGFS_ATTR(epf_ntb_, spad_count);
CONFIGFS_ATTR(epf_ntb_, db_count);
CONFIGFS_ATTR(epf_ntb_, num_mws);
+CONFIGFS_ATTR(epf_ntb_, use_dma);
+CONFIGFS_ATTR(epf_ntb_, dma_bar);
CONFIGFS_ATTR(epf_ntb_, mw1);
CONFIGFS_ATTR(epf_ntb_, mw2);
CONFIGFS_ATTR(epf_ntb_, mw3);
@@ -1243,6 +1844,8 @@ static struct configfs_attribute *epf_ntb_attrs[] = {
&epf_ntb_attr_spad_count,
&epf_ntb_attr_db_count,
&epf_ntb_attr_num_mws,
+ &epf_ntb_attr_use_dma,
+ &epf_ntb_attr_dma_bar,
&epf_ntb_attr_mw1,
&epf_ntb_attr_mw2,
&epf_ntb_attr_mw3,
@@ -1423,6 +2026,15 @@ static int vntb_epf_mw_set_trans(struct ntb_dev *ndev, int pidx, int idx,
dev = &ntb->ntb.dev;
barno = ntb->epf_ntb_bar[BAR_MW1 + idx];
epf_bar = &ntb->epf->bar[barno];
+ if (epf_ntb_dma_shares_bar(ntb, barno)) {
+ if (size != ntb->mws_size[idx])
+ return -EINVAL;
+
+ guard(mutex)(&ntb->dma->lock);
+
+ return epf_ntb_dma_set_bar_locked(ntb, addr, true);
+ }
+
epf_bar->phys_addr = addr;
epf_bar->barno = barno;
epf_bar->size = size;
@@ -1743,6 +2355,12 @@ static int epf_ntb_bind(struct pci_epf *epf)
return ret;
}
+ ret = epf_ntb_dma_collect(ntb);
+ if (ret) {
+ dev_err(dev, "Failed to prepare NTB DMA export\n");
+ return ret;
+ }
+
ret = epf_ntb_config_spad_bar_alloc(ntb);
if (ret) {
dev_err(dev, "Failed to allocate BAR memory\n");
@@ -1779,6 +2397,7 @@ static int epf_ntb_bind(struct pci_epf *epf)
epf_ntb_epc_cleanup(ntb);
err_bar_alloc:
epf_ntb_config_spad_bar_free(ntb);
+ epf_ntb_dma_release(ntb, false);
return ret;
}
@@ -1795,6 +2414,7 @@ static void epf_ntb_unbind(struct pci_epf *epf)
epf_ntb_epc_cleanup(ntb);
epf_ntb_config_spad_bar_free(ntb);
+ epf_ntb_dma_release(ntb, true);
pci_unregister_driver(&vntb_pci_driver);
}
--
2.51.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 5/5] NTB: ntb_hw_epf: Discover vNTB-embedded DMA
2026-08-28 17:09 [PATCH v2 0/5] PCI: endpoint: Remote DMA support via vNTB Koichiro Den
` (3 preceding siblings ...)
2026-08-28 17:09 ` [PATCH v2 4/5] PCI: endpoint: pci-epf-vntb: Export endpoint DMA channels Koichiro Den
@ 2026-08-28 17:09 ` Koichiro Den
2026-08-28 17:24 ` sashiko-bot
4 siblings, 1 reply; 14+ messages in thread
From: Koichiro Den @ 2026-08-28 17:09 UTC (permalink / raw)
To: Manivannan Sadhasivam, Krzysztof Wilczyński,
Kishon Vijay Abraham I, Frank Li, Jon Mason, Dave Jiang,
Allen Hubbe, Niklas Cassel
Cc: Bjorn Helgaas, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Jingoo Han, Lorenzo Pieralisi, Rob Herring, Jerome Brunet,
linux-pci, linux-doc, linux-kernel, ntb
A vNTB PCI function can now expose endpoint-local DMA channels to its
host. Decode the extension and register the advertised channels before
the NTB device, so clients of that NTB device can use the DMA
functionality in the same PCI function.
Add the host-side discovery and setup to ntb_hw_epf. Endpoints without
the extension keep the existing path.
Only DW eDMA is supported for now. Dispatch on the advertised DMA type
and keep type-specific setup in local helpers. Support for another
implementation can add a new case and its own helpers.
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
Changes in v2:
- Completely rework v1 patches 11-13: drop the generic DMA locator and
auxiliary child, and keep discovery and registration in ntb_hw_epf.
https://lore.kernel.org/r/20260312165005.1148676-1-den@valinux.co.jp/
- Rework host-side discovery from PCI DMA EPF v7 patch 8 into
ntb_hw_epf.
https://lore.kernel.org/r/20260813063757.3131865-9-den@valinux.co.jp/
- Place DMA interrupt vectors after the NTB doorbell vectors.
drivers/ntb/hw/epf/ntb_hw_epf.c | 360 ++++++++++++++++++++++++++++++--
1 file changed, 348 insertions(+), 12 deletions(-)
diff --git a/drivers/ntb/hw/epf/ntb_hw_epf.c b/drivers/ntb/hw/epf/ntb_hw_epf.c
index c47607d4f8a7..cae1ad4834c7 100644
--- a/drivers/ntb/hw/epf/ntb_hw_epf.c
+++ b/drivers/ntb/hw/epf/ntb_hw_epf.c
@@ -8,6 +8,7 @@
#include <linux/atomic.h>
#include <linux/delay.h>
+#include <linux/dma/edma.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>
@@ -20,6 +21,8 @@
#define CMD_TEARDOWN_MW 4
#define CMD_LINK_UP 5
#define CMD_LINK_DOWN 6
+#define CMD_CONFIGURE_DMA 7
+#define CMD_TEARDOWN_DMA 8
#define NTB_EPF_ARGUMENT 0x4
#define MSIX_ENABLE BIT(16)
@@ -44,6 +47,32 @@
#define NTB_EPF_DB_DATA(n) (0x34 + (n) * 4)
#define NTB_EPF_DB_OFFSET(n) (0xB4 + (n) * 4)
+/* Private DMA wire extension produced by pci-epf-vntb. */
+#define NTB_EPF_DMA_BASE 0x134
+#define NTB_EPF_DMA_MAGIC (NTB_EPF_DMA_BASE + 0x00)
+#define NTB_EPF_DMA_REV_LEN (NTB_EPF_DMA_BASE + 0x04)
+#define NTB_EPF_DMA_TYPE (NTB_EPF_DMA_BASE + 0x08)
+#define NTB_EPF_DMA_REGION_BAR(base) ((base) + 0x00)
+#define NTB_EPF_DMA_REGION_OFFSET(base) ((base) + 0x04)
+#define NTB_EPF_DMA_REGION_SIZE(base) ((base) + 0x08)
+#define NTB_EPF_DMA_REGION_SIZEOF 0x0C
+#define NTB_EPF_DMA_SUBMAP_BASE (NTB_EPF_DMA_BASE + 0x0C)
+#define NTB_EPF_DMA_COMMON_SIZE 0x18
+#define NTB_EPF_DMA_REG_BASE (NTB_EPF_DMA_BASE + NTB_EPF_DMA_COMMON_SIZE)
+#define NTB_EPF_DMA_CHAN_BASE (NTB_EPF_DMA_REG_BASE + \
+ NTB_EPF_DMA_REGION_SIZEOF)
+#define NTB_EPF_DMA_CHAN_SIZE 0x14
+#define NTB_EPF_DMA_CHAN_DESC_BASE(n) (NTB_EPF_DMA_CHAN_BASE + \
+ (n) * NTB_EPF_DMA_CHAN_SIZE + 0x00)
+#define NTB_EPF_DMA_CHAN_DESC_ADDR_LO(n) (NTB_EPF_DMA_CHAN_BASE + \
+ (n) * NTB_EPF_DMA_CHAN_SIZE + 0x0C)
+#define NTB_EPF_DMA_CHAN_DESC_ADDR_HI(n) (NTB_EPF_DMA_CHAN_BASE + \
+ (n) * NTB_EPF_DMA_CHAN_SIZE + 0x10)
+
+#define NTB_EPF_DMA_MAGIC_VALUE 0x414d444e /* "NDMA": NTB DMA */
+#define NTB_EPF_DMA_REVISION 1
+#define NTB_EPF_DMA_TYPE_DW_EDMA 1
+
/*
* Legacy doorbell slot layout when paired with pci-epf-*ntb:
*
@@ -97,6 +126,25 @@ struct ntb_epf_irq_ctx {
unsigned int irq_no;
};
+struct ntb_epf_dma_region {
+ unsigned int bar;
+ resource_size_t offset;
+ resource_size_t size;
+ void __iomem *vaddr;
+};
+
+struct ntb_epf_dw_edma {
+ struct dw_edma_chip chip;
+ struct ntb_epf_dma_region reg;
+ struct ntb_epf_dma_region ll[EDMA_MAX_RD_CH];
+};
+
+struct ntb_epf_dma {
+ struct ntb_epf_dma_region submap;
+ unsigned int nr_irqs;
+ u32 type;
+};
+
struct ntb_epf_dev {
struct ntb_dev ntb;
struct device *dev;
@@ -108,6 +156,8 @@ struct ntb_epf_dev {
unsigned int mw_count;
unsigned int spad_count;
unsigned int db_count;
+ struct ntb_epf_dma dma;
+ struct ntb_epf_dw_edma dw_edma;
void __iomem *ctrl_reg;
void __iomem *db_reg;
@@ -162,6 +212,244 @@ static int ntb_epf_send_command(struct ntb_epf_dev *ndev, u32 command,
return ret;
}
+static bool ntb_epf_dma_region_parse(struct ntb_epf_dev *ndev, u32 base,
+ bool optional,
+ struct ntb_epf_dma_region *region)
+{
+ resource_size_t bar_len;
+ u32 bar, offset, size;
+
+ bar = readl(ndev->ctrl_reg + NTB_EPF_DMA_REGION_BAR(base));
+ offset = readl(ndev->ctrl_reg + NTB_EPF_DMA_REGION_OFFSET(base));
+ size = readl(ndev->ctrl_reg + NTB_EPF_DMA_REGION_SIZE(base));
+ region->bar = bar;
+ region->offset = offset;
+ region->size = size;
+ if (!size)
+ return optional && bar == U32_MAX && !offset;
+ if (bar > BAR_5)
+ return false;
+
+ bar_len = pci_resource_len(ndev->ntb.pdev, bar);
+ if (offset > bar_len || size > bar_len - offset)
+ return false;
+
+ return true;
+}
+
+/* DW eDMA */
+
+static int ntb_epf_dw_edma_parse(struct ntb_epf_dev *ndev, u32 length)
+{
+ struct ntb_epf_dw_edma *edma = &ndev->dw_edma;
+ struct dw_edma_chip *chip = &edma->chip;
+ u32 count, chan_offset;
+ unsigned int i;
+
+ chan_offset = NTB_EPF_DMA_CHAN_BASE - NTB_EPF_DMA_BASE;
+ if (length < chan_offset + NTB_EPF_DMA_CHAN_SIZE ||
+ (length - chan_offset) % NTB_EPF_DMA_CHAN_SIZE)
+ return -EINVAL;
+
+ count = (length - chan_offset) / NTB_EPF_DMA_CHAN_SIZE;
+ if (count > EDMA_MAX_RD_CH)
+ return -EINVAL;
+
+ if (!ntb_epf_dma_region_parse(ndev, NTB_EPF_DMA_REG_BASE, false,
+ &edma->reg))
+ return -EINVAL;
+
+ chip->mf = EDMA_MF_EDMA_UNROLL;
+ chip->nr_irqs = count;
+ chip->ll_rd_cnt = count;
+ chip->func_no = PCI_FUNC(ndev->ntb.pdev->devfn);
+
+ for (i = 0; i < count; i++) {
+ u64 paddr;
+
+ if (!ntb_epf_dma_region_parse(ndev,
+ NTB_EPF_DMA_CHAN_DESC_BASE(i),
+ false, &edma->ll[i]))
+ return -EINVAL;
+
+ paddr = readl(ndev->ctrl_reg +
+ NTB_EPF_DMA_CHAN_DESC_ADDR_LO(i));
+ paddr |= (u64)readl(ndev->ctrl_reg +
+ NTB_EPF_DMA_CHAN_DESC_ADDR_HI(i)) << 32;
+ if (paddr == U64_MAX)
+ return -EINVAL;
+
+ chip->ll_region_rd[i].sz = edma->ll[i].size;
+ chip->ll_region_rd[i].paddr = paddr;
+ }
+ ndev->dma.nr_irqs = count;
+
+ return 0;
+}
+
+static int ntb_epf_dw_edma_irq_vector(struct device *dev, unsigned int nr)
+{
+ struct ntb_epf_dev *ndev = dev_get_drvdata(dev);
+
+ if (nr >= ndev->dma.nr_irqs)
+ return -EINVAL;
+
+ return pci_irq_vector(ndev->ntb.pdev, ndev->db_count + 1 + nr);
+}
+
+static const struct dw_edma_plat_ops ntb_epf_dw_edma_ops = {
+ .irq_vector = ntb_epf_dw_edma_irq_vector,
+};
+
+static int ntb_epf_dw_edma_map_region(struct pci_dev *pdev,
+ struct ntb_epf_dma_region *region)
+{
+ region->vaddr = pci_iomap_range(pdev, region->bar, region->offset,
+ region->size);
+ return region->vaddr ? 0 : -ENOMEM;
+}
+
+static void ntb_epf_dw_edma_unmap_regions(struct ntb_epf_dev *ndev)
+{
+ struct ntb_epf_dw_edma *edma = &ndev->dw_edma;
+ struct pci_dev *pdev = ndev->ntb.pdev;
+ unsigned int i;
+
+ for (i = 0; i < edma->chip.ll_rd_cnt; i++) {
+ if (!edma->ll[i].vaddr)
+ continue;
+ pci_iounmap(pdev, edma->ll[i].vaddr);
+ }
+ if (edma->reg.vaddr)
+ pci_iounmap(pdev, edma->reg.vaddr);
+}
+
+static int ntb_epf_dw_edma_init(struct ntb_epf_dev *ndev)
+{
+ struct ntb_epf_dw_edma *edma = &ndev->dw_edma;
+ struct dw_edma_chip *chip = &edma->chip;
+ struct pci_dev *pdev = ndev->ntb.pdev;
+ unsigned int i;
+ int ret;
+
+ ret = ntb_epf_send_command(ndev, CMD_CONFIGURE_DMA, 0);
+ if (ret)
+ return ret;
+
+ ret = ntb_epf_dw_edma_map_region(pdev, &edma->reg);
+ if (ret)
+ goto err_teardown;
+ for (i = 0; i < chip->ll_rd_cnt; i++) {
+ ret = ntb_epf_dw_edma_map_region(pdev, &edma->ll[i]);
+ if (ret)
+ goto err_iounmap;
+ }
+
+ chip->dev = ndev->dev;
+ chip->ops = &ntb_epf_dw_edma_ops;
+ chip->flags = DW_EDMA_CHIP_PARTIAL;
+ chip->reg_base = edma->reg.vaddr;
+ for (i = 0; i < chip->ll_rd_cnt; i++)
+ chip->ll_region_rd[i].vaddr.io = edma->ll[i].vaddr;
+
+ ret = dw_edma_probe(chip);
+ if (ret)
+ goto err_iounmap;
+
+ return 0;
+
+err_iounmap:
+ ntb_epf_dw_edma_unmap_regions(ndev);
+err_teardown:
+ ntb_epf_send_command(ndev, CMD_TEARDOWN_DMA, 0);
+ return ret;
+}
+
+static void ntb_epf_dw_edma_deinit(struct ntb_epf_dev *ndev)
+{
+ struct ntb_epf_dw_edma *edma = &ndev->dw_edma;
+ int ret;
+
+ dw_edma_remove(&edma->chip);
+ ntb_epf_dw_edma_unmap_regions(ndev);
+
+ ret = ntb_epf_send_command(ndev, CMD_TEARDOWN_DMA, 0);
+ if (ret)
+ dev_warn(ndev->dev, "Failed to teardown endpoint DMA\n");
+}
+
+/* Common endpoint DMA */
+
+static int ntb_epf_dma_parse(struct ntb_epf_dev *ndev)
+{
+ u32 magic, rev_len, length, type;
+ u32 spad_off;
+ int ret;
+
+ spad_off = readl(ndev->ctrl_reg + NTB_EPF_SPAD_OFFSET);
+ if (spad_off < NTB_EPF_DMA_BASE + NTB_EPF_DMA_COMMON_SIZE)
+ return 0;
+
+ magic = readl(ndev->ctrl_reg + NTB_EPF_DMA_MAGIC);
+ if (!magic)
+ return 0;
+ if (magic == U32_MAX)
+ return -EIO;
+ if (magic != NTB_EPF_DMA_MAGIC_VALUE)
+ return -EINVAL;
+
+ rev_len = readl(ndev->ctrl_reg + NTB_EPF_DMA_REV_LEN);
+ length = upper_16_bits(rev_len);
+ if (lower_16_bits(rev_len) != NTB_EPF_DMA_REVISION ||
+ length < NTB_EPF_DMA_COMMON_SIZE ||
+ spad_off < NTB_EPF_DMA_BASE + length)
+ return -EINVAL;
+ if (!ntb_epf_dma_region_parse(ndev, NTB_EPF_DMA_SUBMAP_BASE, true,
+ &ndev->dma.submap))
+ return -EINVAL;
+
+ type = readl(ndev->ctrl_reg + NTB_EPF_DMA_TYPE);
+ if (type == U32_MAX)
+ return -EIO;
+
+ switch (type) {
+ case NTB_EPF_DMA_TYPE_DW_EDMA:
+ ret = ntb_epf_dw_edma_parse(ndev, length);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ if (!ret)
+ ndev->dma.type = type;
+
+ return ret;
+}
+
+static int ntb_epf_dma_init(struct ntb_epf_dev *ndev)
+{
+ switch (ndev->dma.type) {
+ case 0:
+ return 0;
+ case NTB_EPF_DMA_TYPE_DW_EDMA:
+ return ntb_epf_dw_edma_init(ndev);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static void ntb_epf_dma_deinit(struct ntb_epf_dev *ndev)
+{
+ if (!ndev->dma.type)
+ return;
+
+ switch (ndev->dma.type) {
+ case NTB_EPF_DMA_TYPE_DW_EDMA:
+ ntb_epf_dw_edma_deinit(ndev);
+ break;
+ }
+}
+
static int ntb_epf_mw_to_bar(struct ntb_epf_dev *ndev, int idx)
{
struct device *dev = ndev->dev;
@@ -174,6 +462,24 @@ static int ntb_epf_mw_to_bar(struct ntb_epf_dev *ndev, int idx)
return ndev->barno_map[BAR_MW1 + idx];
}
+static resource_size_t ntb_epf_mw_offset(struct ntb_epf_dev *ndev, int idx)
+{
+ return !idx ? readl(ndev->ctrl_reg + NTB_EPF_MW1_OFFSET) : 0;
+}
+
+static resource_size_t ntb_epf_mw_size(struct ntb_epf_dev *ndev, int idx,
+ int bar)
+{
+ resource_size_t offset, end;
+
+ offset = ntb_epf_mw_offset(ndev, idx);
+ end = pci_resource_len(ndev->ntb.pdev, bar);
+ if (ndev->dma.submap.size && ndev->dma.submap.bar == bar)
+ end = min(end, ndev->dma.submap.offset);
+
+ return offset < end ? end - offset : 0;
+}
+
static int ntb_epf_mw_count(struct ntb_dev *ntb, int pidx)
{
struct ntb_epf_dev *ndev = ntb_ndev(ntb);
@@ -212,7 +518,7 @@ static int ntb_epf_mw_get_align(struct ntb_dev *ntb, int pidx, int idx,
*size_align = 1;
if (size_max)
- *size_max = pci_resource_len(ndev->ntb.pdev, bar);
+ *size_max = ntb_epf_mw_size(ndev, idx, bar);
return 0;
}
@@ -373,15 +679,19 @@ static int ntb_epf_init_isr(struct ntb_epf_dev *ndev, int msi_min, int msi_max)
{
struct pci_dev *pdev = ndev->ntb.pdev;
struct device *dev = ndev->dev;
+ unsigned int dma_irqs = ndev->dma.nr_irqs;
+ unsigned int ntb_irqs;
u32 argument = MSIX_ENABLE;
int irq;
int ret;
int i;
- irq = pci_alloc_irq_vectors(pdev, msi_min, msi_max, PCI_IRQ_MSIX);
+ irq = pci_alloc_irq_vectors(pdev, msi_min + dma_irqs,
+ msi_max + dma_irqs, PCI_IRQ_MSIX);
if (irq < 0) {
dev_dbg(dev, "Failed to get MSIX interrupts\n");
- irq = pci_alloc_irq_vectors(pdev, msi_min, msi_max,
+ irq = pci_alloc_irq_vectors(pdev, msi_min + dma_irqs,
+ msi_max + dma_irqs,
PCI_IRQ_MSI);
if (irq < 0) {
dev_err(dev, "Failed to get MSI interrupts\n");
@@ -390,8 +700,9 @@ static int ntb_epf_init_isr(struct ntb_epf_dev *ndev, int msi_min, int msi_max)
argument &= ~MSIX_ENABLE;
}
- ndev->db_count = irq - 1;
- for (i = 0; i < irq; i++) {
+ ntb_irqs = irq - dma_irqs;
+ ndev->db_count = ntb_irqs - 1;
+ for (i = 0; i < ntb_irqs; i++) {
ndev->irq_ctx[i].ndev = ndev;
ndev->irq_ctx[i].irq_no = i;
ret = request_irq(pci_irq_vector(pdev, i), ntb_epf_vec_isr,
@@ -403,7 +714,7 @@ static int ntb_epf_init_isr(struct ntb_epf_dev *ndev, int msi_min, int msi_max)
}
ret = ntb_epf_send_command(ndev, CMD_CONFIGURE_DOORBELL,
- argument | irq);
+ argument | ntb_irqs);
if (ret) {
dev_err(dev, "Failed to configure doorbell\n");
goto err_free_irq;
@@ -486,7 +797,7 @@ static int ntb_epf_mw_set_trans(struct ntb_dev *ntb, int pidx, int idx,
if (bar < 0)
return bar;
- mw_size = pci_resource_len(ntb->pdev, bar);
+ mw_size = ntb_epf_mw_size(ndev, idx, bar);
if (size > mw_size) {
dev_err(dev, "Size:%pa is greater than the MW size %pa\n",
@@ -520,21 +831,20 @@ static int ntb_epf_peer_mw_get_addr(struct ntb_dev *ntb, int idx,
phys_addr_t *base, resource_size_t *size)
{
struct ntb_epf_dev *ndev = ntb_ndev(ntb);
- u32 offset = 0;
+ resource_size_t offset;
int bar;
- if (idx == 0)
- offset = readl(ndev->ctrl_reg + NTB_EPF_MW1_OFFSET);
-
bar = ntb_epf_mw_to_bar(ndev, idx);
if (bar < 0)
return bar;
+ offset = ntb_epf_mw_offset(ndev, idx);
+
if (base)
*base = pci_resource_start(ndev->ntb.pdev, bar) + offset;
if (size)
- *size = pci_resource_len(ndev->ntb.pdev, bar) - offset;
+ *size = ntb_epf_mw_size(ndev, idx, bar);
return 0;
}
@@ -630,12 +940,29 @@ static int ntb_epf_init_dev(struct ntb_epf_dev *ndev)
{
struct device *dev = ndev->dev;
int ret;
+ int i;
ndev->mw_count = readl(ndev->ctrl_reg + NTB_EPF_MW_COUNT);
if (ndev->mw_count > NTB_EPF_MAX_MW_COUNT) {
dev_err(dev, "Unsupported MW count: %u\n", ndev->mw_count);
return -EINVAL;
}
+ ret = ntb_epf_dma_parse(ndev);
+ if (ret) {
+ dev_err(dev, "Invalid endpoint DMA layout\n");
+ return ret;
+ }
+ if (ndev->dma.submap.size) {
+ for (i = 0; i < ndev->mw_count; i++) {
+ int bar = ntb_epf_mw_to_bar(ndev, i);
+
+ if (bar == ndev->dma.submap.bar &&
+ !ntb_epf_mw_size(ndev, i, bar)) {
+ dev_err(dev, "Invalid DMA/MW boundary\n");
+ return -EINVAL;
+ }
+ }
+ }
/* One Link interrupt and rest doorbell interrupt */
ret = ntb_epf_init_isr(ndev, NTB_EPF_MIN_DB_COUNT + 1,
@@ -785,6 +1112,12 @@ static int ntb_epf_pci_probe(struct pci_dev *pdev,
goto err_init_dev;
}
+ ret = ntb_epf_dma_init(ndev);
+ if (ret) {
+ dev_err(dev, "Failed to initialize endpoint DMA\n");
+ goto err_dma_init;
+ }
+
ret = ntb_register_device(&ndev->ntb);
if (ret) {
dev_err(dev, "Failed to register NTB device\n");
@@ -794,6 +1127,8 @@ static int ntb_epf_pci_probe(struct pci_dev *pdev,
return 0;
err_register_dev:
+ ntb_epf_dma_deinit(ndev);
+err_dma_init:
ntb_epf_cleanup_isr(ndev);
err_init_dev:
@@ -807,6 +1142,7 @@ static void ntb_epf_pci_remove(struct pci_dev *pdev)
struct ntb_epf_dev *ndev = pci_get_drvdata(pdev);
ntb_unregister_device(&ndev->ntb);
+ ntb_epf_dma_deinit(ndev);
ntb_epf_cleanup_isr(ndev);
ntb_epf_deinit_pci(ndev);
}
--
2.51.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v2 3/5] PCI: endpoint: pci-epf-vntb: Move epf_ntb_is_bar_used() up
2026-08-28 17:09 ` [PATCH v2 3/5] PCI: endpoint: pci-epf-vntb: Move epf_ntb_is_bar_used() up Koichiro Den
@ 2026-08-28 17:14 ` sashiko-bot
2026-08-28 18:59 ` Frank Li
1 sibling, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-08-28 17:14 UTC (permalink / raw)
To: Koichiro Den; +Cc: ntb, linux-pci
> Move epf_ntb_is_bar_used() up so later BAR selection code can call it
> without a forward declaration. No functional change.
>
> Signed-off-by: Koichiro Den <den@valinux.co.jp>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260828170932.2735807-1-den@valinux.co.jp?part=3
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 1/5] PCI: endpoint: Add DMA auxiliary resource metadata
2026-08-28 17:09 ` [PATCH v2 1/5] PCI: endpoint: Add DMA auxiliary resource metadata Koichiro Den
@ 2026-08-28 17:15 ` sashiko-bot
2026-08-28 18:48 ` Frank Li
1 sibling, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-08-28 17:15 UTC (permalink / raw)
To: Koichiro Den; +Cc: linux-pci, ntb
> Endpoint functions that export controller-local DMA channels need the
> controller register and descriptor-memory layout.
>
> Add DMA control and descriptor-memory resource types. The control resource
> carries the register layout and channel counts. Each descriptor region is
> associated with the static channel ID assigned by the local DMA driver.
>
> Signed-off-by: Koichiro Den <den@valinux.co.jp>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260828170932.2735807-1-den@valinux.co.jp?part=1
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 2/5] PCI: dwc: Expose endpoint DMA resources
2026-08-28 17:09 ` [PATCH v2 2/5] PCI: dwc: Expose endpoint DMA resources Koichiro Den
@ 2026-08-28 17:16 ` sashiko-bot
2026-08-28 18:59 ` Frank Li
1 sibling, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-08-28 17:16 UTC (permalink / raw)
To: Koichiro Den; +Cc: linux-pci, ntb
> Expose the endpoint-integrated eDMA register window and linked-list memory
> through the EPC auxiliary resource API. Endpoint functions can then choose
> which channels to export and map the required windows.
>
> Associate each linked-list region with the direction-flattened static
> channel ID used by the local dw-edma device. Report these resources only
> after that device is registered and only for channels with linked-list
> memory.
>
> When the register window is already part of a reserved BAR, report its BAR
> and offset. Otherwise report its CPU physical address.
>
> While at it, harden the existing doorbell resource query. Report it
> only after the local dw-edma device is registered, and reject VF
> queries because DWC cannot assign DMA/HDMA registers to VFs.
>
> Signed-off-by: Koichiro Den <den@valinux.co.jp>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260828170932.2735807-1-den@valinux.co.jp?part=2
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 5/5] NTB: ntb_hw_epf: Discover vNTB-embedded DMA
2026-08-28 17:09 ` [PATCH v2 5/5] NTB: ntb_hw_epf: Discover vNTB-embedded DMA Koichiro Den
@ 2026-08-28 17:24 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-08-28 17:24 UTC (permalink / raw)
To: Koichiro Den; +Cc: linux-pci, ntb
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Unaligned MMIO offset provided by untrusted device causes alignment fault and kernel panic.
- [Medium] Unsupported DMA extensions or insufficient IRQs cause the core NTB device probe to fail entirely, rather than gracefully falling back to a non-DMA mode.
- [Low] The macro or enum value DW_EDMA_CHIP_PARTIAL is not declared in the kernel tree.
--
commit 47ea035773d643574d3bec0ab584f66f7879214f
Author: Koichiro Den <den@valinux.co.jp>
NTB: ntb_hw_epf: Discover vNTB-embedded DMA
This commit allows a vNTB PCI function to expose endpoint-local DMA channels
to its host. It decodes the extension, registers advertised channels, and
dispatches on the advertised DMA type.
Link: https://lore.kernel.org/r/20260312165005.1148676-1-den@valinux.co.jp/
Link: https://lore.kernel.org/r/20260813063757.3131865-9-den@valinux.co.jp/
> diff --git a/drivers/ntb/hw/epf/ntb_hw_epf.c b/drivers/ntb/hw/epf/ntb_hw_epf.c
> --- a/drivers/ntb/hw/epf/ntb_hw_epf.c
> +++ b/drivers/ntb/hw/epf/ntb_hw_epf.c
[ ... ]
> +static bool ntb_epf_dma_region_parse(struct ntb_epf_dev *ndev, u32 base,
> + bool optional,
> + struct ntb_epf_dma_region *region)
> +{
> + resource_size_t bar_len;
> + u32 bar, offset, size;
> +
> + bar = readl(ndev->ctrl_reg + NTB_EPF_DMA_REGION_BAR(base));
> + offset = readl(ndev->ctrl_reg + NTB_EPF_DMA_REGION_OFFSET(base));
[Severity: High]
Is offset validated for alignment here? Since this is read directly from
the untrusted device configuration space, a malicious or buggy endpoint could
provide an unaligned offset.
[ ... ]
> +static int ntb_epf_dw_edma_map_region(struct pci_dev *pdev,
> + struct ntb_epf_dma_region *region)
> +{
> + region->vaddr = pci_iomap_range(pdev, region->bar, region->offset,
> + region->size);
[Severity: High]
If the region->offset provided by the endpoint is unaligned, does mapping
it via pci_iomap_range() yield an unaligned virtual address?
The dw_edma driver subsequently uses 32-bit accessors (readl) on this
mapping, which can cause an alignment fault (synchronous external abort) and
kernel panic.
[ ... ]
> +static int ntb_epf_dw_edma_init(struct ntb_epf_dev *ndev)
> +{
[ ... ]
> + chip->dev = ndev->dev;
> + chip->ops = &ntb_epf_dw_edma_ops;
> + chip->flags = DW_EDMA_CHIP_PARTIAL;
[Severity: Low]
Does this code build? It looks like DW_EDMA_CHIP_PARTIAL is not declared
in the kernel tree. This might be a leftover from an unmerged or modified
prerequisite patch series.
[ ... ]
> +static int ntb_epf_dma_parse(struct ntb_epf_dev *ndev)
> +{
[ ... ]
> + switch (type) {
> + case NTB_EPF_DMA_TYPE_DW_EDMA:
> + ret = ntb_epf_dw_edma_parse(ndev, length);
> + break;
> + default:
> + return -EOPNOTSUPP;
> + }
[Severity: Medium]
If the endpoint exposes a newer or unrecognized DMA type, this returns
-EOPNOTSUPP and causes the core NTB device probe to fail entirely.
Would it be better to gracefully fall back to a non-DMA mode so core NTB
functionality (doorbells, memory windows) remains available?
[ ... ]
> int i;
>
> - irq = pci_alloc_irq_vectors(pdev, msi_min, msi_max, PCI_IRQ_MSIX);
> + irq = pci_alloc_irq_vectors(pdev, msi_min + dma_irqs,
> + msi_max + dma_irqs, PCI_IRQ_MSIX);
[Severity: Medium]
This now strictly requires dma_irqs to be available. If a constrained host
platform lacks enough MSI/MSI-X vectors to satisfy the msi_min + dma_irqs
requirement, pci_alloc_irq_vectors() will fail.
Can we allow the initialization to proceed without DMA support in this
scenario instead of aborting the whole probe?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260828170932.2735807-1-den@valinux.co.jp?part=5
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 4/5] PCI: endpoint: pci-epf-vntb: Export endpoint DMA channels
2026-08-28 17:09 ` [PATCH v2 4/5] PCI: endpoint: pci-epf-vntb: Export endpoint DMA channels Koichiro Den
@ 2026-08-28 17:24 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-08-28 17:24 UTC (permalink / raw)
To: Koichiro Den; +Cc: ntb, linux-pci
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Massive coherent DMA memory waste/failure when BAR_DMA shares an MW BAR.
- [High] epf_ntb_dma_set_active() overwrites the Memory Window (MW) outbound translation address with local DMA memory, breaking peer-to-peer data transfers.
- [High] Heap out-of-bounds write and memory leak when multiple memory windows share the DMA BAR.
- [High] Premature release of `ntb->dma` in `epf_ntb_unbind` leads to a Use-After-Free and data race with active NTB clients.
- [Medium] Mixing goto-based error handling and scope-based resource management (__free) in the same function.
--
commit 4df8968cc2a35a760bfa7d8e252d1a4b9e0a04c2
Author: Koichiro Den <den@valinux.co.jp>
PCI: endpoint: pci-epf-vntb: Export endpoint DMA channels
This commit allows a Root Complex to use endpoint-local DMA read channels
by exporting them via a private control-region extension. It adds new
configfs attributes and maps DMA resources through an optional DMA BAR.
> diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c
> index d12d134ce5531..142799f64fe17 100644
> --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c
> +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c
[ ... ]
> @@ -219,6 +268,445 @@ static bool epf_ntb_is_bar_used(struct epf_ntb *ntb,
[ ... ]
> +static int
> +epf_ntb_dw_edma_collect(struct epf_ntb *ntb,
> + const struct pci_epc_aux_resource *ctrl,
> + const struct pci_epc_aux_resource *resources,
> + unsigned int count)
> +{
[ ... ]
> + if (ctrl->u.dma_ctrl.ep_to_rc_ch_cnt > EDMA_MAX_WR_CH ||
> + !ctrl->u.dma_ctrl.rc_to_ep_ch_cnt ||
> + ctrl->u.dma_ctrl.rc_to_ep_ch_cnt > EDMA_MAX_RD_CH)
> + return -EINVAL;
> +
> + struct epf_ntb_dma *dma __free(kfree) =
> + kzalloc(sizeof(*dma), GFP_KERNEL);
[Severity: Medium]
Is it permitted to mix scope-based cleanup (__free) with a manual goto label
in the same function here?
The kernel cleanup guidelines explicitly prohibit mixing goto with scope-based
cleanups in the same function, as it creates confusing ownership semantics and
can lead to double-free or resource leak bugs.
> + if (!dma)
> + return -ENOMEM;
[ ... ]
> + for (i = 0; i < dma->rd_ch_cnt; i++) {
> + u16 chan_id = ctrl->u.dma_ctrl.ep_to_rc_ch_cnt + i;
> +
> + ret = epf_ntb_dw_edma_claim(dma_dev, chan_id, &dma->dchan[i]);
> + if (ret)
> + goto err_release;
> + }
[ ... ]
> + ntb->dma = no_free_ptr(dma);
> + return 0;
> +
> +err_release:
> + epf_ntb_dw_edma_release_channels(ntb, dma, false);
> + return ret;
> +}
[ ... ]
> +static int epf_ntb_dma_set_bar_locked(struct epf_ntb *ntb, dma_addr_t addr,
> + bool submapped)
> +{
> + struct epf_ntb_dma *dma = ntb->dma;
> + struct pci_epf_bar *bar;
> + dma_addr_t old_addr;
> + bool old_submapped;
> + int restore, ret;
> +
> + lockdep_assert_held(&dma->lock);
> +
> + bar = &ntb->epf->bar[ntb->epf_ntb_bar[BAR_DMA]];
> + old_submapped = bar->num_submap;
> + if (dma->ctrl.submap.offset) {
> + old_addr = dma->submap[0].phys_addr;
> + dma->submap[0].phys_addr = addr;
[Severity: High]
When epf_ntb_dma_set_active() calls epf_ntb_dma_set_bar_locked(ntb,
bar->phys_addr, active) to toggle DMA, does this overwrite the peer's memory
window outbound translation address?
If a virtual ntb driver has previously set the translation address via
vntb_epf_mw_set_trans(), which writes to dma->submap[0].phys_addr, toggling
the DMA link state seems to permanently overwrite this with the local dummy
memory's physical address. This appears to silently break peer-to-peer data
transfers.
> + }
> + bar->submap = submapped ? dma->submap : NULL;
> + bar->num_submap = submapped ? dma->num_submap : 0;
[ ... ]
> @@ -738,6 +1238,46 @@ static int epf_ntb_db_bar_init(struct epf_ntb *ntb)
>
> static void epf_ntb_mw_bar_clear(struct epf_ntb *ntb, int num_mws);
>
> +static int epf_ntb_dma_bar_init(struct epf_ntb *ntb)
> +{
> + const struct pci_epc_features *features;
> + struct epf_ntb_dma *dma = ntb->dma;
> + struct pci_epf_bar *bar;
> + enum pci_barno barno;
> + u32 mapped_size;
> + int ret;
> +
> + features = pci_epc_get_features(ntb->epf->epc, ntb->epf->func_no,
> + ntb->epf->vfunc_no);
> + if (!features)
> + return -EOPNOTSUPP;
> +
> + barno = ntb->epf_ntb_bar[BAR_DMA];
> + mapped_size = dma->ctrl.submap.offset + dma->ctrl.submap.size;
> + if (!pci_epf_alloc_space(ntb->epf, mapped_size, barno, features,
> + PRIMARY_INTERFACE))
[Severity: High]
When the DMA BAR shares a memory window BAR, will this mapping attempt fail
or cause massive memory waste?
The mapped_size is calculated by adding the large memory window size (often
1GB or more) to the small DMA descriptor size. Then pci_epf_alloc_space()
tries to allocate this amount via dma_alloc_coherent(). Requesting gigabytes
of contiguous DMA coherent memory typically exceeds the page allocator limits
and fails.
[Severity: High]
If configfs allows multiple memory windows to map to the same BAR as dma_bar,
could calling this function repeatedly in a loop in epf_ntb_mw_bar_init()
cause a memory leak?
Since space is allocated for the same BAR without freeing prior allocations,
this appears to leak the previously allocated memory on each iteration.
> + return -ENOMEM;
> +
> + bar = &ntb->epf->bar[barno];
> + if (bar->size > U32_MAX)
> + return -EOVERFLOW;
> +
> + if (dma->ctrl.submap.offset)
> + dma->submap[0].phys_addr = bar->phys_addr;
> + if (mapped_size < bar->size)
> + dma->submap[dma->num_submap++] = (struct pci_epf_bar_submap) {
[Severity: High]
If epf_ntb_dma_bar_init() is called repeatedly for multiple memory windows,
can this overflow the dma->submap array?
If dma->num_submap exceeds the 11-element capacity, it creates a heap
out-of-bounds write that corrupts adjacent fields like reg_submap.
> + .phys_addr = bar->phys_addr + mapped_size,
> + .size = bar->size - mapped_size,
> + };
> +
> + ret = pci_epc_set_bar(ntb->epf->epc, ntb->epf->func_no,
> + ntb->epf->vfunc_no, bar);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
[ ... ]
> @@ -833,20 +1388,43 @@ static int epf_ntb_mw_bar_init(struct epf_ntb *ntb)
> */
> static void epf_ntb_mw_bar_clear(struct epf_ntb *ntb, int num_mws)
> {
[ ... ]
> + if (ntb->dma && ntb->dma->num_submap) {
> + barno = ntb->epf_ntb_bar[BAR_DMA];
> + if (!ntb->epf->bar[barno].addr)
> + return;
> +
> + ntb->epf->bar[barno].submap = NULL;
> + ntb->epf->bar[barno].num_submap = 0;
[Severity: High]
Is it safe to update the submap fields locklessly here?
This can race with concurrent accesses to ntb->dma, as ntb->dma->lock is not
acquired before modifying the submap and num_submap fields.
> + if (!bar_cleared[barno])
> + pci_epc_clear_bar(ntb->epf->epc, ntb->epf->func_no,
> + ntb->epf->vfunc_no,
> + &ntb->epf->bar[barno]);
> + pci_epf_free_space(ntb->epf, ntb->epf->bar[barno].addr, barno,
> + PRIMARY_INTERFACE);
> }
> }
[ ... ]
> @@ -1423,6 +2026,15 @@ static int vntb_epf_mw_set_trans(struct ntb_dev *ndev, int pidx, int idx,
> dev = &ntb->ntb.dev;
> barno = ntb->epf_ntb_bar[BAR_MW1 + idx];
> epf_bar = &ntb->epf->bar[barno];
> + if (epf_ntb_dma_shares_bar(ntb, barno)) {
> + if (size != ntb->mws_size[idx])
> + return -EINVAL;
> +
> + guard(mutex)(&ntb->dma->lock);
[Severity: High]
Can an active concurrent virtual ntb client trigger a use-after-free here
during unbind?
In epf_ntb_unbind(), epf_ntb_dma_release(ntb, true) frees ntb->dma before
pci_unregister_driver() stops the virtual ntb clients. An active client
calling vntb_epf_mw_set_trans() might evaluate epf_ntb_dma_shares_bar() as
true, get preempted, and then dereference the freed ntb->dma to acquire the
lock.
> +
> + return epf_ntb_dma_set_bar_locked(ntb, addr, true);
> + }
> +
> epf_bar->phys_addr = addr;
> epf_bar->barno = barno;
> epf_bar->size = size;
[ ... ]
> @@ -1795,6 +2414,7 @@ static void epf_ntb_unbind(struct pci_epf *epf)
>
> epf_ntb_epc_cleanup(ntb);
> epf_ntb_config_spad_bar_free(ntb);
> + epf_ntb_dma_release(ntb, true);
>
> pci_unregister_driver(&vntb_pci_driver);
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260828170932.2735807-1-den@valinux.co.jp?part=4
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 1/5] PCI: endpoint: Add DMA auxiliary resource metadata
2026-08-28 17:09 ` [PATCH v2 1/5] PCI: endpoint: Add DMA auxiliary resource metadata Koichiro Den
2026-08-28 17:15 ` sashiko-bot
@ 2026-08-28 18:48 ` Frank Li
1 sibling, 0 replies; 14+ messages in thread
From: Frank Li @ 2026-08-28 18:48 UTC (permalink / raw)
To: Koichiro Den
Cc: Manivannan Sadhasivam, Krzysztof Wilczyński,
Kishon Vijay Abraham I, Frank Li, Jon Mason, Dave Jiang,
Allen Hubbe, Niklas Cassel, Bjorn Helgaas, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Jingoo Han, Lorenzo Pieralisi,
Rob Herring, Jerome Brunet, linux-pci, linux-doc, linux-kernel,
ntb
On Sat, Aug 29, 2026 at 02:09:28AM +0900, Koichiro Den wrote:
> Endpoint functions that export controller-local DMA channels need the
> controller register and descriptor-memory layout.
>
> Add DMA control and descriptor-memory resource types. The control resource
> carries the register layout and channel counts. Each descriptor region is
> associated with the static channel ID assigned by the local DMA driver.
>
> Signed-off-by: Koichiro Den <den@valinux.co.jp>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> Changes in v2:
> - Rework PCI DMA EPF v7 patch 3 with only the metadata needed by vNTB.
> https://lore.kernel.org/r/20260813063757.3131865-4-den@valinux.co.jp/
> - Keep descriptor metadata keyed only by static channel IDs. (Frank)
> https://lore.kernel.org/r/lm7tadnxsyrypu4mypptlkx5qkytex4qxsijdr5ydud2n3anvf@yakjfjg5ng4u/
>
> include/linux/pci-epc.h | 34 +++++++++++++++++++++++++++++++---
> 1 file changed, 31 insertions(+), 3 deletions(-)
>
> diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h
> index f247cf9bcf1a..e0348e7bc534 100644
> --- a/include/linux/pci-epc.h
> +++ b/include/linux/pci-epc.h
> @@ -65,6 +65,8 @@ struct pci_epc_map {
> * enum pci_epc_aux_resource_type - auxiliary resource type identifiers
> * @PCI_EPC_AUX_DOORBELL_MMIO: Doorbell MMIO, that might be outside the DMA
> * controller register window
> + * @PCI_EPC_AUX_DMA_CTRL_MMIO: DMA controller MMIO register window
> + * @PCI_EPC_AUX_DMA_DESC_MEM: DMA descriptor memory
> *
> * EPC backends may expose auxiliary blocks (e.g. DMA engines) by mapping their
> * register windows and descriptor memories into BAR space. This enum
> @@ -72,13 +74,25 @@ struct pci_epc_map {
> */
> enum pci_epc_aux_resource_type {
> PCI_EPC_AUX_DOORBELL_MMIO,
> + PCI_EPC_AUX_DMA_CTRL_MMIO,
> + PCI_EPC_AUX_DMA_DESC_MEM,
> };
>
> /**
> - * struct pci_epc_aux_resource - a physical auxiliary resource that may be
> - * exposed for peer use
> + * enum pci_epc_aux_dma_reg_layout - DMA controller register layout
> + * @PCI_EPC_AUX_DMA_REG_LAYOUT_UNKNOWN: unknown or uninitialized layout
> + * @PCI_EPC_AUX_DMA_REG_LAYOUT_DW_EDMA: Synopsys DesignWare eDMA/HDMA layout
> + */
> +enum pci_epc_aux_dma_reg_layout {
> + PCI_EPC_AUX_DMA_REG_LAYOUT_UNKNOWN = 0,
> + PCI_EPC_AUX_DMA_REG_LAYOUT_DW_EDMA,
> +};
> +
> +/**
> + * struct pci_epc_aux_resource - an auxiliary resource that may be exposed for
> + * peer use
> * @type: resource type, see enum pci_epc_aux_resource_type
> - * @phys_addr: physical base address of the resource
> + * @phys_addr: CPU physical base address of an MMIO resource
> * @size: size of the resource in bytes
> * @bar: BAR number where this resource is already exposed to the RC
> * (NO_BAR if not)
> @@ -99,6 +113,20 @@ struct pci_epc_aux_resource {
> int irq; /* IRQ number for the doorbell handler */
> u32 data; /* write value to ring the doorbell */
> } db_mmio;
> +
> + /* PCI_EPC_AUX_DMA_CTRL_MMIO */
> + struct {
> + enum pci_epc_aux_dma_reg_layout reg_layout;
> + u32 reg_layout_data;
> + u16 ep_to_rc_ch_cnt;
> + u16 rc_to_ep_ch_cnt;
> + } dma_ctrl;
> +
> + /* PCI_EPC_AUX_DMA_DESC_MEM */
> + struct {
> + dma_addr_t dma_addr; /* Endpoint-local DMA address */
> + u16 chan_id;
> + } dma_desc;
> } u;
> };
>
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 2/5] PCI: dwc: Expose endpoint DMA resources
2026-08-28 17:09 ` [PATCH v2 2/5] PCI: dwc: Expose endpoint DMA resources Koichiro Den
2026-08-28 17:16 ` sashiko-bot
@ 2026-08-28 18:59 ` Frank Li
1 sibling, 0 replies; 14+ messages in thread
From: Frank Li @ 2026-08-28 18:59 UTC (permalink / raw)
To: Koichiro Den
Cc: Manivannan Sadhasivam, Krzysztof Wilczyński,
Kishon Vijay Abraham I, Frank Li, Jon Mason, Dave Jiang,
Allen Hubbe, Niklas Cassel, Bjorn Helgaas, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Jingoo Han, Lorenzo Pieralisi,
Rob Herring, Jerome Brunet, linux-pci, linux-doc, linux-kernel,
ntb
On Sat, Aug 29, 2026 at 02:09:29AM +0900, Koichiro Den wrote:
> Expose the endpoint-integrated eDMA register window and linked-list memory
> through the EPC auxiliary resource API. Endpoint functions can then choose
> which channels to export and map the required windows.
>
> Associate each linked-list region with the direction-flattened static
> channel ID used by the local dw-edma device. Report these resources only
> after that device is registered and only for channels with linked-list
> memory.
>
> When the register window is already part of a reserved BAR, report its BAR
> and offset. Otherwise report its CPU physical address.
>
> While at it, harden the existing doorbell resource query. Report it
> only after the local dw-edma device is registered, and reject VF
> queries because DWC cannot assign DMA/HDMA registers to VFs.
>
> Signed-off-by: Koichiro Den <den@valinux.co.jp>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> Changes in v2:
> - Rework PCI DMA EPF v7 patch 7 around static channel IDs and the
> reduced metadata.
> https://lore.kernel.org/r/20260813063757.3131865-8-den@valinux.co.jp/
> - Use cfg_non_ll instead of carrying over its LL-region scan.
> - Leave the peer-visible layout to pci-epf-vntb.
>
> .../pci/controller/dwc/pcie-designware-ep.c | 107 ++++++++++++++++--
> 1 file changed, 99 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c
> index de8ee3db4360..c5e07edb52b1 100644
> --- a/drivers/pci/controller/dwc/pcie-designware-ep.c
> +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c
> @@ -858,6 +858,15 @@ dw_pcie_ep_find_bar_rsvd_region(struct dw_pcie_ep *ep,
> return NULL;
> }
>
> +static int dw_pcie_ep_check_edma_vfunc(u8 vfunc_no)
> +{
> + /*
> + * The DWC endpoint databook says it is not possible to assign the
> + * DMA/HDMA registers to any Virtual Function.
> + */
> + return vfunc_no ? -EOPNOTSUPP : 0;
> +}
> +
> static int
> dw_pcie_ep_get_aux_resources_count(struct pci_epc *epc, u8 func_no,
> u8 vfunc_no)
> @@ -865,14 +874,30 @@ dw_pcie_ep_get_aux_resources_count(struct pci_epc *epc, u8 func_no,
> struct dw_pcie_ep *ep = epc_get_drvdata(epc);
> struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
> struct dw_edma_chip *edma = &pci->edma;
> + u16 ll_wr_cnt, ll_rd_cnt;
> + int count = 0;
> + int ret;
>
> if (!pci->edma_reg_size)
> return 0;
>
> - if (edma->db_offset == ~0)
> + ret = dw_pcie_ep_check_edma_vfunc(vfunc_no);
> + if (ret)
> + return ret;
> +
> + if (!edma->dw)
> return 0;
>
> - return 1;
> + ll_wr_cnt = edma->ll_wr_cnt;
> + ll_rd_cnt = edma->ll_rd_cnt;
> +
> + if (!edma->cfg_non_ll)
> + count += 1 + ll_wr_cnt + ll_rd_cnt;
> +
> + if (edma->db_offset != ~0)
> + count++;
> +
> + return count;
> }
>
> static int
> @@ -885,14 +910,32 @@ dw_pcie_ep_get_aux_resources(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
> const struct pci_epc_bar_rsvd_region *rsvd;
> struct dw_edma_chip *edma = &pci->edma;
> enum pci_barno dma_ctrl_bar = NO_BAR;
> - resource_size_t db_offset = edma->db_offset;
> + resource_size_t db_offset;
> resource_size_t dma_ctrl_bar_offset = 0;
> resource_size_t dma_reg_size;
> - int count;
> + u16 ll_wr_cnt, ll_rd_cnt;
> + bool has_ll;
> + unsigned int i;
> + int count, ret;
>
> - count = dw_pcie_ep_get_aux_resources_count(epc, func_no, vfunc_no);
> - if (count < 0)
> - return count;
> + if (!pci->edma_reg_size)
> + return 0;
> +
> + ret = dw_pcie_ep_check_edma_vfunc(vfunc_no);
> + if (ret)
> + return ret;
> +
> + if (!edma->dw)
> + return 0;
> +
> + ll_wr_cnt = edma->ll_wr_cnt;
> + ll_rd_cnt = edma->ll_rd_cnt;
> + db_offset = edma->db_offset;
> + has_ll = !edma->cfg_non_ll;
> +
> + count = db_offset != ~0;
> + if (has_ll)
> + count += 1 + ll_wr_cnt + ll_rd_cnt;
>
> if (num_resources < count)
> return -ENOSPC;
> @@ -909,6 +952,54 @@ dw_pcie_ep_get_aux_resources(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
> if (rsvd && rsvd->size < dma_reg_size)
> dma_reg_size = rsvd->size;
>
> + count = 0;
> + if (has_ll) {
> + resources[count++] = (struct pci_epc_aux_resource) {
> + .type = PCI_EPC_AUX_DMA_CTRL_MMIO,
> + .phys_addr = pci->edma_reg_phys,
> + .size = dma_reg_size,
> + .bar = dma_ctrl_bar,
> + .bar_offset = dma_ctrl_bar_offset,
> + .u.dma_ctrl = {
> + .reg_layout = PCI_EPC_AUX_DMA_REG_LAYOUT_DW_EDMA,
> + .reg_layout_data = edma->mf,
> + .ep_to_rc_ch_cnt = ll_wr_cnt,
> + .rc_to_ep_ch_cnt = ll_rd_cnt,
> + },
> + };
> +
> + for (i = 0; i < ll_wr_cnt; i++) {
> + struct dw_edma_region *ll = &edma->ll_region_wr[i];
> +
> + resources[count++] = (struct pci_epc_aux_resource) {
> + .type = PCI_EPC_AUX_DMA_DESC_MEM,
> + .size = ll->sz,
> + .bar = NO_BAR,
> + .u.dma_desc = {
> + .dma_addr = ll->paddr,
> + .chan_id = i,
> + },
> + };
> + }
> +
> + for (i = 0; i < ll_rd_cnt; i++) {
> + struct dw_edma_region *ll = &edma->ll_region_rd[i];
> +
> + resources[count++] = (struct pci_epc_aux_resource) {
> + .type = PCI_EPC_AUX_DMA_DESC_MEM,
> + .size = ll->sz,
> + .bar = NO_BAR,
> + .u.dma_desc = {
> + .dma_addr = ll->paddr,
> + .chan_id = ll_wr_cnt + i,
> + },
> + };
> + }
> + }
> +
> + if (db_offset == ~0)
> + return 0;
> +
> /*
> * For interrupt-emulation doorbells, report a standalone resource
> * instead of bundling it into the DMA controller MMIO resource.
> @@ -917,7 +1008,7 @@ dw_pcie_ep_get_aux_resources(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
> sizeof(u32), dma_reg_size))
> return -EINVAL;
>
> - resources[0] = (struct pci_epc_aux_resource) {
> + resources[count] = (struct pci_epc_aux_resource) {
> .type = PCI_EPC_AUX_DOORBELL_MMIO,
> .phys_addr = pci->edma_reg_phys + db_offset,
> .size = sizeof(u32),
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 3/5] PCI: endpoint: pci-epf-vntb: Move epf_ntb_is_bar_used() up
2026-08-28 17:09 ` [PATCH v2 3/5] PCI: endpoint: pci-epf-vntb: Move epf_ntb_is_bar_used() up Koichiro Den
2026-08-28 17:14 ` sashiko-bot
@ 2026-08-28 18:59 ` Frank Li
1 sibling, 0 replies; 14+ messages in thread
From: Frank Li @ 2026-08-28 18:59 UTC (permalink / raw)
To: Koichiro Den
Cc: Manivannan Sadhasivam, Krzysztof Wilczyński,
Kishon Vijay Abraham I, Frank Li, Jon Mason, Dave Jiang,
Allen Hubbe, Niklas Cassel, Bjorn Helgaas, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Jingoo Han, Lorenzo Pieralisi,
Rob Herring, Jerome Brunet, linux-pci, linux-doc, linux-kernel,
ntb
On Sat, Aug 29, 2026 at 02:09:30AM +0900, Koichiro Den wrote:
> Move epf_ntb_is_bar_used() up so later BAR selection code can call it
> without a forward declaration. No functional change.
>
> Signed-off-by: Koichiro Den <den@valinux.co.jp>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/pci/endpoint/functions/pci-epf-vntb.c | 40 +++++++++----------
> 1 file changed, 20 insertions(+), 20 deletions(-)
>
> diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c
> index c3caec927d74..d12d134ce553 100644
> --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c
> +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c
> @@ -199,6 +199,26 @@ static int epf_ntb_link_up(struct epf_ntb *ntb, bool link_up)
> return 0;
> }
>
> +/**
> + * epf_ntb_is_bar_used() - Check if a bar is used in the ntb configuration
> + * @ntb: NTB device that facilitates communication between HOST and VHOST
> + * @barno: Checked bar number
> + *
> + * Returns: true if used, false if free.
> + */
> +static bool epf_ntb_is_bar_used(struct epf_ntb *ntb,
> + enum pci_barno barno)
> +{
> + int i;
> +
> + for (i = 0; i < VNTB_BAR_NUM; i++) {
> + if (ntb->epf_ntb_bar[i] == barno)
> + return true;
> + }
> +
> + return false;
> +}
> +
> /**
> * epf_ntb_configure_mw() - Configure the Outbound Address Space for VHOST
> * to access the memory window of HOST
> @@ -830,26 +850,6 @@ static void epf_ntb_mw_bar_clear(struct epf_ntb *ntb, int num_mws)
> }
> }
>
> -/**
> - * epf_ntb_is_bar_used() - Check if a bar is used in the ntb configuration
> - * @ntb: NTB device that facilitates communication between HOST and VHOST
> - * @barno: Checked bar number
> - *
> - * Returns: true if used, false if free.
> - */
> -static bool epf_ntb_is_bar_used(struct epf_ntb *ntb,
> - enum pci_barno barno)
> -{
> - int i;
> -
> - for (i = 0; i < VNTB_BAR_NUM; i++) {
> - if (ntb->epf_ntb_bar[i] == barno)
> - return true;
> - }
> -
> - return false;
> -}
> -
> /**
> * epf_ntb_find_bar() - Assign BAR number when no configuration is provided
> * @ntb: NTB device that facilitates communication between HOST and VHOST
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-08-28 19:00 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-28 17:09 [PATCH v2 0/5] PCI: endpoint: Remote DMA support via vNTB Koichiro Den
2026-08-28 17:09 ` [PATCH v2 1/5] PCI: endpoint: Add DMA auxiliary resource metadata Koichiro Den
2026-08-28 17:15 ` sashiko-bot
2026-08-28 18:48 ` Frank Li
2026-08-28 17:09 ` [PATCH v2 2/5] PCI: dwc: Expose endpoint DMA resources Koichiro Den
2026-08-28 17:16 ` sashiko-bot
2026-08-28 18:59 ` Frank Li
2026-08-28 17:09 ` [PATCH v2 3/5] PCI: endpoint: pci-epf-vntb: Move epf_ntb_is_bar_used() up Koichiro Den
2026-08-28 17:14 ` sashiko-bot
2026-08-28 18:59 ` Frank Li
2026-08-28 17:09 ` [PATCH v2 4/5] PCI: endpoint: pci-epf-vntb: Export endpoint DMA channels Koichiro Den
2026-08-28 17:24 ` sashiko-bot
2026-08-28 17:09 ` [PATCH v2 5/5] NTB: ntb_hw_epf: Discover vNTB-embedded DMA Koichiro Den
2026-08-28 17:24 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox