[PATCH v3 2/8] PCI/ASPM: Add pci_force_enable_link_state() API

Bjorn Helgaas helgaas at kernel.org
Mon Oct 12 15:58:32 PDT 2026


On Wed, Jul 08, 2026 at 04:30:16PM +0200, Manivannan Sadhasivam wrote:
> From: Manivannan Sadhasivam <manivannan.sadhasivam at oss.qualcomm.com>
> 
> pci_enable_link_state() and pci_enable_link_state_locked() APIs only enable
> the ASPM states that were not previously disabled via
> pci_disable_link_state*() API or blacklisted during init (e.g. the pre-1.1
> device blacklist, which is only meant to be overridden with
> 'pcie_aspm=force'). This is an intentional behavior as these APIs must not
> silently re-enable states that were deliberately disabled, since doing so
> could enable ASPM on a link where it is known to be unsafe.

I think this is saying that many uses of pci_disable_link_state() are
to avoid hardware errata, i.e., these are basically quirks, and we
should *never* enable those ASPM states.

> However, some drivers (e.g. the atheros WLAN drivers) save the currently
> enabled ASPM states, disable all ASPM states before firmware download, and
> then try to restore exactly the states that were enabled before. Restoring
> those states requires re-enabling states that were just disabled via
> pci_disable_link_state() API. But, this cannot be achieved using the
> existing APIs.

This sounds like it's also for hardware errata: if ASPM works
correctly, there's no need to disable it during firmware download.  I
think this is *only* the atheros drivers, isn't it?  I didn't see any
others.

> Hence, add pci_force_enable_link_state() API for such callers. Unlike
> pci_enable_link_state(), it re-enables the requested states even if they
> were previously disabled via pci_disable_link_state() or disabled during
> init. The caller is therefore responsible for only enabling states the
> device actually supports, typically the ones previously reported by
> pcie_aspm_enabled().

The problem is that pci_enable_link_state() cannot enable things that
were disabled by pci_disable_link_state().  They're not symmetric,
which violates the caller's expectation.

Instead of adding pci_force_enable_link_state(), I think we should add
a new interface for the *quirk* behavior that permanently disables
states.  Then pci_enable_link_state() and pci_disable_link_state()
could be made symmetric.

