| From f4349ee3d05bbcec3c2327fc9baa3b4b35d47ab8 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 9b1fb7e13be4b71479a969e9265f209622850bee..06cb8067ac7f185f47a32b44e1d022372d2d9a4d 100644 |
| --- a/drivers/pci/pci.c |
| +++ b/drivers/pci/pci.c |
| @@ -1580,7 +1580,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; |
| |
| save_state = pci_find_saved_cap(dev, PCI_CAP_ID_EXP); |
| if (!save_state) |
| @@ -1595,7 +1595,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++]); |
| @@ -1706,6 +1713,7 @@ int pci_save_state(struct pci_dev *dev) |
| pci_save_ltr_state(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); |
| } |
| @@ -1818,6 +1826,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); |
| @@ -3510,6 +3519,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 f1dde81503b53533261786acf64f4f7f7451621f..e5fb2309b6cad01ae726b25b96d0ab101a3c50b5 100644 |
| --- a/drivers/pci/pci.h |
| +++ b/drivers/pci/pci.h |
| @@ -570,10 +570,14 @@ int pcie_retrain_link(struct pci_dev *pdev, bool use_lt); |
| void pcie_aspm_init_link_state(struct pci_dev *pdev); |
| void pcie_aspm_exit_link_state(struct pci_dev *pdev); |
| void pcie_aspm_powersave_config_link(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) { } |
| static inline void pcie_aspm_powersave_config_link(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 50b04ae5c394e3691d3b100eeed3918cd7be9f4c..28eff3c02dcd2eea3c35aeffb2bbeba43fad2342 100644 |
| --- a/drivers/pci/pcie/aspm.c |
| +++ b/drivers/pci/pcie/aspm.c |
| @@ -7,6 +7,7 @@ |
| * Copyright (C) Shaohua Li (shaohua.li@intel.com) |
| */ |
| |
| +#include <linux/dmi.h> |
| #include <linux/bitfield.h> |
| #include <linux/kernel.h> |
| #include <linux/limits.h> |
| @@ -719,6 +720,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.43.0.rc2.451.g8631bc7472-goog |
| |