From 6496ebd7edf446fccf8266a1a70ffcb64252593e Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 21 Oct 2016 16:45:38 -0400 Subject: PCI: Check for PME in targeted sleep state One some systems, the firmware does not allow certain PCI devices to be put in deep D-states. This can cause problems for wakeup signalling, if the device does not support PME# in the deepest allowed suspend state. For example, Pierre reports that on his system, ACPI does not permit his xHCI host controller to go into D3 during runtime suspend -- but D3 is the only state in which the controller can generate PME# signals. As a result, the controller goes into runtime suspend but never wakes up, so it doesn't work properly. USB devices plugged into the controller are never detected. If the device relies on PME# for wakeup signals but is not capable of generating PME# in the target state, the PCI core should accurately report that it cannot do wakeup from runtime suspend. This patch modifies the pci_dev_run_wake() routine to add this check. Reported-by: Pierre de Villemereuil Tested-by: Pierre de Villemereuil Signed-off-by: Alan Stern Signed-off-by: Bjorn Helgaas Acked-by: Rafael J. Wysocki CC: stable@vger.kernel.org CC: Lukas Wunner --- drivers/pci/pci.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/pci') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index ba34907538f6..eda6a7cf0e54 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2106,6 +2106,10 @@ bool pci_dev_run_wake(struct pci_dev *dev) if (!dev->pme_support) return false; + /* PME-capable in principle, but not from the intended sleep state */ + if (!pci_pme_capable(dev, pci_target_state(dev))) + return false; + while (bus->parent) { struct pci_dev *bridge = bus->self; -- cgit v1.2.1 From 738a7edbfc7b623e276686dead046c9f3aae6b2e Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Fri, 28 Oct 2016 10:52:06 +0200 Subject: PCI: Don't acquire ref on parent in pci_bridge_d3_update() This function is always called with an existing pci_dev struct, which holds a reference on the pci_bus struct it resides on, which in turn holds a reference on pci_bus->bridge, which is the pci_dev's parent. Hence there's no need to acquire an additional ref on the parent. More specifically, the pci_dev exists until pci_destroy_dev() drops the final reference on it, so all calls to pci_bridge_d3_update() must be finished before that. It is arguably the caller's responsibility to ensure that it doesn't call pci_bridge_d3_update() with a pci_dev that might suddenly disappear, but in any case the existing callers are all safe: - The call in pci_destroy_dev() happens before the call to put_device(). - The call in pci_bus_add_device() is synchronized with pci_destroy_dev() using pci_lock_rescan_remove(). - The calls to pci_d3cold_disable() from the xhci and nouveau drivers are safe because a ref on the pci_dev is held as long as it's bound to a driver. - The calls to pci_d3cold_enable() / pci_d3cold_disable() when modifying the sysfs "d3cold_allowed" entry are also safe because kernfs_drain() waits for existing sysfs users to finish before removing the entry, and pci_destroy_dev() is called way after that. No functional change intended. Tested-by: Mika Westerberg Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Rafael J. Wysocki --- drivers/pci/pci.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index eda6a7cf0e54..5d6f2970fd6f 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2296,7 +2296,6 @@ static void pci_bridge_d3_update(struct pci_dev *dev, bool remove) if (!bridge || !pci_bridge_d3_possible(bridge)) return; - pci_dev_get(bridge); /* * If the device is removed we do not care about its D3cold * capabilities. @@ -2318,8 +2317,6 @@ static void pci_bridge_d3_update(struct pci_dev *dev, bool remove) /* Propagate change to upstream bridges */ pci_bridge_d3_update(bridge, false); } - - pci_dev_put(bridge); } /** -- cgit v1.2.1 From 1ed276a7b9d84626e5243fc54863440c74a4100a Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Fri, 28 Oct 2016 10:52:06 +0200 Subject: PCI: Autosense device removal in pci_bridge_d3_update() The algorithm to update the flag indicating whether a bridge may go to D3 makes a few optimizations based on whether the update was caused by the removal of a device on the one hand, versus the addition of a device or the change of its D3cold flags on the other hand. The information whether the update pertains to a removal is currently passed in by the caller, but the function may as well determine that itself by examining the device in question, thereby allowing for a considerable simplification and reduction of the code. Out of several options to determine removal, I've chosen the function device_is_registered() because it's cheap: It merely returns the dev->kobj.state_in_sysfs flag. That flag is set through device_add() when the root bus is scanned and cleared through device_remove(). The call to pci_bridge_d3_update() happens after each of these calls, respectively, so the ordering is correct. No functional change intended. Tested-by: Mika Westerberg Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Rafael J. Wysocki --- drivers/pci/bus.c | 2 +- drivers/pci/pci.c | 35 +++++------------------------------ drivers/pci/pci.h | 3 +-- drivers/pci/remove.c | 2 +- 4 files changed, 8 insertions(+), 34 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c index c288e5a52575..bc56cf19afd3 100644 --- a/drivers/pci/bus.c +++ b/drivers/pci/bus.c @@ -320,7 +320,7 @@ void pci_bus_add_device(struct pci_dev *dev) pci_fixup_device(pci_fixup_final, dev); pci_create_sysfs_dev_files(dev); pci_proc_attach_device(dev); - pci_bridge_d3_device_changed(dev); + pci_bridge_d3_update(dev); dev->match_driver = true; retval = device_attach(&dev->dev); diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 5d6f2970fd6f..8200874ef5fc 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2281,14 +2281,14 @@ static int pci_dev_check_d3cold(struct pci_dev *dev, void *data) /* * pci_bridge_d3_update - Update bridge D3 capabilities * @dev: PCI device which is changed - * @remove: Is the device being removed * * Update upstream bridge PM capabilities accordingly depending on if the * device PM configuration was changed or the device is being removed. The * change is also propagated upstream. */ -static void pci_bridge_d3_update(struct pci_dev *dev, bool remove) +void pci_bridge_d3_update(struct pci_dev *dev) { + bool remove = !device_is_registered(&dev->dev); struct pci_dev *bridge; bool d3cold_ok = true; @@ -2315,35 +2315,10 @@ static void pci_bridge_d3_update(struct pci_dev *dev, bool remove) if (bridge->bridge_d3 != d3cold_ok) { bridge->bridge_d3 = d3cold_ok; /* Propagate change to upstream bridges */ - pci_bridge_d3_update(bridge, false); + pci_bridge_d3_update(bridge); } } -/** - * pci_bridge_d3_device_changed - Update bridge D3 capabilities on change - * @dev: PCI device that was changed - * - * If a device is added or its PM configuration, such as is it allowed to - * enter D3cold, is changed this function updates upstream bridge PM - * capabilities accordingly. - */ -void pci_bridge_d3_device_changed(struct pci_dev *dev) -{ - pci_bridge_d3_update(dev, false); -} - -/** - * pci_bridge_d3_device_removed - Update bridge D3 capabilities on remove - * @dev: PCI device being removed - * - * Function updates upstream bridge PM capabilities based on other devices - * still left on the bus. - */ -void pci_bridge_d3_device_removed(struct pci_dev *dev) -{ - pci_bridge_d3_update(dev, true); -} - /** * pci_d3cold_enable - Enable D3cold for device * @dev: PCI device to handle @@ -2356,7 +2331,7 @@ void pci_d3cold_enable(struct pci_dev *dev) { if (dev->no_d3cold) { dev->no_d3cold = false; - pci_bridge_d3_device_changed(dev); + pci_bridge_d3_update(dev); } } EXPORT_SYMBOL_GPL(pci_d3cold_enable); @@ -2373,7 +2348,7 @@ void pci_d3cold_disable(struct pci_dev *dev) { if (!dev->no_d3cold) { dev->no_d3cold = true; - pci_bridge_d3_device_changed(dev); + pci_bridge_d3_update(dev); } } EXPORT_SYMBOL_GPL(pci_d3cold_disable); diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 451856210e18..27048bb88783 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -85,8 +85,7 @@ void pci_pm_init(struct pci_dev *dev); void pci_ea_init(struct pci_dev *dev); void pci_allocate_cap_save_buffers(struct pci_dev *dev); void pci_free_cap_save_buffers(struct pci_dev *dev); -void pci_bridge_d3_device_changed(struct pci_dev *dev); -void pci_bridge_d3_device_removed(struct pci_dev *dev); +void pci_bridge_d3_update(struct pci_dev *dev); static inline void pci_wakeup_event(struct pci_dev *dev) { diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index f9357e09e9b3..73a03d382590 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -40,7 +40,7 @@ static void pci_destroy_dev(struct pci_dev *dev) list_del(&dev->bus_list); up_write(&pci_bus_sem); - pci_bridge_d3_device_removed(dev); + pci_bridge_d3_update(dev); pci_free_resources(dev); put_device(&dev->dev); } -- cgit v1.2.1 From e8559b7100324494acbde6e26bcdc6a5a5b4a4ed Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Fri, 28 Oct 2016 10:52:06 +0200 Subject: PCI: Speed up algorithm in pci_bridge_d3_update() After a device has been added, removed or had its D3cold attributes changed, we recheck whether its parent bridge may runtime suspend to D3hot with pci_bridge_d3_update(). The most naive algorithm would be to iterate over the bridge's children and check if any of them are blocking D3. The function already tries to be a bit smarter than that by first checking the device that was changed. If this device already blocks D3 on the bridge, then walking over all the other children can be skipped. A drawback of this approach is that if the device is *not* blocking D3, it will be checked a second time by pci_walk_bus(). But that's cheap and is outweighed by the performance gain of potentially skipping pci_walk_bus() altogether. The algorithm can be optimized further by taking into account if D3 is currently allowed for the bridge, as shown in the following truth table: (a) remove && bridge_d3: D3 is currently allowed for the bridge and removing one of its children won't change that. No action necessary. (b) remove && !bridge_d3: D3 may now be allowed for the bridge if the removed child was the only one blocking it. Check all its siblings to verify that. (c) !remove && bridge_d3: D3 may now be disallowed but this can only be caused by the added/changed child, not any of its siblings. Check only that single device. (d) !remove && !bridge_d3: D3 may now be allowed for the bridge if the changed child was the only one blocking it. Check all its siblings to verify that. By checking beforehand if the changed child is blocking D3, we may be able to skip checking its siblings. Currently we do not special-case option (a) and in case of option (c) we gratuitously call pci_walk_bus(). Speed up the algorithm by adding these optimizations. Reword the comments a bit in an attempt to improve clarity. No functional change intended. Tested-by: Mika Westerberg Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Rafael J. Wysocki --- drivers/pci/pci.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 8200874ef5fc..4a7f6f54d669 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2297,20 +2297,32 @@ void pci_bridge_d3_update(struct pci_dev *dev) return; /* - * If the device is removed we do not care about its D3cold - * capabilities. + * If D3 is currently allowed for the bridge, removing one of its + * children won't change that. + */ + if (remove && bridge->bridge_d3) + return; + + /* + * If D3 is currently allowed for the bridge and a child is added or + * changed, disallowance of D3 can only be caused by that child, so + * we only need to check that single device, not any of its siblings. + * + * If D3 is currently not allowed for the bridge, checking the device + * first may allow us to skip checking its siblings. */ if (!remove) pci_dev_check_d3cold(dev, &d3cold_ok); - if (d3cold_ok) { - /* - * We need to go through all children to find out if all of - * them can still go to D3cold. - */ + /* + * If D3 is currently not allowed for the bridge, this may be caused + * either by the device being changed/removed or any of its siblings, + * so we need to go through all children to find out if one of them + * continues to block D3. + */ + if (d3cold_ok && !bridge->bridge_d3) pci_walk_bus(bridge->subordinate, pci_dev_check_d3cold, &d3cold_ok); - } if (bridge->bridge_d3 != d3cold_ok) { bridge->bridge_d3 = d3cold_ok; -- cgit v1.2.1 From c6a6330706148e7d5265c3dd658d25843c83390f Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Fri, 28 Oct 2016 10:52:06 +0200 Subject: PCI: Activate runtime PM on a PCIe port only if it can suspend Currently pcie_portdrv_probe() activates runtime PM on a PCIe port even if it will never actually suspend because the BIOS is too old or the "pcie_port_pm=off" option was specified on the kernel command line. A few CPU cycles can be saved by not activating runtime PM at all in these cases, because rpm_idle() and rpm_suspend() will bail out right at the beginning when calling rpm_check_suspend_allowed(), instead of carrying out various locking and assignments, invoking rpm_callback(), getting back -EBUSY and rolling everything back. The conditions checked in pci_bridge_d3_possible() are all static, they never change during uptime of the system, hence it's safe to call this to determine if runtime PM should be activated. No functional change intended. Tested-by: Mika Westerberg Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Rafael J. Wysocki --- drivers/pci/pci.c | 2 +- drivers/pci/pci.h | 1 + drivers/pci/pcie/portdrv_pci.c | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 4a7f6f54d669..720f7e27c3a8 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2230,7 +2230,7 @@ void pci_config_pm_runtime_put(struct pci_dev *pdev) * This function checks if it is possible to move the bridge to D3. * Currently we only allow D3 for recent enough PCIe ports. */ -static bool pci_bridge_d3_possible(struct pci_dev *bridge) +bool pci_bridge_d3_possible(struct pci_dev *bridge) { unsigned int year; diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 27048bb88783..ffffef37ab61 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -85,6 +85,7 @@ void pci_pm_init(struct pci_dev *dev); void pci_ea_init(struct pci_dev *dev); void pci_allocate_cap_save_buffers(struct pci_dev *dev); void pci_free_cap_save_buffers(struct pci_dev *dev); +bool pci_bridge_d3_possible(struct pci_dev *dev); void pci_bridge_d3_update(struct pci_dev *dev); static inline void pci_wakeup_event(struct pci_dev *dev) diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c index 79327cc14e7d..1ae712cb22ec 100644 --- a/drivers/pci/pcie/portdrv_pci.c +++ b/drivers/pci/pcie/portdrv_pci.c @@ -19,6 +19,7 @@ #include #include +#include "../pci.h" #include "portdrv.h" #include "aer/aerdrv.h" @@ -157,7 +158,7 @@ static int pcie_portdrv_probe(struct pci_dev *dev, * subordinate devices). We can't be sure for native PCIe hotplug * either so prevent that as well. */ - if (!dev->is_hotplug_bridge) { + if (pci_bridge_d3_possible(dev) && !dev->is_hotplug_bridge) { /* * Keep the port resumed 100ms to make sure things like * config space accesses from userspace (lspci) will not @@ -175,7 +176,7 @@ static int pcie_portdrv_probe(struct pci_dev *dev, static void pcie_portdrv_remove(struct pci_dev *dev) { - if (!dev->is_hotplug_bridge) { + if (pci_bridge_d3_possible(dev) && !dev->is_hotplug_bridge) { pm_runtime_forbid(&dev->dev); pm_runtime_get_noresume(&dev->dev); pm_runtime_dont_use_autosuspend(&dev->dev); -- cgit v1.2.1 From 97a90aee5dab33aea0cd3f6802b3661990496262 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Fri, 28 Oct 2016 10:52:06 +0200 Subject: PCI: Consolidate conditions to allow runtime PM on PCIe ports The conditions to allow runtime PM on PCIe ports are currently spread across two different files: The condition relating to hotplug ports is located in portdrv_pci.c whereas all other conditions are located in pci.c. Consolidate all conditions in a single place in pci.c, thus making it easier to follow the logic and amend conditions down the road. Note that the condition relating to hotplug ports is inserted *before* the condition relating to the "pcie_port_pm=force" command line option, so runtime PM is not afforded to hotplug ports even if this option is given. That's exactly how the code behaved up until now. If this is not desired, the ordering of the conditions can simply be reversed. No functional change intended. Tested-by: Mika Westerberg Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Rafael J. Wysocki --- drivers/pci/pci.c | 11 +++++++++++ drivers/pci/pcie/portdrv_pci.c | 12 ++---------- 2 files changed, 13 insertions(+), 10 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 720f7e27c3a8..a40ba0225265 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2243,6 +2243,17 @@ bool pci_bridge_d3_possible(struct pci_dev *bridge) case PCI_EXP_TYPE_DOWNSTREAM: if (pci_bridge_d3_disable) 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 + * 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) + return false; + if (pci_bridge_d3_force) return true; diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c index 1ae712cb22ec..8aa3f14bc87d 100644 --- a/drivers/pci/pcie/portdrv_pci.c +++ b/drivers/pci/pcie/portdrv_pci.c @@ -150,15 +150,7 @@ static int pcie_portdrv_probe(struct pci_dev *dev, pci_save_state(dev); - /* - * Prevent runtime PM if the port is advertising support for PCIe - * hotplug. Otherwise the BIOS hotplug SMI code might not be able - * to enumerate devices behind this port properly (the port is - * powered down preventing all config space accesses to the - * subordinate devices). We can't be sure for native PCIe hotplug - * either so prevent that as well. - */ - if (pci_bridge_d3_possible(dev) && !dev->is_hotplug_bridge) { + if (pci_bridge_d3_possible(dev)) { /* * Keep the port resumed 100ms to make sure things like * config space accesses from userspace (lspci) will not @@ -176,7 +168,7 @@ static int pcie_portdrv_probe(struct pci_dev *dev, static void pcie_portdrv_remove(struct pci_dev *dev) { - if (pci_bridge_d3_possible(dev) && !dev->is_hotplug_bridge) { + if (pci_bridge_d3_possible(dev)) { pm_runtime_forbid(&dev->dev); pm_runtime_get_noresume(&dev->dev); pm_runtime_dont_use_autosuspend(&dev->dev); -- cgit v1.2.1 From 718a0609ae263b291848ecd0fa88bcf15ad49280 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Fri, 28 Oct 2016 10:52:06 +0200 Subject: PCI: Unfold conditions to block runtime PM on PCIe ports The conditions to block D3 on parent ports are currently condensed into a single expression in pci_dev_check_d3cold(). Upcoming commits will add further conditions for hotplug ports, making this expression fairly large and impenetrable. Unfold the conditions to maintain readability when they are amended. No functional change intended. Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas CC: Mika Westerberg CC: Rafael J. Wysocki --- drivers/pci/pci.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index a40ba0225265..d86351a2fe6e 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2274,19 +2274,20 @@ bool pci_bridge_d3_possible(struct pci_dev *bridge) static int pci_dev_check_d3cold(struct pci_dev *dev, void *data) { bool *d3cold_ok = data; - bool no_d3cold; - /* - * The device needs to be allowed to go D3cold and if it is wake - * capable to do so from D3cold. - */ - no_d3cold = dev->no_d3cold || !dev->d3cold_allowed || - (device_may_wakeup(&dev->dev) && !pci_pme_capable(dev, PCI_D3cold)) || - !pci_power_manageable(dev); + if (/* The device needs to be allowed to go D3cold ... */ + dev->no_d3cold || !dev->d3cold_allowed || + + /* ... and if it is wakeup capable to do so from D3cold. */ + (device_may_wakeup(&dev->dev) && + !pci_pme_capable(dev, PCI_D3cold)) || + + /* If it is a bridge it must be allowed to go to D3. */ + !pci_power_manageable(dev)) - *d3cold_ok = !no_d3cold; + *d3cold_ok = false; - return no_d3cold; + return !*d3cold_ok; } /* -- cgit v1.2.1 From 6ef13824e0d897858ea4510a2c61b00445922fad Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Fri, 28 Oct 2016 10:52:06 +0200 Subject: ACPI / hotplug / PCI: Use cached copy of PCI_EXP_SLTCAP_HPC bit We cache the PCI_EXP_SLTCAP_HPC bit in pci_dev->is_hotplug_bridge on device probe, so there's no need to read it again when adding the ACPI hotplug context. Here's the call chain to prove that no ordering issue is introduced: pci_scan_child_bus [drivers/pci/probe.c] pci_scan_slot pci_scan_single_device pci_scan_device pci_setup_device set_pcie_hotplug_bridge [is_hotplug_bridge bit is set here] pci_scan_bridge pci_add_new_bus pci_alloc_child_bus pcibios_add_bus [arch/(x86|arm64|ia64)/...] acpi_pci_add_bus [drivers/pci/pci-acpi.c] acpiphp_enumerate_slots [drivers/pci/hotplug/acpiphp_glue.c] acpiphp_add_context device_is_managed_by_native_pciehp [is_hotplug_bridge bit is queried here] No functional change intended. Tested-by: Mika Westerberg Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Rafael J. Wysocki --- drivers/pci/hotplug/acpiphp_glue.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c index a46b585fae31..b286a56e84b3 100644 --- a/drivers/pci/hotplug/acpiphp_glue.c +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -225,14 +225,11 @@ static void acpiphp_post_dock_fixup(struct acpi_device *adev) /* Check whether the PCI device is managed by native PCIe hotplug driver */ static bool device_is_managed_by_native_pciehp(struct pci_dev *pdev) { - u32 reg32; acpi_handle tmp; struct acpi_pci_root *root; /* Check whether the PCIe port supports native PCIe hotplug */ - if (pcie_capability_read_dword(pdev, PCI_EXP_SLTCAP, ®32)) - return false; - if (!(reg32 & PCI_EXP_SLTCAP_HPC)) + if (!pdev->is_hotplug_bridge) return false; /* -- cgit v1.2.1 From 437eb7bf7b28472f8b7689e166dc1dd691367121 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Fri, 28 Oct 2016 10:52:06 +0200 Subject: ACPI / hotplug / PCI: Make device_is_managed_by_native_pciehp() public We're about to add runtime PM of hotplug ports, but we need to restrict it to ports that are handled natively by the OS: If they're handled by the firmware (which is the case for Thunderbolt on non-Macs), things would break if the OS put the ports into D3hot behind the firmware's back. To determine if a hotplug port is handled natively, one has to walk up from the port to the root bridge and check the cached _OSC Control Field for the value of the "PCI Express Native Hot Plug control" bit. There's already a function to do that, device_is_managed_by_native_pciehp(), but it's private to drivers/pci/hotplug/acpiphp_glue.c and only compiled in if CONFIG_HOTPLUG_PCI_ACPI is enabled. Make it public and move it to drivers/pci/pci-acpi.c, so that it is available in the more general CONFIG_ACPI case. The function contains a check if the device in question is a hotplug port and returns false if it's not. The caller we're going to add doesn't need this as it only calls the function if it actually *is* a hotplug port. Move the check out of the function into the single existing caller. Rename it to pciehp_is_native() and add some kerneldoc and polish. No functional change intended. Tested-by: Mika Westerberg Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Rafael J. Wysocki --- drivers/pci/hotplug/acpiphp_glue.c | 28 +--------------------------- drivers/pci/pci-acpi.c | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 27 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c index b286a56e84b3..5ed2dcaa8e27 100644 --- a/drivers/pci/hotplug/acpiphp_glue.c +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -222,32 +222,6 @@ static void acpiphp_post_dock_fixup(struct acpi_device *adev) acpiphp_let_context_go(context); } -/* Check whether the PCI device is managed by native PCIe hotplug driver */ -static bool device_is_managed_by_native_pciehp(struct pci_dev *pdev) -{ - acpi_handle tmp; - struct acpi_pci_root *root; - - /* Check whether the PCIe port supports native PCIe hotplug */ - if (!pdev->is_hotplug_bridge) - return false; - - /* - * Check whether native PCIe hotplug has been enabled for - * this PCIe hierarchy. - */ - tmp = acpi_find_root_bridge_handle(pdev); - if (!tmp) - return false; - root = acpi_pci_find_root(tmp); - if (!root) - return false; - if (!(root->osc_control_set & OSC_PCI_EXPRESS_NATIVE_HP_CONTROL)) - return false; - - return true; -} - /** * acpiphp_add_context - Add ACPIPHP context to an ACPI device object. * @handle: ACPI handle of the object to add a context to. @@ -331,7 +305,7 @@ static acpi_status acpiphp_add_context(acpi_handle handle, u32 lvl, void *data, * expose slots to user space in those cases. */ if ((acpi_pci_check_ejectable(pbus, handle) || is_dock_device(adev)) - && !(pdev && device_is_managed_by_native_pciehp(pdev))) { + && !(pdev && pdev->is_hotplug_bridge && pciehp_is_native(pdev))) { unsigned long long sun; int retval; diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index d966d47c9e80..ed3daa8e7658 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -293,6 +293,30 @@ int pci_get_hp_params(struct pci_dev *dev, struct hotplug_params *hpp) } EXPORT_SYMBOL_GPL(pci_get_hp_params); +/** + * pciehp_is_native - Check whether a hotplug port is handled by the OS + * @pdev: Hotplug port to check + * + * Walk up from @pdev to the host bridge, obtain its cached _OSC Control Field + * and return the value of the "PCI Express Native Hot Plug control" bit. + * On failure to obtain the _OSC Control Field return %false. + */ +bool pciehp_is_native(struct pci_dev *pdev) +{ + struct acpi_pci_root *root; + acpi_handle handle; + + handle = acpi_find_root_bridge_handle(pdev); + if (!handle) + return false; + + root = acpi_pci_find_root(handle); + if (!root) + return false; + + return root->osc_control_set & OSC_PCI_EXPRESS_NATIVE_HP_CONTROL; +} + /** * pci_acpi_wake_bus - Root bus wakeup notification fork function. * @work: Work item to handle. -- cgit v1.2.1 From 68db9bc814362e7f24371c27d12a4f34477d9356 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Fri, 28 Oct 2016 10:52:06 +0200 Subject: PCI: pciehp: Add runtime PM support for PCIe hotplug ports Linux 4.8 added support for runtime suspending PCIe ports to D3hot with commit 006d44e49a25 ("PCI: Add runtime PM support for PCIe ports"), but excluded hotplug ports. Those are now afforded runtime PM by the present commit. Hotplug ports require a few extra considerations: - The configuration space of the port remains accessible in D3hot, so all the functions to read or modify the Slot Status and Slot Control registers need not be modified. Even turning on slot power doesn't seem to require the port to be in D0, at least the PCIe spec doesn't say so and I confirmed that by testing with a Thunderbolt controller. - However D0 is required to access devices on the secondary bus. This happens in pciehp_check_link_status() and pciehp_configure_device() (both called from board_added()) and in pciehp_unconfigure_device() (called from remove_board()), so acquire a runtime PM ref for their invocation. - The hotplug port stays active as long as it has active children. If all hotplugged devices below the port runtime suspend, the port is allowed to runtime suspend as well. Plug and unplug detection continues to work in D3hot. - Hotplug interrupts are delivered in-band, so while the hotplug port itself is allowed to go to D3hot, its parent ports must stay in D0 for interrupts to come through. Add a corresponding restriction to pci_dev_check_d3cold(). - Runtime PM may only be allowed if the hotplug port is handled natively by the OS. On ACPI systems, the port may alternatively be handled by the firmware and things break if the OS puts the port into D3 behind the firmware's back: E.g. Thunderbolt hotplug ports on non-Macs are handled by Intel's firmware in System Management Mode and the firmware is known to access devices on the port's secondary bus without checking first if the port is in D0: https://bugzilla.kernel.org/show_bug.cgi?id=53811 Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Rafael J. Wysocki CC: Mika Westerberg --- drivers/pci/hotplug/pciehp_ctrl.c | 6 ++++++ drivers/pci/pci.c | 12 ++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c index efe69e879455..ffd3fe6646c2 100644 --- a/drivers/pci/hotplug/pciehp_ctrl.c +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include "../pci.h" #include "pciehp.h" @@ -98,6 +99,7 @@ static int board_added(struct slot *p_slot) pciehp_green_led_blink(p_slot); /* Check link training status */ + pm_runtime_get_sync(&ctrl->pcie->port->dev); retval = pciehp_check_link_status(ctrl); if (retval) { ctrl_err(ctrl, "Failed to check link status\n"); @@ -118,12 +120,14 @@ static int board_added(struct slot *p_slot) if (retval != -EEXIST) goto err_exit; } + pm_runtime_put(&ctrl->pcie->port->dev); pciehp_green_led_on(p_slot); pciehp_set_attention_status(p_slot, 0); return 0; err_exit: + pm_runtime_put(&ctrl->pcie->port->dev); set_slot_off(ctrl, p_slot); return retval; } @@ -137,7 +141,9 @@ static int remove_board(struct slot *p_slot) int retval; struct controller *ctrl = p_slot->ctrl; + pm_runtime_get_sync(&ctrl->pcie->port->dev); retval = pciehp_unconfigure_device(p_slot); + pm_runtime_put(&ctrl->pcie->port->dev); if (retval) return retval; diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index d86351a2fe6e..1eb622cc8645 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2245,13 +2245,10 @@ 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) @@ -2283,7 +2280,10 @@ static int pci_dev_check_d3cold(struct pci_dev *dev, void *data) !pci_pme_capable(dev, PCI_D3cold)) || /* If it is a bridge it must be allowed to go to D3. */ - !pci_power_manageable(dev)) + !pci_power_manageable(dev) || + + /* Hotplug interrupts cannot be delivered if the link is down. */ + dev->is_hotplug_bridge) *d3cold_ok = false; -- cgit v1.2.1 From c931225480aeabc6f867f2c1dea3738b3e1622a4 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Mon, 12 Dec 2016 09:45:47 -0600 Subject: x86/platform/intel-mid: Constify mid_pci_platform_pm This struct never needs to be modified. The size of pci-mid.o ELF sections changes thusly: -.data 56 +.data 0 -.rodata 32 +.rodata 88 Signed-off-by: Lukas Wunner Acked-by: Andy Shevchenko Acked-by: Bjorn Helgaas --- drivers/pci/pci-mid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci-mid.c b/drivers/pci/pci-mid.c index 55f453de562e..23ed7316c891 100644 --- a/drivers/pci/pci-mid.c +++ b/drivers/pci/pci-mid.c @@ -49,7 +49,7 @@ static bool mid_pci_need_resume(struct pci_dev *dev) return false; } -static struct pci_platform_pm_ops mid_pci_platform_pm = { +static const struct pci_platform_pm_ops mid_pci_platform_pm = { .is_manageable = mid_pci_power_manageable, .set_state = mid_pci_set_power_state, .choose_state = mid_pci_choose_state, -- cgit v1.2.1