pci_epf_alloc_doorbell() currently allocates MSI-backed doorbells using
the MSI domain returned by of_msi_map_get_device_domain(...,
DOMAIN_BUS_PLATFORM_MSI). On platforms where such an MSI irq domain is
not available, EPF drivers cannot provide doorbells to the RC. Even if
it's available and MSI device domain successfully created, the write
into the message address via BAR inbound mapping might not work for
platform-specific reasons (e.g. write into GITS_TRANSLATOR via iATU IB
mapping does not reach ITS at least on R-Car Gen4 Spider).

Add an "embedded (DMA) doorbell" fallback path that uses EPC-provided
auxiliary resources to build doorbell address/data pairs backed by a
platform device MMIO region (e.g. dw-edma) and a shared platform IRQ.

To let EPF drivers request interrupts correctly, extend struct
pci_epf_doorbell_msg with the doorbell type and required IRQ request
flags. Update pci-epf-test to handle shared IRQ constraints by using a
trivial primary handler to wake the threaded handler, and update
pci-epf-vntb to use the required irq_flags.

Signed-off-by: Koichiro Den <[email protected]>
---
 drivers/pci/endpoint/functions/pci-epf-test.c |  29 +++-
 drivers/pci/endpoint/functions/pci-epf-vntb.c |   3 +-
 drivers/pci/endpoint/pci-ep-msi.c             | 140 ++++++++++++++++--
 include/linux/pci-epf.h                       |  17 ++-
 4 files changed, 171 insertions(+), 18 deletions(-)

diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c 
b/drivers/pci/endpoint/functions/pci-epf-test.c
index 23034f548c90..2f3b2e6a3e29 100644
--- a/drivers/pci/endpoint/functions/pci-epf-test.c
+++ b/drivers/pci/endpoint/functions/pci-epf-test.c
@@ -711,6 +711,26 @@ static irqreturn_t pci_epf_test_doorbell_handler(int irq, 
void *data)
        return IRQ_HANDLED;
 }
 
