PCI: Allow specifying devices using a base bus and path of devfns

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>
This commit is contained in:
Logan Gunthorpe 2018-07-30 10:18:38 -06:00 committed by Bjorn Helgaas
parent 07d8d7e57c
commit 45db33709c
2 changed files with 103 additions and 23 deletions

View File

@ -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
@ -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.

View 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;
}