]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
PCI/MSI: Provide a sane mechanism for TPH
authorThomas Gleixner <tglx@linutronix.de>
Thu, 13 Mar 2025 13:03:48 +0000 (14:03 +0100)
committerThomas Gleixner <tglx@linutronix.de>
Thu, 13 Mar 2025 17:58:00 +0000 (18:58 +0100)
The PCI/TPH driver fiddles with the MSI-X control word of an active
interrupt completely unserialized against concurrent operations issued
from the interrupt core. It also brings the PCI/MSI-X internal cached
control word out of sync.

Provide a function, which has the required serialization and keeps the
control word cache in sync.

Unfortunately this requires to look up and lock the interrupt descriptor,
which should be only done in the interrupt core code. But confining this
particular oddity in the PCI/MSI core is the lesser of all evil. A
interrupt core implementation would require a larger pile of infrastructure
and indirections for dubious value.

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Acked-by: Bjorn Helgaas <bhelgaas@google.com>
Link: https://lore.kernel.org/all/20250313130321.822790423@linutronix.de
drivers/pci/msi/msi.c
drivers/pci/pci.h

index b243510999e49cc5fad5babe1e79aac33c2bba23..dc78d9d402c3b2dcd752c69341191a5905fb81b7 100644 (file)
@@ -916,6 +916,53 @@ void pci_free_msi_irqs(struct pci_dev *dev)
        }
 }
 
+#ifdef CONFIG_PCIE_TPH
+/**
+ * pci_msix_write_tph_tag - Update the TPH tag for a given MSI-X vector
+ * @pdev:      The PCIe device to update
+ * @index:     The MSI-X index to update
+ * @tag:       The tag to write
+ *
+ * Returns: 0 on success, error code on failure
+ */
+int pci_msix_write_tph_tag(struct pci_dev *pdev, unsigned int index, u16 tag)
+{
+       struct msi_desc *msi_desc;
+       struct irq_desc *irq_desc;
+       unsigned int virq;
+
+       if (!pdev->msix_enabled)
+               return -ENXIO;
+
+       guard(msi_descs_lock)(&pdev->dev);
+       virq = msi_get_virq(&pdev->dev, index);
+       if (!virq)
+               return -ENXIO;
+       /*
+        * This is a horrible hack, but short of implementing a PCI
+        * specific interrupt chip callback and a huge pile of
+        * infrastructure, this is the minor nuissance. It provides the
+        * protection against concurrent operations on this entry and keeps
+        * the control word cache in sync.
+        */
+       irq_desc = irq_to_desc(virq);
+       if (!irq_desc)
+               return -ENXIO;
+
+       guard(raw_spinlock_irq)(&irq_desc->lock);
+       msi_desc = irq_data_get_msi_desc(&irq_desc->irq_data);
+       if (!msi_desc || msi_desc->pci.msi_attrib.is_virtual)
+               return -ENXIO;
+
+       msi_desc->pci.msix_ctrl &= ~PCI_MSIX_ENTRY_CTRL_ST;
+       msi_desc->pci.msix_ctrl |= FIELD_PREP(PCI_MSIX_ENTRY_CTRL_ST, tag);
+       pci_msix_write_vector_ctrl(msi_desc, msi_desc->pci.msix_ctrl);
+       /* Flush the write */
+       readl(pci_msix_desc_addr(msi_desc));
+       return 0;
+}
+#endif
+
 /* Misc. infrastructure */
 
 struct pci_dev *msi_desc_to_pci_dev(struct msi_desc *desc)
index 01e51db8d285af54673db3ea526ceda073c94ec9..2e9cf26a9ee9b92359964ba4ed54fed7c4b73789 100644 (file)
@@ -989,6 +989,15 @@ int pcim_request_region_exclusive(struct pci_dev *pdev, int bar,
                                  const char *name);
 void pcim_release_region(struct pci_dev *pdev, int bar);
 
+#ifdef CONFIG_PCI_MSI
+int pci_msix_write_tph_tag(struct pci_dev *pdev, unsigned int index, u16 tag);
+#else
+static inline int pci_msix_write_tph_tag(struct pci_dev *pdev, unsigned int index, u16 tag)
+{
+       return -ENODEV;
+}
+#endif
+
 /*
  * Config Address for PCI Configuration Mechanism #1
  *