Re: [PATCH v4 2/4] PCI: Move PCIe ports to D3 during suspend

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, Apr 26, 2016 at 11:04:55PM +0200, Rafael J. Wysocki wrote:
> 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.

OK

> >                         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 ..".

I'll use "put the bridge into D3". Thanks.

> > + * @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".

OK

> > +                */
> > +               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;

Works for me.

Thanks for the comments!
--
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



[Index of Archives]     [DMA Engine]     [Linux Coverity]     [Linux USB]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Greybus]

  Powered by Linux