Commit 1c3b002c authored by Frank Li's avatar Frank Li Committed by Bjorn Helgaas
Browse files

PCI: endpoint: Add RC-to-EP doorbell support using platform MSI controller



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: default avatarFrank Li <Frank.Li@nxp.com>
[mani: minor code cleanups and reworded commit message]
Signed-off-by: default avatarManivannan Sadhasivam <mani@kernel.org>
[bhelgaas: fix kernel-doc]
Signed-off-by: default avatarBjorn Helgaas <bhelgaas@google.com>
Tested-by: default avatarNiklas Cassel <cassel@kernel.org>
Link: https://patch.msgid.link/20250710-ep-msi-v21-3-57683fc7fb25@nxp.com
parent 19272b37
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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
+92 −0
Original line number Diff line number Diff line
// 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);
+28 −0
Original line number Diff line number Diff line
/* 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__ */
+15 −0
Original line number Diff line number Diff line
@@ -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;
};

/**