mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

Some MSI controllers can change address/data pair during the execution of irq_chip::irq_set_affinity() callback. Since the current PCI Endpoint framework cannot support mutable MSI controllers, call irq_domain_is_msi_immutable() API to check if the controller is immutable or not. Also ensure that the MSI domain is a parent MSI domain so that it can allocate address/data pairs. Signed-off-by: Frank Li <Frank.Li@nxp.com> [mani: reworded error message and commit message] Signed-off-by: Manivannan Sadhasivam <mani@kernel.org> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Tested-by: Niklas Cassel <cassel@kernel.org> Link: https://patch.msgid.link/20250710-ep-msi-v21-4-57683fc7fb25@nxp.com
100 lines
2.3 KiB
C
100 lines
2.3 KiB
C
// 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;
|
|
}
|
|
|
|
if (!irq_domain_is_msi_parent(domain))
|
|
return -ENODEV;
|
|
|
|
if (!irq_domain_is_msi_immutable(domain)) {
|
|
dev_err(dev, "Mutable MSI controller not supported\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);
|