PoC code only. All sorts of missing checks etc. Signed-off-by: Jonathan Cameron <Jonathan.Cameron@xxxxxxxxxx> --- arch/x86/kernel/apic/msi.c | 3 ++ drivers/pci/msi/api.c | 14 ++++++ drivers/pci/msi/irqdomain.c | 87 +++++++++++++++++++++++++++++++++++++ include/linux/msi.h | 5 +++ include/linux/pci.h | 3 ++ kernel/irq/msi.c | 5 +++ 6 files changed, 117 insertions(+) diff --git a/arch/x86/kernel/apic/msi.c b/arch/x86/kernel/apic/msi.c index 340769242dea..8ab4391d4559 100644 --- a/arch/x86/kernel/apic/msi.c +++ b/arch/x86/kernel/apic/msi.c @@ -218,6 +218,9 @@ static bool x86_init_dev_msi_info(struct device *dev, struct irq_domain *domain, case DOMAIN_BUS_DMAR: case DOMAIN_BUS_AMDVI: break; + case DOMAIN_BUS_PCI_DEVICE_MSIX: + /* Currently needed just for the PCI MSI-X subdevice handling */ + break; default: WARN_ON_ONCE(1); return false; diff --git a/drivers/pci/msi/api.c b/drivers/pci/msi/api.c index b956ce591f96..2b4c15102671 100644 --- a/drivers/pci/msi/api.c +++ b/drivers/pci/msi/api.c @@ -179,6 +179,20 @@ void pci_msix_free_irq(struct pci_dev *dev, struct msi_map map) } EXPORT_SYMBOL_GPL(pci_msix_free_irq); +struct msi_map pci_subdev_msix_alloc_irq_at(struct device *dev, unsigned int index, + const struct irq_affinity_desc *affdesc) +{ + //missing sanity checks + return msi_domain_alloc_irq_at(dev, MSI_DEFAULT_DOMAIN, index, affdesc, NULL); +} +EXPORT_SYMBOL_GPL(pci_subdev_msix_alloc_irq_at); + +void pci_subdev_msix_free_irq(struct device *dev, struct msi_map map) +{ + msi_domain_free_irqs_range(dev, MSI_DEFAULT_DOMAIN, map.index, map.index); +} +EXPORT_SYMBOL_GPL(pci_subdev_msix_free_irq); + /** * pci_disable_msix() - Disable MSI-X interrupt mode on device * @dev: the PCI device to operate on diff --git a/drivers/pci/msi/irqdomain.c b/drivers/pci/msi/irqdomain.c index 569125726b3e..48357a8054ff 100644 --- a/drivers/pci/msi/irqdomain.c +++ b/drivers/pci/msi/irqdomain.c @@ -444,3 +444,90 @@ struct irq_domain *pci_msi_get_device_domain(struct pci_dev *pdev) DOMAIN_BUS_PCI_MSI); return dom; } + +static const struct msi_parent_ops pci_msix_parent_ops = { + .supported_flags = MSI_FLAG_PCI_MSIX | MSI_FLAG_PCI_MSIX_ALLOC_DYN, + .prefix = "PCI-MSIX-PROXY-", + .init_dev_msi_info = msi_parent_init_dev_msi_info, +}; + +int pci_msi_enable_parent_domain(struct pci_dev *pdev) +{ + struct irq_domain *msix_dom; + /* TGLX: Validate has v2 parent domain */ + /* TGLX: validate msix enabled */ + /* TGLX: Validate msix domain supports dynamics msi-x */ + + /* Enable PCI device parent domain */ + msix_dom = dev_msi_get_msix_device_domain(&pdev->dev); + msix_dom->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT; + msix_dom->msi_parent_ops = &pci_msix_parent_ops; + return 0; +} + +void pci_msi_init_subdevice(struct pci_dev *pdev, struct device *dev) +{ + dev_set_msi_domain(dev, dev_msi_get_msix_device_domain(&pdev->dev)); +} + +static bool pci_subdev_create_device_domain(struct device *dev, const struct msi_domain_template *tmpl, + unsigned int hwsize) +{ + struct irq_domain *domain = dev_get_msi_domain(dev); + + if (!domain || !irq_domain_is_msi_parent(domain)) + return true; + + return msi_create_device_irq_domain(dev, MSI_DEFAULT_DOMAIN, tmpl, + hwsize, NULL, NULL); +} + +static void pci_subdev_msix_prepare_desc(struct irq_domain *domain, msi_alloc_info_t *arg, + struct msi_desc *desc) +{ + struct device *parent = desc->dev->parent; + + if (!desc->pci.mask_base) { + /* Not elegant - but needed for irq affinity to work? */ + desc->dev = parent; + msix_prepare_msi_desc(to_pci_dev(parent), desc); + } +} + +static const struct msi_domain_template subdev_template = { + .chip = { + .name = "SUBDEV", + .irq_mask = irq_chip_unmask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_write_msi_msg = pci_msi_domain_write_msg, + .irq_set_affinity = irq_chip_set_affinity_parent, + .flags = IRQCHIP_ONESHOT_SAFE, + }, + .ops = { + /* + * RFC: Sets the desc->dev to the parent PCI device + * Needed for + * irq_setup_affinity() -> + * msi_set_affinity() -> + * parent = irq_d->parent_data; + * parent->chip->irq_set_affinity() to work. + * That could be made more flexible perhaps as + * currently it makes assumption that parent of + * the MSI device is the one to set the affinity on. + */ + .prepare_desc = pci_subdev_msix_prepare_desc, + /* Works because the desc->dev is the parent PCI device */ + .set_desc = pci_msi_domain_set_desc, + }, + .info = { + .flags = MSI_FLAG_PCI_MSIX | MSI_FLAG_PCI_MSIX_ALLOC_DYN | + MSI_FLAG_USE_DEF_CHIP_OPS | MSI_FLAG_USE_DEF_DOM_OPS, + .bus_token = DOMAIN_BUS_PCI_DEVICE_MSIX, + }, +}; + +bool pci_subdev_setup_device_domain(struct device *dev, unsigned int hwsize) +{ + return pci_subdev_create_device_domain(dev, &subdev_template, hwsize); +} +EXPORT_SYMBOL_GPL(pci_subdev_setup_device_domain); diff --git a/include/linux/msi.h b/include/linux/msi.h index 944979763825..ff81b4dcc1d9 100644 --- a/include/linux/msi.h +++ b/include/linux/msi.h @@ -656,8 +656,13 @@ void pci_msi_unmask_irq(struct irq_data *data); struct irq_domain *pci_msi_create_irq_domain(struct fwnode_handle *fwnode, struct msi_domain_info *info, struct irq_domain *parent); +int pci_msi_enable_parent_domain(struct pci_dev *pdev); +struct irq_domain *pci_msi_get_msix_device_domain(struct device *dev); u32 pci_msi_domain_get_msi_rid(struct irq_domain *domain, struct pci_dev *pdev); struct irq_domain *pci_msi_get_device_domain(struct pci_dev *pdev); +struct irq_domain *dev_msi_get_msix_device_domain(struct device *dev); +void pci_msi_init_subdevice(struct pci_dev *pdev, struct device *dev); +bool pci_subdev_setup_device_domain(struct device *dev, unsigned int hwsize); #else /* CONFIG_PCI_MSI */ static inline struct irq_domain *pci_msi_get_device_domain(struct pci_dev *pdev) { diff --git a/include/linux/pci.h b/include/linux/pci.h index 225de9be3006..460551f1bd6e 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1679,6 +1679,9 @@ struct msi_map pci_msix_alloc_irq_at(struct pci_dev *dev, unsigned int index, const struct irq_affinity_desc *affdesc); void pci_msix_free_irq(struct pci_dev *pdev, struct msi_map map); +struct msi_map pci_subdev_msix_alloc_irq_at(struct device *dev, unsigned int index, + const struct irq_affinity_desc *affdesc); +void pci_subdev_msix_free_irq(struct device *dev, struct msi_map map); void pci_free_irq_vectors(struct pci_dev *dev); int pci_irq_vector(struct pci_dev *dev, unsigned int nr); const struct cpumask *pci_irq_get_affinity(struct pci_dev *pdev, int vec); diff --git a/kernel/irq/msi.c b/kernel/irq/msi.c index 5fa0547ece0c..d55a91c7a496 100644 --- a/kernel/irq/msi.c +++ b/kernel/irq/msi.c @@ -1720,3 +1720,8 @@ bool msi_device_has_isolated_msi(struct device *dev) return arch_is_isolated_msi(); } EXPORT_SYMBOL_GPL(msi_device_has_isolated_msi); +struct irq_domain *dev_msi_get_msix_device_domain(struct device *dev) +{ + return dev->msi.data->__domains[0].domain; +} + -- 2.43.0