Extend pci-epf-test with an "embedded doorbell" variant that does not
rely on the EPC doorbell/MSI mechanism.

When the host sets FLAG_DB_EMBEDDED, query EPC remote resources to
locate the embedded DMA MMIO window and a per-channel
interrupt-emulation doorbell register offset. Map the MMIO window into a
free BAR and return BAR+offset to the host as the doorbell target.

Handle the resulting shared IRQ by deferring completion signalling to a
work item, then update the test status and raise the completion IRQ back
to the host.

The existing MSI doorbell remains the default when FLAG_DB_EMBEDDED is
not set.

Signed-off-by: Koichiro Den <[email protected]>
---
 drivers/pci/endpoint/functions/pci-epf-test.c | 193 +++++++++++++++++-
 1 file changed, 185 insertions(+), 8 deletions(-)

diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c 
b/drivers/pci/endpoint/functions/pci-epf-test.c
index 6952ee418622..5871da8cbddf 100644
--- a/drivers/pci/endpoint/functions/pci-epf-test.c
+++ b/drivers/pci/endpoint/functions/pci-epf-test.c
@@ -6,6 +6,7 @@
  * Author: Kishon Vijay Abraham I <[email protected]>
  */
 
+#include <linux/bitops.h>
 #include <linux/crc32.h>
 #include <linux/delay.h>
 #include <linux/dmaengine.h>
@@ -56,6 +57,7 @@
 #define STATUS_BAR_SUBRANGE_CLEAR_FAIL         BIT(17)
 
 #define FLAG_USE_DMA                   BIT(0)
+#define FLAG_DB_EMBEDDED               BIT(1)
 
 #define TIMER_RESOLUTION               1
 
@@ -69,6 +71,12 @@
 
 static struct workqueue_struct *kpcitest_workqueue;
 
+enum pci_epf_test_doorbell_variant {
+       PCI_EPF_TEST_DB_NONE = 0,
+       PCI_EPF_TEST_DB_MSI,
+       PCI_EPF_TEST_DB_EMBEDDED,
+};
+
 struct pci_epf_test {
        void                    *reg[PCI_STD_NUM_BARS];
        struct pci_epf          *epf;
@@ -85,7 +93,11 @@ struct pci_epf_test {
        bool                    dma_supported;
        bool                    dma_private;
        const struct pci_epc_features *epc_features;
+       enum pci_epf_test_doorbell_variant db_variant;
        struct pci_epf_bar      db_bar;
+       int                     db_irq;
+       unsigned long           db_irq_pending;
+       struct work_struct      db_work;
        size_t                  bar_size[PCI_STD_NUM_BARS];
 };
 
@@ -696,7 +708,7 @@ static void pci_epf_test_raise_irq(struct pci_epf_test 
*epf_test,
        }
 }
 
-static irqreturn_t pci_epf_test_doorbell_handler(int irq, void *data)
+static irqreturn_t pci_epf_test_doorbell_msi_handler(int irq, void *data)
 {
        struct pci_epf_test *epf_test = data;
        enum pci_barno test_reg_bar = epf_test->test_reg_bar;
@@ -710,19 +722,58 @@ static irqreturn_t pci_epf_test_doorbell_handler(int irq, 
void *data)
        return IRQ_HANDLED;
 }
 
+static void pci_epf_test_doorbell_embedded_work(struct work_struct *work)
+{
+       struct pci_epf_test *epf_test =
+               container_of(work, struct pci_epf_test, db_work);
+       enum pci_barno test_reg_bar = epf_test->test_reg_bar;
+       struct pci_epf_test_reg *reg = epf_test->reg[test_reg_bar];
+       u32 status = le32_to_cpu(reg->status);
+
+       status |= STATUS_DOORBELL_SUCCESS;
+       reg->status = cpu_to_le32(status);
+       pci_epf_test_raise_irq(epf_test, reg);
+
+       clear_bit(0, &epf_test->db_irq_pending);
+}
+
+static irqreturn_t pci_epf_test_doorbell_embedded_irq_handler(int irq, void 
*data)
+{
+       struct pci_epf_test *epf_test = data;
+
+       if (READ_ONCE(epf_test->db_variant) != PCI_EPF_TEST_DB_EMBEDDED)
+               return IRQ_NONE;
+
+       if (test_and_set_bit(0, &epf_test->db_irq_pending))
+               return IRQ_HANDLED;
+
+       queue_work(kpcitest_workqueue, &epf_test->db_work);
+       return IRQ_HANDLED;
+}
+
 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];
        struct pci_epf *epf = epf_test->epf;
 
