From cc07b903a646bf6592182b27c63457d92f128125 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Tue, 14 Apr 2026 23:15:08 +0900 Subject: PCI: endpoint: Add auxiliary resource query API Endpoint controller drivers may integrate auxiliary blocks (e.g. DMA engines) whose register windows and descriptor memories metadata need to be exposed to a remote peer. Endpoint function drivers need a generic way to discover such resources without hard-coding controller-specific helpers. Add pci_epc_get_aux_resources_count() / pci_epc_get_aux_resources() and the corresponding pci_epc_ops callbacks. The count helper returns the number of available resources, while the get helper fills a caller-provided array of resources described by type, physical address and size, plus type-specific metadata. Suggested-by: Manivannan Sadhasivam Suggested-by: Frank Li Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260414141514.1341429-2-den@valinux.co.jp --- include/linux/pci-epc.h | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) (limited to 'include') diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h index 1eca1264815b..f247cf9bcf1a 100644 --- a/include/linux/pci-epc.h +++ b/include/linux/pci-epc.h @@ -61,6 +61,47 @@ struct pci_epc_map { void __iomem *virt_addr; }; +/** + * enum pci_epc_aux_resource_type - auxiliary resource type identifiers + * @PCI_EPC_AUX_DOORBELL_MMIO: Doorbell MMIO, that might be outside the DMA + * controller register window + * + * EPC backends may expose auxiliary blocks (e.g. DMA engines) by mapping their + * register windows and descriptor memories into BAR space. This enum + * identifies the type of each exposable resource. + */ +enum pci_epc_aux_resource_type { + PCI_EPC_AUX_DOORBELL_MMIO, +}; + +/** + * struct pci_epc_aux_resource - a physical auxiliary resource that may be + * exposed for peer use + * @type: resource type, see enum pci_epc_aux_resource_type + * @phys_addr: physical base address of the resource + * @size: size of the resource in bytes + * @bar: BAR number where this resource is already exposed to the RC + * (NO_BAR if not) + * @bar_offset: offset within @bar where the resource starts (valid iff + * @bar != NO_BAR) + * @u: type-specific metadata + */ +struct pci_epc_aux_resource { + enum pci_epc_aux_resource_type type; + phys_addr_t phys_addr; + resource_size_t size; + enum pci_barno bar; + resource_size_t bar_offset; + + union { + /* PCI_EPC_AUX_DOORBELL_MMIO */ + struct { + int irq; /* IRQ number for the doorbell handler */ + u32 data; /* write value to ring the doorbell */ + } db_mmio; + } u; +}; + /** * struct pci_epc_ops - set of function pointers for performing EPC operations * @write_header: ops to populate configuration space header @@ -84,6 +125,9 @@ struct pci_epc_map { * @start: ops to start the PCI link * @stop: ops to stop the PCI link * @get_features: ops to get the features supported by the EPC + * @get_aux_resources_count: ops to get the number of controller-owned + * auxiliary resources + * @get_aux_resources: ops to retrieve controller-owned auxiliary resources * @owner: the module owner containing the ops */ struct pci_epc_ops { @@ -115,6 +159,11 @@ struct pci_epc_ops { void (*stop)(struct pci_epc *epc); const struct pci_epc_features* (*get_features)(struct pci_epc *epc, u8 func_no, u8 vfunc_no); + int (*get_aux_resources_count)(struct pci_epc *epc, u8 func_no, + u8 vfunc_no); + int (*get_aux_resources)(struct pci_epc *epc, u8 func_no, u8 vfunc_no, + struct pci_epc_aux_resource *resources, + int num_resources); struct module *owner; }; @@ -343,6 +392,11 @@ int pci_epc_start(struct pci_epc *epc); void pci_epc_stop(struct pci_epc *epc); const struct pci_epc_features *pci_epc_get_features(struct pci_epc *epc, u8 func_no, u8 vfunc_no); +int pci_epc_get_aux_resources_count(struct pci_epc *epc, u8 func_no, + u8 vfunc_no); +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); enum pci_barno pci_epc_get_first_free_bar(const struct pci_epc_features *epc_features); enum pci_barno pci_epc_get_next_free_bar(const struct pci_epc_features -- cgit From a3a079e5c5b7748f206c4baeb92593f9eca3184a Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Tue, 14 Apr 2026 23:15:11 +0900 Subject: PCI: endpoint: pci-ep-msi: Refactor doorbell allocation for new backends Prepare pci-ep-msi for non-MSI doorbell backends. Factor MSI doorbell allocation into a helper and extend struct pci_epf_doorbell_msg with: - irq_flags: required IRQ request flags (e.g. IRQF_SHARED for some backends) - type: doorbell backend type - bar/offset: pre-exposed doorbell target location, if any Initialize these fields for the existing MSI-backed doorbell implementation. Also add PCI_EPF_DOORBELL_EMBEDDED type, which is to be implemented in a follow-up patch. No functional changes. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Tested-by: Niklas Cassel Reviewed-by: Frank Li Link: https://patch.msgid.link/20260414141514.1341429-5-den@valinux.co.jp --- drivers/pci/endpoint/pci-ep-msi.c | 54 ++++++++++++++++++++++++++++----------- include/linux/pci-epf.h | 23 +++++++++++++++-- 2 files changed, 60 insertions(+), 17 deletions(-) (limited to 'include') diff --git a/drivers/pci/endpoint/pci-ep-msi.c b/drivers/pci/endpoint/pci-ep-msi.c index 1395919571f8..85fe46103220 100644 --- a/drivers/pci/endpoint/pci-ep-msi.c +++ b/drivers/pci/endpoint/pci-ep-msi.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -35,23 +36,13 @@ 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_msi(struct pci_epf *epf, u16 num_db) { - struct pci_epc *epc = epf->epc; + struct pci_epf_doorbell_msg *msg; struct device *dev = &epf->dev; + struct pci_epc *epc = epf->epc; struct irq_domain *domain; - void *msg; - int ret; - int i; - - /* 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"); - return -EINVAL; - } - - if (epf->db_msg) - return -EBUSY; + int ret, i; domain = of_msi_map_get_device_domain(epc->dev.parent, 0, DOMAIN_BUS_PLATFORM_MSI); @@ -74,6 +65,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 +87,40 @@ 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 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; + + dev_err(dev, "Failed to allocate doorbell: %d\n", ret); return ret; } 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); + if (!epf->db_msg) + return; + + if (epf->db_msg[0].type == PCI_EPF_DOORBELL_MSI) + platform_device_msi_free_irqs_all(epf->epc->dev.parent); kfree(epf->db_msg); epf->db_msg = NULL; diff --git a/include/linux/pci-epf.h b/include/linux/pci-epf.h index 7737a7c03260..cd747447a1ea 100644 --- a/include/linux/pci-epf.h +++ b/include/linux/pci-epf.h @@ -152,14 +152,33 @@ struct pci_epf_bar { struct pci_epf_bar_submap *submap; }; +enum pci_epf_doorbell_type { + PCI_EPF_DOORBELL_MSI = 0, + PCI_EPF_DOORBELL_EMBEDDED, +}; + /** * struct pci_epf_doorbell_msg - represents doorbell message - * @msg: MSI message - * @virq: IRQ number of this doorbell MSI message + * @msg: Doorbell address/data pair to be mapped into BAR space. + * For MSI-backed doorbells this is the MSI message, while for + * "embedded" doorbells this represents an MMIO write that asserts + * an interrupt on the EP side. + * @virq: IRQ number of this doorbell message + * @irq_flags: Required flags for request_irq()/request_threaded_irq(). + * Callers may OR-in additional flags (e.g. IRQF_ONESHOT). + * @type: Doorbell type. + * @bar: BAR number where the doorbell target is already exposed to the RC + * (NO_BAR if not) + * @offset: offset within @bar for the doorbell target (valid iff + * @bar != NO_BAR) */ struct pci_epf_doorbell_msg { struct msi_msg msg; int virq; + unsigned long irq_flags; + enum pci_epf_doorbell_type type; + enum pci_barno bar; + resource_size_t offset; }; /** -- cgit From e4f26243953fd3e87df93786b40293ca3a6a465e Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Tue, 14 Apr 2026 23:15:14 +0900 Subject: PCI: endpoint: pci-ep-msi: Add embedded doorbell fallback Some endpoint platforms cannot use platform MSI / GIC ITS to implement EP-side doorbells. In those cases, EPF drivers cannot provide an interrupt-driven doorbell and often fall back to polling. Add an "embedded" doorbell backend that uses a controller-integrated doorbell target (e.g. DesignWare integrated eDMA interrupt-emulation doorbell). The backend locates the doorbell register and a corresponding Linux IRQ via the EPC aux-resource API. If the doorbell register is already exposed via a fixed BAR mapping, provide BAR+offset. Otherwise provide the DMA address returned by dma_map_resource() (which may be an IOVA when an IOMMU is enabled) so EPF drivers can map it into BAR space. When MSI doorbell allocation fails with -ENODEV, pci_epf_alloc_doorbell() falls back to this embedded backend. Suggested-by: Manivannan Sadhasivam Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260414141514.1341429-8-den@valinux.co.jp --- drivers/pci/endpoint/pci-ep-msi.c | 131 +++++++++++++++++++++++++++++++++++++- include/linux/pci-epf.h | 8 +++ 2 files changed, 136 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/drivers/pci/endpoint/pci-ep-msi.c b/drivers/pci/endpoint/pci-ep-msi.c index 85fe46103220..0855c7930abb 100644 --- a/drivers/pci/endpoint/pci-ep-msi.c +++ b/drivers/pci/endpoint/pci-ep-msi.c @@ -6,6 +6,8 @@ * Author: Frank Li */ +#include +#include #include #include #include @@ -36,6 +38,109 @@ static void pci_epf_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg) pci_epc_put(epc); } +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; + size_t map_size = 0, off = 0; + dma_addr_t iova_base = 0; + phys_addr_t phys_base; + int count, ret, i; + u64 addr; + + 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; + } + + 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; @@ -109,18 +214,38 @@ int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db) if (!ret) return 0; - dev_err(dev, "Failed to allocate doorbell: %d\n", ret); - return ret; + /* + * 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) { + struct pci_epf_doorbell_msg *msg0; + struct pci_epc *epc = epf->epc; + if (!epf->db_msg) return; - if (epf->db_msg[0].type == PCI_EPF_DOORBELL_MSI) + 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/include/linux/pci-epf.h b/include/linux/pci-epf.h index cd747447a1ea..8a6c64a35890 100644 --- a/include/linux/pci-epf.h +++ b/include/linux/pci-epf.h @@ -171,6 +171,12 @@ enum pci_epf_doorbell_type { * (NO_BAR if not) * @offset: offset within @bar for the doorbell target (valid iff * @bar != NO_BAR) + * @iova_base: Internal: base DMA address returned by dma_map_resource() for the + * embedded doorbell MMIO window (used only for unmapping). Valid + * when @type is PCI_EPF_DOORBELL_EMBEDDED and @iova_size is + * non-zero. + * @iova_size: Internal: size of the dma_map_resource() mapping at @iova_base. + * Zero when no mapping was created (e.g. pre-exposed fixed BAR). */ struct pci_epf_doorbell_msg { struct msi_msg msg; @@ -179,6 +185,8 @@ struct pci_epf_doorbell_msg { enum pci_epf_doorbell_type type; enum pci_barno bar; resource_size_t offset; + dma_addr_t iova_base; + size_t iova_size; }; /** -- cgit