]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
PCI: Allow specifying devices using a base bus and path of devfns
authorLogan Gunthorpe <logang@deltatee.com>
Mon, 30 Jul 2018 16:18:38 +0000 (10:18 -0600)
committerBjorn Helgaas <bhelgaas@google.com>
Thu, 9 Aug 2018 21:24:39 +0000 (16:24 -0500)
When specifying PCI devices on the kernel command line using a
bus/device/function address, bus numbers can change when adding or
replacing a device, changing motherboard firmware, or applying kernel
parameters like "pci=assign-buses".  When bus numbers change, it's likely
the command line tweak will be applied to the wrong device.

Therefore, it is useful to be able to specify devices with a base bus
number and the path of devfns needed to get to it, similar to the "device
scope" structure in the Intel VT-d spec, Section 8.3.1.

Thus, we add an option to specify devices in the following format:

  [<domain>:]<bus>:<device>.<func>[/<device>.<func>]*

The path can be any segment within the PCI hierarchy of any length and
determined through the use of 'lspci -t'.  When specified this way, it is
less likely that a renumbered bus will result in a valid device
specification and the tweak won't be applied to the wrong device.

Signed-off-by: Logan Gunthorpe <logang@deltatee.com>
[bhelgaas: use "device" instead of "slot" in documentation since that's the
usual language in the PCI specs]
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Stephen Bates <sbates@raithlin.com>
Reviewed-by: Alex Williamson <alex.williamson@redhat.com>
Acked-by: Christian König <christian.koenig@amd.com>
Documentation/admin-guide/kernel-parameters.txt
drivers/pci/pci.c

index ab36fb34ed01a6451c4ac22c081d33105b0b0848..4fa4c9ff04ae0bee5e8019d570c32aff7e566d89 100644 (file)
                                or a set of devices (<pci_dev>). These are
                                specified in one of the following formats:
 
-                               [<domain>:]<bus>:<device>.<func>
+                               [<domain>:]<bus>:<dev>.<func>[/<dev>.<func>]*
                                pci:<vendor>:<device>[:<subvendor>:<subdevice>]
 
                                Note: the first format specifies a PCI
                                firmware changes, or due to changes caused
                                by other kernel parameters. If the
                                domain is left unspecified, it is
-                               taken to be zero. The second format
+                               taken to be zero. Optionally, a path
+                               to a device through multiple device/function
+                               addresses can be specified after the base
+                               address (this is more robust against
+                               renumbering issues).  The second format
                                selects devices using IDs from the
                                configuration space which may match multiple
                                devices in the system.
index 1574b2da25e7f88645a7ec563062676cf72589c4..a6c38b15ac333fcd9e72bed781c3c2297b5a7aa7 100644 (file)
@@ -191,6 +191,89 @@ void __iomem *pci_ioremap_wc_bar(struct pci_dev *pdev, int bar)
 EXPORT_SYMBOL_GPL(pci_ioremap_wc_bar);
 #endif
 
