[PATCH v7 06/10] PCI: endpoint: pci-epf-vntb: Allow DMA and MW to share a BAR

From: Koichiro Den

Date: Wed Oct 21 2026 - 00:25:56 EST


Some endpoint configurations have no spare BAR for DMA resources. Allow
dma_bar to select an MW BAR and place the DMA ranges after that MW.

DMA submaps have fixed BAR offsets, so a shared MW translation must
cover its configured size. Report that size as the translation alignment
and require it to be a power of two.

Older hosts treat the whole BAR as an MW. Require the host to advertise
MW/DMA boundary support in LINK_UP before enabling a shared-BAR link.

Signed-off-by: Koichiro Den <den@xxxxxxxxxxxxx>
---
Changes since v6:
- Split out the host-side changes. (Frank)
- Require MW/DMA BAR boundary support in LINK_UP for shared BARs.

drivers/pci/endpoint/functions/pci-epf-vntb.c | 154 ++++++++++++++----
include/linux/pci-epf-ntb.h | 3 +
2 files changed, 122 insertions(+), 35 deletions(-)

diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c
index ac83af54122f..721cd841a1dd 100644
--- a/drivers/pci/endpoint/functions/pci-epf-vntb.c
+++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c
@@ -84,12 +84,13 @@ enum epf_irq_slot {
#define MIN_DB_COUNT (EPF_IRQ_DB_START + 1)

struct epf_ntb_dma {
+ struct mutex lock; /* Serialize DMA submap and MW updates */
struct epf_ntb_dma_ctrl ctrl;
struct dma_chan *dchan[EDMA_MAX_RD_CH];
void *bar_scratch;
dma_addr_t bar_scratch_phys;
size_t bar_scratch_size;
- struct pci_epf_bar_submap submap[EDMA_MAX_RD_CH + 2];
+ 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;
@@ -229,6 +230,22 @@ 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->epf_ntb_bar[BAR_DMA] == barno;
+}
+
struct epf_ntb_dma_filter {
struct device *dev;
int chan_id;
@@ -353,6 +370,7 @@ static int epf_ntb_dw_edma_collect(struct epf_ntb *ntb,
unsigned int i;
size_t align;
u32 next = 0;
+ u64 offset;
int ret;

if (ctrl->u.dma_ctrl.reg_layout_data != EDMA_MF_EDMA_UNROLL)
@@ -371,9 +389,30 @@ static int epf_ntb_dw_edma_collect(struct epf_ntb *ntb,
if (!is_power_of_2(align))
return -EINVAL;

- if (epf_ntb_is_bar_used(ntb, barno) ||
- pci_epc_get_next_free_bar(features, barno) != barno)
+ if (barno == ntb->epf_ntb_bar[BAR_CONFIG] ||
+ barno == ntb->epf_ntb_bar[BAR_DB])
+ return -EINVAL;
+
+ offset = epf_ntb_dma_bar_offset(ntb, barno);
+ if (!offset &&
+ (epf_ntb_is_bar_used(ntb, barno) ||
+ pci_epc_get_next_free_bar(features, barno) != barno))
+ return -EINVAL;
+ if (offset > U32_MAX)
+ return -EOVERFLOW;
+ if (offset && !is_power_of_2(offset))
return -EINVAL;
+ dma->ctrl.submap.offset = offset;
+ next = offset;
+ if (next) {
+ /*
+ * submap[0] covers the MW prefix. Keep its temporary
+ * target at address 0 until the MW translation is
+ * installed.
+ */
+ dma->submap[0].size = next;
+ dma->num_submap = 1;
+ }

dma->ctrl.magic = EPF_NTB_DMA_MAGIC;
dma->ctrl.revision = EPF_NTB_DMA_REVISION;
@@ -408,7 +447,7 @@ static int epf_ntb_dw_edma_collect(struct epf_ntb *ntb,
chan->desc_addr_lo = lower_32_bits(dma_addr);
chan->desc_addr_hi = upper_32_bits(dma_addr);
}
- dma->ctrl.submap.size = next;
+ 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++) {
@@ -481,6 +520,8 @@ static int epf_ntb_dma_collect(struct epf_ntb *ntb)
if (!dma)
return -ENOMEM;

+ mutex_init(&dma->lock);
+
switch (ctrl->u.dma_ctrl.reg_layout) {
case PCI_EPC_AUX_DMA_REG_LAYOUT_DW_EDMA:
ret = epf_ntb_dw_edma_collect(ntb, dma, ctrl, resources, count);
@@ -551,7 +592,7 @@ static int epf_ntb_dma_bar_init(struct epf_ntb *ntb)
return -EOPNOTSUPP;

barno = ntb->epf_ntb_bar[BAR_DMA];
- mapped_size = dma->ctrl.submap.size;
+ mapped_size = dma->ctrl.submap.offset + dma->ctrl.submap.size;
/*
* Submaps cannot be installed until the host assigns the BAR address.
* Use address 0 for the temporary BAR Match Mode mapping, as is done
@@ -607,21 +648,25 @@ static void epf_ntb_dma_bar_clear(struct epf_ntb *ntb)
memset(bar, 0, sizeof(*bar));
}

-static int epf_ntb_dma_set_bar(struct epf_ntb *ntb)
+static int epf_ntb_dma_set_bar_locked(struct epf_ntb *ntb,
+ const dma_addr_t *mw_addr)
{
struct pci_epf_bar_submap *old_submap;
struct epf_ntb_dma *dma = ntb->dma;
unsigned int old_num_submap;
struct pci_epf_bar *bar;
+ dma_addr_t old_mw_addr;
int restore, ret;
-
- if (!dma)
- return 0;
+ lockdep_assert_held(&dma->lock);

bar = &ntb->epf->bar[ntb->epf_ntb_bar[BAR_DMA]];

old_submap = bar->submap;
old_num_submap = bar->num_submap;
+ if (mw_addr) {
+ old_mw_addr = dma->submap[0].phys_addr;
+ dma->submap[0].phys_addr = *mw_addr;
+ }
bar->submap = dma->submap;
bar->num_submap = dma->num_submap;

@@ -631,6 +676,8 @@ static int epf_ntb_dma_set_bar(struct epf_ntb *ntb)
return 0;

/* A failed dynamic update may have already removed the old mapping. */
+ if (mw_addr)
+ dma->submap[0].phys_addr = old_mw_addr;
bar->submap = old_submap;
bar->num_submap = old_num_submap;
restore = pci_epc_set_bar(ntb->epf->epc, ntb->epf->func_no,
@@ -642,6 +689,18 @@ static int epf_ntb_dma_set_bar(struct epf_ntb *ntb)
return ret;
}

+static int epf_ntb_dma_set_bar(struct epf_ntb *ntb)
+{
+ struct epf_ntb_dma *dma = ntb->dma;
+
+ if (!dma)
+ return 0;
+
+ guard(mutex)(&dma->lock);
+
+ return epf_ntb_dma_set_bar_locked(ntb, NULL);
+}
+
/**
* epf_ntb_configure_mw() - Configure the Outbound Address Space for VHOST
* to access the memory window of HOST
@@ -780,6 +839,13 @@ static void epf_ntb_cmd_handler(struct work_struct *work)
EPF_NTB_COMMAND_STATUS_OK;
break;
case EPF_NTB_COMMAND_LINK_UP:
+ if (ntb->dma && ntb->dma->ctrl.submap.offset &&
+ !(argument & EPF_NTB_LINK_UP_DMA_MW_SUPPORTED)) {
+ epf_ntb_set_link_enabled(ntb, false);
+ dev_err(dev, "Host does not support MW/DMA BAR sharing\n");
+ ctrl->command_status = EPF_NTB_COMMAND_STATUS_ERROR;
+ break;
+ }
ret = epf_ntb_set_link_enabled(ntb, true);
if (ret < 0)
ctrl->command_status = EPF_NTB_COMMAND_STATUS_ERROR;
@@ -1269,6 +1335,7 @@ static void epf_ntb_db_bar_clear(struct epf_ntb *ntb)
*/
static int epf_ntb_mw_bar_init(struct epf_ntb *ntb)
{
+ bool shared;
int ret = 0;
int i;
u64 size;
@@ -1278,22 +1345,25 @@ static int epf_ntb_mw_bar_init(struct epf_ntb *ntb)
for (i = 0; i < ntb->num_mws; i++) {
size = ntb->mws_size[i];
barno = ntb->epf_ntb_bar[BAR_MW1 + i];
-
- ntb->epf->bar[barno].barno = barno;
- ntb->epf->bar[barno].size = size;
- ntb->epf->bar[barno].addr = NULL;
- ntb->epf->bar[barno].phys_addr = 0;
- ntb->epf->bar[barno].flags |= upper_32_bits(size) ?
- PCI_BASE_ADDRESS_MEM_TYPE_64 :
- PCI_BASE_ADDRESS_MEM_TYPE_32;
-
- ret = pci_epc_set_bar(ntb->epf->epc,
- ntb->epf->func_no,
- ntb->epf->vfunc_no,
- &ntb->epf->bar[barno]);
- if (ret) {
- dev_err(dev, "MW set failed\n");
- goto err_alloc_mem;
+ shared = epf_ntb_dma_shares_bar(ntb, barno);
+
+ if (!shared) {
+ ntb->epf->bar[barno].barno = barno;
+ ntb->epf->bar[barno].size = size;
+ ntb->epf->bar[barno].addr = NULL;
+ ntb->epf->bar[barno].phys_addr = 0;
+ ntb->epf->bar[barno].flags |= upper_32_bits(size) ?
+ PCI_BASE_ADDRESS_MEM_TYPE_64 :
+ PCI_BASE_ADDRESS_MEM_TYPE_32;
+
+ ret = pci_epc_set_bar(ntb->epf->epc,
+ ntb->epf->func_no,
+ ntb->epf->vfunc_no,
+ &ntb->epf->bar[barno]);
+ if (ret) {
+ dev_err(dev, "MW set failed\n");
+ goto err_alloc_mem;
+ }
}

/* Allocate EPC outbound memory windows to vpci vntb device */
@@ -1310,10 +1380,11 @@ static int epf_ntb_mw_bar_init(struct epf_ntb *ntb)
return ret;

err_set_bar:
- pci_epc_clear_bar(ntb->epf->epc,
- ntb->epf->func_no,
- ntb->epf->vfunc_no,
- &ntb->epf->bar[barno]);
+ if (!shared)
+ 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;
@@ -1331,10 +1402,11 @@ static void epf_ntb_mw_bar_clear(struct epf_ntb *ntb, int num_mws)

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 (!epf_ntb_dma_shares_bar(ntb, barno))
+ pci_epc_clear_bar(ntb->epf->epc,
+ ntb->epf->func_no,
+ ntb->epf->vfunc_no,
+ &ntb->epf->bar[barno]);

pci_epc_mem_free_addr(ntb->epf->epc,
ntb->vpci_mw_phy[i],
@@ -1936,6 +2008,16 @@ static int vntb_epf_mw_set_trans(struct ntb_dev *ndev, int pidx, int idx,
dev = &ndev->dev;
barno = ntb->epf_ntb_bar[BAR_MW1 + idx];
epf_bar = &ntb->epf->bar[barno];
+ if (epf_ntb_dma_shares_bar(ntb, barno)) {
+ /* DMA submaps start after the configured MW size. */
+ if (size != ntb->mws_size[idx])
+ return -EINVAL;
+
+ guard(mutex)(&ntb->dma->lock);
+
+ return epf_ntb_dma_set_bar_locked(ntb, &addr);
+ }
+
epf_bar->phys_addr = addr;
epf_bar->barno = barno;
epf_bar->size = size;
@@ -2134,15 +2216,17 @@ static int vntb_epf_mw_get_align(struct ntb_dev *ndev, int pidx, int idx,
resource_size_t *size_max)
{
struct epf_ntb *ntb = ntb_ndev(ndev);
+ enum pci_barno barno = ntb->epf_ntb_bar[BAR_MW1 + idx];
+ u64 size = ntb->mws_size[idx];

if (addr_align)
*addr_align = SZ_4K;

if (size_align)
- *size_align = 1;
+ *size_align = epf_ntb_dma_shares_bar(ntb, barno) ? size : 1;

if (size_max)
- *size_max = ntb->mws_size[idx];
+ *size_max = size;

return 0;
}
diff --git a/include/linux/pci-epf-ntb.h b/include/linux/pci-epf-ntb.h
index 5cc0f0badae0..8131ed4c6ac9 100644
--- a/include/linux/pci-epf-ntb.h
+++ b/include/linux/pci-epf-ntb.h
@@ -24,6 +24,9 @@
#define EPF_NTB_DB_COUNT_MASK GENMASK(15, 0)
#define EPF_NTB_MSIX_ENABLE BIT(16)

+/* LINK_UP argument: the host honors the MW/DMA BAR boundary. */
+#define EPF_NTB_LINK_UP_DMA_MW_SUPPORTED BIT(0)
+
#define EPF_NTB_COMMAND_STATUS_OK 1
#define EPF_NTB_COMMAND_STATUS_ERROR 2

--
2.54.0