[PATCH 11/15] NTB: hw: epf: Parse control-layout version and DMA locator

From: Koichiro Den

Date: Thu Mar 12 2026 - 12:57:03 EST


pci-epf-vntb can now expose either the historical control layout or a
versioned extension that carries per-MW offset/size tuples and an
optional DMA locator.

Teach ntb_hw_epf to parse the control-layout version first, keep
accepting the legacy format, and use the explicit MW size information
when version 1 is present. Also parse the DMA locator and cache its BAR,
offset, size, ABI, and channel count for the follow-up enumeration step.

Finally, reserve the tail of the MSI/MSI-X vector allocation for the
exported DMA child so ntb_hw_epf only requests the link and doorbell
vectors it owns.

Signed-off-by: Koichiro Den <den@xxxxxxxxxxxxx>
---
drivers/ntb/hw/epf/ntb_hw_epf.c | 112 +++++++++++++++++++++++++++++---
1 file changed, 104 insertions(+), 8 deletions(-)

diff --git a/drivers/ntb/hw/epf/ntb_hw_epf.c b/drivers/ntb/hw/epf/ntb_hw_epf.c
index d420699ff7d6..6b427577b1bd 100644
--- a/drivers/ntb/hw/epf/ntb_hw_epf.c
+++ b/drivers/ntb/hw/epf/ntb_hw_epf.c
@@ -31,7 +31,14 @@
#define NTB_EPF_LINK_STATUS 0x0A
#define LINK_STATUS_UP BIT(0)

-#define NTB_EPF_TOPOLOGY 0x0C
+/*
+ * 0x0C was historically NTB_EPF_TOPOLOGY, but neither ntb_hw_epf nor
+ * pci-epf-{v,}ntb ever consumed it. Reuse it as a control-layout version
+ * selector while keeping 0 as the legacy format.
+ */
+#define NTB_EPF_CTRL_VERSION 0x0C
+#define NTB_EPF_CTRL_VERSION_LEGACY 0
+#define NTB_EPF_CTRL_VERSION_V1 1
#define NTB_EPF_LOWER_ADDR 0x10
#define NTB_EPF_UPPER_ADDR 0x14
#define NTB_EPF_LOWER_SIZE 0x18
@@ -39,6 +46,13 @@
#define NTB_EPF_MW_COUNT 0x20
#define NTB_EPF_MW1_OFFSET 0x24
#define NTB_EPF_SPAD_OFFSET 0x28
+#define NTB_EPF_MW_OFFSET(n) (0x134 + (n) * 4)
+#define NTB_EPF_MW_SIZE(n) (0x144 + (n) * 4)
+#define NTB_EPF_DMA_ABI 0x154
+#define NTB_EPF_DMA_BAR 0x158
+#define NTB_EPF_DMA_OFFSET 0x15C
+#define NTB_EPF_DMA_SIZE 0x160
+#define NTB_EPF_DMA_NUM_CHANS 0x164
#define NTB_EPF_SPAD_COUNT 0x2C
#define NTB_EPF_DB_ENTRY_SIZE 0x30
#define NTB_EPF_DB_DATA(n) (0x34 + (n) * 4)
@@ -101,6 +115,15 @@ struct ntb_epf_dev {
unsigned int mw_count;
unsigned int spad_count;
unsigned int db_count;
+ u32 ctrl_version;
+ u32 dma_abi;
+ u32 dma_offset;
+ u32 dma_size;
+ u32 dma_num_chans;
+ u32 dma_irq_base;
+ u32 dma_irq_count;
+ enum pci_barno dma_bar;
+ bool dma_aux_avail;

void __iomem *ctrl_reg;
void __iomem *db_reg;
@@ -375,6 +398,21 @@ static int ntb_epf_init_isr(struct ntb_epf_dev *ndev, int msi_min, int msi_max)
argument &= ~MSIX_ENABLE;
}