+/*
+ * Embedded doorbell fallback uses a platform IRQ which is already owned by a
+ * platform driver (e.g. dw-edma) and therefore must be requested IRQF_SHARED.
+ * We cannot add IRQF_ONESHOT here because shared IRQ handlers must agree on
+ * IRQF_ONESHOT.
+ *
+ * request_threaded_irq() with handler == NULL would be rejected for !ONESHOT
+ * because the default primary handler only wakes the thread and does not
+ * mask/ack the interrupt, which can livelock on level-triggered IRQs.
+ *
+ * In the embedded doorbell fallback, the IRQ owner is responsible for
+ * acknowledging/deasserting the interrupt source in hardirq context before the
+ * IRQ line is unmasked. Therefore this driver only needs a trivial primary
+ * handler to wake the threaded handler.
+ */
+static irqreturn_t pci_epf_test_doorbell_primary(int irq, void *data)
+{
+       return IRQ_WAKE_THREAD;
+}
+
 static void pci_epf_test_doorbell_cleanup(struct pci_epf_test *epf_test)
 {
        struct pci_epf_test_reg *reg = epf_test->reg[epf_test->test_reg_bar];
@@ -731,6 +751,7 @@ 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_epc *epc = epf->epc;
+       unsigned long irq_flags;
        struct msi_msg *msg;
        enum pci_barno bar;
        size_t offset;
@@ -745,10 +766,14 @@ static void pci_epf_test_enable_doorbell(struct 
pci_epf_test *epf_test,
        if (bar < BAR_0)
                goto err_doorbell_cleanup;
 
+       irq_flags = epf->db_msg[0].irq_flags;
+       if (!(irq_flags & IRQF_SHARED))
+               irq_flags |= IRQF_ONESHOT;
        epf_test->db_irq_requested = false;
 
-       ret = request_threaded_irq(epf->db_msg[0].virq, NULL,
-                                  pci_epf_test_doorbell_handler, IRQF_ONESHOT,
+       ret = request_threaded_irq(epf->db_msg[0].virq,
+                                  pci_epf_test_doorbell_primary,
+                                  pci_epf_test_doorbell_handler, irq_flags,
                                   "pci-ep-test-doorbell", epf_test);
        if (ret) {
                dev_err(&epf->dev,
diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c 
b/drivers/pci/endpoint/functions/pci-epf-vntb.c
index 3ecc5059f92b..d2fd1e194088 100644
--- a/drivers/pci/endpoint/functions/pci-epf-vntb.c
+++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c
@@ -535,7 +535,8 @@ static int epf_ntb_db_bar_init_msi_doorbell(struct epf_ntb 
*ntb,
 
        for (i = 0; i < ntb->db_count; i++) {
                ret = request_irq(epf->db_msg[i].virq, epf_ntb_doorbell_handler,
-                                 0, "pci_epf_vntb_db", ntb);
+                                 epf->db_msg[i].irq_flags, "pci_epf_vntb_db",
+                                 ntb);
 
                if (ret) {
                        dev_err(&epf->dev,
diff --git a/drivers/pci/endpoint/pci-ep-msi.c 
b/drivers/pci/endpoint/pci-ep-msi.c
index ad8a81d6ad77..ebbd24b82ae6 100644
--- a/drivers/pci/endpoint/pci-ep-msi.c
+++ b/drivers/pci/endpoint/pci-ep-msi.c
@@ -8,6 +8,7 @@
 
 #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 +36,95 @@ 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)
 {
        struct pci_epc *epc = epf->epc;
-       struct device *dev = &epf->dev;
-       struct irq_domain *domain;
-       void *msg;
-       int ret;
-       int i;
+       const struct pci_epc_aux_resource *dma_ctrl = NULL;
+       struct pci_epf_doorbell_msg *msg;
+       int count, ret, i, db;
 
-       /* 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;
+       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) {
+               ret = -ENODEV;
+               goto out_free_res;
        }
+       if (ret < 0)
+               goto out_free_res;
 
-       if (epf->db_msg)
-               return -EBUSY;
+       count = ret;
+
+       for (i = 0; i < count; i++) {
+               if (res[i].type == PCI_EPC_AUX_DMA_CTRL_MMIO) {
+                       dma_ctrl = &res[i];
+                       break;
+               }
+       }
+       if (!dma_ctrl) {
+               ret = -ENODEV;
+               goto out_free_res;
+       }
+
+       msg = kcalloc(num_db, sizeof(*msg), GFP_KERNEL);
+       if (!msg) {
+               ret = -ENOMEM;
+               goto out_free_res;
+       }
+
+       for (i = 0, db = 0; i < count && db < num_db; i++) {
+               u64 addr;
+
+               if (res[i].type != PCI_EPC_AUX_DMA_CHAN_DESC)
+                       continue;
+
+               if (res[i].u.dma_chan_desc.db_offset >= dma_ctrl->size)
+                       continue;
+
+               addr = (u64)dma_ctrl->phys_addr + 
res[i].u.dma_chan_desc.db_offset;
+
+               msg[db].msg.address_lo = (u32)addr;
+               msg[db].msg.address_hi = (u32)(addr >> 32);
+               msg[db].msg.data = 0;
+               msg[db].virq = res[i].u.dma_chan_desc.irq;
+               msg[db].irq_flags = IRQF_SHARED;
+               msg[db].type = PCI_EPF_DOORBELL_EMBEDDED;
+               db++;
+       }
+
+       if (db != num_db) {
+               ret = -ENOSPC;
+               kfree(msg);
+               goto out_free_res;
+       }
+
+       epf->num_db = num_db;
+       epf->db_msg = msg;
+       ret = 0;
+
+out_free_res:
+       kfree(res);
+       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 +147,11 @@ 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].irq_flags = 0;
+               msg[i].type = PCI_EPF_DOORBELL_MSI;
+       }
+
        epf->num_db = num_db;
        epf->db_msg = msg;
 
@@ -90,13 +168,49 @@ 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;
+
+       if (ret != -ENODEV)
+               return ret;
+
+       ret = pci_epf_alloc_doorbell_embedded(epf, num_db);
+       if (!ret) {
+               dev_info(dev, "Using embedded (DMA) doorbell fallback\n");
+               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..e6625198f401 100644
--- a/include/linux/pci-epf.h
+++ b/include/linux/pci-epf.h
@@ -152,14 +152,27 @@ 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.
  */
 struct pci_epf_doorbell_msg {
        struct msi_msg msg;
        int virq;
+       unsigned long irq_flags;
+       enum pci_epf_doorbell_type type;
 };
 
 /**
-- 
2.51.0


Reply via email to