|
@@ -23,7 +23,6 @@
|
|
|
#include <linux/string.h>
|
|
|
#include <linux/log2.h>
|
|
|
#include <linux/logic_pio.h>
|
|
|
-#include <linux/pci-aspm.h>
|
|
|
#include <linux/pm_wakeup.h>
|
|
|
#include <linux/interrupt.h>
|
|
|
#include <linux/device.h>
|
|
@@ -115,6 +114,9 @@ static bool pcie_ari_disabled;
|
|
|
/* If set, the PCIe ATS capability will not be used. */
|
|
|
static bool pcie_ats_disabled;
|
|
|
|
|
|
+/* If set, the PCI config space of each device is printed during boot. */
|
|
|
+bool pci_early_dump;
|
|
|
+
|
|
|
bool pci_ats_disabled(void)
|
|
|
{
|
|
|
return pcie_ats_disabled;
|
|
@@ -191,6 +193,168 @@ 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
|
|
|
+ * @p: string to match the device against
|
|
|
+ * @endptr: pointer to the string after the match
|
|
|
+ *
|
|
|
+ * 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>[/<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. 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
|
|
|
+ * for any field will match all devices. (Note: this differs from
|
|
|
+ * in-kernel code that uses PCI_ANY_ID which is ~0; this is for
|
|
|
+ * legacy reasons and convenience so users don't have to specify
|
|
|
+ * FFFFFFFFs on the command line.)
|
|
|
+ *
|
|
|
+ * Returns 1 if the string matches the device, 0 if it does not and
|
|
|
+ * a negative error code if the string cannot be parsed.
|
|
|
+ */
|
|
|
+static int pci_dev_str_match(struct pci_dev *dev, const char *p,
|
|
|
+ const char **endptr)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ int count;
|
|
|
+ unsigned short vendor, device, subsystem_vendor, subsystem_device;
|
|
|
+
|
|
|
+ if (strncmp(p, "pci:", 4) == 0) {
|
|
|
+ /* PCI vendor/device (subvendor/subdevice) IDs are specified */
|
|
|
+ p += 4;
|
|
|
+ ret = sscanf(p, "%hx:%hx:%hx:%hx%n", &vendor, &device,
|
|
|
+ &subsystem_vendor, &subsystem_device, &count);
|
|
|
+ if (ret != 4) {
|
|
|
+ ret = sscanf(p, "%hx:%hx%n", &vendor, &device, &count);
|
|
|
+ if (ret != 2)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ subsystem_vendor = 0;
|
|
|
+ subsystem_device = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ p += count;
|
|
|
+
|
|
|
+ if ((!vendor || vendor == dev->vendor) &&
|
|
|
+ (!device || device == dev->device) &&
|
|
|
+ (!subsystem_vendor ||
|
|
|
+ subsystem_vendor == dev->subsystem_vendor) &&
|
|
|
+ (!subsystem_device ||
|
|
|
+ subsystem_device == dev->subsystem_device))
|
|
|
+ goto found;
|
|
|
+ } else {
|
|
|
+ /*
|
|
|
+ * 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ *endptr = p;
|
|
|
+ return 0;
|
|
|
+
|
|
|
+found:
|
|
|
+ *endptr = p;
|
|
|
+ return 1;
|
|
|
+}
|
|
|
|
|
|
static int __pci_find_next_cap_ttl(struct pci_bus *bus, unsigned int devfn,
|
|
|
u8 pos, int cap, int *ttl)
|
|
@@ -1171,6 +1335,33 @@ static void pci_restore_config_space(struct pci_dev *pdev)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+static void pci_restore_rebar_state(struct pci_dev *pdev)
|
|
|
+{
|
|
|
+ unsigned int pos, nbars, i;
|
|
|
+ u32 ctrl;
|
|
|
+
|
|
|
+ pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR);
|
|
|
+ if (!pos)
|
|
|
+ return;
|
|
|
+
|
|
|
+ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
|
|
|
+ nbars = (ctrl & PCI_REBAR_CTRL_NBAR_MASK) >>
|
|
|
+ PCI_REBAR_CTRL_NBAR_SHIFT;
|
|
|
+
|
|
|
+ for (i = 0; i < nbars; i++, pos += 8) {
|
|
|
+ struct resource *res;
|
|
|
+ int bar_idx, size;
|
|
|
+
|
|
|
+ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
|
|
|
+ bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX;
|
|
|
+ res = pdev->resource + bar_idx;
|
|
|
+ size = order_base_2((resource_size(res) >> 20) | 1) - 1;
|
|
|
+ ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
|
|
|
+ ctrl |= size << PCI_REBAR_CTRL_BAR_SHIFT;
|
|
|
+ pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* pci_restore_state - Restore the saved state of a PCI device
|
|
|
* @dev: - PCI device that we're dealing with
|
|
@@ -1186,6 +1377,7 @@ void pci_restore_state(struct pci_dev *dev)
|
|
|
pci_restore_pri_state(dev);
|
|
|
pci_restore_ats_state(dev);
|
|
|
pci_restore_vc_state(dev);
|
|
|
+ pci_restore_rebar_state(dev);
|
|
|
|
|
|
pci_cleanup_aer_error_status_regs(dev);
|
|
|
|
|
@@ -2045,6 +2237,7 @@ static pci_power_t pci_target_state(struct pci_dev *dev, bool wakeup)
|
|
|
case PCI_D2:
|
|
|
if (pci_no_d1d2(dev))
|
|
|
break;
|
|
|
+ /* else: fall through */
|
|
|
default:
|
|
|
target_state = state;
|
|
|
}
|
|
@@ -2290,7 +2483,7 @@ void pci_config_pm_runtime_put(struct pci_dev *pdev)
|
|
|
* @bridge: Bridge to check
|
|
|
*
|
|
|
* This function checks if it is possible to move the bridge to D3.
|
|
|
- * Currently we only allow D3 for recent enough PCIe ports.
|
|
|
+ * Currently we only allow D3 for recent enough PCIe ports and Thunderbolt.
|
|
|
*/
|
|
|
bool pci_bridge_d3_possible(struct pci_dev *bridge)
|
|
|
{
|
|
@@ -2305,18 +2498,27 @@ bool pci_bridge_d3_possible(struct pci_dev *bridge)
|
|
|
return false;
|
|
|
|
|
|
/*
|
|
|
- * Hotplug interrupts cannot be delivered if the link is down,
|
|
|
- * so parents of a hotplug port must stay awake. In addition,
|
|
|
- * hotplug ports handled by firmware in System Management Mode
|
|
|
+ * Hotplug ports handled by firmware in System Management Mode
|
|
|
* may not be put into D3 by the OS (Thunderbolt on non-Macs).
|
|
|
- * For simplicity, disallow in general for now.
|
|
|
*/
|
|
|
- if (bridge->is_hotplug_bridge)
|
|
|
+ if (bridge->is_hotplug_bridge && !pciehp_is_native(bridge))
|
|
|
return false;
|
|
|
|
|
|
if (pci_bridge_d3_force)
|
|
|
return true;
|
|
|
|
|
|
+ /* Even the oldest 2010 Thunderbolt controller supports D3. */
|
|
|
+ if (bridge->is_thunderbolt)
|
|
|
+ return true;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Hotplug ports handled natively by the OS were not validated
|
|
|
+ * by vendors for runtime D3 at least until 2018 because there
|
|
|
+ * was no OS support.
|
|
|
+ */
|
|
|
+ if (bridge->is_hotplug_bridge)
|
|
|
+ return false;
|
|
|
+
|
|
|
/*
|
|
|
* It should be safe to put PCIe ports from 2015 or newer
|
|
|
* to D3.
|
|
@@ -2820,6 +3022,66 @@ void pci_request_acs(void)
|
|
|
pci_acs_enable = 1;
|
|
|
}
|
|
|
|
|
|
+static const char *disable_acs_redir_param;
|
|
|
+
|
|
|
+/**
|
|
|
+ * pci_disable_acs_redir - disable ACS redirect capabilities
|
|
|
+ * @dev: the PCI device
|
|
|
+ *
|
|
|
+ * For only devices specified in the disable_acs_redir parameter.
|
|
|
+ */
|
|
|
+static void pci_disable_acs_redir(struct pci_dev *dev)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+ const char *p;
|
|
|
+ int pos;
|
|
|
+ u16 ctrl;
|
|
|
+
|
|
|
+ if (!disable_acs_redir_param)
|
|
|
+ return;
|
|
|
+
|
|
|
+ p = disable_acs_redir_param;
|
|
|
+ while (*p) {
|
|
|
+ ret = pci_dev_str_match(dev, p, &p);
|
|
|
+ if (ret < 0) {
|
|
|
+ pr_info_once("PCI: Can't parse disable_acs_redir parameter: %s\n",
|
|
|
+ disable_acs_redir_param);
|
|
|
+
|
|
|
+ break;
|
|
|
+ } else if (ret == 1) {
|
|
|
+ /* Found a match */
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (*p != ';' && *p != ',') {
|
|
|
+ /* End of param or invalid format */
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ p++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret != 1)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (!pci_dev_specific_disable_acs_redir(dev))
|
|
|
+ return;
|
|
|
+
|
|
|
+ pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ACS);
|
|
|
+ if (!pos) {
|
|
|
+ pci_warn(dev, "cannot disable ACS redirect for this hardware as it does not have ACS capabilities\n");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ pci_read_config_word(dev, pos + PCI_ACS_CTRL, &ctrl);
|
|
|
+
|
|
|
+ /* P2P Request & Completion Redirect */
|
|
|
+ ctrl &= ~(PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_EC);
|
|
|
+
|
|
|
+ pci_write_config_word(dev, pos + PCI_ACS_CTRL, ctrl);
|
|
|
+
|
|
|
+ pci_info(dev, "disabled ACS redirect\n");
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* pci_std_enable_acs - enable ACS on devices using standard ACS capabilites
|
|
|
* @dev: the PCI device
|
|
@@ -2859,12 +3121,22 @@ static void pci_std_enable_acs(struct pci_dev *dev)
|
|
|
void pci_enable_acs(struct pci_dev *dev)
|
|
|
{
|
|
|
if (!pci_acs_enable)
|
|
|
- return;
|
|
|
+ goto disable_acs_redir;
|
|
|
|
|
|
if (!pci_dev_specific_enable_acs(dev))
|
|
|
- return;
|
|
|
+ goto disable_acs_redir;
|
|
|
|
|
|
pci_std_enable_acs(dev);
|
|
|
+
|
|
|
+disable_acs_redir:
|
|
|
+ /*
|
|
|
+ * Note: pci_disable_acs_redir() must be called even if ACS was not
|
|
|
+ * enabled by the kernel because it may have been enabled by
|
|
|
+ * platform firmware. So if we are told to disable it, we should
|
|
|
+ * always disable it after setting the kernel's default
|
|
|
+ * preferences.
|
|
|
+ */
|
|
|
+ pci_disable_acs_redir(dev);
|
|
|
}
|
|
|
|
|
|
static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags)
|
|
@@ -3070,7 +3342,7 @@ int pci_rebar_get_current_size(struct pci_dev *pdev, int bar)
|
|
|
return pos;
|
|
|
|
|
|
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
|
|
|
- return (ctrl & PCI_REBAR_CTRL_BAR_SIZE) >> 8;
|
|
|
+ return (ctrl & PCI_REBAR_CTRL_BAR_SIZE) >> PCI_REBAR_CTRL_BAR_SHIFT;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -3093,7 +3365,7 @@ int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size)
|
|
|
|
|
|
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
|
|
|
ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
|
|
|
- ctrl |= size << 8;
|
|
|
+ ctrl |= size << PCI_REBAR_CTRL_BAR_SHIFT;
|
|
|
pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
|
|
|
return 0;
|
|
|
}
|
|
@@ -4077,7 +4349,7 @@ static int pci_dev_wait(struct pci_dev *dev, char *reset_type, int timeout)
|
|
|
* Returns true if the device advertises support for PCIe function level
|
|
|
* resets.
|
|
|
*/
|
|
|
-static bool pcie_has_flr(struct pci_dev *dev)
|
|
|
+bool pcie_has_flr(struct pci_dev *dev)
|
|
|
{
|
|
|
u32 cap;
|
|
|
|
|
@@ -4087,6 +4359,7 @@ static bool pcie_has_flr(struct pci_dev *dev)
|
|
|
pcie_capability_read_dword(dev, PCI_EXP_DEVCAP, &cap);
|
|
|
return cap & PCI_EXP_DEVCAP_FLR;
|
|
|
}
|
|
|
+EXPORT_SYMBOL_GPL(pcie_has_flr);
|
|
|
|
|
|
/**
|
|
|
* pcie_flr - initiate a PCIe function level reset
|
|
@@ -4262,19 +4535,18 @@ void __weak pcibios_reset_secondary_bus(struct pci_dev *dev)
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * pci_reset_bridge_secondary_bus - Reset the secondary bus on a PCI bridge.
|
|
|
+ * pci_bridge_secondary_bus_reset - Reset the secondary bus on a PCI bridge.
|
|
|
* @dev: Bridge device
|
|
|
*
|
|
|
* Use the bridge control register to assert reset on the secondary bus.
|
|
|
* Devices on the secondary bus are left in power-on state.
|
|
|
*/
|
|
|
-int pci_reset_bridge_secondary_bus(struct pci_dev *dev)
|
|
|
+int pci_bridge_secondary_bus_reset(struct pci_dev *dev)
|
|
|
{
|
|
|
pcibios_reset_secondary_bus(dev);
|
|
|
|
|
|
return pci_dev_wait(dev, "bus reset", PCIE_RESET_READY_POLL_MS);
|
|
|
}
|
|
|
-EXPORT_SYMBOL_GPL(pci_reset_bridge_secondary_bus);
|
|
|
|
|
|
static int pci_parent_bus_reset(struct pci_dev *dev, int probe)
|
|
|
{
|
|
@@ -4291,9 +4563,7 @@ static int pci_parent_bus_reset(struct pci_dev *dev, int probe)
|
|
|
if (probe)
|
|
|
return 0;
|
|
|
|
|
|
- pci_reset_bridge_secondary_bus(dev->bus->self);
|
|
|
-
|
|
|
- return 0;
|
|
|
+ return pci_bridge_secondary_bus_reset(dev->bus->self);
|
|
|
}
|
|
|
|
|
|
static int pci_reset_hotplug_slot(struct hotplug_slot *hotplug, int probe)
|
|
@@ -4825,7 +5095,7 @@ int pci_probe_reset_slot(struct pci_slot *slot)
|
|
|
EXPORT_SYMBOL_GPL(pci_probe_reset_slot);
|
|
|
|
|
|
/**
|
|
|
- * pci_reset_slot - reset a PCI slot
|
|
|
+ * __pci_reset_slot - Try to reset a PCI slot
|
|
|
* @slot: PCI slot to reset
|
|
|
*
|
|
|
* A PCI bus may host multiple slots, each slot may support a reset mechanism
|
|
@@ -4837,33 +5107,9 @@ EXPORT_SYMBOL_GPL(pci_probe_reset_slot);
|
|
|
* through this function. PCI config space of all devices in the slot and
|
|
|
* behind the slot is saved before and restored after reset.
|
|
|
*
|
|
|
- * Return 0 on success, non-zero on error.
|
|
|
- */
|
|
|
-int pci_reset_slot(struct pci_slot *slot)
|
|
|
-{
|
|
|
- int rc;
|
|
|
-
|
|
|
- rc = pci_slot_reset(slot, 1);
|
|
|
- if (rc)
|
|
|
- return rc;
|
|
|
-
|
|
|
- pci_slot_save_and_disable(slot);
|
|
|
-
|
|
|
- rc = pci_slot_reset(slot, 0);
|
|
|
-
|
|
|
- pci_slot_restore(slot);
|
|
|
-
|
|
|
- return rc;
|
|
|
-}
|
|
|
-EXPORT_SYMBOL_GPL(pci_reset_slot);
|
|
|
-
|
|
|
-/**
|
|
|
- * pci_try_reset_slot - Try to reset a PCI slot
|
|
|
- * @slot: PCI slot to reset
|
|
|
- *
|
|
|
* Same as above except return -EAGAIN if the slot cannot be locked
|
|
|
*/
|
|
|
-int pci_try_reset_slot(struct pci_slot *slot)
|
|
|
+static int __pci_reset_slot(struct pci_slot *slot)
|
|
|
{
|
|
|
int rc;
|
|
|
|
|
@@ -4884,10 +5130,11 @@ int pci_try_reset_slot(struct pci_slot *slot)
|
|
|
|
|
|
return rc;
|
|
|
}
|
|
|
-EXPORT_SYMBOL_GPL(pci_try_reset_slot);
|
|
|
|
|
|
static int pci_bus_reset(struct pci_bus *bus, int probe)
|
|
|
{
|
|
|
+ int ret;
|
|
|
+
|
|
|
if (!bus->self || !pci_bus_resetable(bus))
|
|
|
return -ENOTTY;
|
|
|
|
|
@@ -4898,11 +5145,11 @@ static int pci_bus_reset(struct pci_bus *bus, int probe)
|
|
|
|
|
|
might_sleep();
|
|
|
|
|
|
- pci_reset_bridge_secondary_bus(bus->self);
|
|
|
+ ret = pci_bridge_secondary_bus_reset(bus->self);
|
|
|
|
|
|
pci_bus_unlock(bus);
|
|
|
|
|
|
- return 0;
|
|
|
+ return ret;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -4918,15 +5165,12 @@ int pci_probe_reset_bus(struct pci_bus *bus)
|
|
|
EXPORT_SYMBOL_GPL(pci_probe_reset_bus);
|
|
|
|
|
|
/**
|
|
|
- * pci_reset_bus - reset a PCI bus
|
|
|
+ * __pci_reset_bus - Try to reset a PCI bus
|
|
|
* @bus: top level PCI bus to reset
|
|
|
*
|
|
|
- * Do a bus reset on the given bus and any subordinate buses, saving
|
|
|
- * and restoring state of all devices.
|
|
|
- *
|
|
|
- * Return 0 on success, non-zero on error.
|
|
|
+ * Same as above except return -EAGAIN if the bus cannot be locked
|
|
|
*/
|
|
|
-int pci_reset_bus(struct pci_bus *bus)
|
|
|
+static int __pci_reset_bus(struct pci_bus *bus)
|
|
|
{
|
|
|
int rc;
|
|
|
|
|
@@ -4936,42 +5180,30 @@ int pci_reset_bus(struct pci_bus *bus)
|
|
|
|
|
|
pci_bus_save_and_disable(bus);
|
|
|
|
|
|
- rc = pci_bus_reset(bus, 0);
|
|
|
+ if (pci_bus_trylock(bus)) {
|
|
|
+ might_sleep();
|
|
|
+ rc = pci_bridge_secondary_bus_reset(bus->self);
|
|
|
+ pci_bus_unlock(bus);
|
|
|
+ } else
|
|
|
+ rc = -EAGAIN;
|
|
|
|
|
|
pci_bus_restore(bus);
|
|
|
|
|
|
return rc;
|
|
|
}
|
|
|
-EXPORT_SYMBOL_GPL(pci_reset_bus);
|
|
|
|
|
|
/**
|
|
|
- * pci_try_reset_bus - Try to reset a PCI bus
|
|
|
- * @bus: top level PCI bus to reset
|
|
|
+ * pci_reset_bus - Try to reset a PCI bus
|
|
|
+ * @pdev: top level PCI device to reset via slot/bus
|
|
|
*
|
|
|
* Same as above except return -EAGAIN if the bus cannot be locked
|
|
|
*/
|
|
|
-int pci_try_reset_bus(struct pci_bus *bus)
|
|
|
+int pci_reset_bus(struct pci_dev *pdev)
|
|
|
{
|
|
|
- int rc;
|
|
|
-
|
|
|
- rc = pci_bus_reset(bus, 1);
|
|
|
- if (rc)
|
|
|
- return rc;
|
|
|
-
|
|
|
- pci_bus_save_and_disable(bus);
|
|
|
-
|
|
|
- if (pci_bus_trylock(bus)) {
|
|
|
- might_sleep();
|
|
|
- pci_reset_bridge_secondary_bus(bus->self);
|
|
|
- pci_bus_unlock(bus);
|
|
|
- } else
|
|
|
- rc = -EAGAIN;
|
|
|
-
|
|
|
- pci_bus_restore(bus);
|
|
|
-
|
|
|
- return rc;
|
|
|
+ return pci_probe_reset_slot(pdev->slot) ?
|
|
|
+ __pci_reset_slot(pdev->slot) : __pci_reset_bus(pdev->bus);
|
|
|
}
|
|
|
-EXPORT_SYMBOL_GPL(pci_try_reset_bus);
|
|
|
+EXPORT_SYMBOL_GPL(pci_reset_bus);
|
|
|
|
|
|
/**
|
|
|
* pcix_get_max_mmrbc - get PCI-X maximum designed memory read byte count
|
|
@@ -5304,14 +5536,16 @@ u32 pcie_bandwidth_capable(struct pci_dev *dev, enum pci_bus_speed *speed,
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * pcie_print_link_status - Report the PCI device's link speed and width
|
|
|
+ * __pcie_print_link_status - Report the PCI device's link speed and width
|
|
|
* @dev: PCI device to query
|
|
|
+ * @verbose: Print info even when enough bandwidth is available
|
|
|
*
|
|
|
- * Report the available bandwidth at the device. If this is less than the
|
|
|
- * device is capable of, report the device's maximum possible bandwidth and
|
|
|
- * the upstream link that limits its performance to less than that.
|
|
|
+ * If the available bandwidth at the device is less than the device is
|
|
|
+ * capable of, report the device's maximum possible bandwidth and the
|
|
|
+ * upstream link that limits its performance. If @verbose, always print
|
|
|
+ * the available bandwidth, even if the device isn't constrained.
|
|
|
*/
|
|
|
-void pcie_print_link_status(struct pci_dev *dev)
|
|
|
+void __pcie_print_link_status(struct pci_dev *dev, bool verbose)
|
|
|
{
|
|
|
enum pcie_link_width width, width_cap;
|
|
|
enum pci_bus_speed speed, speed_cap;
|
|
@@ -5321,11 +5555,11 @@ void pcie_print_link_status(struct pci_dev *dev)
|
|
|
bw_cap = pcie_bandwidth_capable(dev, &speed_cap, &width_cap);
|
|
|
bw_avail = pcie_bandwidth_available(dev, &limiting_dev, &speed, &width);
|
|
|
|
|
|
- if (bw_avail >= bw_cap)
|
|
|
+ if (bw_avail >= bw_cap && verbose)
|
|
|
pci_info(dev, "%u.%03u Gb/s available PCIe bandwidth (%s x%d link)\n",
|
|
|
bw_cap / 1000, bw_cap % 1000,
|
|
|
PCIE_SPEED2STR(speed_cap), width_cap);
|
|
|
- else
|
|
|
+ else if (bw_avail < bw_cap)
|
|
|
pci_info(dev, "%u.%03u Gb/s available PCIe bandwidth, limited by %s x%d link at %s (capable of %u.%03u Gb/s with %s x%d link)\n",
|
|
|
bw_avail / 1000, bw_avail % 1000,
|
|
|
PCIE_SPEED2STR(speed), width,
|
|
@@ -5333,6 +5567,17 @@ void pcie_print_link_status(struct pci_dev *dev)
|
|
|
bw_cap / 1000, bw_cap % 1000,
|
|
|
PCIE_SPEED2STR(speed_cap), width_cap);
|
|
|
}
|
|
|
+
|
|
|
+/**
|
|
|
+ * pcie_print_link_status - Report the PCI device's link speed and width
|
|
|
+ * @dev: PCI device to query
|
|
|
+ *
|
|
|
+ * Report the available bandwidth at the device.
|
|
|
+ */
|
|
|
+void pcie_print_link_status(struct pci_dev *dev)
|
|
|
+{
|
|
|
+ __pcie_print_link_status(dev, true);
|
|
|
+}
|
|
|
EXPORT_SYMBOL(pcie_print_link_status);
|
|
|
|
|
|
/**
|
|
@@ -5427,8 +5672,19 @@ int pci_set_vga_state(struct pci_dev *dev, bool decode,
|
|
|
* @dev: the PCI device for which alias is added
|
|
|
* @devfn: alias slot and function
|
|
|
*
|
|
|
- * This helper encodes 8-bit devfn as bit number in dma_alias_mask.
|
|
|
- * It should be called early, preferably as PCI fixup header quirk.
|
|
|
+ * This helper encodes an 8-bit devfn as a bit number in dma_alias_mask
|
|
|
+ * which is used to program permissible bus-devfn source addresses for DMA
|
|
|
+ * requests in an IOMMU. These aliases factor into IOMMU group creation
|
|
|
+ * and are useful for devices generating DMA requests beyond or different
|
|
|
+ * from their logical bus-devfn. Examples include device quirks where the
|
|
|
+ * device simply uses the wrong devfn, as well as non-transparent bridges
|
|
|
+ * where the alias may be a proxy for devices in another domain.
|
|
|
+ *
|
|
|
+ * IOMMU group creation is performed during device discovery or addition,
|
|
|
+ * prior to any potential DMA mapping and therefore prior to driver probing
|
|
|
+ * (especially for userspace assigned devices where IOMMU group definition
|
|
|
+ * cannot be left as a userspace activity). DMA aliases should therefore
|
|
|
+ * be configured via quirks, such as the PCI fixup header quirk.
|
|
|
*/
|
|
|
void pci_add_dma_alias(struct pci_dev *dev, u8 devfn)
|
|
|
{
|
|
@@ -5494,10 +5750,10 @@ static DEFINE_SPINLOCK(resource_alignment_lock);
|
|
|
static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev,
|
|
|
bool *resize)
|
|
|
{
|
|
|
- int seg, bus, slot, func, align_order, count;
|
|
|
- unsigned short vendor, device, subsystem_vendor, subsystem_device;
|
|
|
+ int align_order, count;
|
|
|
resource_size_t align = pcibios_default_alignment();
|
|
|
- char *p;
|
|
|
+ const char *p;
|
|
|
+ int ret;
|
|
|
|
|
|
spin_lock(&resource_alignment_lock);
|
|
|
p = resource_alignment_param;
|
|
@@ -5517,58 +5773,21 @@ static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev,
|
|
|
} else {
|
|
|
align_order = -1;
|
|
|
}
|
|
|
- if (strncmp(p, "pci:", 4) == 0) {
|
|
|
- /* PCI vendor/device (subvendor/subdevice) ids are specified */
|
|
|
- p += 4;
|
|
|
- if (sscanf(p, "%hx:%hx:%hx:%hx%n",
|
|
|
- &vendor, &device, &subsystem_vendor, &subsystem_device, &count) != 4) {
|
|
|
- if (sscanf(p, "%hx:%hx%n", &vendor, &device, &count) != 2) {
|
|
|
- printk(KERN_ERR "PCI: Can't parse resource_alignment parameter: pci:%s\n",
|
|
|
- p);
|
|
|
- break;
|
|
|
- }
|
|
|
- subsystem_vendor = subsystem_device = 0;
|
|
|
- }
|
|
|
- p += count;
|
|
|
- if ((!vendor || (vendor == dev->vendor)) &&
|
|
|
- (!device || (device == dev->device)) &&
|
|
|
- (!subsystem_vendor || (subsystem_vendor == dev->subsystem_vendor)) &&
|
|
|
- (!subsystem_device || (subsystem_device == dev->subsystem_device))) {
|
|
|
- *resize = true;
|
|
|
- if (align_order == -1)
|
|
|
- align = PAGE_SIZE;
|
|
|
- else
|
|
|
- align = 1 << align_order;
|
|
|
- /* Found */
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- else {
|
|
|
- if (sscanf(p, "%x:%x:%x.%x%n",
|
|
|
- &seg, &bus, &slot, &func, &count) != 4) {
|
|
|
- seg = 0;
|
|
|
- if (sscanf(p, "%x:%x.%x%n",
|
|
|
- &bus, &slot, &func, &count) != 3) {
|
|
|
- /* Invalid format */
|
|
|
- printk(KERN_ERR "PCI: Can't parse resource_alignment parameter: %s\n",
|
|
|
- p);
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- p += count;
|
|
|
- if (seg == pci_domain_nr(dev->bus) &&
|
|
|
- bus == dev->bus->number &&
|
|
|
- slot == PCI_SLOT(dev->devfn) &&
|
|
|
- func == PCI_FUNC(dev->devfn)) {
|
|
|
- *resize = true;
|
|
|
- if (align_order == -1)
|
|
|
- align = PAGE_SIZE;
|
|
|
- else
|
|
|
- align = 1 << align_order;
|
|
|
- /* Found */
|
|
|
- break;
|
|
|
- }
|
|
|
+
|
|
|
+ ret = pci_dev_str_match(dev, p, &p);
|
|
|
+ if (ret == 1) {
|
|
|
+ *resize = true;
|
|
|
+ if (align_order == -1)
|
|
|
+ align = PAGE_SIZE;
|
|
|
+ else
|
|
|
+ align = 1 << align_order;
|
|
|
+ break;
|
|
|
+ } else if (ret < 0) {
|
|
|
+ pr_err("PCI: Can't parse resource_alignment parameter: %s\n",
|
|
|
+ p);
|
|
|
+ break;
|
|
|
}
|
|
|
+
|
|
|
if (*p != ';' && *p != ',') {
|
|
|
/* End of param or invalid format */
|
|
|
break;
|
|
@@ -5845,6 +6064,8 @@ static int __init pci_setup(char *str)
|
|
|
pcie_ats_disabled = true;
|
|
|
} else if (!strcmp(str, "noaer")) {
|
|
|
pci_no_aer();
|
|
|
+ } else if (!strcmp(str, "earlydump")) {
|
|
|
+ pci_early_dump = true;
|
|
|
} else if (!strncmp(str, "realloc=", 8)) {
|
|
|
pci_realloc_get_opt(str + 8);
|
|
|
} else if (!strncmp(str, "realloc", 7)) {
|
|
@@ -5881,6 +6102,8 @@ static int __init pci_setup(char *str)
|
|
|
pcie_bus_config = PCIE_BUS_PEER2PEER;
|
|
|
} else if (!strncmp(str, "pcie_scan_all", 13)) {
|
|
|
pci_add_flags(PCI_SCAN_ALL_PCIE_DEVS);
|
|
|
+ } else if (!strncmp(str, "disable_acs_redir=", 18)) {
|
|
|
+ disable_acs_redir_param = str + 18;
|
|
|
} else {
|
|
|
printk(KERN_ERR "PCI: Unknown option `%s'\n",
|
|
|
str);
|