summaryrefslogtreecommitdiff
path: root/drivers/pci
diff options
context:
space:
mode:
authorBjorn Helgaas <bhelgaas@google.com>2016-12-12 20:25:04 +0300
committerBjorn Helgaas <bhelgaas@google.com>2016-12-12 20:25:04 +0300
commitdaaed10443da09ad0d2042b71cb99f3927d52164 (patch)
tree90562b3d55cb61b7c5e9bffb06c65b5dea00eec3 /drivers/pci
parentdb5ba864126476fcaa3778b40d12f25168af206b (diff)
parentc931225480aeabc6f867f2c1dea3738b3e1622a4 (diff)
downloadlinux-daaed10443da09ad0d2042b71cb99f3927d52164.tar.xz
Merge branch 'pci/pm' into next
* pci/pm: x86/platform/intel-mid: Constify mid_pci_platform_pm PCI: pciehp: Add runtime PM support for PCIe hotplug ports ACPI / hotplug / PCI: Make device_is_managed_by_native_pciehp() public ACPI / hotplug / PCI: Use cached copy of PCI_EXP_SLTCAP_HPC bit PCI: Unfold conditions to block runtime PM on PCIe ports PCI: Consolidate conditions to allow runtime PM on PCIe ports PCI: Activate runtime PM on a PCIe port only if it can suspend PCI: Speed up algorithm in pci_bridge_d3_update() PCI: Autosense device removal in pci_bridge_d3_update() PCI: Don't acquire ref on parent in pci_bridge_d3_update() USB: UHCI: report non-PME wakeup signalling for Intel hardware PCI: Check for PME in targeted sleep state
Diffstat (limited to 'drivers/pci')
-rw-r--r--drivers/pci/bus.c2
-rw-r--r--drivers/pci/hotplug/acpiphp_glue.c31
-rw-r--r--drivers/pci/hotplug/pciehp_ctrl.c6
-rw-r--r--drivers/pci/pci-acpi.c24
-rw-r--r--drivers/pci/pci-mid.c2
-rw-r--r--drivers/pci/pci.c104
-rw-r--r--drivers/pci/pci.h4
-rw-r--r--drivers/pci/pcie/portdrv_pci.c13
-rw-r--r--drivers/pci/remove.c2
9 files changed, 91 insertions, 97 deletions
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/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c
index a46b585fae31..5ed2dcaa8e27 100644
--- a/drivers/pci/hotplug/acpiphp_glue.c
+++ b/drivers/pci/hotplug/acpiphp_glue.c
@@ -222,35 +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)
-{
- 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, &reg32))
- return false;
- if (!(reg32 & PCI_EXP_SLTCAP_HPC))
- 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.
@@ -334,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/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c
index ec0b4c11ccd9..10c9c0ba8ff2 100644
--- a/drivers/pci/hotplug/pciehp_ctrl.c
+++ b/drivers/pci/hotplug/pciehp_ctrl.c
@@ -31,6 +31,7 @@
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/slab.h>
+#include <linux/pm_runtime.h>
#include <linux/pci.h>
#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-acpi.c b/drivers/pci/pci-acpi.c
index 92384dece4b4..001860361434 100644
--- a/drivers/pci/pci-acpi.c
+++ b/drivers/pci/pci-acpi.c
@@ -370,6 +370,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.
*/
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,
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index ba34907538f6..1eb622cc8645 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;
@@ -2226,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;
@@ -2239,6 +2243,14 @@ static bool pci_bridge_d3_possible(struct pci_dev *bridge)
case PCI_EXP_TYPE_DOWNSTREAM:
if (pci_bridge_d3_disable)
return false;
+
+ /*
+ * Hotplug ports handled by firmware in System Management Mode
+ * may not be put into D3 by the OS (Thunderbolt on non-Macs).
+ */
+ if (bridge->is_hotplug_bridge && !pciehp_is_native(bridge))
+ return false;
+
if (pci_bridge_d3_force)
return true;
@@ -2259,32 +2271,36 @@ static 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 ||
- *d3cold_ok = !no_d3cold;
+ /* ... and if it is wakeup capable to do so from D3cold. */
+ (device_may_wakeup(&dev->dev) &&
+ !pci_pme_capable(dev, PCI_D3cold)) ||
- return no_d3cold;
+ /* If it is a bridge it must be allowed to go to D3. */
+ !pci_power_manageable(dev) ||
+
+ /* Hotplug interrupts cannot be delivered if the link is down. */
+ dev->is_hotplug_bridge)
+
+ *d3cold_ok = false;
+
+ return !*d3cold_ok;
}
/*
* 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;
@@ -2292,55 +2308,39 @@ 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.
+ * 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;
/* Propagate change to upstream bridges */
- pci_bridge_d3_update(bridge, false);
+ pci_bridge_d3_update(bridge);
}
-
- pci_dev_put(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);
}
/**
@@ -2355,7 +2355,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);
@@ -2372,7 +2372,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 8834cb1c09a5..bb1e07b5a897 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -82,8 +82,8 @@ 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);
+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..8aa3f14bc87d 100644
--- a/drivers/pci/pcie/portdrv_pci.c
+++ b/drivers/pci/pcie/portdrv_pci.c
@@ -19,6 +19,7 @@
#include <linux/dmi.h>
#include <linux/pci-aspm.h>
+#include "../pci.h"
#include "portdrv.h"
#include "aer/aerdrv.h"
@@ -149,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 (!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
@@ -175,7 +168,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)) {
pm_runtime_forbid(&dev->dev);
pm_runtime_get_noresume(&dev->dev);
pm_runtime_dont_use_autosuspend(&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);
}