> This API is implemented by adding a 'force' parameter to the shared
> __pci_enable_link_state() helper. When 'force' is true, the requested
> states are cleared from 'link->aspm_disable' before enabling.
> 
> Suggested-by: Ilpo Järvinen <ilpo.jarvinen at linux.intel.com>
> Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam at oss.qualcomm.com>
> ---
>  drivers/pci/pcie/aspm.c | 37 ++++++++++++++++++++++++++++++++++---
>  include/linux/pci.h     |  3 +++
>  2 files changed, 37 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c
> index 1c15a14718e8..c04fb71de91c 100644
> --- a/drivers/pci/pcie/aspm.c
> +++ b/drivers/pci/pcie/aspm.c
> @@ -1506,7 +1506,8 @@ int pci_disable_link_state(struct pci_dev *pdev, int state)
>  }
>  EXPORT_SYMBOL(pci_disable_link_state);
>  
> -static int __pci_enable_link_state(struct pci_dev *pdev, int state, bool locked)
> +static int __pci_enable_link_state(struct pci_dev *pdev, int state, bool locked,
> +				   bool force)
>  {
>  	struct pcie_link_state *link = pcie_aspm_get_link(pdev);
>  
> @@ -1527,6 +1528,10 @@ static int __pci_enable_link_state(struct pci_dev *pdev, int state, bool locked)
>  		down_read(&pci_bus_sem);
>  	mutex_lock(&aspm_lock);
>  	link->aspm_default = pci_calc_aspm_enable_mask(state);
> +
> +	/* Force enable states that were previously disabled */
> +	if (force)
> +		link->aspm_disable &= ~link->aspm_default;
>  	pcie_config_aspm_link(link, policy_to_aspm_state(link));
>  
>  	link->clkpm_default = (state & PCIE_LINK_STATE_CLKPM) ? 1 : 0;
> @@ -1553,7 +1558,7 @@ static int __pci_enable_link_state(struct pci_dev *pdev, int state, bool locked)
>   */
>  int pci_enable_link_state(struct pci_dev *pdev, int state)
>  {
> -	return __pci_enable_link_state(pdev, state, false);
> +	return __pci_enable_link_state(pdev, state, false, false);
>  }
>  EXPORT_SYMBOL(pci_enable_link_state);
>  
> @@ -1576,10 +1581,36 @@ int pci_enable_link_state_locked(struct pci_dev *pdev, int state)
>  {
>  	lockdep_assert_held_read(&pci_bus_sem);
>  
> -	return __pci_enable_link_state(pdev, state, true);
> +	return __pci_enable_link_state(pdev, state, true, false);
>  }
>  EXPORT_SYMBOL(pci_enable_link_state_locked);
>  
> +/**
> + * pci_force_enable_link_state - Force enable device link state
> + * @pdev: PCI device
> + * @state: Mask of ASPM link states to enable
> + *
> + * Enable device link state, so the link will enter the specified states.
> + * Unlike pci_enable_link_state(), this also re-enables states previously
> + * disabled by pci_disable_link_state() and overrides the ASPM states disabled
> + * during init (e.g., the pre-1.1 device blacklist). The caller is therefore
> + * responsible for only enabling states the device supports, typically the ones
> + * previously reported by pcie_aspm_enabled().
> + *
> + * Note that if the BIOS didn't grant ASPM control to the OS, this does nothing
> + * because we can't touch the LNKCTL register.
> + *
> + * Note: Ensure devices are in D0 before enabling PCI-PM L1 PM Substates, per
> + * PCIe r6.0, sec 5.5.4.
> + *
> + * Return: 0 on success, a negative errno otherwise.
> + */
> +int pci_force_enable_link_state(struct pci_dev *pdev, int state)
> +{
> +	return __pci_enable_link_state(pdev, state, false, true);
> +}
> +EXPORT_SYMBOL(pci_force_enable_link_state);
> +
>  void pcie_aspm_remove_cap(struct pci_dev *pdev, u32 lnkcap)
>  {
>  	if (lnkcap & PCI_EXP_LNKCAP_ASPM_L0S)
> diff --git a/include/linux/pci.h b/include/linux/pci.h
> index ebb5b9d76360..be6719626c8f 100644
> --- a/include/linux/pci.h
> +++ b/include/linux/pci.h
> @@ -1918,6 +1918,7 @@ int pci_disable_link_state(struct pci_dev *pdev, int state);
>  int pci_disable_link_state_locked(struct pci_dev *pdev, int state);
>  int pci_enable_link_state(struct pci_dev *pdev, int state);
>  int pci_enable_link_state_locked(struct pci_dev *pdev, int state);
> +int pci_force_enable_link_state(struct pci_dev *pdev, int state);
>  void pcie_no_aspm(void);
>  bool pcie_aspm_support_enabled(void);
>  bool pcie_aspm_enabled(struct pci_dev *pdev);
> @@ -1930,6 +1931,8 @@ static inline int pci_enable_link_state(struct pci_dev *pdev, int state)
>  { return 0; }
>  static inline int pci_enable_link_state_locked(struct pci_dev *pdev, int state)
>  { return 0; }
> +static inline int pci_force_enable_link_state(struct pci_dev *pdev, int state)
> +{ return 0; }
>  static inline void pcie_no_aspm(void) { }
>  static inline bool pcie_aspm_support_enabled(void) { return false; }
>  static inline bool pcie_aspm_enabled(struct pci_dev *pdev) { return false; }
> 
> -- 
> 2.43.0
> 
> 



More information about the ath12k mailing list