Re: [PATCH v2] PCI/ASPM: Disable ASPM when save/restore PCI state

From: Bjorn Helgaas
Date: Mon Jul 26 2021 - 18:03:21 EST


On Thu, Mar 11, 2021 at 11:34:33AM -0600, Bjorn Helgaas wrote:
> On Thu, Jan 28, 2021 at 03:52:42PM +0000, Victor Ding wrote:
> > Certain PCIe devices (e.g. GL9750) have high penalties (e.g. high Port
> > T_POWER_ON) when exiting L1 but enter L1 aggressively. As a result,
> > such devices enter and exit L1 frequently during pci_save_state and
> > pci_restore_state; eventually causing poor suspend/resume performance.
> >
> > Based on the observation that PCI accesses dominance pci_save_state/
> > pci_restore_state plus these accesses are fairly close to each other, the
> > actual time the device could stay in low power states is negligible.
> > Therefore, the little power-saving benefit from ASPM during suspend/resume
> > does not overweight the performance degradation caused by high L1 exit
> > penalties.
> >
> > Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=211187
>
> Thanks for this!
>
> This device can tolerate unlimited delay for L1 exit (DevCtl Endpoint
> L1 Acceptable Latency is unlimited) and it makes no guarantees about
> how fast it can exit L1 (LnkCap L1 Exit Latency is also unlimited), so
> I think there's basically no restriction on when it can enter ASPM
> L1.0.
>
> I have a hard time interpreting the L1.2 entry conditions in PCIe
> r5.0, sec 5.5.1, but I can believe it enters L1.2 aggressively since
> the device says it can tolerate any latencies.
>
> If L1.2 exit takes 3100us, it could do ~60 L1 exits in 200ms. I guess
> config accesses and code execution can account for some of that, but
> still seems like a lot of L1 entries/exits during suspend. I wouldn't
> think we would touch the device that much and that intermittently.
>
> > Signed-off-by: Victor Ding <victording@xxxxxxxxxx>
> >
> > ---
> >
> > Changes in v2:
> > - Updated commit message to remove unnecessary information
> > - Fixed a bug reading wrong register in pcie_save_aspm_control
> > - Updated to reuse existing pcie_config_aspm_dev where possible
> > - Fixed goto label style
> >
> > drivers/pci/pci.c | 18 +++++++++++++++---
> > drivers/pci/pci.h | 6 ++++++
> > drivers/pci/pcie/aspm.c | 27 +++++++++++++++++++++++++++
> > include/linux/pci.h | 1 +
> > 4 files changed, 49 insertions(+), 3 deletions(-)
> >
> > diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
> > index 32011b7b4c04..9ea88953f90b 100644
> > --- a/drivers/pci/pci.c
> > +++ b/drivers/pci/pci.c
> > @@ -1542,6 +1542,10 @@ static void pci_restore_ltr_state(struct pci_dev *dev)
> > int pci_save_state(struct pci_dev *dev)
> > {
> > int i;
> > +
> > + pcie_save_aspm_control(dev);
> > + pcie_disable_aspm(dev);
>
> If I understand this patch correctly, it basically does this:
>
> pci_save_state
> + pcie_save_aspm_control
> + pcie_disable_aspm
> <save state>
> + pcie_restore_aspm_control
>
> The <save state> part is just a bunch of config reads with very little
> other code execution. I'm really surprised that there's enough time
> between config reads for the link to go to L1. I guess you've
> verified that this does speed up suspend significantly, but this just
> doesn't make sense to me.
>
> In the bugzilla you say the GL9750 can go to L1.2 after ~4us of
> inactivity. That's enough time for a lot of code execution. We must
> be missing something. There's so much PCI traffic during save/restore
> that it should be easy to match up the analyzer trace with the code.
> Can you get any insight into what's going on that way?

I'm dropping this for now, pending a better understanding of what's
going on.

> > /* XXX: 100% dword access ok here? */
> > for (i = 0; i < 16; i++) {
> > pci_read_config_dword(dev, i * 4, &dev->saved_config_space[i]);
> > @@ -1552,18 +1556,22 @@ int pci_save_state(struct pci_dev *dev)
> >
> > i = pci_save_pcie_state(dev);
> > if (i != 0)
> > - return i;
> > + goto exit;
> >
> > i = pci_save_pcix_state(dev);
> > if (i != 0)
> > - return i;
> > + goto exit;
> >
> > pci_save_ltr_state(dev);
> > pci_save_aspm_l1ss_state(dev);
> > pci_save_dpc_state(dev);
> > pci_save_aer_state(dev);
> > pci_save_ptm_state(dev);
> > - return pci_save_vc_state(dev);
> > + i = pci_save_vc_state(dev);
> > +
> > +exit:
> > + pcie_restore_aspm_control(dev);
> > + return i;
> > }
> > EXPORT_SYMBOL(pci_save_state);
> >
> > @@ -1661,6 +1669,8 @@ void pci_restore_state(struct pci_dev *dev)
> > if (!dev->state_saved)
> > return;
> >
> > + pcie_disable_aspm(dev);
> > +
> > /*
> > * Restore max latencies (in the LTR capability) before enabling
> > * LTR itself (in the PCIe capability).
> > @@ -1689,6 +1699,8 @@ void pci_restore_state(struct pci_dev *dev)
> > pci_enable_acs(dev);
> > pci_restore_iov_state(dev);
> >
> > + pcie_restore_aspm_control(dev);
> > +
> > dev->state_saved = false;
> > }
> > EXPORT_SYMBOL(pci_restore_state);
> > diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
> > index a81459159f6d..e074a0cbe73c 100644
> > --- a/drivers/pci/pci.h
> > +++ b/drivers/pci/pci.h
> > @@ -584,6 +584,9 @@ void pcie_aspm_pm_state_change(struct pci_dev *pdev);
> > void pcie_aspm_powersave_config_link(struct pci_dev *pdev);
> > void pci_save_aspm_l1ss_state(struct pci_dev *dev);
> > void pci_restore_aspm_l1ss_state(struct pci_dev *dev);
> > +void pcie_save_aspm_control(struct pci_dev *dev);
> > +void pcie_restore_aspm_control(struct pci_dev *dev);
> > +void pcie_disable_aspm(struct pci_dev *pdev);
> > #else
> > static inline void pcie_aspm_init_link_state(struct pci_dev *pdev) { }
> > static inline void pcie_aspm_exit_link_state(struct pci_dev *pdev) { }
> > @@ -591,6 +594,9 @@ static inline void pcie_aspm_pm_state_change(struct pci_dev *pdev) { }
> > static inline void pcie_aspm_powersave_config_link(struct pci_dev *pdev) { }
> > static inline void pci_save_aspm_l1ss_state(struct pci_dev *dev) { }
> > static inline void pci_restore_aspm_l1ss_state(struct pci_dev *dev) { }
> > +static inline void pcie_save_aspm_control(struct pci_dev *dev) { }
> > +static inline void pcie_restore_aspm_control(struct pci_dev *dev) { }
> > +static inline void pcie_disable_aspm(struct pci_dev *pdev) { }
> > #endif
> >
> > #ifdef CONFIG_PCIE_ECRC
> > diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c
> > index a08e7d6dc248..e1e97db32e8b 100644
> > --- a/drivers/pci/pcie/aspm.c
> > +++ b/drivers/pci/pcie/aspm.c
> > @@ -784,6 +784,33 @@ static void pcie_config_aspm_dev(struct pci_dev *pdev, u32 val)
> > PCI_EXP_LNKCTL_ASPMC, val);
> > }
> >
> > +void pcie_disable_aspm(struct pci_dev *pdev)
> > +{
> > + if (!pci_is_pcie(pdev))
> > + return;
> > +
> > + pcie_config_aspm_dev(pdev, 0);
> > +}
> > +
> > +void pcie_save_aspm_control(struct pci_dev *pdev)
> > +{
> > + u16 lnkctl;
> > +
> > + if (!pci_is_pcie(pdev))
> > + return;
> > +
> > + pcie_capability_read_word(pdev, PCI_EXP_LNKCTL, &lnkctl);
> > + pdev->saved_aspm_ctl = lnkctl & PCI_EXP_LNKCTL_ASPMC;
> > +}
> > +
> > +void pcie_restore_aspm_control(struct pci_dev *pdev)
> > +{
> > + if (!pci_is_pcie(pdev))
> > + return;
> > +
> > + pcie_config_aspm_dev(pdev, pdev->saved_aspm_ctl);
> > +}
> > +
> > static void pcie_config_aspm_link(struct pcie_link_state *link, u32 state)
> > {
> > u32 upstream = 0, dwstream = 0;
> > diff --git a/include/linux/pci.h b/include/linux/pci.h
> > index b32126d26997..a21bfd6e3f89 100644
> > --- a/include/linux/pci.h
> > +++ b/include/linux/pci.h
> > @@ -387,6 +387,7 @@ struct pci_dev {
> > unsigned int ltr_path:1; /* Latency Tolerance Reporting
> > supported from root to here */
> > u16 l1ss; /* L1SS Capability pointer */
> > + u16 saved_aspm_ctl; /* ASPM Control saved at suspend time */
> > #endif
> > unsigned int eetlp_prefix_path:1; /* End-to-End TLP Prefix */
> >
> > --
> > 2.30.0.280.ga3ce27912f-goog
> >