Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

PCI/MSI: Provide post-enable dynamic allocation interfaces for MSI-X

MSI-X vectors can be allocated after the initial MSI-X enablement, but this
needs explicit support of the underlying interrupt domains.

Provide a function to query the ability and functions to allocate/free
individual vectors post-enable.

The allocation can either request a specific index in the MSI-X table or
with the index argument MSI_ANY_INDEX it allocates the next free vector.

The return value is a struct msi_map which on success contains both index
and the Linux interrupt number. In case of failure index is negative and
the Linux interrupt number is 0.

The allocation function is for a single MSI-X index at a time as that's
sufficient for the most urgent use case VFIO to get rid of the 'disable
MSI-X, reallocate, enable-MSI-X' cycle which is prone to lost interrupts
and redirections to the legacy and obviously unhandled INTx.

As single index allocation is also sufficient for the use cases Jason
Gunthorpe pointed out: Allocation of a MSI-X or IMS vector for a network
queue. See Link below.

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
Acked-by: Bjorn Helgaas <bhelgaas@google.com>
Acked-by: Marc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/all/20211126232735.547996838@linutronix.de
Link: https://lore.kernel.org/r/20221124232326.731233614@linutronix.de

+75 -1
+67
drivers/pci/msi/api.c
··· 113 113 EXPORT_SYMBOL(pci_enable_msix_range); 114 114 115 115 /** 116 + * pci_msix_can_alloc_dyn - Query whether dynamic allocation after enabling 117 + * MSI-X is supported 118 + * 119 + * @dev: PCI device to operate on 120 + * 121 + * Return: True if supported, false otherwise 122 + */ 123 + bool pci_msix_can_alloc_dyn(struct pci_dev *dev) 124 + { 125 + if (!dev->msix_cap) 126 + return false; 127 + 128 + return pci_msi_domain_supports(dev, MSI_FLAG_PCI_MSIX_ALLOC_DYN, DENY_LEGACY); 129 + } 130 + EXPORT_SYMBOL_GPL(pci_msix_can_alloc_dyn); 131 + 132 + /** 133 + * pci_msix_alloc_irq_at - Allocate an MSI-X interrupt after enabling MSI-X 134 + * at a given MSI-X vector index or any free vector index 135 + * 136 + * @dev: PCI device to operate on 137 + * @index: Index to allocate. If @index == MSI_ANY_INDEX this allocates 138 + * the next free index in the MSI-X table 139 + * @affdesc: Optional pointer to an affinity descriptor structure. NULL otherwise 140 + * 141 + * Return: A struct msi_map 142 + * 143 + * On success msi_map::index contains the allocated index (>= 0) and 144 + * msi_map::virq contains the allocated Linux interrupt number (> 0). 145 + * 146 + * On fail msi_map::index contains the error code and msi_map::virq 147 + * is set to 0. 148 + */ 149 + struct msi_map pci_msix_alloc_irq_at(struct pci_dev *dev, unsigned int index, 150 + const struct irq_affinity_desc *affdesc) 151 + { 152 + struct msi_map map = { .index = -ENOTSUPP }; 153 + 154 + if (!dev->msix_enabled) 155 + return map; 156 + 157 + if (!pci_msix_can_alloc_dyn(dev)) 158 + return map; 159 + 160 + return msi_domain_alloc_irq_at(&dev->dev, MSI_DEFAULT_DOMAIN, index, affdesc, NULL); 161 + } 162 + EXPORT_SYMBOL_GPL(pci_msix_alloc_irq_at); 163 + 164 + /** 165 + * pci_msix_free_irq - Free an interrupt on a PCI/MSIX interrupt domain 166 + * which was allocated via pci_msix_alloc_irq_at() 167 + * 168 + * @dev: The PCI device to operate on 169 + * @map: A struct msi_map describing the interrupt to free 170 + * as returned from the allocation function. 171 + */ 172 + void pci_msix_free_irq(struct pci_dev *dev, struct msi_map map) 173 + { 174 + if (WARN_ON_ONCE(map.index < 0 || map.virq <= 0)) 175 + return; 176 + if (WARN_ON_ONCE(!pci_msix_can_alloc_dyn(dev))) 177 + return; 178 + msi_domain_free_irqs_range(&dev->dev, MSI_DEFAULT_DOMAIN, map.index, map.index); 179 + } 180 + EXPORT_SYMBOL_GPL(pci_msix_free_irq); 181 + 182 + /** 116 183 * pci_disable_msix() - Disable MSI-X interrupt mode on device 117 184 * @dev: the PCI device to operate on 118 185 *
+2 -1
drivers/pci/msi/irqdomain.c
··· 225 225 }, 226 226 227 227 .info = { 228 - .flags = MSI_COMMON_FLAGS | MSI_FLAG_PCI_MSIX, 228 + .flags = MSI_COMMON_FLAGS | MSI_FLAG_PCI_MSIX | 229 + MSI_FLAG_PCI_MSIX_ALLOC_DYN, 229 230 .bus_token = DOMAIN_BUS_PCI_DEVICE_MSIX, 230 231 }, 231 232 };
+6
include/linux/pci.h
··· 38 38 #include <linux/interrupt.h> 39 39 #include <linux/io.h> 40 40 #include <linux/resource_ext.h> 41 + #include <linux/msi_api.h> 41 42 #include <uapi/linux/pci.h> 42 43 43 44 #include <linux/pci_ids.h> ··· 1559 1558 int pci_alloc_irq_vectors_affinity(struct pci_dev *dev, unsigned int min_vecs, 1560 1559 unsigned int max_vecs, unsigned int flags, 1561 1560 struct irq_affinity *affd); 1561 + 1562 + bool pci_msix_can_alloc_dyn(struct pci_dev *dev); 1563 + struct msi_map pci_msix_alloc_irq_at(struct pci_dev *dev, unsigned int index, 1564 + const struct irq_affinity_desc *affdesc); 1565 + void pci_msix_free_irq(struct pci_dev *pdev, struct msi_map map); 1562 1566 1563 1567 void pci_free_irq_vectors(struct pci_dev *dev); 1564 1568 int pci_irq_vector(struct pci_dev *dev, unsigned int nr);