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.
Tested-by: Niklas Cassel <cassel@kernel.org>
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
drivers/pci/endpoint/pci-ep-msi.c | 139 +++++++++++++++++++++++++++++-
include/linux/pci-epf.h | 8 ++
2 files changed, 144 insertions(+), 3 deletions(-)
diff --git a/drivers/pci/endpoint/pci-ep-msi.c b/drivers/pci/endpoint/pci-ep-msi.c
index 85fe46103220..331d84a79193 100644
--- a/drivers/pci/endpoint/pci-ep-msi.c
+++ b/drivers/pci/endpoint/pci-ep-msi.c
@@ -6,6 +6,8 @@
* 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>
@@ -36,6 +38,117 @@ 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;
+ struct device *dev = &epf->dev;
+ 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(epc, epf->func_no, epf->vfunc_no,
+ NULL, 0);
+ if (count == -EOPNOTSUPP || count == 0)
+ return -ENODEV;
+ if (count < 0)
+ return count;
+
+ 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 == -EOPNOTSUPP || ret == 0)
+ return -ENODEV;
+ if (ret < 0)
+ return ret;
+
+ count = ret;
+
+ for (i = 0; i < count; i++) {
+ if (res[i].type == PCI_EPC_AUX_DOORBELL_MMIO) {
+ if (doorbell) {
+ dev_warn(dev,
+ "Duplicate DOORBELL_MMIO resource found\n");
+ continue;
+ }
+ doorbell = &res[i];
+ }
+ }
+ 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 +222,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;
};
/**
--
2.51.0
On Tue, Mar 24, 2026 at 05:37:28PM +0900, Koichiro Den wrote:
> 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.
>
> Tested-by: Niklas Cassel <cassel@kernel.org>
> Signed-off-by: Koichiro Den <den@valinux.co.jp>
> ---
> drivers/pci/endpoint/pci-ep-msi.c | 139 +++++++++++++++++++++++++++++-
> include/linux/pci-epf.h | 8 ++
> 2 files changed, 144 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/pci/endpoint/pci-ep-msi.c b/drivers/pci/endpoint/pci-ep-msi.c
> index 85fe46103220..331d84a79193 100644
> --- a/drivers/pci/endpoint/pci-ep-msi.c
> +++ b/drivers/pci/endpoint/pci-ep-msi.c
> @@ -6,6 +6,8 @@
> * 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>
> @@ -36,6 +38,117 @@ 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;
> + struct device *dev = &epf->dev;
> + 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(epc, epf->func_no, epf->vfunc_no,
> + NULL, 0);
> + if (count == -EOPNOTSUPP || count == 0)
> + return -ENODEV;
> + if (count < 0)
> + return count;
This should be avoided...
resource_count API should return 0 for success and errno for failure with a
separate argument for resource count.
And resource_get API should return 0 for success and errno for failure.
> +
> + 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 == -EOPNOTSUPP || ret == 0)
> + return -ENODEV;
> + if (ret < 0)
> + return ret;
And here, you do the same :/
> +
> + count = ret;
> +
> + for (i = 0; i < count; i++) {
> + if (res[i].type == PCI_EPC_AUX_DOORBELL_MMIO) {
> + if (doorbell) {
> + dev_warn(dev,
> + "Duplicate DOORBELL_MMIO resource found\n");
I think technically it makes sense to have more than one doorbell resource per
endpoint. You may not warn here, but just ensure that you use the first unused
one. This also means, you need to track the allocated resource.
If you want to avoid the hassle, just assume that there will be only one
doorbell resource and add a TODO to extend it if needed.
- Mani
--
மணிவண்ணன் சதாசிவம்
On Thu, Mar 26, 2026 at 06:09:18PM +0530, Manivannan Sadhasivam wrote:
> On Tue, Mar 24, 2026 at 05:37:28PM +0900, Koichiro Den wrote:
> > 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.
> >
> > Tested-by: Niklas Cassel <cassel@kernel.org>
> > Signed-off-by: Koichiro Den <den@valinux.co.jp>
> > ---
> > drivers/pci/endpoint/pci-ep-msi.c | 139 +++++++++++++++++++++++++++++-
> > include/linux/pci-epf.h | 8 ++
> > 2 files changed, 144 insertions(+), 3 deletions(-)
> >
> > diff --git a/drivers/pci/endpoint/pci-ep-msi.c b/drivers/pci/endpoint/pci-ep-msi.c
> > index 85fe46103220..331d84a79193 100644
> > --- a/drivers/pci/endpoint/pci-ep-msi.c
> > +++ b/drivers/pci/endpoint/pci-ep-msi.c
> > @@ -6,6 +6,8 @@
> > * 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>
> > @@ -36,6 +38,117 @@ 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;
> > + struct device *dev = &epf->dev;
> > + 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(epc, epf->func_no, epf->vfunc_no,
> > + NULL, 0);
> > + if (count == -EOPNOTSUPP || count == 0)
> > + return -ENODEV;
> > + if (count < 0)
> > + return count;
>
> This should be avoided...
>
> resource_count API should return 0 for success and errno for failure with a
> separate argument for resource count.
>
> And resource_get API should return 0 for success and errno for failure.
>
> > +
> > + 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 == -EOPNOTSUPP || ret == 0)
> > + return -ENODEV;
> > + if (ret < 0)
> > + return ret;
>
> And here, you do the same :/
Thanks for the review.
I'll update these to use the new pci_epc_count_aux_resources(), as suggested in
[PATCH v11 1/7].
>
> > +
> > + count = ret;
> > +
> > + for (i = 0; i < count; i++) {
> > + if (res[i].type == PCI_EPC_AUX_DOORBELL_MMIO) {
> > + if (doorbell) {
> > + dev_warn(dev,
> > + "Duplicate DOORBELL_MMIO resource found\n");
>
> I think technically it makes sense to have more than one doorbell resource per
> endpoint. You may not warn here, but just ensure that you use the first unused
> one. This also means, you need to track the allocated resource.
>
> If you want to avoid the hassle, just assume that there will be only one
> doorbell resource and add a TODO to extend it if needed.
I think I'll go with the latter approach for now.
The only provider added in this series (see [PATCH v11 3/7]) exposes a single
doorbell resource, so I think it is reasonable to keep that assumption for now
and extend it later if and when a real multi-doorbell provider appears.
This helps keep this series compact and reviewable too.
Best regards,
Koichiro
>
> - Mani
>
> --
> மணிவண்ணன் சதாசிவம்
© 2016 - 2026 Red Hat, Inc.