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

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

 



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



[Index of Archives]     [Linux Memonry Technology]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux