* [PATCH v3 0/4] PCI: Add support for suspending (including runtime) of PCIe ports
@ 2016-04-14 10:04 Mika Westerberg
2016-04-14 10:04 ` [PATCH v3 1/4] PCI: No need to set d3cold_allowed to " Mika Westerberg
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Mika Westerberg @ 2016-04-14 10:04 UTC (permalink / raw)
To: Bjorn Helgaas, Rafael J. Wysocki
Cc: Qipeng Zha, Qi Zheng, Dave Airlie, Mathias Nyman,
Greg Kroah-Hartman, Lukas Wunner, Andreas Noever, Mika Westerberg,
linux-pci, linux-pm
Current Linux PCI core does not do any kind of power management to PCIe
ports. This means that we waste energy and consume laptop battery even if
the port has nothing connected to. These patches aim to change that to the
right direction.
Previous versions of the patches can be found below:
v1: http://www.spinics.net/lists/linux-pci/msg49313.html
v2: http://www.spinics.net/lists/linux-pci/msg50167.html
This assumes that recent (starting from 2015) PCIe ports are capable of
transition to D3hot/D3cold. We add a new flag to struct pci_dev 'bridge_d3'
that is set whenever the PCI core thinks the port can be put to D3. The
check in pci_pm_suspend_noirq() is then extended to cover devices where
'bridge_d3' is set.
We then add two new functions pci_bridge_d3_device_changed/removed(). These
are used to set and clear 'bridge_d3' whenever there is a change in device
power management policy (or if the device is removed). For example when
userspace forbids the device to enter D3cold pci_bridge_d3_device_changed()
will clear 'bridge_d3' of the upstream bridge.
For all PCI ports where 'bridge_d3' is set we also enable and unblock
runtime PM automatically. Only exception is when the PCIe port claims to
support ACPI based hotplug. More information about that is in the changelog
of patch [4/4].
Since this also touches xhci, I'm adding Mathias and Greg to check if the
change looks reasonable.
Changes to v2:
- Renamed and split pci_enable_d3cold() into two functions
pci_d3cold_enable()/disable().
- Renamed pci_bridge_pm_update() into two functions
pci_bridge_d3_device_changed() and pci_bridge_d3_device_removed() that
should match better what they are doing.
- Propagate ->bridge_d3 change to upstream bridges in
pci_bridge_d3_update().
- Removed pci_can_suspend() in favor of doing ->bridge_d3 check directly
in pci_pm_suspend_noirq().
- Extend runtime PM enabling for ports that are using native PCIe
hotplug.
- Call pm_runtime_no_callbacks() for PCIe port service devices (the are
handled by the parent device).
I did not change the cut-off date from 2015 yet to be on the safe side,
even if older Macs seem to work just fine. Maybe it can be lowered to 2013
or so but I would like to hear from Bjorn and Rafael what they think about
that.
I also tried this series on my Ivy Bridge laptop (from 2013) and I did not
see any problems.
Changes to v1:
- Dropped patch [2/6] as there is no need to use that function from other
files anymore.
- Dropped patches [5-6/6] in favor of using cut-off date.
- Updated changelog of [1/4] to mention where in the PCI core PCI bridge
and PCIe ports are skipped from being power managed.
- Instead of checking at suspend time if it is possible to transition the
port to D3, do it whenever power management status of a device (below a
port) is changed or when it is added or removed to the bus.
- Added patch [3/4] to runtime resume a bridge when ACPI hotplug event is
received.
Mika Westerberg (4):
PCI: No need to set d3cold_allowed to PCIe ports
PCI: Move PCIe ports to D3 during suspend
ACPI / hotplug / PCI: Runtime resume bridge before rescan
PCI: Add runtime PM support for PCIe ports
drivers/pci/bus.c | 1 +
drivers/pci/hotplug/acpiphp_glue.c | 8 +-
drivers/pci/pci-driver.c | 7 +-
drivers/pci/pci-sysfs.c | 1 +
drivers/pci/pci.c | 156 +++++++++++++++++++++++++++++++++++++
drivers/pci/pci.h | 2 +
drivers/pci/pcie/portdrv_core.c | 2 +
drivers/pci/pcie/portdrv_pci.c | 112 ++++++++++++++++++++++++--
drivers/pci/remove.c | 2 +
drivers/usb/host/xhci-pci.c | 2 +-
include/linux/pci.h | 3 +
11 files changed, 287 insertions(+), 9 deletions(-)
--
2.8.0.rc3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3 1/4] PCI: No need to set d3cold_allowed to PCIe ports
2016-04-14 10:04 [PATCH v3 0/4] PCI: Add support for suspending (including runtime) of PCIe ports Mika Westerberg
@ 2016-04-14 10:04 ` Mika Westerberg
2016-04-14 10:04 ` [PATCH v3 2/4] PCI: Move PCIe ports to D3 during suspend Mika Westerberg
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Mika Westerberg @ 2016-04-14 10:04 UTC (permalink / raw)
To: Bjorn Helgaas, Rafael J. Wysocki
Cc: Qipeng Zha, Qi Zheng, Dave Airlie, Mathias Nyman,
Greg Kroah-Hartman, Lukas Wunner, Andreas Noever, Mika Westerberg,
linux-pci, linux-pm
The Linux PCI core skips PCI bridges and PCIe ports when system is
suspended. The PCI core checks return value of pci_has_subordinate() in
pci_pm_suspend_noirq() to skip all devices where it is non-zero (which
means PCI bridges and PCIe ports).
Since PCIe ports are never suspended in the first place, there is no need
to set d3cold_allowed for them.
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
drivers/pci/pcie/portdrv_pci.c | 5 -----
1 file changed, 5 deletions(-)
diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c
index be35da2e105e..6c6bb03392ea 100644
--- a/drivers/pci/pcie/portdrv_pci.c
+++ b/drivers/pci/pcie/portdrv_pci.c
@@ -134,11 +134,6 @@ static int pcie_portdrv_probe(struct pci_dev *dev,
return status;
pci_save_state(dev);
- /*
- * D3cold may not work properly on some PCIe port, so disable
- * it by default.
- */
- dev->d3cold_allowed = false;
return 0;
}
--
2.8.0.rc3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 2/4] PCI: Move PCIe ports to D3 during suspend
2016-04-14 10:04 [PATCH v3 0/4] PCI: Add support for suspending (including runtime) of PCIe ports Mika Westerberg
2016-04-14 10:04 ` [PATCH v3 1/4] PCI: No need to set d3cold_allowed to " Mika Westerberg
@ 2016-04-14 10:04 ` Mika Westerberg
2016-04-14 10:04 ` [PATCH v3 3/4] ACPI / hotplug / PCI: Runtime resume bridge before rescan Mika Westerberg
2016-04-14 10:04 ` [PATCH v3 4/4] PCI: Add runtime PM support for PCIe ports Mika Westerberg
3 siblings, 0 replies; 5+ messages in thread
From: Mika Westerberg @ 2016-04-14 10:04 UTC (permalink / raw)
To: Bjorn Helgaas, Rafael J. Wysocki
Cc: Qipeng Zha, Qi Zheng, Dave Airlie, Mathias Nyman,
Greg Kroah-Hartman, Lukas Wunner, Andreas Noever, Mika Westerberg,
linux-pci, linux-pm
Currently the Linux PCI core does not touch power state of PCI bridges and
PCIe ports when system suspend is entered. Leaving them in D0 consumes
power which is not good thing in portable devices such as laptops. This may
also prevent the CPU from entering deeper C-states.
With recent PCIe hardware we can power down the ports to save power given
that we take into account few restrictions:
- The PCIe port hardware is recent enough, starting from 2015.
- Devices connected to PCIe ports are effectively in D3cold once the port
is moved to D3 (the config space is not accessible anymore and the link
may be powered down).
- Devices behind the PCIe port need to be allowed to transition to D3cold
and back. There is a way both drivers and userspace can forbid this.
- If the device behind the PCIe port is capable of waking the system it
needs to be able to do so from D3cold.
This patch adds a new flag to struct pci_device called 'bridge_d3'. This
flag is set and cleared by the PCI core whenever there is a change in power
management state of any of the devices behind the PCIe port. If the above
restrictions are met we set 'bridge_d3' to true and transition the port to
D3 when system is suspended.
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
drivers/pci/bus.c | 1 +
drivers/pci/pci-driver.c | 7 +-
drivers/pci/pci-sysfs.c | 1 +
drivers/pci/pci.c | 156 ++++++++++++++++++++++++++++++++++++++++++++
drivers/pci/pci.h | 2 +
drivers/pci/remove.c | 2 +
drivers/usb/host/xhci-pci.c | 2 +-
include/linux/pci.h | 3 +
8 files changed, 172 insertions(+), 2 deletions(-)
diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c
index 6c9f5467bc5f..b1738162d69a 100644
--- a/drivers/pci/bus.c
+++ b/drivers/pci/bus.c
@@ -291,6 +291,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);
dev->match_driver = true;
retval = device_attach(&dev->dev);
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index d7ffd66814bb..0f55272fd27c 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -777,7 +777,12 @@ static int pci_pm_suspend_noirq(struct device *dev)
if (!pci_dev->state_saved) {
pci_save_state(pci_dev);
- if (!pci_has_subordinate(pci_dev))
+ /*
+ * Check if given device can go to low power state. Currently
+ * we allow normal PCI devices and PCI bridges if their
+ * bridge_d3 is set.
+ */
+ if (!pci_has_subordinate(pci_dev) || pci_dev->bridge_d3)
pci_prepare_to_sleep(pci_dev);
}
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index e982010f0ed1..518a4e1b5bcf 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -406,6 +406,7 @@ static ssize_t d3cold_allowed_store(struct device *dev,
return -EINVAL;
pdev->d3cold_allowed = !!val;
+ pci_bridge_d3_device_changed(pdev);
pm_runtime_resume(dev);
return count;
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 25e0327d4429..b2bc4c60bd26 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -9,6 +9,7 @@
#include <linux/kernel.h>
#include <linux/delay.h>
+#include <linux/dmi.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_pci.h>
@@ -2156,6 +2157,160 @@ void pci_config_pm_runtime_put(struct pci_dev *pdev)
}
/**
+ * pci_bridge_d3_possible - Is it possible to move the bridge to D3
+ * @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.
+ */
+static bool pci_bridge_d3_possible(struct pci_dev *bridge)
+{
+ unsigned int year;
+
+ if (!pci_is_pcie(bridge))
+ return false;
+
+ switch (pci_pcie_type(bridge)) {
+ case PCI_EXP_TYPE_ROOT_PORT:
+ case PCI_EXP_TYPE_UPSTREAM:
+ case PCI_EXP_TYPE_DOWNSTREAM:
+ /*
+ * PCIe ports from 2015 and newer should be capable of
+ * entering D3.
+ */
+ if (dmi_get_date(DMI_BIOS_DATE, &year, NULL, NULL) &&
+ year >= 2015) {
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+static int pci_dev_check_d3cold(struct pci_dev *dev, void *data)
+{
+ bool *d3cold_ok = data;
+
+ /*
+ * The device needs to be allowed to go D3cold and if it is wake
+ * capable to do so from D3cold. If the device is bridge then it
+ * needs to have D3 allowed.
+ */
+ if (dev->no_d3cold || !dev->d3cold_allowed)
+ *d3cold_ok = false;
+ if (device_may_wakeup(&dev->dev) && !pci_pme_capable(dev, PCI_D3cold))
+ *d3cold_ok = false;
+ if (pci_has_subordinate(dev) && !dev->bridge_d3)
+ *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
+ *
+ * Updates 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)
+{
+ struct pci_dev *bridge;
+ bool d3cold_ok = true;
+
+ bridge = pci_upstream_bridge(dev);
+ 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 (!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.
+ */
+ 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_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);
+}
+
+/**
+ * pci_d3cold_enable - Enable D3cold for device
+ * @dev: PCI device to handle
+ *
+ * This function can be used in drivers to enable D3cold from the device
+ * they handle. It also updates upstream PCI bridge PM capabilities
+ * accordingly.
+ */
+void pci_d3cold_enable(struct pci_dev *dev)
+{
+ if (dev->no_d3cold) {
+ dev->no_d3cold = false;
+ pci_bridge_d3_device_changed(dev);
+ }
+}
+EXPORT_SYMBOL_GPL(pci_d3cold_enable);
+
+/**
+ * pci_d3cold_disable - Disable D3cold for device
+ * @dev: PCI device to handle
+ *
+ * This function can be used in drivers to disable D3cold from the device
+ * they handle. It also updates upstream PCI bridge PM capabilities
+ * accordingly.
+ */
+void pci_d3cold_disable(struct pci_dev *dev)
+{
+ if (!dev->no_d3cold) {
+ dev->no_d3cold = true;
+ pci_bridge_d3_device_changed(dev);
+ }
+}
+EXPORT_SYMBOL_GPL(pci_d3cold_disable);
+
+/**
* pci_pm_init - Initialize PM functions of given PCI device
* @dev: PCI device to handle.
*/
@@ -2189,6 +2344,7 @@ void pci_pm_init(struct pci_dev *dev)
dev->pm_cap = pm;
dev->d3_delay = PCI_PM_D3_WAIT;
dev->d3cold_delay = PCI_PM_D3COLD_WAIT;
+ dev->bridge_d3 = pci_bridge_d3_possible(dev);
dev->d3cold_allowed = true;
dev->d1_support = false;
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index d0fb93481573..78447935519d 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -82,6 +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);
static inline void pci_wakeup_event(struct pci_dev *dev)
{
diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c
index 8982026637d5..d1ef7acf6930 100644
--- a/drivers/pci/remove.c
+++ b/drivers/pci/remove.c
@@ -96,6 +96,8 @@ static void pci_remove_bus_device(struct pci_dev *dev)
dev->subordinate = NULL;
}
+ pci_bridge_d3_device_removed(dev);
+
pci_destroy_dev(dev);
}
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index f0640b7a1c42..427eba7bdf76 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -379,7 +379,7 @@ static int xhci_pci_suspend(struct usb_hcd *hcd, bool do_wakeup)
* need to have the registers polled during D3, so avoid D3cold.
*/
if (xhci->quirks & XHCI_COMP_MODE_QUIRK)
- pdev->no_d3cold = true;
+ pci_d3cold_disable(pdev);
if (xhci->quirks & XHCI_PME_STUCK_QUIRK)
xhci_pme_quirk(hcd);
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 004b8133417d..4efb2a7889eb 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -296,6 +296,7 @@ struct pci_dev {
unsigned int d2_support:1; /* Low power state D2 is supported */
unsigned int no_d1d2:1; /* D1 and D2 are forbidden */
unsigned int no_d3cold:1; /* D3cold is forbidden */
+ unsigned int bridge_d3:1; /* Allow D3 for bridge */
unsigned int d3cold_allowed:1; /* D3cold is allowed by user */
unsigned int mmio_always_on:1; /* disallow turning off io/mem
decoding during bar sizing */
@@ -1085,6 +1086,8 @@ int pci_back_from_sleep(struct pci_dev *dev);
bool pci_dev_run_wake(struct pci_dev *dev);
bool pci_check_pme_status(struct pci_dev *dev);
void pci_pme_wakeup_bus(struct pci_bus *bus);
+void pci_d3cold_enable(struct pci_dev *dev);
+void pci_d3cold_disable(struct pci_dev *dev);
static inline int pci_enable_wake(struct pci_dev *dev, pci_power_t state,
bool enable)
--
2.8.0.rc3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 3/4] ACPI / hotplug / PCI: Runtime resume bridge before rescan
2016-04-14 10:04 [PATCH v3 0/4] PCI: Add support for suspending (including runtime) of PCIe ports Mika Westerberg
2016-04-14 10:04 ` [PATCH v3 1/4] PCI: No need to set d3cold_allowed to " Mika Westerberg
2016-04-14 10:04 ` [PATCH v3 2/4] PCI: Move PCIe ports to D3 during suspend Mika Westerberg
@ 2016-04-14 10:04 ` Mika Westerberg
2016-04-14 10:04 ` [PATCH v3 4/4] PCI: Add runtime PM support for PCIe ports Mika Westerberg
3 siblings, 0 replies; 5+ messages in thread
From: Mika Westerberg @ 2016-04-14 10:04 UTC (permalink / raw)
To: Bjorn Helgaas, Rafael J. Wysocki
Cc: Qipeng Zha, Qi Zheng, Dave Airlie, Mathias Nyman,
Greg Kroah-Hartman, Lukas Wunner, Andreas Noever, Mika Westerberg,
linux-pci, linux-pm
If a PCI bridge (or PCIe port) that is runtime suspended gets an ACPI
hotplug event, such as BUS_CHECK we need to make sure it is resumed before
devices below the bridge are re-scanned. Otherwise the devices behind the
port are not accessible and will be treated as hot-unplugged.
To fix this, resume PCI bridges from runtime suspend while rescanning.
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
drivers/pci/hotplug/acpiphp_glue.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c
index fa49f9143b80..d64ce8aa99b3 100644
--- a/drivers/pci/hotplug/acpiphp_glue.c
+++ b/drivers/pci/hotplug/acpiphp_glue.c
@@ -756,8 +756,10 @@ static void hotplug_event(u32 type, struct acpiphp_context *context)
acpi_lock_hp_context();
bridge = context->bridge;
- if (bridge)
+ if (bridge) {
get_bridge(bridge);
+ pm_runtime_get_sync(&bridge->pci_dev->dev);
+ }
acpi_unlock_hp_context();
@@ -797,8 +799,10 @@ static void hotplug_event(u32 type, struct acpiphp_context *context)
}
pci_unlock_rescan_remove();
- if (bridge)
+ if (bridge) {
+ pm_runtime_put(&bridge->pci_dev->dev);
put_bridge(bridge);
+ }
}
static int acpiphp_hotplug_notify(struct acpi_device *adev, u32 type)
--
2.8.0.rc3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 4/4] PCI: Add runtime PM support for PCIe ports
2016-04-14 10:04 [PATCH v3 0/4] PCI: Add support for suspending (including runtime) of PCIe ports Mika Westerberg
` (2 preceding siblings ...)
2016-04-14 10:04 ` [PATCH v3 3/4] ACPI / hotplug / PCI: Runtime resume bridge before rescan Mika Westerberg
@ 2016-04-14 10:04 ` Mika Westerberg
3 siblings, 0 replies; 5+ messages in thread
From: Mika Westerberg @ 2016-04-14 10:04 UTC (permalink / raw)
To: Bjorn Helgaas, Rafael J. Wysocki
Cc: Qipeng Zha, Qi Zheng, Dave Airlie, Mathias Nyman,
Greg Kroah-Hartman, Lukas Wunner, Andreas Noever, Mika Westerberg,
linux-pci, linux-pm
Add back runtime PM support for PCIe ports that was removed in
commit fe9a743a2601 ("PCI/PM: Drop unused runtime PM support code for
PCIe ports").
First of all we cannot enable it automatically for all ports since there
has been problems previously as can be seen in [1]. In summary suspended
PCIe ports were not able to deal with ACPI based hotplug reliably. One
reason why this might happen is the fact that when a PCIe port is powered
down, config space access to the devices behind the port is not possible.
If the BIOS hotplug SMI handler assumes the port is always in D0 it will
not be able to find the hotplugged devices. To be on the safe side only
enable runtime PM if the port does not claim to use ACPI based hotplug.
Ports using native PCIe hotplug are not restricted by this.
Furthermore we need to check that the PCI core thinks the port can go to D3
in the first place. The PCI core sets 'bridge_d3' in that case. If both
conditions are met we enable and allow runtime PM for the PCIe port. Since
'bridge_d3' can be changed anytime we check this in driver ->runtime_idle()
and ->runtime_suspend() and only allow runtime suspend if the flag is still
set. The actual power transition to D3 and back is handled in the PCI core.
Idea to automatically unblock (allow) runtime PM for PCIe ports came from
Dave Airlie.
[1] https://bugzilla.kernel.org/show_bug.cgi?id=53811
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
drivers/pci/pcie/portdrv_core.c | 2 +
drivers/pci/pcie/portdrv_pci.c | 107 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 109 insertions(+)
diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c
index 88122dc2e1b1..65b1a624826b 100644
--- a/drivers/pci/pcie/portdrv_core.c
+++ b/drivers/pci/pcie/portdrv_core.c
@@ -11,6 +11,7 @@
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/pm.h>
+#include <linux/pm_runtime.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/pcieport_if.h>
@@ -343,6 +344,7 @@ static int pcie_device_init(struct pci_dev *pdev, int service, int irq)
get_descriptor_id(pci_pcie_type(pdev), service));
device->parent = &pdev->dev;
device_enable_async_suspend(device);
+ pm_runtime_no_callbacks(device);
retval = device_register(device);
if (retval) {
diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c
index 6c6bb03392ea..83150650570e 100644
--- a/drivers/pci/pcie/portdrv_pci.c
+++ b/drivers/pci/pcie/portdrv_pci.c
@@ -17,6 +17,8 @@
#include <linux/aer.h>
#include <linux/dmi.h>
#include <linux/pci-aspm.h>
+#include <linux/pci-acpi.h>
+#include <linux/acpi.h>
#include "portdrv.h"
#include "aer/aerdrv.h"
@@ -58,6 +60,10 @@ __setup("pcie_ports=", pcie_port_setup);
/* global data */
+struct pcie_port_data {
+ bool runtime_pm_enabled;
+};
+
/**
* pcie_clear_root_pme_status - Clear root port PME interrupt status.
* @dev: PCIe root port or event collector.
@@ -93,6 +99,78 @@ static int pcie_port_resume_noirq(struct device *dev)
return 0;
}
+static int pcie_port_runtime_suspend(struct device *dev)
+{
+ return to_pci_dev(dev)->bridge_d3 ? 0 : -EBUSY;
+}
+
+static int pcie_port_runtime_resume(struct device *dev)
+{
+ return 0;
+}
+
+static int pcie_port_runtime_idle(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+
+ /*
+ * Rely the PCI core has set bridge_d3 whenever it thinks the port
+ * should be good to go to D3. Everything else, including moving
+ * the port to D3, is handled by the PCI core.
+ */
+ if (pdev->bridge_d3) {
+ pm_schedule_suspend(dev, 10);
+ return 0;
+ }
+ return -EBUSY;
+}
+
+#ifdef CONFIG_ACPI
+static bool pcie_port_uses_acpi_hotplug(struct pci_dev *pdev)
+{
+ const struct acpi_pci_root *root;
+ acpi_handle handle;
+
+ if (!pdev->is_hotplug_bridge)
+ return false;
+
+ 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);
+}
+#else
+static inline bool pcie_port_uses_acpi_hotplug(struct pci_dev *pdev)
+{
+ return false;
+}
+#endif
+
+static bool pcie_port_runtime_pm_possible(struct pci_dev *pdev)
+{
+ /*
+ * Only enable runtime PM if the PCI core agrees that this port can
+ * even go to D3.
+ */
+ if (!pdev->bridge_d3)
+ return false;
+
+ /*
+ * Prevent runtime PM if the port is using ACPI based 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). Native hotplug is expected to work with runtime PM
+ * enabled.
+ */
+ return !pcie_port_uses_acpi_hotplug(pdev);
+}
+
static const struct dev_pm_ops pcie_portdrv_pm_ops = {
.suspend = pcie_port_device_suspend,
.resume = pcie_port_device_resume,
@@ -101,12 +179,20 @@ static const struct dev_pm_ops pcie_portdrv_pm_ops = {
.poweroff = pcie_port_device_suspend,
.restore = pcie_port_device_resume,
.resume_noirq = pcie_port_resume_noirq,
+ .runtime_suspend = pcie_port_runtime_suspend,
+ .runtime_resume = pcie_port_runtime_resume,
+ .runtime_idle = pcie_port_runtime_idle,
};
#define PCIE_PORTDRV_PM_OPS (&pcie_portdrv_pm_ops)
#else /* !PM */
+static inline bool pcie_port_runtime_pm_possible(struct pci_dev *pdev)
+{
+ return false;
+}
+
#define PCIE_PORTDRV_PM_OPS NULL
#endif /* !PM */
@@ -121,6 +207,7 @@ static const struct dev_pm_ops pcie_portdrv_pm_ops = {
static int pcie_portdrv_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
+ struct pcie_port_data *pdata;
int status;
if (!pci_is_pcie(dev) ||
@@ -134,11 +221,31 @@ static int pcie_portdrv_probe(struct pci_dev *dev,
return status;
pci_save_state(dev);
+
+ pdata = devm_kzalloc(&dev->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+
+ pci_set_drvdata(dev, pdata);
+ if (pcie_port_runtime_pm_possible(dev)) {
+ pm_runtime_put_noidle(&dev->dev);
+ pm_runtime_allow(&dev->dev);
+
+ pdata->runtime_pm_enabled = true;
+ }
+
return 0;
}
static void pcie_portdrv_remove(struct pci_dev *dev)
{
+ const struct pcie_port_data *pdata = pci_get_drvdata(dev);
+
+ if (pdata->runtime_pm_enabled) {
+ pm_runtime_forbid(&dev->dev);
+ pm_runtime_get_noresume(&dev->dev);
+ }
+
pcie_port_device_remove(dev);
}
--
2.8.0.rc3
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2016-04-14 10:04 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2016-04-14 10:04 [PATCH v3 0/4] PCI: Add support for suspending (including runtime) of PCIe ports Mika Westerberg
2016-04-14 10:04 ` [PATCH v3 1/4] PCI: No need to set d3cold_allowed to " Mika Westerberg
2016-04-14 10:04 ` [PATCH v3 2/4] PCI: Move PCIe ports to D3 during suspend Mika Westerberg
2016-04-14 10:04 ` [PATCH v3 3/4] ACPI / hotplug / PCI: Runtime resume bridge before rescan Mika Westerberg
2016-04-14 10:04 ` [PATCH v3 4/4] PCI: Add runtime PM support for PCIe ports Mika Westerberg
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).