-       free_irq(epf->db_msg[0].virq, epf_test);
-       reg->doorbell_bar = cpu_to_le32(NO_BAR);
+       if (epf_test->db_irq) {
+               free_irq(epf_test->db_irq, epf_test);
+               epf_test->db_irq = 0;
+       }
+
+       if (epf_test->db_variant == PCI_EPF_TEST_DB_EMBEDDED) {
+               cancel_work_sync(&epf_test->db_work);
+               clear_bit(0, &epf_test->db_irq_pending);
+       } else if (epf_test->db_variant == PCI_EPF_TEST_DB_MSI) {
+               pci_epf_free_doorbell(epf);
+       }
 
-       pci_epf_free_doorbell(epf);
+       reg->doorbell_bar = cpu_to_le32(NO_BAR);
+       epf_test->db_variant = PCI_EPF_TEST_DB_NONE;
 }
 
-static void pci_epf_test_enable_doorbell(struct pci_epf_test *epf_test,
-                                        struct pci_epf_test_reg *reg)
+static void pci_epf_test_enable_doorbell_msi(struct pci_epf_test *epf_test,
+                                            struct pci_epf_test_reg *reg)
 {
        u32 status = le32_to_cpu(reg->status);
        struct pci_epf *epf = epf_test->epf;
@@ -736,20 +787,23 @@ static void pci_epf_test_enable_doorbell(struct 
pci_epf_test *epf_test,
        if (ret)
                goto set_status_err;
 
+       epf_test->db_variant = PCI_EPF_TEST_DB_MSI;
        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;
 
        ret = request_threaded_irq(epf->db_msg[0].virq, NULL,
-                                  pci_epf_test_doorbell_handler, IRQF_ONESHOT,
-                                  "pci-ep-test-doorbell", epf_test);
+                                  pci_epf_test_doorbell_msi_handler,
+                                  IRQF_ONESHOT, "pci-ep-test-doorbell",
+                                  epf_test);
        if (ret) {
                dev_err(&epf->dev,
                        "Failed to request doorbell IRQ: %d\n",
                        epf->db_msg[0].virq);
                goto err_doorbell_cleanup;
        }
+       epf_test->db_irq = epf->db_msg[0].virq;
 
        reg->doorbell_data = cpu_to_le32(msg->data);
        reg->doorbell_bar = cpu_to_le32(bar);
@@ -782,6 +836,125 @@ static void pci_epf_test_enable_doorbell(struct 
pci_epf_test *epf_test,
        reg->status = cpu_to_le32(status);
 }
 
+static void pci_epf_test_enable_doorbell_embedded(struct pci_epf_test 
*epf_test,
+                                                 struct pci_epf_test_reg *reg)
+{
+       struct pci_epc_remote_resource *dma_ctrl = NULL, *chan0 = NULL;
+       const char *irq_name = "pci-ep-test-doorbell-embedded";
+       u32 status = le32_to_cpu(reg->status);
+       struct pci_epf *epf = epf_test->epf;
+       struct pci_epc *epc = epf->epc;
+       struct device *dev = &epf->dev;
+       enum pci_barno bar;
+       size_t align_off;
+       unsigned int i;
+       int cnt, ret;
+       u32 db_off;
+
+       cnt = pci_epc_get_remote_resources(epc, epf->func_no, epf->vfunc_no,
+                                          NULL, 0);
+       if (cnt <= 0) {
+               dev_err(dev, "No remote resources available for embedded 
doorbell\n");
+               goto set_status_err;
+       }
+
+       struct pci_epc_remote_resource *resources __free(kfree) =
+                               kcalloc(cnt, sizeof(*resources), GFP_KERNEL);
+       if (!resources)
+               goto set_status_err;
+
+       ret = pci_epc_get_remote_resources(epc, epf->func_no, epf->vfunc_no,
+                                          resources, cnt);
+       if (ret < 0) {
+               dev_err(dev, "Failed to get remote resources: %d\n", ret);
+               goto set_status_err;
+       }
+       cnt = ret;
+
+       for (i = 0; i < cnt; i++) {
+               if (resources[i].type == PCI_EPC_RR_DMA_CTRL_MMIO)
+                       dma_ctrl = &resources[i];
+               else if (resources[i].type == PCI_EPC_RR_DMA_CHAN_DESC &&
+                        !chan0)
+                       chan0 = &resources[i];
+       }
+
+       if (!dma_ctrl || !chan0) {
+               dev_err(dev, "Missing DMA ctrl MMIO or channel #0 info\n");
+               goto set_status_err;
+       }
+
+       bar = pci_epc_get_next_free_bar(epf_test->epc_features,
+                                       epf_test->test_reg_bar + 1);
+       if (bar < BAR_0) {
+               dev_err(dev, "No free BAR for embedded doorbell\n");
+               goto set_status_err;
+       }
+
+       ret = pci_epf_align_inbound_addr(epf, bar, dma_ctrl->phys_addr,
+                                        &epf_test->db_bar.phys_addr,
+                                        &align_off);
+       if (ret)
+               goto set_status_err;
+
+       db_off = chan0->u.dma_chan_desc.db_offset;
+       if (db_off >= dma_ctrl->size ||
+           align_off + db_off >= epf->bar[bar].size) {
+               dev_err(dev, "BAR%d too small for embedded doorbell (off %#zx + 
%#x)\n",
+                       bar, align_off, db_off);
+               goto set_status_err;
+       }
+
+       epf_test->db_variant = PCI_EPF_TEST_DB_EMBEDDED;
+
+       ret = request_irq(chan0->u.dma_chan_desc.irq,
+                         pci_epf_test_doorbell_embedded_irq_handler,
+                         IRQF_SHARED, irq_name, epf_test);
+       if (ret) {
+               dev_err(dev, "Failed to request embedded doorbell IRQ: %d\n",
+                       chan0->u.dma_chan_desc.irq);
+               goto err_cleanup;
+       }
+       epf_test->db_irq = chan0->u.dma_chan_desc.irq;
+
+       reg->doorbell_data = cpu_to_le32(0);
+       reg->doorbell_bar = cpu_to_le32(bar);
+       reg->doorbell_offset = cpu_to_le32(align_off + db_off);
+
+       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_cleanup;
+
+       status |= STATUS_DOORBELL_ENABLE_SUCCESS;
+       reg->status = cpu_to_le32(status);
+       return;
+
+err_cleanup:
+       pci_epf_test_doorbell_cleanup(epf_test);
+set_status_err:
+       status |= STATUS_DOORBELL_ENABLE_FAIL;
+       reg->status = cpu_to_le32(status);
+}
+
+static void pci_epf_test_enable_doorbell(struct pci_epf_test *epf_test,
+                                        struct pci_epf_test_reg *reg)
+{
+       u32 flags = le32_to_cpu(reg->flags);
+
+       /* If already enabled, drop previous setup first. */
+       if (epf_test->db_variant != PCI_EPF_TEST_DB_NONE)
+               pci_epf_test_doorbell_cleanup(epf_test);
+
+       if (flags & FLAG_DB_EMBEDDED)
+               pci_epf_test_enable_doorbell_embedded(epf_test, reg);
+       else
+               pci_epf_test_enable_doorbell_msi(epf_test, reg);
+}
+
 static void pci_epf_test_disable_doorbell(struct pci_epf_test *epf_test,
                                          struct pci_epf_test_reg *reg)
 {
@@ -1309,6 +1482,9 @@ static void pci_epf_test_unbind(struct pci_epf *epf)
 
        cancel_delayed_work_sync(&epf_test->cmd_handler);
        if (epc->init_complete) {
+               /* In case userspace never disabled doorbell explicitly. */
+               if (epf_test->db_variant != PCI_EPF_TEST_DB_NONE)
+                       pci_epf_test_doorbell_cleanup(epf_test);
                pci_epf_test_clean_dma_chan(epf_test);
                pci_epf_test_clear_bar(epf);
        }
@@ -1427,6 +1603,7 @@ static int pci_epf_test_probe(struct pci_epf *epf,
                epf_test->bar_size[bar] = default_bar_size[bar];
 
        INIT_DELAYED_WORK(&epf_test->cmd_handler, pci_epf_test_cmd_handler);
+       INIT_WORK(&epf_test->db_work, pci_epf_test_doorbell_embedded_work);
 
        epf->event_ops = &pci_epf_test_event_ops;
 
-- 
2.51.0


Reply via email to