x86/PCI: Handle PIRQ routing tables with no router device given
authorMaciej W. Rozycki <macro@orcam.me.uk>
Thu, 31 Mar 2022 07:11:01 +0000 (08:11 +0100)
committerThomas Gleixner <tglx@linutronix.de>
Sun, 10 Apr 2022 10:48:14 +0000 (12:48 +0200)
PIRQ routing tables provided by the PCI BIOS usually specify the PCI
vendor:device ID as well as the bus address of the device implementing
the PIRQ router, e.g.:

PCI: Interrupt Routing Table found at 0xc00fde10
[...]
PCI: Attempting to find IRQ router for [8086:7000]
pci 0000:00:07.0: PIIX/ICH IRQ router [8086:7000]

however in some cases they do not, in which case we fail to match the
router handler, e.g.:

PCI: Interrupt Routing Table found at 0xc00fdae0
[...]
PCI: Attempting to find IRQ router for [0000:0000]
PCI: Interrupt router not found at 00:00

This is because we always match the vendor:device ID and the bus address
literally, even if they are all zeros.

Handle this case then and iterate over all PCI devices until we find a
matching router handler if the vendor ID given by the routing table is
the invalid value of zero:

PCI: Attempting to find IRQ router for [0000:0000]
PCI: Trying IRQ router for [1039:0496]
pci 0000:00:05.0: SiS85C497 IRQ router [1039:0496]

Signed-off-by: Maciej W. Rozycki <macro@orcam.me.uk>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Tested-by: Nikolai Zhubr <zhubr.2@gmail.com>
Link: https://lore.kernel.org/r/alpine.DEB.2.21.2203302018570.9038@angie.orcam.me.uk
arch/x86/pci/irq.c

index ef97b26..d4ecf88 100644 (file)
@@ -1175,10 +1175,32 @@ static struct pci_dev *pirq_router_dev;
  *     chipset" ?
  */
 
+static bool __init pirq_try_router(struct irq_router *r,
+                                  struct irq_routing_table *rt,
+                                  struct pci_dev *dev)
+{
+       struct irq_router_handler *h;
+
+       DBG(KERN_DEBUG "PCI: Trying IRQ router for [%04x:%04x]\n",
+           dev->vendor, dev->device);
+
+       for (h = pirq_routers; h->vendor; h++) {
+               /* First look for a router match */
+               if (rt->rtr_vendor == h->vendor &&
+                   h->probe(r, dev, rt->rtr_device))
+                       return true;
+               /* Fall back to a device match */
+               if (dev->vendor == h->vendor &&
+                   h->probe(r, dev, dev->device))
+                       return true;
+       }
+       return false;
+}
+
 static void __init pirq_find_router(struct irq_router *r)
 {
        struct irq_routing_table *rt = pirq_table;
-       struct irq_router_handler *h;
+       struct pci_dev *dev;
 
 #ifdef CONFIG_PCI_BIOS
        if (!rt->signature) {
@@ -1197,27 +1219,29 @@ static void __init pirq_find_router(struct irq_router *r)
        DBG(KERN_DEBUG "PCI: Attempting to find IRQ router for [%04x:%04x]\n",
            rt->rtr_vendor, rt->rtr_device);
 
-       pirq_router_dev = pci_get_domain_bus_and_slot(0, rt->rtr_bus,
-                                                     rt->rtr_devfn);
-       if (!pirq_router_dev) {
-               DBG(KERN_DEBUG "PCI: Interrupt router not found at "
-                       "%02x:%02x\n", rt->rtr_bus, rt->rtr_devfn);
-               return;
+       /* Use any vendor:device provided by the routing table or try all.  */
+       if (rt->rtr_vendor) {
+               dev = pci_get_domain_bus_and_slot(0, rt->rtr_bus,
+                                                 rt->rtr_devfn);
+               if (dev && pirq_try_router(r, rt, dev))
+                       pirq_router_dev = dev;
+       } else {
+               dev = NULL;
+               for_each_pci_dev(dev) {
+                       if (pirq_try_router(r, rt, dev)) {
+                               pirq_router_dev = dev;
+                               break;
+                       }
+               }
        }
 
-       for (h = pirq_routers; h->vendor; h++) {
-               /* First look for a router match */
-               if (rt->rtr_vendor == h->vendor &&
-                       h->probe(r, pirq_router_dev, rt->rtr_device))
-                       break;
-               /* Fall back to a device match */
-               if (pirq_router_dev->vendor == h->vendor &&
-                       h->probe(r, pirq_router_dev, pirq_router_dev->device))
-                       break;
-       }
-       dev_info(&pirq_router_dev->dev, "%s IRQ router [%04x:%04x]\n",
-                pirq_router.name,
-                pirq_router_dev->vendor, pirq_router_dev->device);
+       if (pirq_router_dev)
+               dev_info(&pirq_router_dev->dev, "%s IRQ router [%04x:%04x]\n",
+                        pirq_router.name,
+                        pirq_router_dev->vendor, pirq_router_dev->device);
+       else
+               DBG(KERN_DEBUG "PCI: Interrupt router not found at "
+                   "%02x:%02x\n", rt->rtr_bus, rt->rtr_devfn);
 
        /* The device remains referenced for the kernel lifetime */
 }