+ if (irq >= msi_min + ndev->dma_irq_count) {
+ ndev->dma_aux_avail = true;
+
+ /*
+ * Reserve the tail of the vector space for the exported DMA
+ * child. ntb_hw_epf only requests the prefix used for link and
+ * doorbell events.
+ */
+ ndev->dma_irq_base = irq - ndev->dma_irq_count;
+ irq = ndev->dma_irq_base;
+ } else {
+ ndev->dma_aux_avail = false;
+ irq = min(NTB_EPF_MAX_DB_COUNT + 1, irq);
+ }
+
for (i = 0; i < irq; i++) {
ret = request_irq(pci_irq_vector(pdev, i), ntb_epf_vec_isr,
0, "ntb_epf", ndev);
@@ -504,21 +542,32 @@ 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 bar_sz, mw_size;
+ u32 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;

+ bar_sz = pci_resource_len(ndev->ntb.pdev, bar);
+
+ if (ndev->ctrl_version >= NTB_EPF_CTRL_VERSION_V1) {
+ offset = readl(ndev->ctrl_reg + NTB_EPF_MW_OFFSET(idx));
+ mw_size = readl(ndev->ctrl_reg + NTB_EPF_MW_SIZE(idx));
+ } else {
+ offset = idx == 0 ? readl(ndev->ctrl_reg + NTB_EPF_MW1_OFFSET) : 0;
+ mw_size = bar_sz - offset;
+ }
+
+ if (!mw_size || offset + mw_size > bar_sz)
+ return -EINVAL;
+
if (base)
*base = pci_resource_start(ndev->ntb.pdev, bar) + offset;

if (size)
- *size = pci_resource_len(ndev->ntb.pdev, bar) - offset;
+ *size = mw_size;

return 0;
}
@@ -610,14 +659,61 @@ static inline void ntb_epf_init_struct(struct ntb_epf_dev *ndev,
ndev->ntb.ops = &ntb_epf_ops;
}

+static int ntb_epf_parse_ctrl_version(struct ntb_epf_dev *ndev)
+{
+ struct device *dev = ndev->dev;
+ u32 ver;
+
+ ver = readl(ndev->ctrl_reg + NTB_EPF_CTRL_VERSION);
+ switch (ver) {
+ case NTB_EPF_CTRL_VERSION_LEGACY:
+ case NTB_EPF_CTRL_VERSION_V1:
+ ndev->ctrl_version = ver;
+ return 0;
+ default:
+ dev_err(dev, "Unsupported NTB EPF control version %u\n", ver);
+ return -EINVAL;
+ }
+}
+
+static void ntb_epf_parse_dma_locator(struct ntb_epf_dev *ndev)
+{
+ if (ndev->ctrl_version < NTB_EPF_CTRL_VERSION_V1) {
+ ndev->dma_abi = 0;
+ ndev->dma_bar = NO_BAR;
+ ndev->dma_offset = 0;
+ ndev->dma_size = 0;
+ ndev->dma_irq_count = 0;
+ return;
+ }
+
+ ndev->dma_abi = readl(ndev->ctrl_reg + NTB_EPF_DMA_ABI);
+ ndev->dma_bar = readl(ndev->ctrl_reg + NTB_EPF_DMA_BAR);
+ ndev->dma_offset = readl(ndev->ctrl_reg + NTB_EPF_DMA_OFFSET);
+ ndev->dma_size = readl(ndev->ctrl_reg + NTB_EPF_DMA_SIZE);
+ ndev->dma_num_chans = readl(ndev->ctrl_reg + NTB_EPF_DMA_NUM_CHANS);
+ if (ndev->dma_abi && !ndev->dma_num_chans)
+ ndev->dma_num_chans = 1;
+ ndev->dma_irq_count = ndev->dma_num_chans;
+}
+
static int ntb_epf_init_dev(struct ntb_epf_dev *ndev)
{
struct device *dev = ndev->dev;
int ret;

- /* One Link interrupt and rest doorbell interrupt */
+ ret = ntb_epf_parse_ctrl_version(ndev);
+ if (ret)
+ return ret;
+
+ ntb_epf_parse_dma_locator(ndev);
+
+ /*
+ * One Link interrupt and rest doorbell interrupt.
+ * Remote DMA interrupt is best effort.
+ */
ret = ntb_epf_init_isr(ndev, NTB_EPF_MIN_DB_COUNT + 1,
- NTB_EPF_MAX_DB_COUNT + 1);
+ NTB_EPF_MAX_DB_COUNT + 1 + ndev->dma_irq_count);
if (ret) {
dev_err(dev, "Failed to init ISR\n");
return ret;
--
2.51.0