blob: f034169d6e304518c1011921c6face98ce6afd9e [file] [log] [blame] [edit]
From 87ee89223bdc38d50ba149e4eddba552513ee72a Mon Sep 17 00:00:00 2001
From: Sukumar Ghorai <sukumar.ghorai@intel.com>
Date: Fri, 18 Aug 2023 08:25:47 -0700
Subject: [PATCH] BACKPORT: FROMLIST: PCI/ASPM: Add back L1 PM Substate save
and restore
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Commit a7152be79b62 ("Revert "PCI/ASPM: Save L1 PM Substates Capability
for suspend/resume"") reverted saving and restoring of ASPM L1 Substates
due to a regression that caused resume from suspend to fail on certain
systems. However, we never added this capability back and this is now
causing systems fail to enter low power CPU states, drawing more power
from the battery.
The original revert mentioned that we restore L1 PM substate configuration
even though ASPM L1 may already be enabled. This is due the fact that
the pci_restore_aspm_l1ss_state() was called before
pci_restore_pcie_state().
Try to enable this functionality again by:
1) Moving the restore happen after PCIe capability is restored
following PCIe r6.0, sec 5.5.4.
2) Following the PCIe spec more closely to restore L1 PM substate
configuration (program enable bits last).
3) Adding denylist that skips the restoring on the ASUS system where
the original regression happened, just in case.
- Write L1 enables on the upstream component first then downstream
(this is taken care by the parent child order of the Linux PM).
- Program L1 SS before L1 enables
- Program L1 SS enables after rest of the fields in the capability
BUG=b:290138639
TEST='lspci -vvv | egrep '^[0-9a-f]|L1SubCtl1:|L1SubCap:'
And verify ASPM_L1.2+ ASPM_L1.1+ are enabled after suspend/resume.
UPSTREAM-TASK=b:297527919
(am from https://patchwork.kernel.org/patch/13294023)
Reported-by: Koba Ko <koba.ko@canonical.com>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=217321
Cc: Tasev Nikola <tasev.stefanoska@skynet.be>
Cc: Mark Enriquez <enriquezmark36@gmail.com>
Cc: Thomas Witt <kernel@witt.link>
Change-Id: Ib9965c3fe6cb43987dd2fdec3dc80bbcc102ad4e
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: Sukumar GhoraI <sukumar.ghorai@intel.com>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/kernel/+/4794589
Reviewed-by: Paz Zcharya <pazz@google.com>
Reviewed-by: Stanisław Kardach <skardach@google.com>
---
drivers/pci/pci.c | 18 ++++++-
drivers/pci/pci.h | 4 ++
drivers/pci/pcie/aspm.c | 109 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 129 insertions(+), 2 deletions(-)
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 99d12f02058de4476ef9dfbfb1efe6a7dde57155..d7156c0b2f9d8e0c0fce5925a7fe176178e74347 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -1692,7 +1692,7 @@ static void pci_restore_pcie_state(struct pci_dev *dev)
{
int i = 0;
struct pci_cap_saved_state *save_state;
- u16 *cap;
+ u16 *cap, val;
/*
* Restore max latencies (in the LTR capability) before enabling
@@ -1714,7 +1714,14 @@ static void pci_restore_pcie_state(struct pci_dev *dev)
cap = (u16 *)&save_state->cap.data[0];
pcie_capability_write_word(dev, PCI_EXP_DEVCTL, cap[i++]);
- pcie_capability_write_word(dev, PCI_EXP_LNKCTL, cap[i++]);
+ /*
+ * Restoring ASPM L1 substates has special requirements
+ * according to the PCIe spec 6.0. So we restore here only the
+ * LNKCTL register with the ASPM control field clear. ASPM will
+ * be restored in pci_restore_aspm_state().
+ */
+ val = cap[i++] & ~PCI_EXP_LNKCTL_ASPMC;
+ pcie_capability_write_word(dev, PCI_EXP_LNKCTL, val);
pcie_capability_write_word(dev, PCI_EXP_SLTCTL, cap[i++]);
pcie_capability_write_word(dev, PCI_EXP_RTCTL, cap[i++]);
pcie_capability_write_word(dev, PCI_EXP_DEVCTL2, cap[i++]);
@@ -1784,6 +1791,7 @@ int pci_save_state(struct pci_dev *dev)
pci_save_dpc_state(dev);
pci_save_aer_state(dev);
+ pci_save_aspm_state(dev);
pci_save_ptm_state(dev);
return pci_save_vc_state(dev);
}
@@ -1890,6 +1898,7 @@ void pci_restore_state(struct pci_dev *dev)
pci_restore_rebar_state(dev);
pci_restore_dpc_state(dev);
pci_restore_ptm_state(dev);
+ pci_restore_aspm_state(dev);
pci_aer_clear_status(dev);
pci_restore_aer_state(dev);
@@ -3477,6 +3486,11 @@ void pci_allocate_cap_save_buffers(struct pci_dev *dev)
if (error)
pci_err(dev, "unable to allocate suspend buffer for LTR\n");
+ error = pci_add_ext_cap_save_buffer(dev, PCI_EXT_CAP_ID_L1SS,
+ 2 * sizeof(u32));
+ if (error)
+ pci_err(dev, "unable to allocate suspend buffer for ASPM-L1SS\n");
+
pci_allocate_vc_save_buffers(dev);
}
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 64cfe5cca06b366c67c2a7f82c5821d7c84c8fa9..490a48b2d5ff967889d24f88fb57f229cd2eeac0 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -584,6 +584,8 @@ void pcie_aspm_pm_state_change(struct pci_dev *pdev, bool locked);
void pcie_aspm_powersave_config_link(struct pci_dev *pdev);
void pci_configure_ltr(struct pci_dev *pdev);
void pci_bridge_reconfigure_ltr(struct pci_dev *pdev);
+void pci_save_aspm_state(struct pci_dev *pdev);
+void pci_restore_aspm_state(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 +593,8 @@ static inline void pcie_aspm_pm_state_change(struct pci_dev *pdev, bool locked)
static inline void pcie_aspm_powersave_config_link(struct pci_dev *pdev) { }
static inline void pci_configure_ltr(struct pci_dev *pdev) { }
static inline void pci_bridge_reconfigure_ltr(struct pci_dev *pdev) { }
+static inline void pci_save_aspm_state(struct pci_dev *pdev) { }
+static inline void pci_restore_aspm_state(struct pci_dev *pdev) { }
#endif
#ifdef CONFIG_PCIE_ECRC
diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c
index cee2365e54b8b23d270f9e6fbd1ead201fda6abb..629388a359f393cbdd636e45c849e4809cc62901 100644
--- a/drivers/pci/pcie/aspm.c
+++ b/drivers/pci/pcie/aspm.c
@@ -10,6 +10,7 @@
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/build_bug.h>
+#include <linux/dmi.h>
#include <linux/kernel.h>
#include <linux/limits.h>
#include <linux/math.h>
@@ -899,6 +900,114 @@ static void pcie_config_aspm_l1ss(struct pcie_link_state *link, u32 state)
PCI_L1SS_CTL1_L1SS_MASK, val);
}
+void pci_save_aspm_state(struct pci_dev *pdev)
+{
+ struct pci_cap_saved_state *save_state;
+ u16 l1ss = pdev->l1ss;
+ u32 *cap;
+
+ /*
+ * Save L1 substate configuration. The ASPM L0s/L1 configuration
+ * is already saved in pci_save_pcie_state().
+ */
+ if (!l1ss)
+ return;
+
+ save_state = pci_find_saved_ext_cap(pdev, PCI_EXT_CAP_ID_L1SS);
+ if (!save_state)
+ return;
+
+ cap = (u32 *)&save_state->cap.data[0];
+ pci_read_config_dword(pdev, l1ss + PCI_L1SS_CTL2, cap++);
+ pci_read_config_dword(pdev, l1ss + PCI_L1SS_CTL1, cap++);
+}
+
+/*
+ * Do not restore L1 substates for the below systems even if BIOS has
+ * enabled it initially. This breaks resume from suspend otherwise on
+ * these.
+ */
+static const struct dmi_system_id aspm_l1ss_denylist[] = {
+ {
+ /* https://bugzilla.kernel.org/show_bug.cgi?id=216782 */
+ .ident = "ASUS UX305FA",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_BOARD_NAME, "UX305FA"),
+ },
+ },
+ { }
+};
+
+static void pcie_restore_aspm_l1ss(struct pci_dev *pdev)
+{
+ struct pci_cap_saved_state *save_state;
+ u32 *cap, ctl1, ctl2, l1_2_enable;
+ u16 l1ss = pdev->l1ss;
+
+ if (!l1ss)
+ return;
+
+ if (dmi_check_system(aspm_l1ss_denylist)) {
+ pci_dbg(pdev, "skipping restoring L1 substates on this system\n");
+ return;
+ }
+
+ save_state = pci_find_saved_ext_cap(pdev, PCI_EXT_CAP_ID_L1SS);
+ if (!save_state)
+ return;
+
+ cap = (u32 *)&save_state->cap.data[0];
+ ctl2 = *cap++;
+ ctl1 = *cap;
+
+ /*
+ * In addition, Common_Mode_Restore_Time and LTR_L1.2_THRESHOLD
+ * in PCI_L1SS_CTL1 must be programmed *before* setting the L1.2
+ * enable bits, even though they're all in PCI_L1SS_CTL1.
+ */
+ l1_2_enable = ctl1 & PCI_L1SS_CTL1_L1_2_MASK;
+ ctl1 &= ~PCI_L1SS_CTL1_L1_2_MASK;
+
+ /* Write back without enables first (above we cleared them in ctl1) */
+ pci_write_config_dword(pdev, l1ss + PCI_L1SS_CTL1, ctl1);
+ pci_write_config_dword(pdev, l1ss + PCI_L1SS_CTL2, ctl2);
+
+ /* Then write back the enables */
+ if (l1_2_enable)
+ pci_write_config_dword(pdev, l1ss + PCI_L1SS_CTL1,
+ ctl1 | l1_2_enable);
+}
+
+void pci_restore_aspm_state(struct pci_dev *pdev)
+{
+ struct pci_cap_saved_state *save_state;
+ u16 *cap, val, tmp;
+
+ save_state = pci_find_saved_cap(pdev, PCI_CAP_ID_EXP);
+ if (!save_state)
+ return;
+
+ cap = (u16 *)&save_state->cap.data[0];
+ /*
+ * Must match the ordering in pci_save/restore_pcie_state().
+ * This is PCI_EXP_LNKCTL.
+ */
+ val = cap[1] & PCI_EXP_LNKCTL_ASPMC;
+ if (!val)
+ return;
+
+ /*
+ * We restore L1 substate configuration first before enabling L1
+ * as the PCIe spec 6.0 sec 5.5.4 suggests.
+ * */
+ pcie_restore_aspm_l1ss(pdev);
+
+ pcie_capability_read_word(pdev, PCI_EXP_LNKCTL, &tmp);
+ /* Re-enable L0s/L1 */
+ pcie_capability_write_word(pdev, PCI_EXP_LNKCTL, tmp | val);
+}
+
static void pcie_config_aspm_dev(struct pci_dev *pdev, u32 val)
{
pcie_capability_clear_and_set_word(pdev, PCI_EXP_LNKCTL,
--
2.45.1.288.g0e0cd299f1-goog