Merge branch 'pci/aspm'

- Avoid L0s for Realtek RTS525A, where it causes an AER interrupt storm
  (Max Lee)

- Program the same ASPM Control values for every function of multi-function
  devices, as recommended by the PCIe spec (Krishna Chaitanya Chundru)

- Avoid ASPM L0s, L1, and L1 PM Substates based on 'aspm-no-l0s',
  'aspm-no-l1' [1], and 'aspm-no-l1ss' DT properties (Krishna Chaitanya
  Chundru)

* pci/aspm:
  PCI/ASPM: Mask ASPM states based on Devicetree properties
  PCI/ASPM: Disable/restore ASPM on every function for multi-function devices
  PCI/ASPM: Use pcie_capability_clear_and_set_word() for ASPM disable/restore
  PCI/ASPM: Avoid L0s for Realtek RTS525A
This commit is contained in:
Bjorn Helgaas 2026-08-21 16:40:33 -05:00
commit 606866edbc
2 changed files with 168 additions and 110 deletions

View File

@ -839,116 +839,6 @@ static void aspm_l1ss_init(struct pcie_link_state *link)
#define FLAG(x, y, d) (((x) & (PCIE_LINK_STATE_##y)) ? d : "")
static void pcie_aspm_override_default_link_state(struct pcie_link_state *link)
{
struct pci_dev *pdev = link->downstream;
u32 override;
/* For devicetree platforms, enable L0s and L1 by default */
if (of_have_populated_dt()) {
if (link->aspm_support & PCIE_LINK_STATE_L0S)
link->aspm_default |= PCIE_LINK_STATE_L0S;
if (link->aspm_support & PCIE_LINK_STATE_L1)
link->aspm_default |= PCIE_LINK_STATE_L1;
override = link->aspm_default & ~link->aspm_enabled;
if (override)
pci_info(pdev, "ASPM: default states%s%s\n",
FLAG(override, L0S, " L0s"),
FLAG(override, L1, " L1"));
}
}
static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
{
struct pci_dev *child = link->downstream, *parent = link->pdev;
u16 parent_lnkctl, child_lnkctl;
struct pci_bus *linkbus = parent->subordinate;
if (blacklist) {
/* Set enabled/disable so that we will disable ASPM later */
link->aspm_enabled = PCIE_LINK_STATE_ASPM_ALL;
link->aspm_disable = PCIE_LINK_STATE_ASPM_ALL;
return;
}
/*
* If ASPM not supported, don't mess with the clocks and link,
* bail out now.
*/
if (!(parent->aspm_l0s_support && child->aspm_l0s_support) &&
!(parent->aspm_l1_support && child->aspm_l1_support))
return;
/* Configure common clock before checking latencies */
pcie_aspm_configure_common_clock(link);
/*
* Re-read upstream/downstream components' register state after
* clock configuration. L0s & L1 exit latencies in the otherwise
* read-only Link Capabilities may change depending on common clock
* configuration (PCIe r5.0, sec 7.5.3.6).
*/
pcie_capability_read_word(parent, PCI_EXP_LNKCTL, &parent_lnkctl);
pcie_capability_read_word(child, PCI_EXP_LNKCTL, &child_lnkctl);
/* Disable L0s/L1 before updating L1SS config */
if (FIELD_GET(PCI_EXP_LNKCTL_ASPMC, child_lnkctl) ||
FIELD_GET(PCI_EXP_LNKCTL_ASPMC, parent_lnkctl)) {
pcie_capability_write_word(child, PCI_EXP_LNKCTL,
child_lnkctl & ~PCI_EXP_LNKCTL_ASPMC);
pcie_capability_write_word(parent, PCI_EXP_LNKCTL,
parent_lnkctl & ~PCI_EXP_LNKCTL_ASPMC);
}
/*
* Setup L0s state
*
* Note that we must not enable L0s in either direction on a
* given link unless components on both sides of the link each
* support L0s.
*/
if (parent->aspm_l0s_support && child->aspm_l0s_support)
link->aspm_support |= PCIE_LINK_STATE_L0S;
if (child_lnkctl & PCI_EXP_LNKCTL_ASPM_L0S)
link->aspm_enabled |= PCIE_LINK_STATE_L0S_UP;
if (parent_lnkctl & PCI_EXP_LNKCTL_ASPM_L0S)
link->aspm_enabled |= PCIE_LINK_STATE_L0S_DW;
/* Setup L1 state */
if (parent->aspm_l1_support && child->aspm_l1_support)
link->aspm_support |= PCIE_LINK_STATE_L1;
if (parent_lnkctl & child_lnkctl & PCI_EXP_LNKCTL_ASPM_L1)
link->aspm_enabled |= PCIE_LINK_STATE_L1;
aspm_l1ss_init(link);
/* Restore L0s/L1 if they were enabled */
if (FIELD_GET(PCI_EXP_LNKCTL_ASPMC, child_lnkctl) ||
FIELD_GET(PCI_EXP_LNKCTL_ASPMC, parent_lnkctl)) {
pcie_capability_write_word(parent, PCI_EXP_LNKCTL, parent_lnkctl);
pcie_capability_write_word(child, PCI_EXP_LNKCTL, child_lnkctl);
}
/* Save default state */
link->aspm_default = link->aspm_enabled;
pcie_aspm_override_default_link_state(link);
/* Setup initial capable state. Will be updated later */
link->aspm_capable = link->aspm_support;
/* Get and check endpoint acceptable latencies */
list_for_each_entry(child, &linkbus->devices, bus_list) {
if (pci_pcie_type(child) != PCI_EXP_TYPE_ENDPOINT &&
pci_pcie_type(child) != PCI_EXP_TYPE_LEG_END)
continue;
pcie_aspm_check_latency(child);
}
}
/* Configure the ASPM L1 substates. Caller must disable L1 first. */
static void pcie_config_aspm_l1ss(struct pcie_link_state *link, u32 state)
{
@ -985,6 +875,171 @@ static void pcie_config_aspm_l1ss(struct pcie_link_state *link, u32 state)
PCI_L1SS_CTL1_L1SS_MASK, val);
}
static bool pcie_link_has_aspm_override(const struct pcie_link_state *link,
const char *aspm)
{
return (device_property_present(&link->pdev->dev, aspm) ||
device_property_present(&link->downstream->dev, aspm));
}
static void pcie_aspm_override_default_link_state(struct pcie_link_state *link)
{
struct pci_dev *pdev = link->downstream;
u32 override;
/* For devicetree platforms, enable L0s and L1 by default */
if (of_have_populated_dt()) {
bool no_l0s = pcie_link_has_aspm_override(link, "aspm-no-l0s");
bool no_l1 = pcie_link_has_aspm_override(link, "aspm-no-l1");
bool no_l1ss = pcie_link_has_aspm_override(link, "aspm-no-l1ss");
if (no_l0s) {
link->aspm_support &= ~PCIE_LINK_STATE_L0S;
link->aspm_default &= ~PCIE_LINK_STATE_L0S;
link->aspm_enabled &= ~PCIE_LINK_STATE_L0S;
}
/*
* Clear L1SS in hardware before updating aspm_support. Once
* aspm_capable is derived from aspm_support, pcie_config_aspm_link()
* skips pcie_config_aspm_l1ss() entirely via the aspm_capable guard,
* leaving firmware-enabled L1SS substates active in hardware.
* This applies equally when disabling L1 (which implies L1SS).
*/
if ((no_l1 || no_l1ss) && (link->aspm_enabled & PCIE_LINK_STATE_L1SS))
pcie_config_aspm_l1ss(link, 0);
if (no_l1) {
link->aspm_support &= ~(PCIE_LINK_STATE_L1 | PCIE_LINK_STATE_L1SS);
link->aspm_default &= ~(PCIE_LINK_STATE_L1 | PCIE_LINK_STATE_L1SS);
link->aspm_enabled &= ~(PCIE_LINK_STATE_L1 | PCIE_LINK_STATE_L1SS);
} else if (no_l1ss) {
link->aspm_support &= ~PCIE_LINK_STATE_L1SS;
link->aspm_default &= ~PCIE_LINK_STATE_L1SS;
link->aspm_enabled &= ~PCIE_LINK_STATE_L1SS;
}
if (link->aspm_support & PCIE_LINK_STATE_L0S)
link->aspm_default |= PCIE_LINK_STATE_L0S;
if (link->aspm_support & PCIE_LINK_STATE_L1)
link->aspm_default |= PCIE_LINK_STATE_L1;
override = link->aspm_default & ~link->aspm_enabled;
if (override)
pci_info(pdev, "ASPM: default states%s%s\n",
FLAG(override, L0S, " L0s"),
FLAG(override, L1, " L1"));
}
}
static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
{
struct pci_dev *child = link->downstream, *parent = link->pdev;
struct pci_dev *fn;
u16 parent_lnkctl, child_lnkctl;
struct pci_bus *linkbus = parent->subordinate;
if (blacklist) {
/* Set enabled/disable so that we will disable ASPM later */
link->aspm_enabled = PCIE_LINK_STATE_ASPM_ALL;
link->aspm_disable = PCIE_LINK_STATE_ASPM_ALL;
return;
}
/*
* If ASPM not supported, don't mess with the clocks and link,
* bail out now.
*/
if (!(parent->aspm_l0s_support && child->aspm_l0s_support) &&
!(parent->aspm_l1_support && child->aspm_l1_support))
return;
/* Configure common clock before checking latencies */
pcie_aspm_configure_common_clock(link);
/*
* Re-read upstream/downstream components' register state after
* clock configuration. L0s & L1 exit latencies in the otherwise
* read-only Link Capabilities may change depending on common clock
* configuration (PCIe r5.0, sec 7.5.3.6).
*/
pcie_capability_read_word(parent, PCI_EXP_LNKCTL, &parent_lnkctl);
pcie_capability_read_word(child, PCI_EXP_LNKCTL, &child_lnkctl);
/* Disable L0s/L1 before updating L1SS config */
if (FIELD_GET(PCI_EXP_LNKCTL_ASPMC, child_lnkctl) ||
FIELD_GET(PCI_EXP_LNKCTL_ASPMC, parent_lnkctl)) {
list_for_each_entry(fn, &linkbus->devices, bus_list)
pcie_capability_clear_and_set_word(fn, PCI_EXP_LNKCTL,
PCI_EXP_LNKCTL_ASPMC, 0);
pcie_capability_clear_and_set_word(parent, PCI_EXP_LNKCTL,
PCI_EXP_LNKCTL_ASPMC, 0);
}
/*
* Setup L0s state
*
* Note that we must not enable L0s in either direction on a
* given link unless components on both sides of the link each
* support L0s.
*/
if (parent->aspm_l0s_support && child->aspm_l0s_support)
link->aspm_support |= PCIE_LINK_STATE_L0S;
if (child_lnkctl & PCI_EXP_LNKCTL_ASPM_L0S)
link->aspm_enabled |= PCIE_LINK_STATE_L0S_UP;
if (parent_lnkctl & PCI_EXP_LNKCTL_ASPM_L0S)
link->aspm_enabled |= PCIE_LINK_STATE_L0S_DW;
/* Setup L1 state */
if (parent->aspm_l1_support && child->aspm_l1_support)
link->aspm_support |= PCIE_LINK_STATE_L1;
if (parent_lnkctl & child_lnkctl & PCI_EXP_LNKCTL_ASPM_L1)
link->aspm_enabled |= PCIE_LINK_STATE_L1;
aspm_l1ss_init(link);
/* Save default state */
link->aspm_default = link->aspm_enabled;
pcie_aspm_override_default_link_state(link);
/*
* Restore L0s/L1 if they were enabled, but don't restore any
* state a Devicetree override just disabled in aspm_support above.
*/
if (FIELD_GET(PCI_EXP_LNKCTL_ASPMC, child_lnkctl) ||
FIELD_GET(PCI_EXP_LNKCTL_ASPMC, parent_lnkctl)) {
if (!(link->aspm_support & PCIE_LINK_STATE_L0S)) {
child_lnkctl &= ~PCI_EXP_LNKCTL_ASPM_L0S;
parent_lnkctl &= ~PCI_EXP_LNKCTL_ASPM_L0S;
}
if (!(link->aspm_support & PCIE_LINK_STATE_L1)) {
child_lnkctl &= ~PCI_EXP_LNKCTL_ASPM_L1;
parent_lnkctl &= ~PCI_EXP_LNKCTL_ASPM_L1;
}
pcie_capability_clear_and_set_word(parent, PCI_EXP_LNKCTL,
PCI_EXP_LNKCTL_ASPMC,
parent_lnkctl & PCI_EXP_LNKCTL_ASPMC);
list_for_each_entry(fn, &linkbus->devices, bus_list)
pcie_capability_clear_and_set_word(fn, PCI_EXP_LNKCTL,
PCI_EXP_LNKCTL_ASPMC,
child_lnkctl & PCI_EXP_LNKCTL_ASPMC);
}
/* Setup initial capable state. Will be updated later */
link->aspm_capable = link->aspm_support;
/* Get and check endpoint acceptable latencies */
list_for_each_entry(child, &linkbus->devices, bus_list) {
if (pci_pcie_type(child) != PCI_EXP_TYPE_ENDPOINT &&
pci_pcie_type(child) != PCI_EXP_TYPE_LEG_END)
continue;
pcie_aspm_check_latency(child);
}
}
static void pcie_config_aspm_dev(struct pci_dev *pdev, u32 val)
{
pcie_capability_clear_and_set_word(pdev, PCI_EXP_LNKCTL,

View File

@ -2507,6 +2507,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x10f1, quirk_disable_aspm_l0s);
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x10f4, quirk_disable_aspm_l0s);
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1508, quirk_disable_aspm_l0s);
/* Realtek RTS525A generates a Replay Timer Timeout storm when L0s is enabled. */
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_REALTEK, 0x525a, quirk_disable_aspm_l0s);
static void quirk_disable_aspm_l0s_l1(struct pci_dev *dev)
{
pcie_aspm_remove_cap(dev,