* [PATCH v3] PCI/ASPM: Mask ASPM states based on Devicetree properties
@ 2026-07-04 2:50 Krishna Chaitanya Chundru
2026-07-04 3:04 ` sashiko-bot
0 siblings, 1 reply; 2+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-07-04 2:50 UTC (permalink / raw)
To: Bjorn Helgaas; +Cc: linux-pci, linux-kernel, mani, Krishna Chaitanya Chundru
Some platforms require selectively disabling specific ASPM states on a
given PCIe link to avoid link instability or functional failures caused
by board-level connectivity constraints such as PCB routing, connectors,
slots, or external cabling.
Devicetree supports disabling ASPM L0s, L1, and L1 PM Substates via the
'aspm-no-l0s', 'aspm-no-l1' [1], and 'aspm-no-l1ss' [2] properties.
However, the ASPM driver does not currently honor these properties when
initializing the default link state.
When firmware enables L1 PM Substates before the kernel takes over,
masking aspm_support alone is insufficient to disable them in hardware.
pcie_config_aspm_link() guards L1SS configuration behind a check on
aspm_capable, which is derived from aspm_support. Once aspm_support is
masked, pcie_config_aspm_l1ss() is never called, leaving
firmware-enabled L1SS substates active in hardware.
Fix this by introducing pcie_link_has_aspm_override() to check for DT
override properties on either endpoint of the link. In
pcie_aspm_override_default_link_state(), use it to:
- Mask aspm_support and aspm_default for any disabled state.
- Explicitly call pcie_config_aspm_l1ss(link, 0) before masking
aspm_support when firmware has L1SS active and DT requests disabling
L1 or L1SS, since pcie_config_aspm_link() will no longer do so once
aspm_capable is derived from the masked aspm_support.
Move the aspm_default initialization and
pcie_aspm_override_default_link_state() call in pcie_aspm_cap_init() to
before the "Restore L0s/L1" block. pcie_aspm_cap_init() disables L1 in
hardware prior to aspm_l1ss_init() and re-enables it only in the
restore block. Calling pcie_config_aspm_l1ss() while L1 is already
disabled satisfies its precondition ("Caller must disable L1 first"),
whereas the previous placement after the restore violated it.
Move pcie_config_aspm_l1ss() earlier in the file so it can be called
from pcie_aspm_override_default_link_state().
Link [1]: https://github.com/devicetree-org/dt-schema/pull/188
Link [2]: https://github.com/devicetree-org/dt-schema/pull/190
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
Changes in v3:
- Move pcie_aspm_override_default_link_state() call in pcie_aspm_cap_init() to
before the "Restore L0s/L1" block. pcie_aspm_cap_init() disables L1 in
hardware prior to aspm_l1ss_init() and re-enables it only in the
restore block. Calling pcie_config_aspm_l1ss() while L1 is already
disabled satisfies its precondition ("Caller must disable L1 first"),
whereas the previous placement after the restore violated it (sashiko).
- Link to v2: https://patch.msgid.link/20260624-aspm-v2-1-800a4151ba3a@oss.qualcomm.com
Changes in v2:
- Disable L1ss when L1 is disabled as pointed by sashiko.
- Disable L1ss if bootloader enables them but we are disabling via
devicetree pointed by sashiko.
- Link to v1: https://patch.msgid.link/20260511-aspm-v1-1-b4a9fe955cf9@oss.qualcomm.com
---
Changes in v3:
- Move pcie_aspm_override_default_link_state() to before the LNKCTL
restore block so that L1 is already disabled in hardware when
pcie_config_aspm_l1ss() is called, satisfying its precondition pointed
by sashiko.
- Link to v2: https://patch.msgid.link/20260624-aspm-v2-1-800a4151ba3a@oss.qualcomm.com
Changes in v2:
- Disable L1ss when L1 is disabled as pointed by sashiko.
- Disable L1ss if bootloader enables them but we are disabling via
devicetree pointed by sashiko.
- Link to v1: https://patch.msgid.link/20260511-aspm-v1-1-b4a9fe955cf9@oss.qualcomm.com
---
drivers/pci/pcie/aspm.c | 116 +++++++++++++++++++++++++++++++-----------------
1 file changed, 75 insertions(+), 41 deletions(-)
diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c
index 172783e7f519..f00285ee6be6 100644
--- a/drivers/pci/pcie/aspm.c
+++ b/drivers/pci/pcie/aspm.c
@@ -839,6 +839,49 @@ static void aspm_l1ss_init(struct pcie_link_state *link)
#define FLAG(x, y, d) (((x) & (PCIE_LINK_STATE_##y)) ? d : "")
+/* Configure the ASPM L1 substates. Caller must disable L1 first. */
+static void pcie_config_aspm_l1ss(struct pcie_link_state *link, u32 state)
+{
+ u32 val = 0;
+ struct pci_dev *child = link->downstream, *parent = link->pdev;
+
+ if (state & PCIE_LINK_STATE_L1_1)
+ val |= PCI_L1SS_CTL1_ASPM_L1_1;
+ if (state & PCIE_LINK_STATE_L1_2)
+ val |= PCI_L1SS_CTL1_ASPM_L1_2;
+ if (state & PCIE_LINK_STATE_L1_1_PCIPM)
+ val |= PCI_L1SS_CTL1_PCIPM_L1_1;
+ if (state & PCIE_LINK_STATE_L1_2_PCIPM)
+ val |= PCI_L1SS_CTL1_PCIPM_L1_2;
+
+ /*
+ * PCIe r6.2, sec 5.5.4, rules for enabling L1 PM Substates:
+ * - Clear L1.x enable bits at child first, then at parent
+ * - Set L1.x enable bits at parent first, then at child
+ * - ASPM/PCIPM L1.2 must be disabled while programming timing
+ * parameters
+ */
+
+ /* Disable all L1 substates */
+ pci_clear_and_set_config_dword(child, child->l1ss + PCI_L1SS_CTL1,
+ PCI_L1SS_CTL1_L1SS_MASK, 0);
+ pci_clear_and_set_config_dword(parent, parent->l1ss + PCI_L1SS_CTL1,
+ PCI_L1SS_CTL1_L1SS_MASK, 0);
+
+ /* Enable what we need to enable */
+ pci_clear_and_set_config_dword(parent, parent->l1ss + PCI_L1SS_CTL1,
+ PCI_L1SS_CTL1_L1SS_MASK, val);
+ pci_clear_and_set_config_dword(child, child->l1ss + PCI_L1SS_CTL1,
+ 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;
@@ -846,6 +889,33 @@ static void pcie_aspm_override_default_link_state(struct pcie_link_state *link)
/* 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;
+ }
+
+ /*
+ * 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);
+ } else if (no_l1ss) {
+ link->aspm_support &= ~PCIE_LINK_STATE_L1SS;
+ link->aspm_default &= ~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)
@@ -924,6 +994,11 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
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 */
if (FIELD_GET(PCI_EXP_LNKCTL_ASPMC, child_lnkctl) ||
FIELD_GET(PCI_EXP_LNKCTL_ASPMC, parent_lnkctl)) {
@@ -931,11 +1006,6 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
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;
@@ -949,42 +1019,6 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
}
}
-/* Configure the ASPM L1 substates. Caller must disable L1 first. */
-static void pcie_config_aspm_l1ss(struct pcie_link_state *link, u32 state)
-{
- u32 val = 0;
- struct pci_dev *child = link->downstream, *parent = link->pdev;
-
- if (state & PCIE_LINK_STATE_L1_1)
- val |= PCI_L1SS_CTL1_ASPM_L1_1;
- if (state & PCIE_LINK_STATE_L1_2)
- val |= PCI_L1SS_CTL1_ASPM_L1_2;
- if (state & PCIE_LINK_STATE_L1_1_PCIPM)
- val |= PCI_L1SS_CTL1_PCIPM_L1_1;
- if (state & PCIE_LINK_STATE_L1_2_PCIPM)
- val |= PCI_L1SS_CTL1_PCIPM_L1_2;
-
- /*
- * PCIe r6.2, sec 5.5.4, rules for enabling L1 PM Substates:
- * - Clear L1.x enable bits at child first, then at parent
- * - Set L1.x enable bits at parent first, then at child
- * - ASPM/PCIPM L1.2 must be disabled while programming timing
- * parameters
- */
-
- /* Disable all L1 substates */
- pci_clear_and_set_config_dword(child, child->l1ss + PCI_L1SS_CTL1,
- PCI_L1SS_CTL1_L1SS_MASK, 0);
- pci_clear_and_set_config_dword(parent, parent->l1ss + PCI_L1SS_CTL1,
- PCI_L1SS_CTL1_L1SS_MASK, 0);
-
- /* Enable what we need to enable */
- pci_clear_and_set_config_dword(parent, parent->l1ss + PCI_L1SS_CTL1,
- PCI_L1SS_CTL1_L1SS_MASK, val);
- pci_clear_and_set_config_dword(child, child->l1ss + PCI_L1SS_CTL1,
- PCI_L1SS_CTL1_L1SS_MASK, val);
-}
-
static void pcie_config_aspm_dev(struct pci_dev *pdev, u32 val)
{
pcie_capability_clear_and_set_word(pdev, PCI_EXP_LNKCTL,
---
base-commit: 71dfdfb0209b43dfd6f494f84f5548e4cfd18cb5
change-id: 20260429-aspm-da55a54279e4
Best regards,
--
Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH v3] PCI/ASPM: Mask ASPM states based on Devicetree properties
2026-07-04 2:50 [PATCH v3] PCI/ASPM: Mask ASPM states based on Devicetree properties Krishna Chaitanya Chundru
@ 2026-07-04 3:04 ` sashiko-bot
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-07-04 3:04 UTC (permalink / raw)
To: Krishna Chaitanya Chundru; +Cc: linux-pci
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Medium] Software ASPM state (`link->aspm_enabled`) is not updated after L1 PM Substates are explicitly disabled in hardware, causing sysfs to report incorrect state.
Pre-existing issues:
- [High] The 'aspm-no-l0s' and 'aspm-no-l1' DT properties fail to prevent the firmware's L0s and L1 states from being temporarily or permanently re-enabled, leaving broken link states active.
--
commit cd6ce2c13b26fecd26cf240bdae31606cf6afab9
Author: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
PCI/ASPM: Mask ASPM states based on Devicetree properties
This patch introduces pcie_link_has_aspm_override to check Devicetree
properties and mask ASPM support accordingly. It explicitly disables
L1SS if requested and moves the initialization sequence before the
restore block.
Link [1]: https://github.com/devicetree-org/dt-schema/pull/188
Link [2]: https://github.com/devicetree-org/dt-schema/pull/190
> diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c
> --- a/drivers/pci/pcie/aspm.c
> +++ b/drivers/pci/pcie/aspm.c
> @@ -846,6 +889,33 @@ static void pcie_aspm_override_default_link_state(struct pcie_link_state *link)
[ ... ]
> /* 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");
[ ... ]
> + /*
> + * 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);
[Severity: Medium]
Does this explicit disable step leave software's tracking of enabled ASPM
states out of sync with the actual hardware state?
By directly calling pcie_config_aspm_l1ss with a state of 0 without also
clearing the corresponding PCIE_LINK_STATE_L1SS bits in link->aspm_enabled,
could sysfs attributes and kernel APIs like pcie_aspm_enabled incorrectly
report L1SS as enabled until pcie_config_aspm_link is later invoked to
reconfigure the link?
> @@ -924,6 +994,11 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
>
> 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 */
> if (FIELD_GET(PCI_EXP_LNKCTL_ASPMC, child_lnkctl) ||
> FIELD_GET(PCI_EXP_LNKCTL_ASPMC, parent_lnkctl)) {
> @@ -931,11 +1006,6 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
> pcie_capability_write_word(child, PCI_EXP_LNKCTL, child_lnkctl);
> }
[Severity: High]
This is a pre-existing issue, but does moving the override logic above the
restore block still fail to prevent the firmware's L0s and L1 states from
being temporarily or permanently re-enabled?
Because the local variables child_lnkctl and parent_lnkctl hold the original
firmware state and are not masked by the Devicetree overrides processed in
pcie_aspm_override_default_link_state, will this restore block
unconditionally write the original state back to the hardware and re-enable
L0s/L1 despite the Devicetree prohibition?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260704-aspm-v3-1-157217aff76f@oss.qualcomm.com?part=1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-07-04 3:04 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-04 2:50 [PATCH v3] PCI/ASPM: Mask ASPM states based on Devicetree properties Krishna Chaitanya Chundru
2026-07-04 3:04 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.