Commit 45db3370 authored by Logan Gunthorpe's avatar Logan Gunthorpe Committed by Bjorn Helgaas

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: default avatarLogan 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: default avatarBjorn Helgaas <bhelgaas@google.com>
Reviewed-by: default avatarStephen Bates <sbates@raithlin.com>
Reviewed-by: default avatarAlex Williamson <alex.williamson@redhat.com>
Acked-by: default avatarChristian König <christian.koenig@amd.com>
parent 07d8d7e5
...@@ -3000,7 +3000,7 @@ ...@@ -3000,7 +3000,7 @@
or a set of devices (<pci_dev>). These are or a set of devices (<pci_dev>). These are
specified in one of the following formats: specified in one of the following formats:
[<domain>:]<bus>:<device>.<func> [<domain>:]<bus>:<dev>.<func>[/<dev>.<func>]*
pci:<vendor>:<device>[:<subvendor>:<subdevice>] pci:<vendor>:<device>[:<subvendor>:<subdevice>]
Note: the first format specifies a PCI Note: the first format specifies a PCI
...@@ -3009,7 +3009,11 @@ ...@@ -3009,7 +3009,11 @@
firmware changes, or due to changes caused firmware changes, or due to changes caused
by other kernel parameters. If the by other kernel parameters. If the
domain is left unspecified, it is 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 selects devices using IDs from the
configuration space which may match multiple configuration space which may match multiple
devices in the system. devices in the system.
......
...@@ -191,6 +191,89 @@ void __iomem *pci_ioremap_wc_bar(struct pci_dev *pdev, int bar) ...@@ -191,6 +191,89 @@ void __iomem *pci_ioremap_wc_bar(struct pci_dev *pdev, int bar)
EXPORT_SYMBOL_GPL(pci_ioremap_wc_bar); EXPORT_SYMBOL_GPL(pci_ioremap_wc_bar);
#endif #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 * pci_dev_str_match - test if a string matches a device
* @dev: the PCI device to test * @dev: the PCI device to test
...@@ -200,13 +283,16 @@ EXPORT_SYMBOL_GPL(pci_ioremap_wc_bar); ...@@ -200,13 +283,16 @@ EXPORT_SYMBOL_GPL(pci_ioremap_wc_bar);
* Test if a string (typically from a kernel parameter) matches a specified * Test if a string (typically from a kernel parameter) matches a specified
* PCI device. The string may be of one of the following formats: * 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>] * pci:<vendor>:<device>[:<subvendor>:<subdevice>]
* *
* The first format specifies a PCI bus/device/function address which * The first format specifies a PCI bus/device/function address which
* may change if new hardware is inserted, if motherboard firmware changes, * may change if new hardware is inserted, if motherboard firmware changes,
* or due to changes caused in kernel parameters. If the domain is * 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 * The second format matches devices using IDs in the configuration
* space which may match multiple devices in the system. A value of 0 * 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, ...@@ -222,7 +308,7 @@ static int pci_dev_str_match(struct pci_dev *dev, const char *p,
const char **endptr) const char **endptr)
{ {
int ret; int ret;
int seg, bus, slot, func, count; int count;
unsigned short vendor, device, subsystem_vendor, subsystem_device; unsigned short vendor, device, subsystem_vendor, subsystem_device;
if (strncmp(p, "pci:", 4) == 0) { if (strncmp(p, "pci:", 4) == 0) {
...@@ -248,25 +334,15 @@ static int pci_dev_str_match(struct pci_dev *dev, const char *p, ...@@ -248,25 +334,15 @@ static int pci_dev_str_match(struct pci_dev *dev, const char *p,
(!subsystem_device || (!subsystem_device ||
subsystem_device == dev->subsystem_device)) subsystem_device == dev->subsystem_device))
goto found; goto found;
} else { } else {
/* PCI Bus, Device, Function IDs are specified */ /*
ret = sscanf(p, "%x:%x:%x.%x%n", &seg, &bus, &slot, * PCI Bus, Device, Function IDs are specified
&func, &count); * (optionally, may include a path of devfns following it)
if (ret != 4) { */
seg = 0; ret = pci_dev_str_match_path(dev, p, &p);
ret = sscanf(p, "%x:%x.%x%n", &bus, &slot, if (ret < 0)
&func, &count); return ret;
if (ret != 3) else if (ret)
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))
goto found; goto found;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment