On Mon, Apr 25, 2016 at 11:53 AM, Mika Westerberg <mika.westerberg@xxxxxxxxxxxxxxx> wrote: > 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 (we provide > helper functions that should be used to change state of the device, like > pci_d3cold_disable()). 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. > > Signed-off-by: Mika Westerberg <mika.westerberg@xxxxxxxxxxxxxxx> > --- > Documentation/kernel-parameters.txt | 4 + > drivers/pci/bus.c | 1 + > drivers/pci/pci-driver.c | 10 +- > drivers/pci/pci-sysfs.c | 5 + > drivers/pci/pci.c | 176 ++++++++++++++++++++++++++++++++++++ > drivers/pci/pci.h | 2 + > drivers/pci/remove.c | 2 + > drivers/usb/host/xhci-pci.c | 2 +- > include/linux/pci.h | 3 + > 9 files changed, 200 insertions(+), 5 deletions(-) > > diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt > index 0b3de80ec8f6..90e1f43760be 100644 > --- a/Documentation/kernel-parameters.txt > +++ b/Documentation/kernel-parameters.txt > @@ -3008,6 +3008,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 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..e3ece0e25fa3 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) I'd add a helper for this, say pci_power_manageable() or similar. > pci_prepare_to_sleep(pci_dev); > } > > @@ -1144,7 +1149,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 +1165,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 342b6918bbde..1a0854be5b18 100644 > --- a/drivers/pci/pci-sysfs.c > +++ b/drivers/pci/pci-sysfs.c > @@ -406,6 +406,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 25e0327d4429..69878d5e2c2f 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> > @@ -101,6 +102,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 > @@ -2156,6 +2172,165 @@ void pci_config_pm_runtime_put(struct pci_dev *pdev) > } > > /** > + * pci_bridge_d3_possible - Is it possible to move the bridge to D3 It is better to say "transition" than "move" here IMO. Or even "put ... into ..". > + * @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; > + > + /* > + * PCIe ports from 2015 and newer should be capable of > + * entering D3. This is more about safety than capability. I'm sure that older PCIe ports can go to D3 too, but at least some of them will not work correctly after going back to D0. So I'd say "It should be safe to put PCIe ports from 2015 or newer into 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; What about: bool no_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; The rest of the patch looks fine. -- To unsubscribe from this list: send the line "unsubscribe linux-pci" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html