+/**
+ * pci_dev_str_match_path - test if a path string matches a device
+ * @dev:    the PCI device to test
+ * @p:      string to match the device against
+ * @endptr: pointer to the string after the match
+ *
+ * Test if a string (typically from a kernel parameter) formatted as a
+ * path of device/function addresses matches a PCI device. The string must
+ * be of the form:
+ *
+ *   [<domain>:]<bus>:<device>.<func>[/<device>.<func>]*
+ *
+ * A path for a device can be obtained using 'lspci -t'.  Using a path
+ * is more robust against bus renumbering than using only a single bus,
+ * device and function address.
+ *
+ * Returns 1 if the string matches the device, 0 if it does not and
+ * a negative error code if it fails to parse the string.
+ */
+static int pci_dev_str_match_path(struct pci_dev *dev, const char *path,
+                                 const char **endptr)
+{
+       int ret;
+       int seg, bus, slot, func;
+       char *wpath, *p;
+       char end;
+
+       *endptr = strchrnul(path, ';');
+
+       wpath = kmemdup_nul(path, *endptr - path, GFP_KERNEL);
+       if (!wpath)
+               return -ENOMEM;
+
+       while (1) {
+               p = strrchr(wpath, '/');
+               if (!p)
+                       break;
+               ret = sscanf(p, "/%x.%x%c", &slot, &func, &end);
+               if (ret != 2) {
+                       ret = -EINVAL;
+                       goto free_and_exit;
+               }
+
+               if (dev->devfn != PCI_DEVFN(slot, func)) {
+                       ret = 0;
+                       goto free_and_exit;
+               }
+
+               /*
+                * Note: we don't need to get a reference to the upstream
+                * bridge because we hold a reference to the top level
+                * device which should hold a reference to the bridge,
+                * and so on.
+                */
+               dev = pci_upstream_bridge(dev);
+               if (!dev) {
+                       ret = 0;
+                       goto free_and_exit;
+               }
+
+               *p = 0;
+       }
+
+       ret = sscanf(wpath, "%x:%x:%x.%x%c", &seg, &bus, &slot,
+                    &func, &end);
+       if (ret != 4) {
+               seg = 0;
+               ret = sscanf(wpath, "%x:%x.%x%c", &bus, &slot, &func, &end);
+               if (ret != 3) {
+                       ret = -EINVAL;
+                       goto free_and_exit;
+               }
+       }
+
+       ret = (seg == pci_domain_nr(dev->bus) &&
+              bus == dev->bus->number &&
+              dev->devfn == PCI_DEVFN(slot, func));
+
+free_and_exit:
+       kfree(wpath);
+       return ret;
+}
+
 /**
  * pci_dev_str_match - test if a string matches a device
  * @dev:    the PCI device to test
@@ -200,13 +283,16 @@ EXPORT_SYMBOL_GPL(pci_ioremap_wc_bar);
  * Test if a string (typically from a kernel parameter) matches a specified
  * PCI device. The string may be of one of the following formats:
  *
- *   [<domain>:]<bus>:<device>.<func>
+ *   [<domain>:]<bus>:<device>.<func>[/<device>.<func>]*
  *   pci:<vendor>:<device>[:<subvendor>:<subdevice>]
  *
  * The first format specifies a PCI bus/device/function address which
  * may change if new hardware is inserted, if motherboard firmware changes,
  * or due to changes caused in kernel parameters. If the domain is
- * left unspecified, it is taken to be 0.
+ * left unspecified, it is taken to be 0.  In order to be robust against
+ * bus renumbering issues, a path of PCI device/function numbers may be used
+ * to address the specific device.  The path for a device can be determined
+ * through the use of 'lspci -t'.
  *
  * The second format matches devices using IDs in the configuration
  * space which may match multiple devices in the system. A value of 0
@@ -222,7 +308,7 @@ static int pci_dev_str_match(struct pci_dev *dev, const char *p,
                             const char **endptr)
 {
        int ret;
-       int seg, bus, slot, func, count;
+       int count;
        unsigned short vendor, device, subsystem_vendor, subsystem_device;
 
        if (strncmp(p, "pci:", 4) == 0) {
@@ -248,25 +334,15 @@ static int pci_dev_str_match(struct pci_dev *dev, const char *p,
                    (!subsystem_device ||
                            subsystem_device == dev->subsystem_device))
                        goto found;
-
        } else {
-               /* PCI Bus, Device, Function IDs are specified */
-               ret = sscanf(p, "%x:%x:%x.%x%n", &seg, &bus, &slot,
-                            &func, &count);
-               if (ret != 4) {
-                       seg = 0;
-                       ret = sscanf(p, "%x:%x.%x%n", &bus, &slot,
-                                    &func, &count);
-                       if (ret != 3)
-                               return -EINVAL;
-               }
-
-               p += count;
-
-               if (seg == pci_domain_nr(dev->bus) &&
-                   bus == dev->bus->number &&
-                   slot == PCI_SLOT(dev->devfn) &&
-                   func == PCI_FUNC(dev->devfn))
+               /*
+                * PCI Bus, Device, Function IDs are specified
+                *  (optionally, may include a path of devfns following it)
+                */
+               ret = pci_dev_str_match_path(dev, p, &p);
+               if (ret < 0)
+                       return ret;
+               else if (ret)
                        goto found;
        }