diff mbox

[SRU,Xenial] PCI: Put PCIe ports into D3 during suspend

Message ID 1498120032-26049-2-git-send-email-acelan.kao@canonical.com
State New
Headers show

Commit Message

AceLan Kao June 22, 2017, 8:27 a.m. UTC
From: Mika Westerberg <mika.westerberg@linux.intel.com>

BugLink: http://bugs.launchpad.net/bugs/1693393

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 unnecessarily and may 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 transitioned 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.  When system
later on is suspended we only need to check this flag and if it is true
transition the port to D3 otherwise we leave it in D0.

Also provide override mechanism via command line parameter
"pcie_port_pm=[off|force]" that can be used to disable or enable the
feature regardless of the BIOS manufacturing date.

Tested-by: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
(cherry picked from commit 9d26d3a8f1b0c442339a235f9508bdad8af91043)
Signed-off-by: AceLan Kao <acelan.kao@canonical.com>
---
 Documentation/kernel-parameters.txt |   4 +
 drivers/pci/bus.c                   |   1 +
 drivers/pci/pci-driver.c            |   5 +-
 drivers/pci/pci-sysfs.c             |   5 ++
 drivers/pci/pci.c                   | 175 ++++++++++++++++++++++++++++++++++++
 drivers/pci/pci.h                   |  11 +++
 drivers/pci/remove.c                |   2 +
 drivers/usb/host/xhci-pci.c         |   2 +-
 include/linux/pci.h                 |   3 +
 9 files changed, 203 insertions(+), 5 deletions(-)

Comments

Stefan Bader June 22, 2017, 9:06 a.m. UTC | #1
On 22.06.2017 10:27, AceLan Kao wrote:
> From: Mika Westerberg <mika.westerberg@linux.intel.com>
> 
> BugLink: http://bugs.launchpad.net/bugs/1693393

Thanks for adding that one. So looking at the bug report, a bit more closely due
to the amount of change this patch adds, I miss a more detailed SRU
justification that tells us about how much risk this change might introduce.
Please be sure to add that to the bug reports in future. This is because that
might be looked at by members of the SRU team (which is not us). But also is
some help for us to get a feeling how much risk a requested change will have.

And when looking for the abbreviated sha1 of your proposed patch I find at least
one additional patch which would at least be needed as well.

commit 035ee288ae7ade4152f1c3cf23a587b04fdc526c
Author: Lukas Wunner <lukas@wunner.de>
Date:   Tue Sep 6 06:20:46 2016 +0200

    PCI: Fix bridge_d3 update on device removal

So all this does not make me very confident that this change which affects any
system with PCIe ports has been carefully checked against regressions.

-Stefan

