Skip to content

Commit

Permalink
PCI: Allow specifying devices using a base bus and path of devfns
Browse files Browse the repository at this point in the history
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 <[email protected]>
[bhelgaas: use "device" instead of "slot" in documentation since that's the
usual language in the PCI specs]
Signed-off-by: Bjorn Helgaas <[email protected]>
Reviewed-by: Stephen Bates <[email protected]>
Reviewed-by: Alex Williamson <[email protected]>
Acked-by: Christian König <[email protected]>
  • Loading branch information
lsgunth authored and bjorn-helgaas committed Aug 9, 2018
1 parent 07d8d7e commit 45db337
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 23 deletions.
8 changes: 6 additions & 2 deletions Documentation/admin-guide/kernel-parameters.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3000,7 +3000,7 @@
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
Expand All @@ -3009,7 +3009,11 @@
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.
Expand Down
118 changes: 97 additions & 21 deletions drivers/pci/pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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) {
Expand All @@ -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;
}

Expand Down

0 comments on commit 45db337

Please sign in to comment.