Re: [PATCH v4 3/4] PCI: Decouple D3Hot and D3Cold handling for bridges

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

 



On Fri, Jul 26, 2024 at 4:02 PM Manivannan Sadhasivam
<manivannan.sadhasivam@xxxxxxxxxx> wrote:
>
> Currently, there is no proper distinction between D3Hot and D3Cold while
> handling the power management for PCI bridges. For instance,
> pci_bridge_d3_allowed() API decides whether it is allowed to put the
> bridge in D3, but it doesn't explicitly specify whether D3Hot or D3Cold
> is allowed in a scenario. This often leads to confusion and may be prone
> to errors.
>
> So let's split the D3Hot and D3Cold handling where possible. The current
> pci_bridge_d3_allowed() API is now split into pci_bridge_d3hot_allowed()
> and pci_bridge_d3cold_allowed() APIs and used in relevant places.
>
> Also, pci_bridge_d3_update() API is now renamed to
> pci_bridge_d3cold_update() since it was only used to check the possibility
> of D3Cold.
>
> Note that it is assumed that only D3Hot needs to be checked while
> transitioning the bridge during runtime PM and D3Cold in other places. In
> the ACPI case, wakeup is now only enabled if both D3Hot and D3Cold are
> allowed for the bridge.
>
> Still, there are places where just 'd3' is used opaquely, but those are
> hard to distinguish, hence left for future cleanups.
>
> Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@xxxxxxxxxx>
> ---
>  drivers/pci/bus.c          |  2 +-
>  drivers/pci/pci-acpi.c     |  5 +--
>  drivers/pci/pci-sysfs.c    |  2 +-
>  drivers/pci/pci.c          | 78 ++++++++++++++++++++++++++++++----------------
>  drivers/pci/pci.h          | 12 ++++---
>  drivers/pci/pcie/portdrv.c | 16 +++++-----
>  drivers/pci/remove.c       |  2 +-
>  include/linux/pci.h        |  3 +-
>  8 files changed, 75 insertions(+), 45 deletions(-)
>
> diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c
> index 826b5016a101..cb1a1aaefa90 100644
> --- a/drivers/pci/bus.c
> +++ b/drivers/pci/bus.c
> @@ -346,7 +346,7 @@ void pci_bus_add_device(struct pci_dev *dev)
>                 of_pci_make_dev_node(dev);
>         pci_create_sysfs_dev_files(dev);
>         pci_proc_attach_device(dev);
> -       pci_bridge_d3_update(dev);
> +       pci_bridge_d3cold_update(dev);
>
>         dev->match_driver = !dn || of_device_is_available(dn);
>         retval = device_attach(&dev->dev);
> diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c
> index 0f260cdc4592..aaf5a68e7984 100644
> --- a/drivers/pci/pci-acpi.c
> +++ b/drivers/pci/pci-acpi.c
> @@ -1434,7 +1434,7 @@ void pci_acpi_setup(struct device *dev, struct acpi_device *adev)
>          * reason is that the bridge may have additional methods such as
>          * _DSW that need to be called.
>          */
> -       if (pci_dev->bridge_d3_allowed)
> +       if (pci_dev->bridge_d3cold_allowed && pci_dev->bridge_d3hot_allowed)
>                 device_wakeup_enable(dev);
>
>         acpi_pci_wakeup(pci_dev, false);
> @@ -1452,7 +1452,8 @@ void pci_acpi_cleanup(struct device *dev, struct acpi_device *adev)
>         pci_acpi_remove_pm_notifier(adev);
>         if (adev->wakeup.flags.valid) {
>                 acpi_device_power_remove_dependent(adev, dev);
> -               if (pci_dev->bridge_d3_allowed)
> +               if (pci_dev->bridge_d3cold_allowed &&
> +                   pci_dev->bridge_d3hot_allowed)
>                         device_wakeup_disable(dev);
>
>                 device_set_wakeup_capable(dev, false);
> diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
> index 40cfa716392f..45628b0dd116 100644
> --- a/drivers/pci/pci-sysfs.c
> +++ b/drivers/pci/pci-sysfs.c
> @@ -529,7 +529,7 @@ static ssize_t d3cold_allowed_store(struct device *dev,
>                 return -EINVAL;
>
>         pdev->d3cold_allowed = !!val;
> -       pci_bridge_d3_update(pdev);
> +       pci_bridge_d3cold_update(pdev);
>
>         pm_runtime_resume(dev);
>
> diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
> index 0edc4e448c2d..48e2ca0cd8a0 100644
> --- a/drivers/pci/pci.c
> +++ b/drivers/pci/pci.c
> @@ -166,9 +166,9 @@ bool pci_ats_disabled(void)
>  }
>  EXPORT_SYMBOL_GPL(pci_ats_disabled);
>
> -/* Disable bridge_d3 for all PCIe ports */
> +/* Disable both D3Hot and D3Cold for all PCIe ports */
>  static bool pci_bridge_d3_disable;
> -/* Force bridge_d3 for all PCIe ports */
> +/* Force both D3Hot and D3Cold for all PCIe ports */
>  static bool pci_bridge_d3_force;
>
>  static int __init pcie_port_pm_setup(char *str)
> @@ -2966,14 +2966,11 @@ static const struct dmi_system_id bridge_d3_blacklist[] = {
>         { }
>  };
>
> -/**
> - * pci_bridge_d3_allowed - Is it allowed to put the bridge into D3
> - * @bridge: Bridge to check
> - *
> - * This function checks if the bridge is allowed to move to D3.
> - * Currently we only allow D3 for recent enough PCIe ports and Thunderbolt.
> +/*
> + * Helper function to check whether it is allowed to put the bridge into D3
> + * states (D3Hot and D3Cold).
>   */
> -bool pci_bridge_d3_allowed(struct pci_dev *bridge)
> +static bool pci_bridge_d3_allowed(struct pci_dev *bridge, pci_power_t state)
>  {
>         if (!pci_is_pcie(bridge))
>                 return false;
> @@ -3026,6 +3023,32 @@ bool pci_bridge_d3_allowed(struct pci_dev *bridge)
>         return false;
>  }
>
> +/**
> + * pci_bridge_d3cold_allowed - Is it allowed to put the bridge into D3Cold
> + * @bridge: Bridge to check
> + *
> + * This function checks if the bridge is allowed to move to D3Cold.
> + * Currently we only allow D3Cold for recent enough PCIe ports on ACPI based
> + * platforms and Thunderbolt.
> + */
> +bool pci_bridge_d3cold_allowed(struct pci_dev *bridge)
> +{
> +       return pci_bridge_d3_allowed(bridge, PCI_D3cold);
> +}
> +
> +/**
> + * pci_bridge_d3cold_allowed - Is it allowed to put the bridge into D3Hot

typo? pci_bridge_d3hot_allowed.

> + * @bridge: Bridge to check
> + *
> + * This function checks if the bridge is allowed to move to D3Hot.
> + * Currently we only allow D3Hot for recent enough PCIe ports on ACPI based
> + * platforms and Thunderbolt.
> + */
> +bool pci_bridge_d3hot_allowed(struct pci_dev *bridge)
> +{
> +       return pci_bridge_d3_allowed(bridge, PCI_D3hot);
> +}
> +
>  static int pci_dev_check_d3cold(struct pci_dev *dev, void *data)
>  {
>         bool *d3cold_ok = data;
> @@ -3046,55 +3069,55 @@ static int pci_dev_check_d3cold(struct pci_dev *dev, void *data)
>  }
>
>  /*
> - * pci_bridge_d3_update - Update bridge D3 capabilities
> + * pci_bridge_d3cold_update - Update bridge D3Cold capabilities
>   * @dev: PCI device which is changed
>   *
>   * 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.
>   */
> -void pci_bridge_d3_update(struct pci_dev *dev)
> +void pci_bridge_d3cold_update(struct pci_dev *dev)
>  {
>         bool remove = !device_is_registered(&dev->dev);
>         struct pci_dev *bridge;
>         bool d3cold_ok = true;
>
>         bridge = pci_upstream_bridge(dev);
> -       if (!bridge || !pci_bridge_d3_allowed(bridge))
> +       if (!bridge || !pci_bridge_d3cold_allowed(bridge))
>                 return;
>
>         /*
> -        * If D3 is currently allowed for the bridge, removing one of its
> +        * If D3Cold is currently allowed for the bridge, removing one of its
>          * children won't change that.
>          */
> -       if (remove && bridge->bridge_d3_allowed)
> +       if (remove && bridge->bridge_d3cold_allowed)
>                 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
> +        * If D3Cold is currently allowed for the bridge and a child is added or
> +        * changed, disallowance of D3Cold 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 D3Cold 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 D3 is currently not allowed for the bridge, this may be caused
> +        * If D3Cold 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.
> +        * continues to block D3Cold.
>          */
> -       if (d3cold_ok && !bridge->bridge_d3_allowed)
> +       if (d3cold_ok && !bridge->bridge_d3cold_allowed)
>                 pci_walk_bus(bridge->subordinate, pci_dev_check_d3cold,
>                              &d3cold_ok);
>
> -       if (bridge->bridge_d3_allowed != d3cold_ok) {
> -               bridge->bridge_d3_allowed = d3cold_ok;
> +       if (bridge->bridge_d3cold_allowed != d3cold_ok) {
> +               bridge->bridge_d3cold_allowed = d3cold_ok;
>                 /* Propagate change to upstream bridges */
> -               pci_bridge_d3_update(bridge);
> +               pci_bridge_d3cold_update(bridge);
>         }
>  }
>
> @@ -3110,7 +3133,7 @@ void pci_d3cold_enable(struct pci_dev *dev)
>  {
>         if (dev->no_d3cold) {
>                 dev->no_d3cold = false;
> -               pci_bridge_d3_update(dev);
> +               pci_bridge_d3cold_update(dev);
>         }
>  }
>  EXPORT_SYMBOL_GPL(pci_d3cold_enable);
> @@ -3127,7 +3150,7 @@ void pci_d3cold_disable(struct pci_dev *dev)
>  {
>         if (!dev->no_d3cold) {
>                 dev->no_d3cold = true;
> -               pci_bridge_d3_update(dev);
> +               pci_bridge_d3cold_update(dev);
>         }
>  }
>  EXPORT_SYMBOL_GPL(pci_d3cold_disable);
> @@ -3167,7 +3190,8 @@ void pci_pm_init(struct pci_dev *dev)
>         dev->pm_cap = pm;
>         dev->d3hot_delay = PCI_PM_D3HOT_WAIT;
>         dev->d3cold_delay = PCI_PM_D3COLD_WAIT;
> -       dev->bridge_d3_allowed = pci_bridge_d3_allowed(dev);
> +       dev->bridge_d3cold_allowed = pci_bridge_d3cold_allowed(dev);
> +       dev->bridge_d3hot_allowed = pci_bridge_d3hot_allowed(dev);
>         dev->d3cold_allowed = true;
>
>         dev->d1_support = false;
> diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
> index 53ca75639201..f819eab793fc 100644
> --- a/drivers/pci/pci.h
> +++ b/drivers/pci/pci.h
> @@ -92,8 +92,9 @@ void pci_pm_init(struct pci_dev *dev);
>  void pci_ea_init(struct pci_dev *dev);
>  void pci_msi_init(struct pci_dev *dev);
>  void pci_msix_init(struct pci_dev *dev);
> -bool pci_bridge_d3_allowed(struct pci_dev *dev);
> -void pci_bridge_d3_update(struct pci_dev *dev);
> +bool pci_bridge_d3cold_allowed(struct pci_dev *dev);
> +bool pci_bridge_d3hot_allowed(struct pci_dev *dev);
> +void pci_bridge_d3cold_update(struct pci_dev *dev);
>  int pci_bridge_wait_for_secondary_bus(struct pci_dev *dev, char *reset_type);
>
>  static inline void pci_wakeup_event(struct pci_dev *dev)
> @@ -111,9 +112,12 @@ 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.
> +        * into D3 states if both bridge_d3cold_allowed and bridge_d3hot_allowed
> +        * are set.
>          */

If pm requires both D3hot and D3cold, can we add a flag for DT to
support D3cold? Otherwise during suspend, pcieport still stays at D0.

[   42.202016] mt7921e 0000:01:00.0: PM: calling
pci_pm_suspend_noirq+0x0/0x300 @ 77, parent: 0000:00:00.0
[   42.231681] mt7921e 0000:01:00.0: PCI PM: Suspend power state: D3hot
[   42.238048] mt7921e 0000:01:00.0: PM:
pci_pm_suspend_noirq+0x0/0x300 returned 0 after 26583 usecs
[   42.247083] pcieport 0000:00:00.0: PM: calling
pci_pm_suspend_noirq+0x0/0x300 @ 3196, parent: pci0000:00
[   42.296325] pcieport 0000:00:00.0: PCI PM: Suspend power state: D0
[   42.302500] pcieport 0000:00:00.0: PCI PM: Skipped

Thanks.


> -       return !pci_has_subordinate(pci_dev) || pci_dev->bridge_d3_allowed;
> +       return !pci_has_subordinate(pci_dev) ||
> +              (pci_dev->bridge_d3cold_allowed &&
> +               pci_dev->bridge_d3hot_allowed);
>  }
>
>  static inline bool pcie_downstream_port(const struct pci_dev *dev)
> diff --git a/drivers/pci/pcie/portdrv.c b/drivers/pci/pcie/portdrv.c
> index 8401a0f7b394..655754b9f06a 100644
> --- a/drivers/pci/pcie/portdrv.c
> +++ b/drivers/pci/pcie/portdrv.c
> @@ -632,7 +632,7 @@ __setup("pcie_ports=", pcie_port_setup);
>  #ifdef CONFIG_PM
>  static int pcie_port_runtime_suspend(struct device *dev)
>  {
> -       if (!to_pci_dev(dev)->bridge_d3_allowed)
> +       if (!to_pci_dev(dev)->bridge_d3hot_allowed)
>                 return -EBUSY;
>
>         return pcie_port_device_runtime_suspend(dev);
> @@ -641,11 +641,11 @@ static int pcie_port_runtime_suspend(struct device *dev)
>  static int pcie_port_runtime_idle(struct device *dev)
>  {
>         /*
> -        * Assume the PCI core has set bridge_d3_allowed 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.
> +        * Assume the PCI core has set bridge_d3hot_allowed whenever it thinks
> +        * the port should be good to go to D3Hot.  Everything else, including
> +        * moving the port to D3Hot, is handled by the PCI core.
>          */
> -       return to_pci_dev(dev)->bridge_d3_allowed ? 0 : -EBUSY;
> +       return to_pci_dev(dev)->bridge_d3hot_allowed ? 0 : -EBUSY;
>  }
>
>  static const struct dev_pm_ops pcie_portdrv_pm_ops = {
> @@ -702,7 +702,7 @@ static int pcie_portdrv_probe(struct pci_dev *dev,
>         dev_pm_set_driver_flags(&dev->dev, DPM_FLAG_NO_DIRECT_COMPLETE |
>                                            DPM_FLAG_SMART_SUSPEND);
>
> -       if (dev->bridge_d3_allowed) {
> +       if (dev->bridge_d3hot_allowed) {
>                 /*
>                  * Keep the port resumed 100ms to make sure things like
>                  * config space accesses from userspace (lspci) will not
> @@ -720,7 +720,7 @@ static int pcie_portdrv_probe(struct pci_dev *dev,
>
>  static void pcie_portdrv_remove(struct pci_dev *dev)
>  {
> -       if (dev->bridge_d3_allowed) {
> +       if (dev->bridge_d3hot_allowed) {
>                 pm_runtime_forbid(&dev->dev);
>                 pm_runtime_get_noresume(&dev->dev);
>                 pm_runtime_dont_use_autosuspend(&dev->dev);
> @@ -733,7 +733,7 @@ static void pcie_portdrv_remove(struct pci_dev *dev)
>
>  static void pcie_portdrv_shutdown(struct pci_dev *dev)
>  {
> -       if (dev->bridge_d3_allowed) {
> +       if (dev->bridge_d3hot_allowed) {
>                 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 d749ea8250d6..36d8cb50b582 100644
> --- a/drivers/pci/remove.c
> +++ b/drivers/pci/remove.c
> @@ -41,7 +41,7 @@ static void pci_destroy_dev(struct pci_dev *dev)
>
>         pci_doe_destroy(dev);
>         pcie_aspm_exit_link_state(dev);
> -       pci_bridge_d3_update(dev);
> +       pci_bridge_d3cold_update(dev);
>         pci_free_resources(dev);
>         put_device(&dev->dev);
>  }
> diff --git a/include/linux/pci.h b/include/linux/pci.h
> index 2a48c88512e1..d0947f932b9a 100644
> --- a/include/linux/pci.h
> +++ b/include/linux/pci.h
> @@ -375,7 +375,8 @@ 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_allowed:1;    /* Allow D3 for bridge */
> +       unsigned int    bridge_d3cold_allowed:1;        /* Allow D3Cold for bridge */
> +       unsigned int    bridge_d3hot_allowed:1;         /* Allow D3Hot 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 */
>
> --
> 2.25.1
>





[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