* [PATCH 1/3] PCI: endpoint: Support hardware-owned MSI-X table and PBA
2026-08-27 18:20 [PATCH 0/3] PCI: endpoint: Support hardware-owned MSI-X table and PBA Koichiro Den
@ 2026-08-27 18:20 ` Koichiro Den
2026-08-27 18:20 ` [PATCH 2/3] PCI: dw-rockchip: Support fixed MSI-X table and PBA on RK3588 Koichiro Den
2026-08-27 18:20 ` [PATCH 3/3] PCI: endpoint: pci-epf-vntb: Honor MSI-X selection Koichiro Den
2 siblings, 0 replies; 5+ messages in thread
From: Koichiro Den @ 2026-08-27 18:20 UTC (permalink / raw)
To: Manivannan Sadhasivam, Krzysztof Wilczyński,
Kishon Vijay Abraham I, Frank Li, Bjorn Helgaas, Jingoo Han,
Niklas Cassel
Cc: Lorenzo Pieralisi, Rob Herring, Aksh Garg, Christoph Hellwig,
Sagi Grimberg, Chaitanya Kulkarni, Jon Mason, Dave Jiang,
Allen Hubbe, Heiko Stuebner, Shawn Lin, Manikanta Maddireddy,
Shin'ichiro Kawasaki, linux-pci, linux-nvme, ntb,
linux-rockchip, linux-arm-kernel, linux-kernel
Some endpoint controllers expose the MSI-X table and Pending Bit Array
(PBA) in fixed, hardware-owned BAR regions. The EPC set_msix() callback
currently receives only the table BAR and offset and assumes that the PBA
immediately follows the table in the same BAR. It cannot describe a fixed
layout whose table and PBA have independent locations.
Add struct pci_epc_msix_layout and pass the full layout to
pci_epc_set_msix(). Leave layout selection to EPF implementations. For
instance, pci-epf-ntb reads the MSI-X Table from its own BAR. Using a
fixed layout there would require a way to read hardware-owned Table
entries. Add pci_epc_get_hw_msix_layout() for EPFs that want the fixed
layout. Update the existing callers and validate the supplied layout.
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
drivers/nvme/target/pci-epf.c | 16 ++-
.../pci/controller/cadence/pcie-cadence-ep.c | 9 +-
.../pci/controller/dwc/pcie-designware-ep.c | 7 +-
drivers/pci/endpoint/functions/pci-epf-ntb.c | 30 +++---
drivers/pci/endpoint/functions/pci-epf-test.c | 17 +--
drivers/pci/endpoint/pci-epc-core.c | 102 +++++++++++++++++-
include/linux/pci-epc.h | 25 ++++-
7 files changed, 168 insertions(+), 38 deletions(-)
diff --git a/drivers/nvme/target/pci-epf.c b/drivers/nvme/target/pci-epf.c
index 4e9db96ebfec..37182f6d29b1 100644
--- a/drivers/nvme/target/pci-epf.c
+++ b/drivers/nvme/target/pci-epf.c
@@ -201,7 +201,7 @@ struct nvmet_pci_epf {
const struct pci_epc_features *epc_features;
void *reg_bar;
- size_t msix_table_offset;
+ struct pci_epc_msix_layout msix_layout;
unsigned int irq_type;
unsigned int nr_vectors;
@@ -2187,8 +2187,14 @@ static int nvmet_pci_epf_configure_bar(struct nvmet_pci_epf *nvme_epf)
size_t pba_size;
msix_table_size = PCI_MSIX_ENTRY_SIZE * epf->msix_interrupts;
- nvme_epf->msix_table_offset = reg_size;
- pba_size = ALIGN(DIV_ROUND_UP(epf->msix_interrupts, 8), 8);
+ pba_size = BITS_TO_U64(epf->msix_interrupts) * sizeof(u64);
+
+ nvme_epf->msix_layout.table_bar = BAR_0;
+ nvme_epf->msix_layout.table_offset = reg_size;
+ nvme_epf->msix_layout.table_size = msix_table_size;
+ nvme_epf->msix_layout.pba_bar = BAR_0;
+ nvme_epf->msix_layout.pba_offset = reg_size + msix_table_size;
+ nvme_epf->msix_layout.pba_size = pba_size;
reg_size += msix_table_size + pba_size;
}
@@ -2245,8 +2251,8 @@ static int nvmet_pci_epf_init_irq(struct nvmet_pci_epf *nvme_epf)
/* Enable MSI-X if supported, otherwise, use MSI. */
if (epc_features->msix_capable && epf->msix_interrupts) {
ret = pci_epc_set_msix(epf->epc, epf->func_no, epf->vfunc_no,
- epf->msix_interrupts, BAR_0,
- nvme_epf->msix_table_offset);
+ epf->msix_interrupts,
+ &nvme_epf->msix_layout);
if (ret) {
dev_err(&epf->dev, "Failed to configure MSI-X\n");
return ret;
diff --git a/drivers/pci/controller/cadence/pcie-cadence-ep.c b/drivers/pci/controller/cadence/pcie-cadence-ep.c
index c0e1194a936b..2b69ea88aed2 100644
--- a/drivers/pci/controller/cadence/pcie-cadence-ep.c
+++ b/drivers/pci/controller/cadence/pcie-cadence-ep.c
@@ -293,7 +293,8 @@ static int cdns_pcie_ep_get_msix(struct pci_epc *epc, u8 func_no, u8 vfunc_no)
}
static int cdns_pcie_ep_set_msix(struct pci_epc *epc, u8 fn, u8 vfn,
- u16 nr_irqs, enum pci_barno bir, u32 offset)
+ u16 nr_irqs,
+ const struct pci_epc_msix_layout *layout)
{
struct cdns_pcie_ep *ep = epc_get_drvdata(epc);
struct cdns_pcie *pcie = &ep->pcie;
@@ -311,12 +312,12 @@ static int cdns_pcie_ep_set_msix(struct pci_epc *epc, u8 fn, u8 vfn,
/* Set MSI-X BAR and offset */
reg = cap + PCI_MSIX_TABLE;
- val = offset | bir;
+ val = layout->table_offset | layout->table_bar;
cdns_pcie_ep_fn_writel(pcie, fn, reg, val);
- /* Set PBA BAR and offset. BAR must match MSI-X BAR */
+ /* Set PBA BAR and offset */
reg = cap + PCI_MSIX_PBA;
- val = (offset + (nr_irqs * PCI_MSIX_ENTRY_SIZE)) | bir;
+ val = layout->pba_offset | layout->pba_bar;
cdns_pcie_ep_fn_writel(pcie, fn, reg, val);
return 0;
diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c
index 7d2794945704..147b043589f0 100644
--- a/drivers/pci/controller/dwc/pcie-designware-ep.c
+++ b/drivers/pci/controller/dwc/pcie-designware-ep.c
@@ -737,7 +737,8 @@ static int dw_pcie_ep_get_msix(struct pci_epc *epc, u8 func_no, u8 vfunc_no)
}
static int dw_pcie_ep_set_msix(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
- u16 nr_irqs, enum pci_barno bir, u32 offset)
+ u16 nr_irqs,
+ const struct pci_epc_msix_layout *layout)
{
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
@@ -757,11 +758,11 @@ static int dw_pcie_ep_set_msix(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
dw_pcie_ep_writew_dbi(ep, func_no, reg, val);
reg = ep_func->msix_cap + PCI_MSIX_TABLE;
- val = offset | bir;
+ val = layout->table_offset | layout->table_bar;
dw_pcie_ep_writel_dbi(ep, func_no, reg, val);
reg = ep_func->msix_cap + PCI_MSIX_PBA;
- val = (offset + (nr_irqs * PCI_MSIX_ENTRY_SIZE)) | bir;
+ val = layout->pba_offset | layout->pba_bar;
dw_pcie_ep_writel_dbi(ep, func_no, reg, val);
dw_pcie_dbi_ro_wr_dis(pci);
diff --git a/drivers/pci/endpoint/functions/pci-epf-ntb.c b/drivers/pci/endpoint/functions/pci-epf-ntb.c
index 5314aca2188a..f3e0e1b3ffb9 100644
--- a/drivers/pci/endpoint/functions/pci-epf-ntb.c
+++ b/drivers/pci/endpoint/functions/pci-epf-ntb.c
@@ -90,12 +90,11 @@ struct epf_ntb_epc {
u8 vfunc_no;
bool linkup;
bool is_msix;
- int msix_bar;
u32 spad_size;
struct pci_epc *epc;
struct epf_ntb *epf_ntb;
void __iomem *mw_addr[6];
- size_t msix_table_offset;
+ struct pci_epc_msix_layout msix_layout;
struct epf_ntb_ctrl *reg;
struct pci_epf_bar *epf_bar;
enum pci_barno epf_ntb_bar[6];
@@ -475,9 +474,9 @@ static int epf_ntb_configure_msi(struct epf_ntb *ntb,
*
* The MSI-X address is in the MSI-X table of EP CONTROLLER 2 and
* the count of doorbell is in ctrl->argument of epf_ntb_epc that is connected
- * to HOST2. MSI-X table is stored memory mapped to ntb_epc->msix_bar and the
- * offset is in ntb_epc->msix_table_offset. From this epf_ntb_configure_msix()
- * gets the MSI-X address and data.
+ * to HOST2. The location of the memory-mapped MSI-X table is described by
+ * ntb_epc->msix_layout. From this epf_ntb_configure_msix() gets the MSI-X
+ * address and data.
*
* epf_ntb_configure_msix() also stores the MSI-X data to raise each interrupt
* in db_data of the peer's control region. This helps the peer to raise
@@ -505,8 +504,8 @@ static int epf_ntb_configure_msix(struct epf_ntb *ntb,
ntb_epc = ntb->epc[type];
epc = ntb_epc->epc;
- epf_bar = &ntb_epc->epf_bar[ntb_epc->msix_bar];
- msix_tbl = epf_bar->addr + ntb_epc->msix_table_offset;
+ epf_bar = &ntb_epc->epf_bar[ntb_epc->msix_layout.table_bar];
+ msix_tbl = epf_bar->addr + ntb_epc->msix_layout.table_offset;
peer_ntb_epc = ntb->epc[!type];
peer_barno = peer_ntb_epc->epf_ntb_bar[BAR_DB_MW1];
@@ -1036,10 +1035,14 @@ static int epf_ntb_config_spad_bar_alloc(struct epf_ntb *ntb,
if (msix_capable) {
msix_table_size = PCI_MSIX_ENTRY_SIZE * ntb->db_count;
ctrl_size = ALIGN(ctrl_size, 8);
- ntb_epc->msix_table_offset = ctrl_size;
- ntb_epc->msix_bar = barno;
- /* Align to QWORD or 8 Bytes */
- pba_size = ALIGN(DIV_ROUND_UP(ntb->db_count, 8), 8);
+ pba_size = BITS_TO_U64(ntb->db_count) * sizeof(u64);
+
+ ntb_epc->msix_layout.table_bar = barno;
+ ntb_epc->msix_layout.table_offset = ctrl_size;
+ ntb_epc->msix_layout.table_size = msix_table_size;
+ ntb_epc->msix_layout.pba_bar = barno;
+ ntb_epc->msix_layout.pba_offset = ctrl_size + msix_table_size;
+ ntb_epc->msix_layout.pba_size = pba_size;
ctrl_size = ctrl_size + msix_table_size + pba_size;
}
@@ -1317,10 +1320,9 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb,
if (msix_capable) {
ret = pci_epc_set_msix(epc, func_no, vfunc_no, ntb->db_count,
- ntb_epc->msix_bar,
- ntb_epc->msix_table_offset);
+ &ntb_epc->msix_layout);
if (ret) {
- dev_err(dev, "MSI configuration failed\n");
+ dev_err(dev, "MSI-X configuration failed\n");
return ret;
}
}
diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c
index 4802d4f80f78..e48292e0fb37 100644
--- a/drivers/pci/endpoint/functions/pci-epf-test.c
+++ b/drivers/pci/endpoint/functions/pci-epf-test.c
@@ -82,7 +82,7 @@ struct pci_epf_test {
struct pci_epf *epf;
struct config_group group;
enum pci_barno test_reg_bar;
- size_t msix_table_offset;
+ struct pci_epc_msix_layout msix_layout;
struct delayed_work cmd_handler;
struct dma_chan *dma_chan_tx;
struct dma_chan *dma_chan_rx;
@@ -1221,8 +1221,7 @@ static int pci_epf_test_epc_init(struct pci_epf *epf)
if (epc_features->msix_capable) {
ret = pci_epc_set_msix(epc, epf->func_no, epf->vfunc_no,
epf->msix_interrupts,
- epf_test->test_reg_bar,
- epf_test->msix_table_offset);
+ &epf_test->msix_layout);
if (ret) {
dev_err(dev, "MSI-X configuration failed\n");
return ret;
@@ -1288,9 +1287,15 @@ static int pci_epf_test_alloc_space(struct pci_epf *epf)
if (epc_features->msix_capable) {
msix_table_size = PCI_MSIX_ENTRY_SIZE * epf->msix_interrupts;
- epf_test->msix_table_offset = test_reg_bar_size;
- /* Align to QWORD or 8 Bytes */
- pba_size = ALIGN(DIV_ROUND_UP(epf->msix_interrupts, 8), 8);
+ pba_size = BITS_TO_U64(epf->msix_interrupts) * sizeof(u64);
+
+ epf_test->msix_layout.table_bar = test_reg_bar;
+ epf_test->msix_layout.table_offset = test_reg_bar_size;
+ epf_test->msix_layout.table_size = msix_table_size;
+ epf_test->msix_layout.pba_bar = test_reg_bar;
+ epf_test->msix_layout.pba_offset = test_reg_bar_size +
+ msix_table_size;
+ epf_test->msix_layout.pba_size = pba_size;
}
test_reg_size = test_reg_bar_size + msix_table_size + pba_size;
diff --git a/drivers/pci/endpoint/pci-epc-core.c b/drivers/pci/endpoint/pci-epc-core.c
index 831b40458dcd..4a0ceb62f38f 100644
--- a/drivers/pci/endpoint/pci-epc-core.c
+++ b/drivers/pci/endpoint/pci-epc-core.c
@@ -6,6 +6,7 @@
* Author: Kishon Vijay Abraham I <kishon@ti.com>
*/
+#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/module.h>
@@ -113,6 +114,84 @@ enum pci_barno pci_epc_get_next_free_bar(const struct pci_epc_features
}
EXPORT_SYMBOL_GPL(pci_epc_get_next_free_bar);
+static int pci_epc_get_msix_region(const struct pci_epc_features *epc_features,
+ enum pci_epc_bar_rsvd_region_type type,
+ enum pci_barno *bar, u32 *offset,
+ resource_size_t *size)
+{
+ const struct pci_epc_bar_rsvd_region *region;
+ const struct pci_epc_bar_desc *bar_desc;
+ bool found = false;
+ int i, j;
+
+ if (!epc_features)
+ return -ENOENT;
+
+ for (i = BAR_0; i < PCI_STD_NUM_BARS; i++) {
+ bar_desc = &epc_features->bar[i];
+ if (bar_desc->nr_rsvd_regions && !bar_desc->rsvd_regions)
+ return -EINVAL;
+
+ for (j = 0; j < bar_desc->nr_rsvd_regions; j++) {
+ region = &bar_desc->rsvd_regions[j];
+ if (region->type != type)
+ continue;
+
+ if (found || bar_desc->type != BAR_RESERVED || !region->size ||
+ region->offset > PCI_MSIX_TABLE_OFFSET ||
+ !IS_ALIGNED(region->offset, 8))
+ return -EINVAL;
+
+ found = true;
+ *bar = i;
+ *offset = region->offset;
+ *size = region->size;
+ }
+ }
+
+ return found ? 0 : -ENOENT;
+}
+
+/**
+ * pci_epc_get_hw_msix_layout() - get a hardware-owned MSI-X table and PBA layout
+ * @epc_features: features provided by an EPC for an endpoint function
+ * @layout: layout to populate
+ *
+ * Return: 0 if the EPC describes both hardware-owned MSI-X regions, -ENOENT if
+ * neither region is described, or an error if the description is invalid.
+ */
+int pci_epc_get_hw_msix_layout(const struct pci_epc_features *epc_features,
+ struct pci_epc_msix_layout *layout)
+{
+ struct pci_epc_msix_layout hw_layout;
+ int table_ret, pba_ret;
+
+ if (!layout)
+ return -EINVAL;
+
+ table_ret = pci_epc_get_msix_region(epc_features,
+ PCI_EPC_BAR_RSVD_MSIX_TBL_RAM,
+ &hw_layout.table_bar,
+ &hw_layout.table_offset,
+ &hw_layout.table_size);
+ pba_ret = pci_epc_get_msix_region(epc_features,
+ PCI_EPC_BAR_RSVD_MSIX_PBA_RAM,
+ &hw_layout.pba_bar,
+ &hw_layout.pba_offset,
+ &hw_layout.pba_size);
+
+ if (table_ret == -ENOENT && pba_ret == -ENOENT)
+ return -ENOENT;
+
+ if (table_ret || pba_ret)
+ return -EINVAL;
+
+ *layout = hw_layout;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pci_epc_get_hw_msix_layout);
+
static bool pci_epc_function_is_valid(struct pci_epc *epc,
u8 func_no, u8 vfunc_no)
{
@@ -443,14 +522,14 @@ EXPORT_SYMBOL_GPL(pci_epc_get_msix);
* @func_no: the physical endpoint function number in the EPC device
* @vfunc_no: the virtual endpoint function number in the physical function
* @nr_irqs: number of MSI-X interrupts required by the EPF
- * @bir: BAR where the MSI-X table resides
- * @offset: Offset pointing to the start of MSI-X table
+ * @layout: MSI-X table and PBA layout selected by the EPF
*
* Invoke to set the required number of MSI-X interrupts.
*/
int pci_epc_set_msix(struct pci_epc *epc, u8 func_no, u8 vfunc_no, u16 nr_irqs,
- enum pci_barno bir, u32 offset)
+ const struct pci_epc_msix_layout *layout)
{
+ size_t table_size, pba_size;
int ret;
if (!pci_epc_function_is_valid(epc, func_no, vfunc_no))
@@ -459,11 +538,26 @@ int pci_epc_set_msix(struct pci_epc *epc, u8 func_no, u8 vfunc_no, u16 nr_irqs,
if (nr_irqs < 1 || nr_irqs > 2048)
return -EINVAL;
+ if (!layout || layout->table_bar < BAR_0 ||
+ layout->table_bar >= PCI_STD_NUM_BARS ||
+ layout->pba_bar < BAR_0 || layout->pba_bar >= PCI_STD_NUM_BARS ||
+ !IS_ALIGNED(layout->table_offset, 8) ||
+ !IS_ALIGNED(layout->pba_offset, 8) ||
+ layout->table_offset > PCI_MSIX_TABLE_OFFSET ||
+ layout->pba_offset > PCI_MSIX_PBA_OFFSET)
+ return -EINVAL;
+
+ table_size = nr_irqs * PCI_MSIX_ENTRY_SIZE;
+ pba_size = BITS_TO_U64(nr_irqs) * sizeof(u64);
+
+ if (layout->table_size < table_size || layout->pba_size < pba_size)
+ return -ENOSPC;
+
if (!epc->ops->set_msix)
return 0;
mutex_lock(&epc->lock);
- ret = epc->ops->set_msix(epc, func_no, vfunc_no, nr_irqs, bir, offset);
+ ret = epc->ops->set_msix(epc, func_no, vfunc_no, nr_irqs, layout);
mutex_unlock(&epc->lock);
return ret;
diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h
index f247cf9bcf1a..3d28231f092a 100644
--- a/include/linux/pci-epc.h
+++ b/include/linux/pci-epc.h
@@ -102,6 +102,24 @@ struct pci_epc_aux_resource {
} u;
};
+/**
+ * struct pci_epc_msix_layout - layout of an MSI-X table and PBA
+ * @table_bar: BAR containing the MSI-X table
+ * @table_offset: offset of the MSI-X table within @table_bar
+ * @table_size: size of the MSI-X table region
+ * @pba_bar: BAR containing the MSI-X Pending Bit Array (PBA)
+ * @pba_offset: offset of the MSI-X PBA within @pba_bar
+ * @pba_size: size of the MSI-X PBA region
+ */
+struct pci_epc_msix_layout {
+ enum pci_barno table_bar;
+ u32 table_offset;
+ resource_size_t table_size;
+ enum pci_barno pba_bar;
+ u32 pba_offset;
+ resource_size_t pba_size;
+};
+
/**
* struct pci_epc_ops - set of function pointers for performing EPC operations
* @write_header: ops to populate configuration space header
@@ -147,7 +165,8 @@ struct pci_epc_ops {
u8 nr_irqs);
int (*get_msi)(struct pci_epc *epc, u8 func_no, u8 vfunc_no);
int (*set_msix)(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
- u16 nr_irqs, enum pci_barno, u32 offset);
+ u16 nr_irqs,
+ const struct pci_epc_msix_layout *layout);
int (*get_msix)(struct pci_epc *epc, u8 func_no, u8 vfunc_no);
int (*raise_irq)(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
unsigned int type, u16 interrupt_num);
@@ -381,8 +400,10 @@ void pci_epc_unmap_addr(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
int pci_epc_set_msi(struct pci_epc *epc, u8 func_no, u8 vfunc_no, u8 nr_irqs);
int pci_epc_get_msi(struct pci_epc *epc, u8 func_no, u8 vfunc_no);
int pci_epc_set_msix(struct pci_epc *epc, u8 func_no, u8 vfunc_no, u16 nr_irqs,
- enum pci_barno, u32 offset);
+ const struct pci_epc_msix_layout *layout);
int pci_epc_get_msix(struct pci_epc *epc, u8 func_no, u8 vfunc_no);
+int pci_epc_get_hw_msix_layout(const struct pci_epc_features *epc_features,
+ struct pci_epc_msix_layout *layout);
int pci_epc_map_msi_irq(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
phys_addr_t phys_addr, u8 interrupt_num,
u32 entry_size, u32 *msi_data, u32 *msi_addr_offset);
--
2.51.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 2/3] PCI: dw-rockchip: Support fixed MSI-X table and PBA on RK3588
2026-08-27 18:20 [PATCH 0/3] PCI: endpoint: Support hardware-owned MSI-X table and PBA Koichiro Den
2026-08-27 18:20 ` [PATCH 1/3] " Koichiro Den
@ 2026-08-27 18:20 ` Koichiro Den
2026-08-27 18:20 ` [PATCH 3/3] PCI: endpoint: pci-epf-vntb: Honor MSI-X selection Koichiro Den
2 siblings, 0 replies; 5+ messages in thread
From: Koichiro Den @ 2026-08-27 18:20 UTC (permalink / raw)
To: Manivannan Sadhasivam, Krzysztof Wilczyński,
Kishon Vijay Abraham I, Frank Li, Bjorn Helgaas, Jingoo Han,
Niklas Cassel
Cc: Lorenzo Pieralisi, Rob Herring, Aksh Garg, Christoph Hellwig,
Sagi Grimberg, Chaitanya Kulkarni, Jon Mason, Dave Jiang,
Allen Hubbe, Heiko Stuebner, Shawn Lin, Manikanta Maddireddy,
Shin'ichiro Kawasaki, linux-pci, linux-nvme, ntb,
linux-rockchip, linux-arm-kernel, linux-kernel
The RK3588 PCIe endpoint controller exposes its MSI-X table at
BAR4+0x4000 and its PBA at BAR4+0x5000. Describe both as reserved regions
so EPF drivers can select the hardware-owned layout.
The regular DesignWare MSI-X interrupt path reads an EPF-owned table
through epf_bar[]. A reserved, hardware-owned table has no such backing.
Record whether the layout selected for each function matches the
hardware-owned layout, and use the controller MSI-X doorbell on RK3588
only in that case. Continue to use the regular path for EPF-owned
layouts.
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
.../pci/controller/dwc/pcie-designware-ep.c | 21 ++++++++++++++
drivers/pci/controller/dwc/pcie-designware.h | 1 +
drivers/pci/controller/dwc/pcie-dw-rockchip.c | 28 +++++++++++++++++--
3 files changed, 47 insertions(+), 3 deletions(-)
diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c
index 147b043589f0..bc2b63814dea 100644
--- a/drivers/pci/controller/dwc/pcie-designware-ep.c
+++ b/drivers/pci/controller/dwc/pcie-designware-ep.c
@@ -736,6 +736,26 @@ static int dw_pcie_ep_get_msix(struct pci_epc *epc, u8 func_no, u8 vfunc_no)
return val + 1;
}
+static bool
+dw_pcie_ep_msix_layout_is_hw_owned(struct dw_pcie_ep *ep,
+ const struct pci_epc_msix_layout *layout)
+{
+ const struct pci_epc_features *features;
+ struct pci_epc_msix_layout hw_layout;
+
+ if (!ep->ops->get_features)
+ return false;
+
+ features = ep->ops->get_features(ep);
+ if (pci_epc_get_hw_msix_layout(features, &hw_layout))
+ return false;
+
+ return layout->table_bar == hw_layout.table_bar &&
+ layout->table_offset == hw_layout.table_offset &&
+ layout->pba_bar == hw_layout.pba_bar &&
+ layout->pba_offset == hw_layout.pba_offset;
+}
+
static int dw_pcie_ep_set_msix(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
u16 nr_irqs,
const struct pci_epc_msix_layout *layout)
@@ -764,6 +784,7 @@ static int dw_pcie_ep_set_msix(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
reg = ep_func->msix_cap + PCI_MSIX_PBA;
val = layout->pba_offset | layout->pba_bar;
dw_pcie_ep_writel_dbi(ep, func_no, reg, val);
+ ep_func->msix_hw_owned = dw_pcie_ep_msix_layout_is_hw_owned(ep, layout);
dw_pcie_dbi_ro_wr_dis(pci);
diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index de4b245b1758..043c39b5881d 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -495,6 +495,7 @@ struct dw_pcie_ep_func {
u8 func_no;
u8 msi_cap; /* MSI capability offset */
u8 msix_cap; /* MSI-X capability offset */
+ bool msix_hw_owned;
u8 bar_to_atu[PCI_STD_NUM_BARS];
struct pci_epf_bar *epf_bar[PCI_STD_NUM_BARS];
diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c
index 731d93663cca..d622723dfcf8 100644
--- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c
+++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c
@@ -120,6 +120,7 @@ struct rockchip_pcie {
struct rockchip_pcie_of_data {
enum dw_pcie_device_mode mode;
const struct pci_epc_features *epc_features;
+ bool msix_doorbell;
};
static int rockchip_pcie_readl_apb(struct rockchip_pcie *rockchip, u32 reg)
@@ -481,6 +482,8 @@ static int rockchip_pcie_raise_irq(struct dw_pcie_ep *ep, u8 func_no,
unsigned int type, u16 interrupt_num)
{
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
+ struct rockchip_pcie *rockchip = to_rockchip_pcie(pci);
+ struct dw_pcie_ep_func *ep_func;
switch (type) {
case PCI_IRQ_INTX:
@@ -488,6 +491,12 @@ static int rockchip_pcie_raise_irq(struct dw_pcie_ep *ep, u8 func_no,
case PCI_IRQ_MSI:
return dw_pcie_ep_raise_msi_irq(ep, func_no, interrupt_num);
case PCI_IRQ_MSIX:
+ ep_func = dw_pcie_ep_get_func_from_ep(ep, func_no);
+ if (rockchip->data->msix_doorbell && ep_func &&
+ ep_func->msix_hw_owned)
+ return dw_pcie_ep_raise_msix_irq_doorbell(ep, func_no,
+ interrupt_num);
+
return dw_pcie_ep_raise_msix_irq(ep, func_no, interrupt_num);
default:
dev_err(pci->dev, "UNKNOWN IRQ type\n");
@@ -517,12 +526,24 @@ static const struct pci_epc_bar_rsvd_region rk3588_bar4_rsvd[] = {
.offset = 0x0,
.size = 0x2000,
},
+ {
+ /* MSI-X Table (BAR4: MSI-X Table) */
+ .type = PCI_EPC_BAR_RSVD_MSIX_TBL_RAM,
+ .offset = 0x4000,
+ .size = SZ_4K,
+ },
+ {
+ /* MSI-X PBA (BAR4: MSI-X PBA) */
+ .type = PCI_EPC_BAR_RSVD_MSIX_PBA_RAM,
+ .offset = 0x5000,
+ .size = SZ_4K,
+ },
};
/*
- * BAR4 on rk3588 exposes the ATU Port Logic Structure to the host regardless of
- * iATU settings for BAR4. This means that BAR4 cannot be used by an EPF driver,
- * so mark it as RESERVED.
+ * BAR4 on RK3588 exposes the DMA and ATU Port Logic Structures and the MSI-X
+ * table and PBA to the host regardless of iATU settings for BAR4. This means
+ * that BAR4 cannot be used by an EPF driver, so mark it as RESERVED.
*/
static const struct pci_epc_features rockchip_pcie_epc_features_rk3588 = {
DWC_EPC_COMMON_FEATURES,
@@ -841,6 +862,7 @@ static const struct rockchip_pcie_of_data rockchip_pcie_ep_of_data_rk3568 = {
static const struct rockchip_pcie_of_data rockchip_pcie_ep_of_data_rk3588 = {
.mode = DW_PCIE_EP_TYPE,
.epc_features = &rockchip_pcie_epc_features_rk3588,
+ .msix_doorbell = true,
};
static const struct of_device_id rockchip_pcie_of_match[] = {
--
2.51.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 3/3] PCI: endpoint: pci-epf-vntb: Honor MSI-X selection
2026-08-27 18:20 [PATCH 0/3] PCI: endpoint: Support hardware-owned MSI-X table and PBA Koichiro Den
2026-08-27 18:20 ` [PATCH 1/3] " Koichiro Den
2026-08-27 18:20 ` [PATCH 2/3] PCI: dw-rockchip: Support fixed MSI-X table and PBA on RK3588 Koichiro Den
@ 2026-08-27 18:20 ` Koichiro Den
2026-08-27 19:23 ` Frank Li
2 siblings, 1 reply; 5+ messages in thread
From: Koichiro Den @ 2026-08-27 18:20 UTC (permalink / raw)
To: Manivannan Sadhasivam, Krzysztof Wilczyński,
Kishon Vijay Abraham I, Frank Li, Bjorn Helgaas, Jingoo Han,
Niklas Cassel
Cc: Lorenzo Pieralisi, Rob Herring, Aksh Garg, Christoph Hellwig,
Sagi Grimberg, Chaitanya Kulkarni, Jon Mason, Dave Jiang,
Allen Hubbe, Heiko Stuebner, Shawn Lin, Manikanta Maddireddy,
Shin'ichiro Kawasaki, linux-pci, linux-nvme, ntb,
linux-rockchip, linux-arm-kernel, linux-kernel
ntb_hw_epf tries MSI-X first and falls back to MSI. It reports the
result in COMMAND_CONFIGURE_DOORBELL. pci-epf-vntb ignores MSIX_ENABLE,
configures only MSI, and always raises peer doorbells with PCI_IRQ_MSI.
When MSI-X is selected, the host does not program MSI, so raising it can
issue a write to an invalid address. This was observed with an IOMMU
enabled on the RC.
Configure MSI-X when supported and use the selected type for peer
doorbells. Use the hardware-owned layout when available. Otherwise
allocate an EPF-owned Table and PBA in the config BAR. Configure db_count
entries to cover the link event, the reserved slot, and the doorbell
slots.
Fixes: e35f56bb0330 ("PCI: endpoint: Support NTB transfer between RC and EP")
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
drivers/pci/endpoint/functions/pci-epf-vntb.c | 67 ++++++++++++++-----
1 file changed, 51 insertions(+), 16 deletions(-)
diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c
index c3caec927d74..d9622a5d4710 100644
--- a/drivers/pci/endpoint/functions/pci-epf-vntb.c
+++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c
@@ -148,6 +148,7 @@ struct epf_ntb {
u16 vntb_vid;
bool linkup;
+ bool peer_msix;
/*
* True when doorbells are interrupt-driven (MSI or embedded), false
@@ -155,6 +156,7 @@ struct epf_ntb {
*/
bool msi_doorbell;
u32 spad_size;
+ struct pci_epc_msix_layout msix_layout;
enum pci_barno epf_ntb_bar[VNTB_BAR_NUM];
@@ -303,6 +305,7 @@ static void epf_ntb_cmd_handler(struct work_struct *work)
switch (command) {
case COMMAND_CONFIGURE_DOORBELL:
+ WRITE_ONCE(ntb->peer_msix, argument & MSIX_ENABLE);
ctrl->command_status = COMMAND_STATUS_OK;
break;
case COMMAND_TEARDOWN_DOORBELL:
@@ -439,9 +442,9 @@ 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 described in the above diagram.
+ * If the EPC does not provide a hardware-owned MSI-X table and PBA, allocate
+ * space for them between the control and scratchpad regions.
*
* Returns: Zero for success, or an error code in case of failure
*/
@@ -454,7 +457,7 @@ static int epf_ntb_config_spad_bar_alloc(struct epf_ntb *ntb)
struct device *dev = &epf->dev;
u32 spad_count;
void *base;
- int i;
+ int i, ret;
const struct pci_epc_features *epc_features = pci_epc_get_features(epf->epc,
epf->func_no,
epf->vfunc_no);
@@ -462,6 +465,29 @@ static int epf_ntb_config_spad_bar_alloc(struct epf_ntb *ntb)
spad_count = ntb->spad_count;
ctrl_size = ALIGN(sizeof(struct epf_ntb_ctrl), sizeof(u32));
+ if (epc_features->msix_capable) {
+ ret = pci_epc_get_hw_msix_layout(epc_features,
+ &ntb->msix_layout);
+ if (ret && ret != -ENOENT) {
+ dev_err(dev, "Invalid hardware-owned MSI-X layout\n");
+ return ret;
+ }
+
+ if (ret == -ENOENT) {
+ ntb->msix_layout.table_bar = barno;
+ ntb->msix_layout.table_offset = ALIGN(ctrl_size, 8);
+ ntb->msix_layout.table_size =
+ ntb->db_count * PCI_MSIX_ENTRY_SIZE;
+ ntb->msix_layout.pba_bar = barno;
+ ntb->msix_layout.pba_offset =
+ ntb->msix_layout.table_offset +
+ ntb->msix_layout.table_size;
+ ntb->msix_layout.pba_size =
+ BITS_TO_U64(ntb->db_count) * sizeof(u64);
+ ctrl_size = ntb->msix_layout.pba_offset +
+ ntb->msix_layout.pba_size;
+ }
+ }
spad_size = 2 * spad_count * sizeof(u32);
base = pci_epf_alloc_space(epf, ctrl_size + spad_size,
@@ -502,6 +528,7 @@ static int epf_ntb_config_spad_bar_alloc(struct epf_ntb *ntb)
static int epf_ntb_configure_interrupt(struct epf_ntb *ntb)
{
const struct pci_epc_features *epc_features;
+ struct pci_epf *epf = ntb->epf;
struct device *dev;
int ret;
@@ -521,16 +548,22 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb)
}
if (epc_features->msi_capable) {
- ret = pci_epc_set_msi(ntb->epf->epc,
- ntb->epf->func_no,
- ntb->epf->vfunc_no,
- 16);
+ ret = pci_epc_set_msi(epf->epc, epf->func_no, epf->vfunc_no, 16);
if (ret) {
dev_err(dev, "MSI configuration failed\n");
return ret;
}
}
+ if (epc_features->msix_capable) {
+ ret = pci_epc_set_msix(epf->epc, epf->func_no, epf->vfunc_no,
+ ntb->db_count, &ntb->msix_layout);
+ if (ret) {
+ dev_err(dev, "MSI-X configuration failed\n");
+ return ret;
+ }
+ }
+
return 0;
}
@@ -1512,6 +1545,7 @@ static void vntb_epf_peer_db_work(struct work_struct *work)
struct epf_ntb *ntb = container_of(work, struct epf_ntb, peer_db_work);
struct pci_epf *epf = ntb->epf;
unsigned int budget = VNTB_PEER_DB_WORK_BUDGET;
+ unsigned int irq_type;
u8 func_no, vfunc_no;
unsigned int db_bit;
u32 interrupt_num;
@@ -1523,6 +1557,7 @@ static void vntb_epf_peer_db_work(struct work_struct *work)
func_no = epf->func_no;
vfunc_no = epf->vfunc_no;
+ irq_type = READ_ONCE(ntb->peer_msix) ? PCI_IRQ_MSIX : PCI_IRQ_MSI;
/*
* Drain doorbells from peer_db_pending in snapshots (atomic64_xchg()).
@@ -1536,16 +1571,16 @@ static void vntb_epf_peer_db_work(struct work_struct *work)
while (db_bits) {
/*
- * pci_epc_raise_irq() for MSI expects a 1-based
- * interrupt number. The first usable doorbell starts
- * at EPF_IRQ_DB_START in the legacy slot layout.
+ * pci_epc_raise_irq() expects a 1-based interrupt
+ * number for MSI and MSI-X. The first usable doorbell
+ * starts at EPF_IRQ_DB_START in the legacy slot layout.
*
* Legacy mapping (kept for compatibility):
*
- * MSI #1 : link event (reserved)
- * MSI #2 : unused (historical offset)
- * MSI #3 : doorbell bit 0 (DB#0)
- * MSI #4 : doorbell bit 1 (DB#1)
+ * IRQ #1 : link event (reserved)
+ * IRQ #2 : unused (historical offset)
+ * IRQ #3 : doorbell bit 0 (DB#0)
+ * IRQ #4 : doorbell bit 1 (DB#1)
* ...
*
* Do not change this mapping to avoid breaking
@@ -1556,7 +1591,7 @@ static void vntb_epf_peer_db_work(struct work_struct *work)
db_bits &= ~BIT_ULL(db_bit);
ret = pci_epc_raise_irq(epf->epc, func_no, vfunc_no,
- PCI_IRQ_MSI, interrupt_num);
+ irq_type, interrupt_num);
if (ret)
dev_err(&ntb->ntb.dev,
"Failed to raise IRQ for interrupt_num %u: %d\n",
--
2.51.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 5+ messages in thread