From: Bjorn Helgaas <helgaas@kernel.org>
To: linux-pci@vger.kernel.org
Cc: Christian Zigotzky <chzigotzky@xenosoft.de>,
Manivannan Sadhasivam <mani@kernel.org>,
mad skateman <madskateman@gmail.com>,
"R . T . Dickinson" <rtd2@xtra.co.nz>,
Darren Stevens <darren@stevens-zone.net>,
John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>,
Lukas Wunner <lukas@wunner.de>,
luigi burdo <intermediadc@hotmail.com>, Al <al@datazap.net>,
Roland <rol7and@gmx.com>, Hongxing Zhu <hongxing.zhu@nxp.com>,
hypexed@yahoo.com.au, linuxppc-dev@lists.ozlabs.org,
debian-powerpc@lists.debian.org, linux-kernel@vger.kernel.org,
Bjorn Helgaas <bhelgaas@google.com>
Subject: [PATCH 1/2] PCI/ASPM: Cache Link Capabilities so quirks can override them
Date: Thu, 6 Nov 2025 12:36:38 -0600 [thread overview]
Message-ID: <20251106183643.1963801-2-helgaas@kernel.org> (raw)
In-Reply-To: <20251106183643.1963801-1-helgaas@kernel.org>
From: Bjorn Helgaas <bhelgaas@google.com>
Cache the PCIe Link Capabilities register in struct pci_dev so quirks can
remove features to avoid hardware defects. The idea is:
- set_pcie_port_type() reads PCIe Link Capabilities and caches it in
dev->lnkcap
- HEADER quirks can update the cached dev->lnkcap to remove advertised
features that don't work correctly
- pcie_aspm_cap_init() relies on dev->lnkcap and ignores any features not
advertised there
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
---
drivers/pci/pcie/aspm.c | 42 ++++++++++++++++++++---------------------
drivers/pci/probe.c | 5 ++---
include/linux/pci.h | 1 +
3 files changed, 24 insertions(+), 24 deletions(-)
diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c
index 7cc8281e7011..07536891f1f6 100644
--- a/drivers/pci/pcie/aspm.c
+++ b/drivers/pci/pcie/aspm.c
@@ -391,15 +391,13 @@ static void pcie_clkpm_override_default_link_state(struct pcie_link_state *link,
static void pcie_clkpm_cap_init(struct pcie_link_state *link, int blacklist)
{
int capable = 1, enabled = 1;
- u32 reg32;
u16 reg16;
struct pci_dev *child;
struct pci_bus *linkbus = link->pdev->subordinate;
/* All functions should have the same cap and state, take the worst */
list_for_each_entry(child, &linkbus->devices, bus_list) {
- pcie_capability_read_dword(child, PCI_EXP_LNKCAP, ®32);
- if (!(reg32 & PCI_EXP_LNKCAP_CLKPM)) {
+ if (!(child->lnkcap & PCI_EXP_LNKCAP_CLKPM)) {
capable = 0;
enabled = 0;
break;
@@ -581,7 +579,7 @@ static void encode_l12_threshold(u32 threshold_us, u32 *scale, u32 *value)
static void pcie_aspm_check_latency(struct pci_dev *endpoint)
{
- u32 latency, encoding, lnkcap_up, lnkcap_dw;
+ u32 latency, encoding;
u32 l1_switch_latency = 0, latency_up_l0s;
u32 latency_up_l1, latency_dw_l0s, latency_dw_l1;
u32 acceptable_l0s, acceptable_l1;
@@ -606,14 +604,10 @@ static void pcie_aspm_check_latency(struct pci_dev *endpoint)
struct pci_dev *dev = pci_function_0(link->pdev->subordinate);
/* Read direction exit latencies */
- pcie_capability_read_dword(link->pdev, PCI_EXP_LNKCAP,
- &lnkcap_up);
- pcie_capability_read_dword(dev, PCI_EXP_LNKCAP,
- &lnkcap_dw);
- latency_up_l0s = calc_l0s_latency(lnkcap_up);
- latency_up_l1 = calc_l1_latency(lnkcap_up);
- latency_dw_l0s = calc_l0s_latency(lnkcap_dw);
- latency_dw_l1 = calc_l1_latency(lnkcap_dw);
+ latency_up_l0s = calc_l0s_latency(link->pdev->lnkcap);
+ latency_up_l1 = calc_l1_latency(link->pdev->lnkcap);
+ latency_dw_l0s = calc_l0s_latency(dev->lnkcap);
+ latency_dw_l1 = calc_l1_latency(dev->lnkcap);
/* Check upstream direction L0s latency */
if ((link->aspm_capable & PCIE_LINK_STATE_L0S_UP) &&
@@ -830,7 +824,7 @@ static void pcie_aspm_override_default_link_state(struct pcie_link_state *link)
static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
{
struct pci_dev *child = link->downstream, *parent = link->pdev;
- u32 parent_lnkcap, child_lnkcap;
+ u32 lnkcap;
u16 parent_lnkctl, child_lnkctl;
struct pci_bus *linkbus = parent->subordinate;
@@ -845,9 +839,7 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
* If ASPM not supported, don't mess with the clocks and link,
* bail out now.
*/
- pcie_capability_read_dword(parent, PCI_EXP_LNKCAP, &parent_lnkcap);
- pcie_capability_read_dword(child, PCI_EXP_LNKCAP, &child_lnkcap);
- if (!(parent_lnkcap & child_lnkcap & PCI_EXP_LNKCAP_ASPMS))
+ if (!(parent->lnkcap & child->lnkcap & PCI_EXP_LNKCAP_ASPMS))
return;
/* Configure common clock before checking latencies */
@@ -857,10 +849,18 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
* 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).
+ * configuration (PCIe r5.0, sec 7.5.3.6). Update only the exit
+ * latencies in the cached dev->lnkcap because quirks may have
+ * removed broken features advertised by the device.
*/
- pcie_capability_read_dword(parent, PCI_EXP_LNKCAP, &parent_lnkcap);
- pcie_capability_read_dword(child, PCI_EXP_LNKCAP, &child_lnkcap);
+ pcie_capability_read_dword(parent, PCI_EXP_LNKCAP, &lnkcap);
+ parent->lnkcap &= ~(PCI_EXP_LNKCAP_L0SEL | PCI_EXP_LNKCAP_L1EL);
+ parent->lnkcap |= lnkcap & (PCI_EXP_LNKCAP_L0SEL | PCI_EXP_LNKCAP_L1EL);
+
+ pcie_capability_read_dword(child, PCI_EXP_LNKCAP, &lnkcap);
+ child->lnkcap &= ~(PCI_EXP_LNKCAP_L0SEL | PCI_EXP_LNKCAP_L1EL);
+ child->lnkcap |= lnkcap & (PCI_EXP_LNKCAP_L0SEL | PCI_EXP_LNKCAP_L1EL);
+
pcie_capability_read_word(parent, PCI_EXP_LNKCTL, &parent_lnkctl);
pcie_capability_read_word(child, PCI_EXP_LNKCTL, &child_lnkctl);
@@ -880,7 +880,7 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
* given link unless components on both sides of the link each
* support L0s.
*/
- if (parent_lnkcap & child_lnkcap & PCI_EXP_LNKCAP_ASPM_L0S)
+ if (parent->lnkcap & child->lnkcap & PCI_EXP_LNKCAP_ASPM_L0S)
link->aspm_support |= PCIE_LINK_STATE_L0S;
if (child_lnkctl & PCI_EXP_LNKCTL_ASPM_L0S)
@@ -889,7 +889,7 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
link->aspm_enabled |= PCIE_LINK_STATE_L0S_DW;
/* Setup L1 state */
- if (parent_lnkcap & child_lnkcap & PCI_EXP_LNKCAP_ASPM_L1)
+ if (parent->lnkcap & child->lnkcap & PCI_EXP_LNKCAP_ASPM_L1)
link->aspm_support |= PCIE_LINK_STATE_L1;
if (parent_lnkctl & child_lnkctl & PCI_EXP_LNKCTL_ASPM_L1)
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index c83e75a0ec12..db4635b1ec47 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -1640,7 +1640,6 @@ void set_pcie_port_type(struct pci_dev *pdev)
{
int pos;
u16 reg16;
- u32 reg32;
int type;
struct pci_dev *parent;
@@ -1659,8 +1658,8 @@ void set_pcie_port_type(struct pci_dev *pdev)
pci_read_config_dword(pdev, pos + PCI_EXP_DEVCAP, &pdev->devcap);
pdev->pcie_mpss = FIELD_GET(PCI_EXP_DEVCAP_PAYLOAD, pdev->devcap);
- pcie_capability_read_dword(pdev, PCI_EXP_LNKCAP, ®32);
- if (reg32 & PCI_EXP_LNKCAP_DLLLARC)
+ pcie_capability_read_dword(pdev, PCI_EXP_LNKCAP, &pdev->lnkcap);
+ if (pdev->lnkcap & PCI_EXP_LNKCAP_DLLLARC)
pdev->link_active_reporting = 1;
parent = pci_upstream_bridge(pdev);
diff --git a/include/linux/pci.h b/include/linux/pci.h
index d1fdf81fbe1e..ec4133ae9cae 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -361,6 +361,7 @@ struct pci_dev {
struct pci_dev *rcec; /* Associated RCEC device */
#endif
u32 devcap; /* PCIe Device Capabilities */
+ u32 lnkcap; /* PCIe Link Capabilities */
u16 rebar_cap; /* Resizable BAR capability offset */
u8 pcie_cap; /* PCIe capability offset */
u8 msi_cap; /* MSI capability offset */
--
2.43.0
next prev parent reply other threads:[~2025-11-06 18:36 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-11-06 18:36 [PATCH 0/2] PCI/ASPM: Allow quirks to avoid L0s and L1 Bjorn Helgaas
2025-11-06 18:36 ` Bjorn Helgaas [this message]
2025-11-07 1:17 ` [PATCH 1/2] PCI/ASPM: Cache Link Capabilities so quirks can override them Shawn Lin
2025-11-07 6:03 ` Manivannan Sadhasivam
2025-11-07 6:16 ` Shawn Lin
2025-11-07 5:32 ` Lukas Wunner
2025-11-07 15:25 ` Bjorn Helgaas
2025-11-06 18:36 ` [PATCH 2/2] PCI/ASPM: Avoid L0s and L1 on Freescale Root Ports Bjorn Helgaas
2025-11-07 5:35 ` Lukas Wunner
2025-11-07 6:09 ` Manivannan Sadhasivam
2025-11-07 21:55 ` Bjorn Helgaas
2025-11-06 23:45 ` [PATCH 0/2] PCI/ASPM: Allow quirks to avoid L0s and L1 Bjorn Helgaas
2025-11-07 2:33 ` Hongxing Zhu
2025-11-07 5:40 ` Manivannan Sadhasivam
2025-11-07 6:33 ` Lukas Wunner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20251106183643.1963801-2-helgaas@kernel.org \
--to=helgaas@kernel.org \
--cc=al@datazap.net \
--cc=bhelgaas@google.com \
--cc=chzigotzky@xenosoft.de \
--cc=darren@stevens-zone.net \
--cc=debian-powerpc@lists.debian.org \
--cc=glaubitz@physik.fu-berlin.de \
--cc=hongxing.zhu@nxp.com \
--cc=hypexed@yahoo.com.au \
--cc=intermediadc@hotmail.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pci@vger.kernel.org \
--cc=linuxppc-dev@lists.ozlabs.org \
--cc=lukas@wunner.de \
--cc=madskateman@gmail.com \
--cc=mani@kernel.org \
--cc=rol7and@gmx.com \
--cc=rtd2@xtra.co.nz \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.