aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
authorBjorn Helgaas <bhelgaas@google.com>2026-06-23 17:32:04 -0500
committerBjorn Helgaas <bhelgaas@google.com>2026-06-23 17:32:04 -0500
commitae385ca8123256618d259efbb132586002e77e70 (patch)
tree40d4fc6d667b1ca47fa01269286e51789787e71b /drivers
parentd1b80f7511d007c0c08b1304124db3faabb84acc (diff)
parent1fda82e37e00eb679d762756867b2e7508dd73f9 (diff)
downloadath-ae385ca8123256618d259efbb132586002e77e70.tar.gz
Merge branch 'pci/endpoint'
- Add endpoint controller APIs for use by function drivers to discover auxiliary blocks like DMA engines (Koichiro Den) - Remember DesignWare eDMA engine base/size and expose them via the EPC aux-resource API (Koichiro Den) - Refactor endpoint doorbell allocation to allow non-MSI doorbells (Koichiro Den) - Add endpoint embedded doorbell fallback, used if MSI allocation fails (Koichiro Den) - Validate BAR index and remove dead BAR read in endpoint doorbell test (Carlos Bilbao) - Unwind MSI/MSI-X vectors if NTB initialization fails part-way through (Koichiro Den) - Cache sleepable pci_irq_vector() value at ISR setup to avoid calling it from hardirq context (Koichiro Den) - Validate doorbell count when configuring NTB and vNTB doorbells (Manivannan Sadhasivam) - Call sleepable pci_epc_raise_irq() from a work item instead of atomic context, e.g., when setting bits in NTB peer doorbells in the ntb_peer_db_set() path (Koichiro Den) - Report 0-based vNTB doorbell vector to account for link event 0 and historically skipped slot 1 (Koichiro Den) - Reject unusable vNTB doorbell counts, e.g., if they don't allow space for link event 0 and historically skipped slot 1 (Koichiro Den) - Prevent configfs writes to vNTB db_count and other values that are already in use after EPC attach (Koichiro Den) - Account for vNTB db_valid reserved slots (link event 0 and historically skipped slot 1) so they don't appear as valid doorbells (Koichiro Den) - Implement vNTB .db_vector_count()/mask() for doorbells so clients can use multiple vectors and avoid thundering herds (Koichiro Den) - Report 0-based NTB doorbell vector to account for link event 0 and historically skipped slot 1 (Koichiro Den) - Fix doorbell bitmask and IRQ vector handling to clear only specified bits, use the correct vector for non-contiguous Linux IRQ numbers, and validate incoming vectors (Koichiro Den) - Implement NTB .db_vector_count()/mask() for doorbells so clients can use multiple vectors (Koichiro Den) * pci/endpoint: NTB: epf: Implement .db_vector_count()/mask() for doorbells NTB: epf: Fix doorbell bitmask and IRQ vector handling NTB: epf: Report 0-based doorbell vector via ntb_db_event() NTB: epf: Make db_valid_mask cover only real doorbell bits NTB: epf: Document legacy doorbell slot offset in ntb_epf_peer_db_set() PCI: endpoint: pci-epf-vntb: Implement .db_vector_count()/mask() for doorbells PCI: endpoint: pci-epf-vntb: Exclude reserved slots from db_valid_mask PCI: endpoint: pci-epf-vntb: Guard configfs writes after EPC attach PCI: endpoint: pci-epf-vntb: Reject unusable doorbell counts PCI: endpoint: pci-epf-vntb: Report 0-based doorbell vector via ntb_db_event() PCI: endpoint: pci-epf-vntb: Defer pci_epc_raise_irq() out of atomic context PCI: endpoint: pci-epf-vntb: Document legacy MSI doorbell offset PCI: endpoint: pci-epf-ntb: Add check to detect 'db_count' value of 0 PCI: endpoint: pci-epf-vntb: Add check to detect 'db_count' value of 0 NTB: epf: Avoid calling pci_irq_vector() from hardirq context NTB: epf: Fix request_irq() unwind in ntb_epf_init_isr() misc: pci_endpoint_test: Remove dead BAR read before doorbell trigger misc: pci_endpoint_test: Validate BAR index in doorbell test PCI: endpoint: pci-ep-msi: Add embedded doorbell fallback PCI: endpoint: pci-epf-test: Reuse pre-exposed doorbell targets PCI: endpoint: pci-epf-vntb: Reuse pre-exposed doorbells and IRQ flags PCI: endpoint: pci-ep-msi: Refactor doorbell allocation for new backends PCI: dwc: ep: Expose integrated eDMA resources via EPC aux-resource API PCI: dwc: Record integrated eDMA register window PCI: endpoint: Add auxiliary resource query API
Diffstat (limited to 'drivers')
-rw-r--r--drivers/misc/pci_endpoint_test.c6
-rw-r--r--drivers/ntb/hw/epf/ntb_hw_epf.c141
-rw-r--r--drivers/pci/controller/dwc/pcie-designware-ep.c119
-rw-r--r--drivers/pci/controller/dwc/pcie-designware.c4
-rw-r--r--drivers/pci/controller/dwc/pcie-designware.h2
-rw-r--r--drivers/pci/endpoint/functions/pci-epf-ntb.c21
-rw-r--r--drivers/pci/endpoint/functions/pci-epf-test.c86
-rw-r--r--drivers/pci/endpoint/functions/pci-epf-vntb.c274
-rw-r--r--drivers/pci/endpoint/pci-ep-msi.c175
-rw-r--r--drivers/pci/endpoint/pci-epc-core.c80
10 files changed, 800 insertions, 108 deletions
diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c
index dbd017cabbb92..3635741c3e7a7 100644
--- a/drivers/misc/pci_endpoint_test.c
+++ b/drivers/misc/pci_endpoint_test.c
@@ -1100,7 +1100,6 @@ static int pci_endpoint_test_doorbell(struct pci_endpoint_test *test)
data = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_DB_DATA);
addr = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_DB_OFFSET);
- bar = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_DB_BAR);
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE, irq_type);
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, 1);
@@ -1108,6 +1107,11 @@ static int pci_endpoint_test_doorbell(struct pci_endpoint_test *test)
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_STATUS, 0);
bar = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_DB_BAR);
+ if (bar < BAR_0 || bar >= PCI_STD_NUM_BARS) {
+ dev_err(dev, "BAR %d reported by endpoint out of range [0, %u]\n",
+ bar, PCI_STD_NUM_BARS - 1);
+ return -ERANGE;
+ }
writel(data, test->bar[bar] + addr);
diff --git a/drivers/ntb/hw/epf/ntb_hw_epf.c b/drivers/ntb/hw/epf/ntb_hw_epf.c
index d3ecf25a51625..af57554728429 100644
--- a/drivers/ntb/hw/epf/ntb_hw_epf.c
+++ b/drivers/ntb/hw/epf/ntb_hw_epf.c
@@ -6,6 +6,7 @@
* Author: Kishon Vijay Abraham I <kishon@ti.com>
*/
+#include <linux/atomic.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/pci.h>
@@ -43,6 +44,18 @@
#define NTB_EPF_DB_DATA(n) (0x34 + (n) * 4)
#define NTB_EPF_DB_OFFSET(n) (0xB4 + (n) * 4)
+/*
+ * Legacy doorbell slot layout when paired with pci-epf-*ntb:
+ *
+ * slot 0 : reserved for link events
+ * slot 1 : unused (historical extra offset)
+ * slot 2 : DB#0
+ * slot 3 : DB#1
+ * ...
+ *
+ * Thus, NTB_EPF_MIN_DB_COUNT=3 means that we at least create vectors for
+ * doorbells DB#0 and DB#1.
+ */
#define NTB_EPF_MIN_DB_COUNT 3
#define NTB_EPF_MAX_DB_COUNT 31
@@ -69,8 +82,21 @@ enum epf_ntb_bar {
NTB_BAR_NUM,
};
+enum epf_irq_slot {
+ EPF_IRQ_LINK = 0,
+ EPF_IRQ_RESERVED_DB, /* Historically skipped slot */
+ EPF_IRQ_DB_START,
+};
+
#define NTB_EPF_MAX_MW_COUNT (NTB_BAR_NUM - BAR_MW1)
+struct ntb_epf_dev;
+
+struct ntb_epf_irq_ctx {
+ struct ntb_epf_dev *ndev;
+ unsigned int irq_no;
+};
+
struct ntb_epf_dev {
struct ntb_dev ntb;
struct device *dev;
@@ -90,8 +116,9 @@ struct ntb_epf_dev {
unsigned int self_spad;
unsigned int peer_spad;
- int db_val;
+ atomic64_t db_val;
u64 db_valid_mask;
+ struct ntb_epf_irq_ctx irq_ctx[NTB_EPF_MAX_DB_COUNT + 1];
};
#define ntb_ndev(__ntb) container_of(__ntb, struct ntb_epf_dev, ntb)
@@ -315,16 +342,29 @@ static int ntb_epf_link_disable(struct ntb_dev *ntb)
static irqreturn_t ntb_epf_vec_isr(int irq, void *dev)
{
- struct ntb_epf_dev *ndev = dev;
- int irq_no;
-
- irq_no = irq - pci_irq_vector(ndev->ntb.pdev, 0);
- ndev->db_val = irq_no + 1;
+ struct ntb_epf_irq_ctx *ctx = dev;
+ struct ntb_epf_dev *ndev = ctx->ndev;
+ unsigned int db_vector;
+ unsigned int irq_no = ctx->irq_no;
- if (irq_no == 0)
+ if (irq_no == EPF_IRQ_LINK) {
ntb_link_event(&ndev->ntb);
- else
- ntb_db_event(&ndev->ntb, irq_no);
+ } else if (irq_no == EPF_IRQ_RESERVED_DB) {
+ dev_warn_ratelimited(ndev->dev,
+ "Unexpected reserved doorbell slot IRQ received\n");
+ } else {
+ db_vector = irq_no - EPF_IRQ_DB_START;
+ if (ndev->db_count < NTB_EPF_MIN_DB_COUNT ||
+ db_vector >= ndev->db_count - 1) {
+ dev_warn_ratelimited(ndev->dev,
+ "Unexpected doorbell vector %u (db_count %u)\n",
+ db_vector, ndev->db_count);
+ return IRQ_HANDLED;
+ }
+
+ atomic64_or(BIT_ULL(db_vector), &ndev->db_val);
+ ntb_db_event(&ndev->ntb, db_vector);
+ }
return IRQ_HANDLED;
}
@@ -350,31 +390,30 @@ 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++) {
+ 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,
- 0, "ntb_epf", ndev);
+ 0, "ntb_epf", &ndev->irq_ctx[i]);
if (ret) {
dev_err(dev, "Failed to request irq\n");
- goto err_request_irq;
+ goto err_free_irq;
}
}
- ndev->db_count = irq - 1;
-
ret = ntb_epf_send_command(ndev, CMD_CONFIGURE_DOORBELL,
argument | irq);
if (ret) {
dev_err(dev, "Failed to configure doorbell\n");
- goto err_configure_db;
+ goto err_free_irq;
}
return 0;
-err_configure_db:
- for (i = 0; i < ndev->db_count + 1; i++)
- free_irq(pci_irq_vector(pdev, i), ndev);
-
-err_request_irq:
+err_free_irq:
+ while (i--)
+ free_irq(pci_irq_vector(pdev, i), &ndev->irq_ctx[i]);
pci_free_irq_vectors(pdev);
return ret;
@@ -395,6 +434,36 @@ static u64 ntb_epf_db_valid_mask(struct ntb_dev *ntb)
return ntb_ndev(ntb)->db_valid_mask;
}
+static int ntb_epf_db_vector_count(struct ntb_dev *ntb)
+{
+ struct ntb_epf_dev *ndev = ntb_ndev(ntb);
+ unsigned int db_count = ndev->db_count;
+
+ /*
+ * db_count includes an extra skipped slot due to the legacy
+ * doorbell layout. Expose only the real doorbell vectors.
+ */
+ if (db_count < NTB_EPF_MIN_DB_COUNT)
+ return 0;
+
+ return db_count - 1;
+}
+
+static u64 ntb_epf_db_vector_mask(struct ntb_dev *ntb, int db_vector)
+{
+ int nr_vec;
+
+ /*
+ * db_count includes one skipped slot in the legacy layout. Valid
+ * doorbell vectors are therefore [0 .. (db_count - 2)].
+ */
+ nr_vec = ntb_epf_db_vector_count(ntb);
+ if (db_vector < 0 || db_vector >= nr_vec)
+ return 0;
+
+ return BIT_ULL(db_vector);
+}
+
static int ntb_epf_db_set_mask(struct ntb_dev *ntb, u64 db_bits)
{
return 0;
@@ -473,6 +542,14 @@ static int ntb_epf_peer_mw_get_addr(struct ntb_dev *ntb, int idx,
static int ntb_epf_peer_db_set(struct ntb_dev *ntb, u64 db_bits)
{
struct ntb_epf_dev *ndev = ntb_ndev(ntb);
+ /*
+ * ffs() returns a 1-based bit index (bit 0 -> 1).
+ *
+ * With slot 0 reserved for link events, DB#0 would naturally map to
+ * slot 1. Historically an extra +1 offset was added, so DB#0 maps to
+ * slot 2 and slot 1 remains unused. Keep this mapping for
+ * backward-compatibility.
+ */
u32 interrupt_num = ffs(db_bits) + 1;
struct device *dev = ndev->dev;
u32 db_entry_size;
@@ -499,7 +576,7 @@ static u64 ntb_epf_db_read(struct ntb_dev *ntb)
{
struct ntb_epf_dev *ndev = ntb_ndev(ntb);
- return ndev->db_val;
+ return atomic64_read(&ndev->db_val);
}
static int ntb_epf_db_clear_mask(struct ntb_dev *ntb, u64 db_bits)
@@ -511,7 +588,7 @@ static int ntb_epf_db_clear(struct ntb_dev *ntb, u64 db_bits)
{
struct ntb_epf_dev *ndev = ntb_ndev(ntb);
- ndev->db_val = 0;
+ atomic64_and(~db_bits, &ndev->db_val);
return 0;
}
@@ -521,6 +598,8 @@ static const struct ntb_dev_ops ntb_epf_ops = {
.spad_count = ntb_epf_spad_count,
.peer_mw_count = ntb_epf_peer_mw_count,
.db_valid_mask = ntb_epf_db_valid_mask,
+ .db_vector_count = ntb_epf_db_vector_count,
+ .db_vector_mask = ntb_epf_db_vector_mask,
.db_set_mask = ntb_epf_db_set_mask,
.mw_set_trans = ntb_epf_mw_set_trans,
.mw_clear_trans = ntb_epf_mw_clear_trans,
@@ -552,6 +631,12 @@ static int ntb_epf_init_dev(struct ntb_epf_dev *ndev)
struct device *dev = ndev->dev;
int ret;
+ 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;
+ }
+
/* One Link interrupt and rest doorbell interrupt */
ret = ntb_epf_init_isr(ndev, NTB_EPF_MIN_DB_COUNT + 1,
NTB_EPF_MAX_DB_COUNT + 1);
@@ -560,15 +645,13 @@ static int ntb_epf_init_dev(struct ntb_epf_dev *ndev)
return ret;
}
- ndev->db_valid_mask = BIT_ULL(ndev->db_count) - 1;
- ndev->mw_count = readl(ndev->ctrl_reg + NTB_EPF_MW_COUNT);
+ /*
+ * ndev->db_count includes an extra skipped slot due to the legacy
+ * doorbell layout, hence -1.
+ */
+ ndev->db_valid_mask = BIT_ULL(ndev->db_count - 1) - 1;
ndev->spad_count = readl(ndev->ctrl_reg + NTB_EPF_SPAD_COUNT);
- if (ndev->mw_count > NTB_EPF_MAX_MW_COUNT) {
- dev_err(dev, "Unsupported MW count: %u\n", ndev->mw_count);
- return -EINVAL;
- }
-
return 0;
}
@@ -662,7 +745,7 @@ static void ntb_epf_cleanup_isr(struct ntb_epf_dev *ndev)
ntb_epf_send_command(ndev, CMD_TEARDOWN_DOORBELL, ndev->db_count + 1);
for (i = 0; i < ndev->db_count + 1; i++)
- free_irq(pci_irq_vector(pdev, i), ndev);
+ free_irq(pci_irq_vector(pdev, i), &ndev->irq_ctx[i]);
pci_free_irq_vectors(pdev);
}
diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c
index d4dc3b24da607..e4c6f71934950 100644
--- a/drivers/pci/controller/dwc/pcie-designware-ep.c
+++ b/drivers/pci/controller/dwc/pcie-designware-ep.c
@@ -9,6 +9,7 @@
#include <linux/align.h>
#include <linux/bitfield.h>
#include <linux/of.h>
+#include <linux/overflow.h>
#include <linux/platform_device.h>
#include "pcie-designware.h"
@@ -817,6 +818,122 @@ dw_pcie_ep_get_features(struct pci_epc *epc, u8 func_no, u8 vfunc_no)
return ep->ops->get_features(ep);
}
+static const struct pci_epc_bar_rsvd_region *
+dw_pcie_ep_find_bar_rsvd_region(struct dw_pcie_ep *ep,
+ enum pci_epc_bar_rsvd_region_type type,
+ enum pci_barno *bar,
+ resource_size_t *bar_offset)
+{
+ const struct pci_epc_features *features;
+ const struct pci_epc_bar_desc *bar_desc;
+ const struct pci_epc_bar_rsvd_region *r;
+ int i, j;
+
+ if (!ep->ops->get_features)
+ return NULL;
+
+ features = ep->ops->get_features(ep);
+ if (!features)
+ return NULL;
+
+ for (i = BAR_0; i <= BAR_5; i++) {
+ bar_desc = &features->bar[i];
+
+ if (!bar_desc->nr_rsvd_regions || !bar_desc->rsvd_regions)
+ continue;
+
+ for (j = 0; j < bar_desc->nr_rsvd_regions; j++) {
+ r = &bar_desc->rsvd_regions[j];
+
+ if (r->type != type)
+ continue;
+
+ if (bar)
+ *bar = i;
+ if (bar_offset)
+ *bar_offset = r->offset;
+ return r;
+ }
+ }
+
+ return NULL;
+}
+
+static int
+dw_pcie_ep_get_aux_resources_count(struct pci_epc *epc, u8 func_no,
+ u8 vfunc_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;
+
+ if (!pci->edma_reg_size)
+ return 0;
+
+ if (edma->db_offset == ~0)
+ return 0;
+
+ return 1;
+}
+
+static int
+dw_pcie_ep_get_aux_resources(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
+ struct pci_epc_aux_resource *resources,
+ int num_resources)
+{
+ struct dw_pcie_ep *ep = epc_get_drvdata(epc);
+ struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
+ 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 dma_ctrl_bar_offset = 0;
+ resource_size_t dma_reg_size;
+ int count;
+
+ count = dw_pcie_ep_get_aux_resources_count(epc, func_no, vfunc_no);
+ if (count < 0)
+ return count;
+
+ if (num_resources < count)
+ return -ENOSPC;
+
+ if (!count)
+ return 0;
+
+ dma_reg_size = pci->edma_reg_size;
+
+ rsvd = dw_pcie_ep_find_bar_rsvd_region(ep,
+ PCI_EPC_BAR_RSVD_DMA_CTRL_MMIO,
+ &dma_ctrl_bar,
+ &dma_ctrl_bar_offset);
+ if (rsvd && rsvd->size < dma_reg_size)
+ dma_reg_size = rsvd->size;
+
+ /*
+ * For interrupt-emulation doorbells, report a standalone resource
+ * instead of bundling it into the DMA controller MMIO resource.
+ */
+ if (range_end_overflows_t(resource_size_t, db_offset,
+ sizeof(u32), dma_reg_size))
+ return -EINVAL;
+
+ resources[0] = (struct pci_epc_aux_resource) {
+ .type = PCI_EPC_AUX_DOORBELL_MMIO,
+ .phys_addr = pci->edma_reg_phys + db_offset,
+ .size = sizeof(u32),
+ .bar = dma_ctrl_bar,
+ .bar_offset = dma_ctrl_bar != NO_BAR ?
+ dma_ctrl_bar_offset + db_offset : 0,
+ .u.db_mmio = {
+ .irq = edma->db_irq,
+ .data = 0, /* write 0 to assert */
+ },
+ };
+
+ return 0;
+}
+
static const struct pci_epc_ops epc_ops = {
.write_header = dw_pcie_ep_write_header,
.set_bar = dw_pcie_ep_set_bar,
@@ -832,6 +949,8 @@ static const struct pci_epc_ops epc_ops = {
.start = dw_pcie_ep_start,
.stop = dw_pcie_ep_stop,
.get_features = dw_pcie_ep_get_features,
+ .get_aux_resources_count = dw_pcie_ep_get_aux_resources_count,
+ .get_aux_resources = dw_pcie_ep_get_aux_resources,
};
/**
diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
index c11cf61b8319e..22164e0068a9e 100644
--- a/drivers/pci/controller/dwc/pcie-designware.c
+++ b/drivers/pci/controller/dwc/pcie-designware.c
@@ -162,8 +162,12 @@ int dw_pcie_get_resources(struct dw_pcie *pci)
pci->edma.reg_base = devm_ioremap_resource(pci->dev, res);
if (IS_ERR(pci->edma.reg_base))
return PTR_ERR(pci->edma.reg_base);
+ pci->edma_reg_phys = res->start;
+ pci->edma_reg_size = resource_size(res);
} else if (pci->atu_size >= 2 * DEFAULT_DBI_DMA_OFFSET) {
pci->edma.reg_base = pci->atu_base + DEFAULT_DBI_DMA_OFFSET;
+ pci->edma_reg_phys = pci->atu_phys_addr + DEFAULT_DBI_DMA_OFFSET;
+ pci->edma_reg_size = pci->atu_size - DEFAULT_DBI_DMA_OFFSET;
}
}
diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index 3e69ef60165b0..f3314ceff8d77 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -544,6 +544,8 @@ struct dw_pcie {
int max_link_speed;
u8 n_fts[2];
struct dw_edma_chip edma;
+ phys_addr_t edma_reg_phys;
+ resource_size_t edma_reg_size;
bool l1ss_support; /* L1 PM Substates support */
struct clk_bulk_data app_clks[DW_PCIE_NUM_APP_CLKS];
struct clk_bulk_data core_clks[DW_PCIE_NUM_CORE_CLKS];
diff --git a/drivers/pci/endpoint/functions/pci-epf-ntb.c b/drivers/pci/endpoint/functions/pci-epf-ntb.c
index 2bdcc35b652cf..5314aca2188a0 100644
--- a/drivers/pci/endpoint/functions/pci-epf-ntb.c
+++ b/drivers/pci/endpoint/functions/pci-epf-ntb.c
@@ -559,12 +559,15 @@ static int epf_ntb_configure_db(struct epf_ntb *ntb,
struct pci_epc *epc;
int ret;
- if (db_count > MAX_DB_COUNT)
- return -EINVAL;
-
ntb_epc = ntb->epc[type];
epc = ntb_epc->epc;
+ if (!db_count || db_count > MAX_DB_COUNT) {
+ dev_err(&epc->dev, "DB count %d out of range (1 - %d)\n",
+ db_count, MAX_DB_COUNT);
+ return -EINVAL;
+ }
+
if (msix)
ret = epf_ntb_configure_msix(ntb, type, db_count);
else
@@ -1278,7 +1281,6 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb,
u8 func_no, vfunc_no;
struct pci_epc *epc;
struct device *dev;
- u32 db_count;
int ret;
ntb_epc = ntb->epc[type];
@@ -1296,17 +1298,16 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb,
func_no = ntb_epc->func_no;
vfunc_no = ntb_epc->vfunc_no;
- db_count = ntb->db_count;
- if (db_count > MAX_DB_COUNT) {
- dev_err(dev, "DB count cannot be more than %d\n", MAX_DB_COUNT);
+ if (!ntb->db_count || ntb->db_count > MAX_DB_COUNT) {
+ dev_err(dev, "DB count %d out of range (1 - %d)\n",
+ ntb->db_count, MAX_DB_COUNT);
return -EINVAL;
}
- ntb->db_count = db_count;
epc = ntb_epc->epc;
if (msi_capable) {
- ret = pci_epc_set_msi(epc, func_no, vfunc_no, db_count);
+ ret = pci_epc_set_msi(epc, func_no, vfunc_no, ntb->db_count);
if (ret) {
dev_err(dev, "%s intf: MSI configuration failed\n",
pci_epc_interface_string(type));
@@ -1315,7 +1316,7 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb,
}
if (msix_capable) {
- ret = pci_epc_set_msix(epc, func_no, vfunc_no, db_count,
+ ret = pci_epc_set_msix(epc, func_no, vfunc_no, ntb->db_count,
ntb_epc->msix_bar,
ntb_epc->msix_table_offset);
if (ret) {
diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c
index 591d301fa89d8..4802d4f80f780 100644
--- a/drivers/pci/endpoint/functions/pci-epf-test.c
+++ b/drivers/pci/endpoint/functions/pci-epf-test.c
@@ -94,6 +94,7 @@ struct pci_epf_test {
bool dma_private;
const struct pci_epc_features *epc_features;
struct pci_epf_bar db_bar;
+ bool db_bar_programmed;
size_t bar_size[PCI_STD_NUM_BARS];
};
@@ -733,7 +734,9 @@ static void pci_epf_test_enable_doorbell(struct pci_epf_test *epf_test,
{
u32 status = le32_to_cpu(reg->status);
struct pci_epf *epf = epf_test->epf;
+ struct pci_epf_doorbell_msg *db;
struct pci_epc *epc = epf->epc;
+ unsigned long irq_flags;
struct msi_msg *msg;
enum pci_barno bar;
size_t offset;
@@ -743,13 +746,28 @@ static void pci_epf_test_enable_doorbell(struct pci_epf_test *epf_test,
if (ret)
goto set_status_err;
- msg = &epf->db_msg[0].msg;
- bar = pci_epc_get_next_free_bar(epf_test->epc_features, epf_test->test_reg_bar + 1);
- if (bar < BAR_0)
- goto err_doorbell_cleanup;
+ db = &epf->db_msg[0];
+ msg = &db->msg;
+ epf_test->db_bar_programmed = false;
+
+ if (db->bar != NO_BAR) {
+ /*
+ * The doorbell target is already exposed via a platform-owned
+ * fixed BAR
+ */
+ bar = db->bar;
+ offset = db->offset;
+ } else {
+ bar = pci_epc_get_next_free_bar(epf_test->epc_features,
+ epf_test->test_reg_bar + 1);
+ if (bar < BAR_0)
+ goto err_doorbell_cleanup;
+ }
+
+ irq_flags = epf->db_msg[0].irq_flags | IRQF_ONESHOT;
ret = request_threaded_irq(epf->db_msg[0].virq, NULL,
- pci_epf_test_doorbell_handler, IRQF_ONESHOT,
+ pci_epf_test_doorbell_handler, irq_flags,
"pci-ep-test-doorbell", epf_test);
if (ret) {
dev_err(&epf->dev,
@@ -761,22 +779,30 @@ static void pci_epf_test_enable_doorbell(struct pci_epf_test *epf_test,
reg->doorbell_data = cpu_to_le32(msg->data);
reg->doorbell_bar = cpu_to_le32(bar);
- msg = &epf->db_msg[0].msg;
- ret = pci_epf_align_inbound_addr(epf, bar, ((u64)msg->address_hi << 32) | msg->address_lo,
- &epf_test->db_bar.phys_addr, &offset);
+ if (db->bar == NO_BAR) {
+ ret = pci_epf_align_inbound_addr(epf, bar,
+ ((u64)msg->address_hi << 32) |
+ msg->address_lo,
+ &epf_test->db_bar.phys_addr,
+ &offset);
- if (ret)
- goto err_free_irq;
+ if (ret)
+ goto err_free_irq;
+ }
reg->doorbell_offset = cpu_to_le32(offset);
- epf_test->db_bar.barno = bar;
- epf_test->db_bar.size = epf->bar[bar].size;
- epf_test->db_bar.flags = epf->bar[bar].flags;
+ if (db->bar == NO_BAR) {
+ epf_test->db_bar.barno = bar;
+ epf_test->db_bar.size = epf->bar[bar].size;
+ epf_test->db_bar.flags = epf->bar[bar].flags;
- ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, &epf_test->db_bar);
- if (ret)
- goto err_free_irq;
+ ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, &epf_test->db_bar);
+ if (ret)
+ goto err_free_irq;
+
+ epf_test->db_bar_programmed = true;
+ }
status |= STATUS_DOORBELL_ENABLE_SUCCESS;
reg->status = cpu_to_le32(status);
@@ -806,17 +832,23 @@ static void pci_epf_test_disable_doorbell(struct pci_epf_test *epf_test,
free_irq(epf->db_msg[0].virq, epf_test);
pci_epf_test_doorbell_cleanup(epf_test);
- /*
- * The doorbell feature temporarily overrides the inbound translation
- * to point to the address stored in epf_test->db_bar.phys_addr, i.e.,
- * it calls set_bar() twice without ever calling clear_bar(), as
- * calling clear_bar() would clear the BAR's PCI address assigned by
- * the host. Thus, when disabling the doorbell, restore the inbound
- * translation to point to the memory allocated for the BAR.
- */
- ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, &epf->bar[bar]);
- if (ret)
- goto set_status_err;
+ if (epf_test->db_bar_programmed) {
+ /*
+ * The doorbell feature temporarily overrides the inbound
+ * translation to point to the address stored in
+ * epf_test->db_bar.phys_addr, i.e., it calls set_bar()
+ * twice without ever calling clear_bar(), as calling
+ * clear_bar() would clear the BAR's PCI address assigned
+ * by the host. Thus, when disabling the doorbell, restore
+ * the inbound translation to point to the memory allocated
+ * for the BAR.
+ */
+ ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, &epf->bar[bar]);
+ if (ret)
+ goto set_status_err;
+
+ epf_test->db_bar_programmed = false;
+ }
status |= STATUS_DOORBELL_DISABLE_SUCCESS;
reg->status = cpu_to_le32(status);
diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c
index 2256c3062b1ae..c3caec927d748 100644
--- a/drivers/pci/endpoint/functions/pci-epf-vntb.c
+++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c
@@ -37,6 +37,7 @@
*/
#include <linux/atomic.h>
+#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
@@ -66,9 +67,11 @@ static struct workqueue_struct *kpcintb_workqueue;
#define NTB_MW_OFFSET 2
#define DB_COUNT_MASK GENMASK(15, 0)
#define MSIX_ENABLE BIT(16)
-#define MAX_DB_COUNT 32
#define MAX_MW 4
+/* Limit per-work execution to avoid monopolizing kworker on doorbell storms. */
+#define VNTB_PEER_DB_WORK_BUDGET 5
+
enum epf_ntb_bar {
BAR_CONFIG,
BAR_DB,
@@ -79,6 +82,15 @@ enum epf_ntb_bar {
VNTB_BAR_NUM,
};
+enum epf_irq_slot {
+ EPF_IRQ_LINK = 0,
+ EPF_IRQ_RESERVED_DB, /* Historically skipped slot */
+ EPF_IRQ_DB_START,
+};
+
+#define MIN_DB_COUNT (EPF_IRQ_DB_START + 1)
+#define MAX_DB_COUNT 32
+
/*
* +--------------------------------------------------+ Base
* | |
@@ -129,11 +141,18 @@ struct epf_ntb {
u32 spad_count;
u64 mws_size[MAX_MW];
atomic64_t db;
+ atomic64_t peer_db_pending;
+ struct work_struct peer_db_work;
u32 vbus_number;
u16 vntb_pid;
u16 vntb_vid;
bool linkup;
+
+ /*
+ * True when doorbells are interrupt-driven (MSI or embedded), false
+ * when polled.
+ */
bool msi_doorbell;
u32 spad_size;
@@ -261,10 +280,11 @@ static void epf_ntb_cmd_handler(struct work_struct *work)
ntb = container_of(work, struct epf_ntb, cmd_handler.work);
- for (i = 1; i < ntb->db_count && !ntb->msi_doorbell; i++) {
+ for (i = EPF_IRQ_DB_START; i < ntb->db_count && !ntb->msi_doorbell;
+ i++) {
if (ntb->epf_db[i]) {
- atomic64_or(1 << (i - 1), &ntb->db);
- ntb_db_event(&ntb->ntb, i);
+ atomic64_or(1 << (i - EPF_IRQ_DB_START), &ntb->db);
+ ntb_db_event(&ntb->ntb, i - EPF_IRQ_DB_START);
ntb->epf_db[i] = 0;
}
}
@@ -330,10 +350,10 @@ static irqreturn_t epf_ntb_doorbell_handler(int irq, void *data)
struct epf_ntb *ntb = data;
int i;
- for (i = 1; i < ntb->db_count; i++)
+ for (i = EPF_IRQ_DB_START; i < ntb->db_count; i++)
if (irq == ntb->epf->db_msg[i].virq) {
- atomic64_or(1 << (i - 1), &ntb->db);
- ntb_db_event(&ntb->ntb, i);
+ atomic64_or(1 << (i - EPF_IRQ_DB_START), &ntb->db);
+ ntb_db_event(&ntb->ntb, i - EPF_IRQ_DB_START);
}
return IRQ_HANDLED;
@@ -483,7 +503,6 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb)
{
const struct pci_epc_features *epc_features;
struct device *dev;
- u32 db_count;
int ret;
dev = &ntb->epf->dev;
@@ -495,14 +514,12 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb)
return -EINVAL;
}
- db_count = ntb->db_count;
- if (db_count > MAX_DB_COUNT) {
- dev_err(dev, "DB count cannot be more than %d\n", MAX_DB_COUNT);
+ if (ntb->db_count < MIN_DB_COUNT || ntb->db_count > MAX_DB_COUNT) {
+ dev_err(dev, "DB count %d out of range (%d - %d)\n",
+ ntb->db_count, MIN_DB_COUNT, MAX_DB_COUNT);
return -EINVAL;
}
- ntb->db_count = db_count;
-
if (epc_features->msi_capable) {
ret = pci_epc_set_msi(ntb->epf->epc,
ntb->epf->func_no,
@@ -517,6 +534,17 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb)
return 0;
}
+static bool epf_ntb_db_irq_is_duplicated(const struct pci_epf *epf, unsigned int idx)
+{
+ unsigned int i;
+
+ for (i = 0; i < idx; i++)
+ if (epf->db_msg[i].virq == epf->db_msg[idx].virq)
+ return true;
+
+ return false;
+}
+
static int epf_ntb_db_bar_init_msi_doorbell(struct epf_ntb *ntb,
struct pci_epf_bar *db_bar,
const struct pci_epc_features *epc_features,
@@ -533,9 +561,24 @@ static int epf_ntb_db_bar_init_msi_doorbell(struct epf_ntb *ntb,
if (ret)
return ret;
+ /*
+ * The doorbell target may already be exposed by a platform-owned fixed
+ * BAR. In that case, we must reuse it and the requested db_bar must
+ * match.
+ */
+ if (epf->db_msg[0].bar != NO_BAR && epf->db_msg[0].bar != barno) {
+ ret = -EINVAL;
+ goto err_free_doorbell;
+ }
+
for (req = 0; req < ntb->db_count; req++) {
+ /* Avoid requesting duplicate handlers */
+ if (epf_ntb_db_irq_is_duplicated(epf, req))
+ continue;
+
ret = request_irq(epf->db_msg[req].virq, epf_ntb_doorbell_handler,
- 0, "pci_epf_vntb_db", ntb);
+ epf->db_msg[req].irq_flags, "pci_epf_vntb_db",
+ ntb);
if (ret) {
dev_err(&epf->dev,
@@ -545,6 +588,22 @@ static int epf_ntb_db_bar_init_msi_doorbell(struct epf_ntb *ntb,
}
}
+ if (epf->db_msg[0].bar != NO_BAR) {
+ for (i = 0; i < ntb->db_count; i++) {
+ msg = &epf->db_msg[i].msg;
+
+ if (epf->db_msg[i].bar != barno) {
+ ret = -EINVAL;
+ goto err_free_irq;
+ }
+
+ ntb->reg->db_data[i] = msg->data;
+ ntb->reg->db_offset[i] = epf->db_msg[i].offset;
+ }
+ goto out;
+ }
+
+ /* Program inbound mapping for the doorbell */
msg = &epf->db_msg[0].msg;
high = 0;
@@ -591,6 +650,7 @@ static int epf_ntb_db_bar_init_msi_doorbell(struct epf_ntb *ntb,
ntb->reg->db_offset[i] = offset;
}
+out:
ntb->reg->db_entry_size = 0;
ntb->msi_doorbell = true;
@@ -598,9 +658,13 @@ static int epf_ntb_db_bar_init_msi_doorbell(struct epf_ntb *ntb,
return 0;
err_free_irq:
- for (req--; req >= 0; req--)
+ for (req--; req >= 0; req--) {
+ if (epf_ntb_db_irq_is_duplicated(epf, req))
+ continue;
free_irq(epf->db_msg[req].virq, ntb);
+ }
+err_free_doorbell:
pci_epf_free_doorbell(ntb->epf);
return ret;
}
@@ -666,8 +730,11 @@ static void epf_ntb_db_bar_clear(struct epf_ntb *ntb)
if (ntb->msi_doorbell) {
int i;
- for (i = 0; i < ntb->db_count; i++)
+ for (i = 0; i < ntb->db_count; i++) {
+ if (epf_ntb_db_irq_is_duplicated(ntb->epf, i))
+ continue;
free_irq(ntb->epf->db_msg[i].virq, ntb);
+ }
}
if (ntb->epf->db_msg)
@@ -920,6 +987,9 @@ static int epf_ntb_epc_init(struct epf_ntb *ntb)
INIT_DELAYED_WORK(&ntb->cmd_handler, epf_ntb_cmd_handler);
queue_work(kpcintb_workqueue, &ntb->cmd_handler.work);
+ atomic64_set(&ntb->peer_db_pending, 0);
+ enable_work(&ntb->peer_db_work);
+
return 0;
err_write_header:
@@ -943,11 +1013,18 @@ err_config_interrupt:
static void epf_ntb_epc_cleanup(struct epf_ntb *ntb)
{
disable_delayed_work_sync(&ntb->cmd_handler);
+ disable_work_sync(&ntb->peer_db_work);
+ atomic64_set(&ntb->peer_db_pending, 0);
epf_ntb_mw_bar_clear(ntb, ntb->num_mws);
epf_ntb_db_bar_clear(ntb);
epf_ntb_config_sspad_bar_clear(ntb);
}
+static bool epf_ntb_epc_attached(struct epf_ntb *ntb)
+{
+ return ntb->epf->epc || ntb->epf->sec_epc;
+}
+
#define EPF_NTB_R(_name) \
static ssize_t epf_ntb_##_name##_show(struct config_item *item, \
char *page) \
@@ -967,6 +1044,9 @@ static ssize_t epf_ntb_##_name##_store(struct config_item *item, \
u32 val; \
int ret; \
\
+ if (epf_ntb_epc_attached(ntb)) \
+ return -EOPNOTSUPP; \
+ \
ret = kstrtou32(page, 0, &val); \
if (ret) \
return ret; \
@@ -1009,6 +1089,9 @@ static ssize_t epf_ntb_##_name##_store(struct config_item *item, \
u64 val; \
int ret; \
\
+ if (epf_ntb_epc_attached(ntb)) \
+ return -EOPNOTSUPP; \
+ \
ret = kstrtou64(page, 0, &val); \
if (ret) \
return ret; \
@@ -1047,6 +1130,9 @@ static ssize_t epf_ntb_##_name##_store(struct config_item *item, \
int val; \
int ret; \
\
+ if (epf_ntb_epc_attached(ntb)) \
+ return -EOPNOTSUPP; \
+ \
ret = kstrtoint(page, 0, &val); \
if (ret) \
return ret; \
@@ -1067,6 +1153,9 @@ static ssize_t epf_ntb_num_mws_store(struct config_item *item,
u32 val;
int ret;
+ if (epf_ntb_epc_attached(ntb))
+ return -EOPNOTSUPP;
+
ret = kstrtou32(page, 0, &val);
if (ret)
return ret;
@@ -1079,10 +1168,32 @@ static ssize_t epf_ntb_num_mws_store(struct config_item *item,
return len;
}
+static ssize_t epf_ntb_db_count_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);
+ u32 val;
+ int ret;
+
+ if (epf_ntb_epc_attached(ntb))
+ return -EOPNOTSUPP;
+
+ ret = kstrtou32(page, 0, &val);
+ if (ret)
+ return ret;
+
+ if (val < MIN_DB_COUNT || val > MAX_DB_COUNT)
+ return -EINVAL;
+
+ WRITE_ONCE(ntb->db_count, val);
+
+ return len;
+}
+
EPF_NTB_R(spad_count)
EPF_NTB_W(spad_count)
EPF_NTB_R(db_count)
-EPF_NTB_W(db_count)
EPF_NTB_R(num_mws)
EPF_NTB_R(vbus_number)
EPF_NTB_W(vbus_number)
@@ -1251,9 +1362,48 @@ static int vntb_epf_peer_mw_count(struct ntb_dev *ntb)
return ntb_ndev(ntb)->num_mws;
}
+static int vntb_epf_db_vector_count(struct ntb_dev *ntb)
+{
+ struct epf_ntb *ndev = ntb_ndev(ntb);
+ u32 db_count = READ_ONCE(ndev->db_count);
+
+ /*
+ * db_count is the total number of doorbell slots exposed to
+ * the peer, including:
+ * - slot #0 reserved for link events
+ * - slot #1 historically unused (kept for protocol compatibility)
+ *
+ * Report only usable per-vector doorbell interrupts.
+ */
+ if (db_count < MIN_DB_COUNT || db_count > MAX_DB_COUNT)
+ return 0;
+
+ return db_count - EPF_IRQ_DB_START;
+}
+
static u64 vntb_epf_db_valid_mask(struct ntb_dev *ntb)
{
- return BIT_ULL(ntb_ndev(ntb)->db_count) - 1;
+ int nr_vec = vntb_epf_db_vector_count(ntb);
+
+ if (!nr_vec)
+ return 0;
+
+ return GENMASK_ULL(nr_vec - 1, 0);
+}
+
+static u64 vntb_epf_db_vector_mask(struct ntb_dev *ntb, int db_vector)
+{
+ int nr_vec;
+
+ /*
+ * Doorbell vectors are numbered [0 .. nr_vec - 1], where nr_vec
+ * excludes the two reserved slots described above.
+ */
+ nr_vec = vntb_epf_db_vector_count(ntb);
+ if (db_vector < 0 || db_vector >= nr_vec)
+ return 0;
+
+ return BIT_ULL(db_vector);
}
static int vntb_epf_db_set_mask(struct ntb_dev *ntb, u64 db_bits)
@@ -1357,22 +1507,84 @@ static int vntb_epf_peer_spad_write(struct ntb_dev *ndev, int pidx, int idx, u32
return 0;
}
-static int vntb_epf_peer_db_set(struct ntb_dev *ndev, u64 db_bits)
+static void vntb_epf_peer_db_work(struct work_struct *work)
{
- u32 interrupt_num = ffs(db_bits) + 1;
- struct epf_ntb *ntb = ntb_ndev(ndev);
+ 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;
u8 func_no, vfunc_no;
+ unsigned int db_bit;
+ u32 interrupt_num;
+ u64 db_bits;
int ret;
- func_no = ntb->epf->func_no;
- vfunc_no = ntb->epf->vfunc_no;
+ if (!epf || !epf->epc)
+ return;
- ret = pci_epc_raise_irq(ntb->epf->epc, func_no, vfunc_no,
- PCI_IRQ_MSI, interrupt_num + 1);
- if (ret)
- dev_err(&ntb->ntb.dev, "Failed to raise IRQ\n");
+ func_no = epf->func_no;
+ vfunc_no = epf->vfunc_no;
- return ret;
+ /*
+ * Drain doorbells from peer_db_pending in snapshots (atomic64_xchg()).
+ * Limit the number of snapshots handled per run so we don't monopolize
+ * the workqueue under a doorbell storm.
+ */
+ while (budget--) {
+ db_bits = atomic64_xchg(&ntb->peer_db_pending, 0);
+ if (!db_bits)
+ return;
+
+ 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.
+ *
+ * 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)
+ * ...
+ *
+ * Do not change this mapping to avoid breaking
+ * interoperability with older peers.
+ */
+ db_bit = __ffs64(db_bits);
+ interrupt_num = db_bit + EPF_IRQ_DB_START + 1;
+ db_bits &= ~BIT_ULL(db_bit);
+
+ ret = pci_epc_raise_irq(epf->epc, func_no, vfunc_no,
+ PCI_IRQ_MSI, interrupt_num);
+ if (ret)
+ dev_err(&ntb->ntb.dev,
+ "Failed to raise IRQ for interrupt_num %u: %d\n",
+ interrupt_num, ret);
+ }
+ }
+
+ if (atomic64_read(&ntb->peer_db_pending))
+ queue_work(kpcintb_workqueue, &ntb->peer_db_work);
+}
+
+static int vntb_epf_peer_db_set(struct ntb_dev *ndev, u64 db_bits)
+{
+ struct epf_ntb *ntb = ntb_ndev(ndev);
+
+ db_bits &= vntb_epf_db_valid_mask(ndev);
+ if (!db_bits)
+ return 0;
+
+ /*
+ * .peer_db_set() may be called from atomic context. pci_epc_raise_irq()
+ * can sleep (it takes epc->lock), so defer MSI raising to process
+ * context. Doorbell requests are coalesced in peer_db_pending.
+ */
+ atomic64_or(db_bits, &ntb->peer_db_pending);
+ queue_work(kpcintb_workqueue, &ntb->peer_db_work);
+
+ return 0;
}
static u64 vntb_epf_db_read(struct ntb_dev *ndev)
@@ -1441,6 +1653,8 @@ static const struct ntb_dev_ops vntb_epf_ops = {
.spad_count = vntb_epf_spad_count,
.peer_mw_count = vntb_epf_peer_mw_count,
.db_valid_mask = vntb_epf_db_valid_mask,
+ .db_vector_count = vntb_epf_db_vector_count,
+ .db_vector_mask = vntb_epf_db_vector_mask,
.db_set_mask = vntb_epf_db_set_mask,
.mw_set_trans = vntb_epf_mw_set_trans,
.mw_clear_trans = vntb_epf_mw_clear_trans,
@@ -1619,6 +1833,10 @@ static int epf_ntb_probe(struct pci_epf *epf,
ntb->epf = epf;
ntb->vbus_number = 0xff;
+ INIT_WORK(&ntb->peer_db_work, vntb_epf_peer_db_work);
+ disable_work(&ntb->peer_db_work);
+ atomic64_set(&ntb->peer_db_pending, 0);
+
/* Initially, no bar is assigned */
for (i = 0; i < VNTB_BAR_NUM; i++)
ntb->epf_ntb_bar[i] = NO_BAR;
diff --git a/drivers/pci/endpoint/pci-ep-msi.c b/drivers/pci/endpoint/pci-ep-msi.c
index 1395919571f83..0855c7930abb4 100644
--- a/drivers/pci/endpoint/pci-ep-msi.c
+++ b/drivers/pci/endpoint/pci-ep-msi.c
@@ -6,8 +6,11 @@
* Author: Frank Li <Frank.Li@nxp.com>
*/
+#include <linux/align.h>
+#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/export.h>
+#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include <linux/module.h>
#include <linux/msi.h>
@@ -35,23 +38,116 @@ static void pci_epf_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
pci_epc_put(epc);
}
-int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db)
+static int pci_epf_alloc_doorbell_embedded(struct pci_epf *epf, u16 num_db)
{
+ const struct pci_epc_aux_resource *doorbell = NULL;
+ struct pci_epf_doorbell_msg *msg;
struct pci_epc *epc = epf->epc;
- struct device *dev = &epf->dev;
- struct irq_domain *domain;
- void *msg;
- int ret;
- int i;
+ size_t map_size = 0, off = 0;
+ dma_addr_t iova_base = 0;
+ phys_addr_t phys_base;
+ int count, ret, i;
+ u64 addr;
- /* TODO: Multi-EPF support */
- if (list_first_entry_or_null(&epc->pci_epf, struct pci_epf, list) != epf) {
- dev_err(dev, "MSI doorbell doesn't support multiple EPF\n");
+ count = pci_epc_get_aux_resources_count(epc, epf->func_no,
+ epf->vfunc_no);
+ if (count < 0)
+ return count;
+ if (!count)
+ return -ENODEV;
+
+ struct pci_epc_aux_resource *res __free(kfree) =
+ kcalloc(count, sizeof(*res), GFP_KERNEL);
+ if (!res)
+ return -ENOMEM;
+
+ ret = pci_epc_get_aux_resources(epc, epf->func_no, epf->vfunc_no,
+ res, count);
+ if (ret)
+ return ret;
+
+ /* TODO: Support multiple DOORBELL_MMIO resources per EPC. */
+ for (i = 0; i < count; i++) {
+ if (res[i].type != PCI_EPC_AUX_DOORBELL_MMIO)
+ continue;
+
+ doorbell = &res[i];
+ break;
+ }
+ if (!doorbell)
+ return -ENODEV;
+ addr = doorbell->phys_addr;
+ if (!IS_ALIGNED(addr, sizeof(u32)))
return -EINVAL;
+
+ /*
+ * Reuse the pre-exposed BAR window if available. Otherwise map the MMIO
+ * doorbell resource here. Any required IOMMU mapping is handled
+ * internally, matching the MSI doorbell semantics.
+ */
+ if (doorbell->bar == NO_BAR) {
+ phys_base = addr & PAGE_MASK;
+ off = addr - phys_base;
+ map_size = PAGE_ALIGN(off + sizeof(u32));
+
+ iova_base = dma_map_resource(epc->dev.parent, phys_base,
+ map_size, DMA_FROM_DEVICE, 0);
+ if (dma_mapping_error(epc->dev.parent, iova_base))
+ return -EIO;
+
+ addr = iova_base + off;
}
- if (epf->db_msg)
- return -EBUSY;
+ msg = kcalloc(num_db, sizeof(*msg), GFP_KERNEL);
+ if (!msg) {
+ ret = -ENOMEM;
+ goto err_unmap;
+ }
+
+ /*
+ * Embedded doorbell backends (e.g. DesignWare eDMA interrupt emulation)
+ * typically provide a single IRQ and do not offer per-doorbell
+ * distinguishable address/data pairs. The EPC aux resource therefore
+ * exposes one DOORBELL_MMIO entry (u.db_mmio.irq).
+ *
+ * Still, pci_epf_alloc_doorbell() allows requesting multiple doorbells.
+ * For such backends we replicate the same address/data for each entry
+ * and mark the IRQ as shared (IRQF_SHARED). Consumers must treat them
+ * as equivalent "kick" doorbells.
+ */
+ for (i = 0; i < num_db; i++)
+ msg[i] = (struct pci_epf_doorbell_msg) {
+ .msg.address_lo = (u32)addr,
+ .msg.address_hi = (u32)(addr >> 32),
+ .msg.data = doorbell->u.db_mmio.data,
+ .virq = doorbell->u.db_mmio.irq,
+ .irq_flags = IRQF_SHARED,
+ .type = PCI_EPF_DOORBELL_EMBEDDED,
+ .bar = doorbell->bar,
+ .offset = (doorbell->bar == NO_BAR) ? 0 :
+ doorbell->bar_offset,
+ .iova_base = iova_base,
+ .iova_size = map_size,
+ };
+
+ epf->num_db = num_db;
+ epf->db_msg = msg;
+ return 0;
+
+err_unmap:
+ if (map_size)
+ dma_unmap_resource(epc->dev.parent, iova_base, map_size,
+ DMA_FROM_DEVICE, 0);
+ return ret;
+}
+
+static int pci_epf_alloc_doorbell_msi(struct pci_epf *epf, u16 num_db)
+{
+ struct pci_epf_doorbell_msg *msg;
+ struct device *dev = &epf->dev;
+ struct pci_epc *epc = epf->epc;
+ struct irq_domain *domain;
+ int ret, i;
domain = of_msi_map_get_device_domain(epc->dev.parent, 0,
DOMAIN_BUS_PLATFORM_MSI);
@@ -74,6 +170,12 @@ int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db)
if (!msg)
return -ENOMEM;
+ for (i = 0; i < num_db; i++)
+ msg[i] = (struct pci_epf_doorbell_msg) {
+ .type = PCI_EPF_DOORBELL_MSI,
+ .bar = NO_BAR,
+ };
+
epf->num_db = num_db;
epf->db_msg = msg;
@@ -90,13 +192,60 @@ int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db)
for (i = 0; i < num_db; i++)
epf->db_msg[i].virq = msi_get_virq(epc->dev.parent, i);
- return ret;
+ return 0;
+}
+
+int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db)
+{
+ struct pci_epc *epc = epf->epc;
+ struct device *dev = &epf->dev;
+ int ret;
+
+ /* TODO: Multi-EPF support */
+ if (list_first_entry_or_null(&epc->pci_epf, struct pci_epf, list) != epf) {
+ dev_err(dev, "Doorbell doesn't support multiple EPF\n");
+ return -EINVAL;
+ }
+
+ if (epf->db_msg)
+ return -EBUSY;
+
+ ret = pci_epf_alloc_doorbell_msi(epf, num_db);
+ if (!ret)
+ return 0;
+
+ /*
+ * Fall back to embedded doorbell only when platform MSI is unavailable
+ * for this EPC.
+ */
+ if (ret != -ENODEV)
+ return ret;
+
+ ret = pci_epf_alloc_doorbell_embedded(epf, num_db);
+ if (ret) {
+ dev_err(dev, "Failed to allocate doorbell: %d\n", ret);
+ return ret;
+ }
+
+ dev_info(dev, "Using embedded (DMA) doorbell fallback\n");
+ return 0;
}
EXPORT_SYMBOL_GPL(pci_epf_alloc_doorbell);
void pci_epf_free_doorbell(struct pci_epf *epf)
{
- platform_device_msi_free_irqs_all(epf->epc->dev.parent);
+ struct pci_epf_doorbell_msg *msg0;
+ struct pci_epc *epc = epf->epc;
+
+ if (!epf->db_msg)
+ return;
+
+ msg0 = &epf->db_msg[0];
+ if (msg0->type == PCI_EPF_DOORBELL_MSI)
+ platform_device_msi_free_irqs_all(epf->epc->dev.parent);
+ else if (msg0->type == PCI_EPF_DOORBELL_EMBEDDED && msg0->iova_size)
+ dma_unmap_resource(epc->dev.parent, msg0->iova_base,
+ msg0->iova_size, DMA_FROM_DEVICE, 0);
kfree(epf->db_msg);
epf->db_msg = NULL;
diff --git a/drivers/pci/endpoint/pci-epc-core.c b/drivers/pci/endpoint/pci-epc-core.c
index 6c3c58185fc5d..831b40458dcd8 100644
--- a/drivers/pci/endpoint/pci-epc-core.c
+++ b/drivers/pci/endpoint/pci-epc-core.c
@@ -157,6 +157,86 @@ const struct pci_epc_features *pci_epc_get_features(struct pci_epc *epc,
EXPORT_SYMBOL_GPL(pci_epc_get_features);
/**
+ * pci_epc_get_aux_resources_count() - get the number of EPC-provided auxiliary resources
+ * @epc: EPC device
+ * @func_no: function number
+ * @vfunc_no: virtual function number
+ *
+ * Some EPC backends integrate auxiliary blocks (e.g. DMA engines) whose control
+ * registers and/or descriptor memories can be exposed to the host by mapping
+ * them into BAR space. This helper queries how many such resources the backend
+ * provides.
+ *
+ * Return: the number of available resources on success, -EOPNOTSUPP if the
+ * backend does not support auxiliary resource queries, or another -errno on
+ * failure.
+ */
+int pci_epc_get_aux_resources_count(struct pci_epc *epc, u8 func_no,
+ u8 vfunc_no)
+{
+ int count;
+
+ if (!epc || !epc->ops)
+ return -EINVAL;
+
+ if (!pci_epc_function_is_valid(epc, func_no, vfunc_no))
+ return -EINVAL;
+
+ if (!epc->ops->get_aux_resources_count)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&epc->lock);
+ count = epc->ops->get_aux_resources_count(epc, func_no,
+ vfunc_no);
+ mutex_unlock(&epc->lock);
+
+ return count;
+}
+EXPORT_SYMBOL_GPL(pci_epc_get_aux_resources_count);
+
+/**
+ * pci_epc_get_aux_resources() - query EPC-provided auxiliary resources
+ * @epc: EPC device
+ * @func_no: function number
+ * @vfunc_no: virtual function number
+ * @resources: output array
+ * @num_resources: size of @resources array in entries
+ *
+ * Some EPC backends integrate auxiliary blocks (e.g. DMA engines) whose control
+ * registers and/or descriptor memories can be exposed to the host by mapping
+ * them into BAR space. This helper queries the backend for such resources.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the backend does not support auxiliary
+ * resource queries, or another -errno on failure.
+ */
+int pci_epc_get_aux_resources(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
+ struct pci_epc_aux_resource *resources,
+ int num_resources)
+{
+ int ret;
+
+ if (!resources || num_resources <= 0)
+ return -EINVAL;
+
+ if (!epc || !epc->ops)
+ return -EINVAL;
+
+ if (!pci_epc_function_is_valid(epc, func_no, vfunc_no))
+ return -EINVAL;
+
+ if (!epc->ops->get_aux_resources)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&epc->lock);
+ ret = epc->ops->get_aux_resources(epc, func_no, vfunc_no, resources,
+ num_resources);
+ mutex_unlock(&epc->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pci_epc_get_aux_resources);
+
+/**
* pci_epc_stop() - stop the PCI link
* @epc: the link of the EPC device that has to be stopped
*