> 
> 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 unnecessarily and may 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 transitioned 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.  When system
> later on is suspended we only need to check this flag and if it is true
> transition the port to D3 otherwise we leave it in D0.
> 
> Also provide override mechanism via command line parameter
> "pcie_port_pm=[off|force]" that can be used to disable or enable the
> feature regardless of the BIOS manufacturing date.
> 
> Tested-by: Lukas Wunner <lukas@wunner.de>
> Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
> Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
> (cherry picked from commit 9d26d3a8f1b0c442339a235f9508bdad8af91043)
> Signed-off-by: AceLan Kao <acelan.kao@canonical.com>
> ---
>  Documentation/kernel-parameters.txt |   4 +
>  drivers/pci/bus.c                   |   1 +
>  drivers/pci/pci-driver.c            |   5 +-
>  drivers/pci/pci-sysfs.c             |   5 ++
>  drivers/pci/pci.c                   | 175 ++++++++++++++++++++++++++++++++++++
>  drivers/pci/pci.h                   |  11 +++
>  drivers/pci/remove.c                |   2 +
>  drivers/usb/host/xhci-pci.c         |   2 +-
>  include/linux/pci.h                 |   3 +
>  9 files changed, 203 insertions(+), 5 deletions(-)
> 
> diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt
> index 648bc5a..c32a831 100644
> --- a/Documentation/kernel-parameters.txt
> +++ b/Documentation/kernel-parameters.txt
> @@ -2921,6 +2921,10 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
>  		compat	Treat PCIe ports as PCI-to-PCI bridges, disable the PCIe
>  			ports driver.
>  
> +	pcie_port_pm=	[PCIE] PCIe port power management handling:
> +		off	Disable power management of all PCIe ports
> +		force	Forcibly enable power management of all PCIe ports
> +
>  	pcie_pme=	[PCIE,PM] Native PCIe PME signaling options:
>  		nomsi	Do not use MSI for native PCIe PME signaling (this makes
>  			all PCIe root ports use INTx for all services).
> diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c
> index 89b3bef..5698cad 100644
> --- a/drivers/pci/bus.c
> +++ b/drivers/pci/bus.c
> @@ -288,6 +288,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 d7ffd66..e39a67c 100644
> --- a/drivers/pci/pci-driver.c
> +++ b/drivers/pci/pci-driver.c
> @@ -777,7 +777,7 @@ 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))
> +		if (pci_power_manageable(pci_dev))
>  			pci_prepare_to_sleep(pci_dev);
>  	}
>  
> @@ -1144,7 +1144,6 @@ static int pci_pm_runtime_suspend(struct device *dev)
>  		return -ENOSYS;
>  
>  	pci_dev->state_saved = false;
> -	pci_dev->no_d3cold = false;
>  	error = pm->runtime_suspend(dev);
>  	if (error) {
>  		/*
> @@ -1161,8 +1160,6 @@ static int pci_pm_runtime_suspend(struct device *dev)
>  
>  		return error;
>  	}
> -	if (!pci_dev->d3cold_allowed)
> -		pci_dev->no_d3cold = true;
>  
>  	pci_fixup_device(pci_fixup_suspend, pci_dev);
>  
> diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
> index 47468ff..ad9948a 100644
> --- a/drivers/pci/pci-sysfs.c
> +++ b/drivers/pci/pci-sysfs.c
> @@ -407,6 +407,11 @@ static ssize_t d3cold_allowed_store(struct device *dev,
>  		return -EINVAL;
>  
>  	pdev->d3cold_allowed = !!val;
> +	if (pdev->d3cold_allowed)
> +		pci_d3cold_enable(pdev);
> +	else
> +		pci_d3cold_disable(pdev);
> +
>  	pm_runtime_resume(dev);
>  
>  	return count;
> diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
> index 1a14ca8..01158b0 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>
> @@ -102,6 +103,21 @@ unsigned int pcibios_max_latency = 255;
>  /* If set, the PCIe ARI capability will not be used. */
>  static bool pcie_ari_disabled;
>  
> +/* Disable bridge_d3 for all PCIe ports */
> +static bool pci_bridge_d3_disable;
> +/* Force bridge_d3 for all PCIe ports */
> +static bool pci_bridge_d3_force;
> +
> +static int __init pcie_port_pm_setup(char *str)
> +{
> +	if (!strcmp(str, "off"))
> +		pci_bridge_d3_disable = true;
> +	else if (!strcmp(str, "force"))
> +		pci_bridge_d3_force = true;
> +	return 1;
> +}
> +__setup("pcie_port_pm=", pcie_port_pm_setup);
> +
>  /**
>   * pci_bus_max_busnr - returns maximum PCI bus number of given bus' children
>   * @bus: pointer to PCI bus structure to search
> @@ -2158,6 +2174,164 @@ void pci_config_pm_runtime_put(struct pci_dev *pdev)
>  }
>  
>  /**
> + * pci_bridge_d3_possible - Is it possible to put the bridge into 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:
> +		if (pci_bridge_d3_disable)
> +			return false;
> +		if (pci_bridge_d3_force)
> +			return true;
> +
> +		/*
> +		 * It should be safe to put PCIe ports from 2015 or newer
> +		 * to 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;
> +	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);
> +
> +	*d3cold_ok = !no_d3cold;
> +
> +	return no_d3cold;
> +}
> +
> +/*
> + * 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)
> +{
> +	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.
>   */
> @@ -2191,6 +2365,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 c43e448..eaa9108 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)
>  {
> @@ -94,6 +96,15 @@ static inline bool pci_has_subordinate(struct pci_dev *pci_dev)
>  	return !!(pci_dev->subordinate);
>  }
>  
> +static inline bool pci_power_manageable(struct pci_dev *pci_dev)
> +{
> +	/*
> +	 * Currently we allow normal PCI devices and PCI bridges transition
> +	 * into D3 if their bridge_d3 is set.
> +	 */
> +	return !pci_has_subordinate(pci_dev) || pci_dev->bridge_d3;
> +}
> +
>  struct pci_vpd_ops {
>  	ssize_t (*read)(struct pci_dev *dev, loff_t pos, size_t count, void *buf);
>  	ssize_t (*write)(struct pci_dev *dev, loff_t pos, size_t count, const void *buf);
> diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c
> index 8a280e9..c8ef695 100644
> --- a/drivers/pci/remove.c
> +++ b/drivers/pci/remove.c
> @@ -93,6 +93,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 30c4ae8..9604600 100644
> --- a/drivers/usb/host/xhci-pci.c
> +++ b/drivers/usb/host/xhci-pci.c
> @@ -395,7 +395,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, true);
> diff --git a/include/linux/pci.h b/include/linux/pci.h
> index b3605de..428f203 100644
> --- a/include/linux/pci.h
> +++ b/include/linux/pci.h
> @@ -302,6 +302,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 */
> @@ -1073,6 +1074,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)
>
diff mbox

Patch

diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt
index 648bc5a..c32a831 100644
--- a/Documentation/kernel-parameters.txt
+++ b/Documentation/kernel-parameters.txt
@@ -2921,6 +2921,10 @@  bytes respectively. Such letter suffixes can also be entirely omitted.
 		compat	Treat PCIe ports as PCI-to-PCI bridges, disable the PCIe
 			ports driver.
 
+	pcie_port_pm=	[PCIE] PCIe port power management handling:
+		off	Disable power management of all PCIe ports
+		force	Forcibly enable power management of all PCIe ports
+
 	pcie_pme=	[PCIE,PM] Native PCIe PME signaling options:
 		nomsi	Do not use MSI for native PCIe PME signaling (this makes
 			all PCIe root ports use INTx for all services).
diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c
index 89b3bef..5698cad 100644
--- a/drivers/pci/bus.c
+++ b/drivers/pci/bus.c
@@ -288,6 +288,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 d7ffd66..e39a67c 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -777,7 +777,7 @@  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))
+		if (pci_power_manageable(pci_dev))
 			pci_prepare_to_sleep(pci_dev);
 	}
 
@@ -1144,7 +1144,6 @@  static int pci_pm_runtime_suspend(struct device *dev)
 		return -ENOSYS;
 
 	pci_dev->state_saved = false;
-	pci_dev->no_d3cold = false;
 	error = pm->runtime_suspend(dev);
 	if (error) {
 		/*
@@ -1161,8 +1160,6 @@  static int pci_pm_runtime_suspend(struct device *dev)
 
 		return error;
 	}
-	if (!pci_dev->d3cold_allowed)
-		pci_dev->no_d3cold = true;
 
 	pci_fixup_device(pci_fixup_suspend, pci_dev);
 
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index 47468ff..ad9948a 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -407,6 +407,11 @@  static ssize_t d3cold_allowed_store(struct device *dev,
 		return -EINVAL;
 
 	pdev->d3cold_allowed = !!val;
+	if (pdev->d3cold_allowed)
+		pci_d3cold_enable(pdev);
+	else
+		pci_d3cold_disable(pdev);
+
 	pm_runtime_resume(dev);
 
 	return count;
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 1a14ca8..01158b0 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>
@@ -102,6 +103,21 @@  unsigned int pcibios_max_latency = 255;
 /* If set, the PCIe ARI capability will not be used. */
 static bool pcie_ari_disabled;
 
+/* Disable bridge_d3 for all PCIe ports */
+static bool pci_bridge_d3_disable;
+/* Force bridge_d3 for all PCIe ports */
+static bool pci_bridge_d3_force;
+
+static int __init pcie_port_pm_setup(char *str)
+{
+	if (!strcmp(str, "off"))
+		pci_bridge_d3_disable = true;
+	else if (!strcmp(str, "force"))
+		pci_bridge_d3_force = true;
+	return 1;
+}
+__setup("pcie_port_pm=", pcie_port_pm_setup);
+
 /**
  * pci_bus_max_busnr - returns maximum PCI bus number of given bus' children
  * @bus: pointer to PCI bus structure to search
@@ -2158,6 +2174,164 @@  void pci_config_pm_runtime_put(struct pci_dev *pdev)
 }
 
 /**
+ * pci_bridge_d3_possible - Is it possible to put the bridge into 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:
+		if (pci_bridge_d3_disable)
+			return false;
+		if (pci_bridge_d3_force)
+			return true;
+
+		/*
+		 * It should be safe to put PCIe ports from 2015 or newer
+		 * to 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;
+	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);
+
+	*d3cold_ok = !no_d3cold;
+
+	return no_d3cold;
+}
+
+/*
+ * 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)
+{
+	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.
  */
@@ -2191,6 +2365,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 c43e448..eaa9108 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)
 {
@@ -94,6 +96,15 @@  static inline bool pci_has_subordinate(struct pci_dev *pci_dev)
 	return !!(pci_dev->subordinate);
 }
 
+static inline bool pci_power_manageable(struct pci_dev *pci_dev)
+{
+	/*
+	 * Currently we allow normal PCI devices and PCI bridges transition
+	 * into D3 if their bridge_d3 is set.
+	 */
+	return !pci_has_subordinate(pci_dev) || pci_dev->bridge_d3;
+}
+
 struct pci_vpd_ops {
 	ssize_t (*read)(struct pci_dev *dev, loff_t pos, size_t count, void *buf);
 	ssize_t (*write)(struct pci_dev *dev, loff_t pos, size_t count, const void *buf);
diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c
index 8a280e9..c8ef695 100644
--- a/drivers/pci/remove.c
+++ b/drivers/pci/remove.c
@@ -93,6 +93,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 30c4ae8..9604600 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -395,7 +395,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, true);
diff --git a/include/linux/pci.h b/include/linux/pci.h
index b3605de..428f203 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -302,6 +302,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 */
@@ -1073,6 +1074,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)