]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
PCI: endpoint: Add RC-to-EP doorbell support using platform MSI controller
authorFrank Li <Frank.Li@nxp.com>
Thu, 10 Jul 2025 19:13:49 +0000 (15:13 -0400)
committerBjorn Helgaas <bhelgaas@google.com>
Thu, 24 Jul 2025 21:51:17 +0000 (16:51 -0500)
Implement the doorbell feature by mapping the EP's MSI interrupt controller
message address to a dedicated BAR.

The EPF driver should pass the actual message data to be written to the
message address by the host through implementation-specific logic.

Signed-off-by: Frank Li <Frank.Li@nxp.com>
[mani: minor code cleanups and reworded commit message]
Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
[bhelgaas: fix kernel-doc]
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Tested-by: Niklas Cassel <cassel@kernel.org>
Link: https://patch.msgid.link/20250710-ep-msi-v21-3-57683fc7fb25@nxp.com
drivers/pci/endpoint/Kconfig
drivers/pci/endpoint/Makefile
drivers/pci/endpoint/pci-ep-msi.c [new file with mode: 0644]
include/linux/pci-ep-msi.h [new file with mode: 0644]
include/linux/pci-epf.h

index 1c5d82eb57d440fb18c788921fc27096b75b061e..8dad291be8b80e740f86c768460c05985c8c7bb5 100644 (file)
@@ -28,6 +28,14 @@ config PCI_ENDPOINT_CONFIGFS
           configure the endpoint function and used to bind the
           function with an endpoint controller.
 
+config PCI_ENDPOINT_MSI_DOORBELL
+       bool "PCI Endpoint MSI Doorbell Support"
+       depends on PCI_ENDPOINT && GENERIC_MSI_IRQ
+       help
+         This enables the EP's MSI interrupt controller to function as a
+         doorbell. The RC can trigger doorbell in EP by writing data to a
+         dedicated BAR, which the EP maps to the controller's message address.
+
 source "drivers/pci/endpoint/functions/Kconfig"
 
 endmenu
index 95b2fe47e3b06be6ecd210e8d834610021a22470..b4869d52053aade87298289b29b3beb547001bfa 100644 (file)
@@ -6,3 +6,4 @@
 obj-$(CONFIG_PCI_ENDPOINT_CONFIGFS)    += pci-ep-cfs.o
 obj-$(CONFIG_PCI_ENDPOINT)             += pci-epc-core.o pci-epf-core.o\
                                           pci-epc-mem.o functions/
+obj-$(CONFIG_PCI_ENDPOINT_MSI_DOORBELL)        += pci-ep-msi.o
diff --git a/drivers/pci/endpoint/pci-ep-msi.c b/drivers/pci/endpoint/pci-ep-msi.c
new file mode 100644 (file)
index 0000000..95a47ce
--- /dev/null
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PCI Endpoint *Controller* (EPC) MSI library
+ *
+ * Copyright (C) 2025 NXP
+ * Author: Frank Li <Frank.Li@nxp.com>
+ */
+
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/msi.h>
+#include <linux/of_irq.h>
+#include <linux/pci-epc.h>
+#include <linux/pci-epf.h>
+#include <linux/pci-ep-cfs.h>
+#include <linux/pci-ep-msi.h>
+#include <linux/slab.h>
+
+static void pci_epf_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
+{
+       struct pci_epc *epc;
+       struct pci_epf *epf;
+
+       epc = pci_epc_get(dev_name(msi_desc_to_dev(desc)));
+       if (!epc)
+               return;
+
+       epf = list_first_entry_or_null(&epc->pci_epf, struct pci_epf, list);
+
+       if (epf && epf->db_msg && desc->msi_index < epf->num_db)
+               memcpy(&epf->db_msg[desc->msi_index].msg, msg, sizeof(*msg));
+
+       pci_epc_put(epc);
+}
+
+int pci_epf_alloc_doorbell(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;
+
+       /* 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;
+       }
+
+       domain = of_msi_map_get_device_domain(epc->dev.parent, 0,
+                                             DOMAIN_BUS_PLATFORM_MSI);
+       if (!domain) {
+               dev_err(dev, "Can't find MSI domain for EPC\n");
+               return -ENODEV;
+       }
+
+       dev_set_msi_domain(epc->dev.parent, domain);
+
+       msg = kcalloc(num_db, sizeof(struct pci_epf_doorbell_msg), GFP_KERNEL);
+       if (!msg)
+               return -ENOMEM;
+
+       epf->num_db = num_db;
+       epf->db_msg = msg;
+
+       ret = platform_device_msi_init_and_alloc_irqs(epc->dev.parent, num_db,
+                                                     pci_epf_write_msi_msg);
+       if (ret) {
+               dev_err(dev, "Failed to allocate MSI\n");
+               kfree(msg);
+               return ret;
+       }
+
+       for (i = 0; i < num_db; i++)
+               epf->db_msg[i].virq = msi_get_virq(epc->dev.parent, i);
+
+       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);
+
+       kfree(epf->db_msg);
+       epf->db_msg = NULL;
+       epf->num_db = 0;
+}
+EXPORT_SYMBOL_GPL(pci_epf_free_doorbell);
diff --git a/include/linux/pci-ep-msi.h b/include/linux/pci-ep-msi.h
new file mode 100644 (file)
index 0000000..7c5db90
--- /dev/null
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * PCI Endpoint *Function* side MSI header file
+ *
+ * Copyright (C) 2024 NXP
+ * Author: Frank Li <Frank.Li@nxp.com>
+ */
+
+#ifndef __PCI_EP_MSI__
+#define __PCI_EP_MSI__
+
+struct pci_epf;
+
+#ifdef CONFIG_PCI_ENDPOINT_MSI_DOORBELL
+int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 nums);
+void pci_epf_free_doorbell(struct pci_epf *epf);
+#else
+static inline int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 nums)
+{
+       return -ENODATA;
+}
+
+static inline void pci_epf_free_doorbell(struct pci_epf *epf)
+{
+}
+#endif /* CONFIG_GENERIC_MSI_IRQ */
+
+#endif /* __PCI_EP_MSI__ */
index 749cee0bcf2cc0600ad3be1582b0c497c5a21bed..52e07602f08e8a6ea8716af367e27ead64e61309 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/configfs.h>
 #include <linux/device.h>
 #include <linux/mod_devicetable.h>
+#include <linux/msi.h>
 #include <linux/pci.h>
 
 struct pci_epf;
@@ -128,6 +129,16 @@ struct pci_epf_bar {
        int             flags;
 };
 
+/**
+ * struct pci_epf_doorbell_msg - represents doorbell message
+ * @msg: MSI message
+ * @virq: IRQ number of this doorbell MSI message
+ */
+struct pci_epf_doorbell_msg {
+       struct msi_msg msg;
+       int virq;
+};
+
 /**
  * struct pci_epf - represents the PCI EPF device
  * @dev: the PCI EPF device
@@ -155,6 +166,8 @@ struct pci_epf_bar {
  * @vfunction_num_map: bitmap to manage virtual function number
  * @pci_vepf: list of virtual endpoint functions associated with this function
  * @event_ops: callbacks for capturing the EPC events
+ * @db_msg: data for MSI from RC side
+ * @num_db: number of doorbells
  */
 struct pci_epf {
        struct device           dev;
@@ -185,6 +198,8 @@ struct pci_epf {
        unsigned long           vfunction_num_map;
        struct list_head        pci_vepf;
        const struct pci_epc_event_ops *event_ops;
+       struct pci_epf_doorbell_msg *db_msg;
+       u16 num_db;
 };
 
 /**