PCI/ASPM: Update save_state when configuration changes

Many PCIe device drivers save the configuration state of their device
during probe and restore it when their .slot_reset() hook is called during
PCIe error recovery.

If the ASPM configuration is changed after the driver's probe is called and
before an error event occurs, .slot_reset() restores the ASPM configuration
to what it was at the time of probe, not to what it was just before the
occurrence of the error event.  This leads to a mismatch in ASPM
configuration between the device and its upstream device.

Update the saved configuration of the device when the ASPM configuration
changes.

Link: https://lore.kernel.org/r/20240222174436.3565146-1-vidyas@nvidia.com
Signed-off-by: Vidya Sagar <vidyas@nvidia.com>
[bhelgaas: commit log, rebase to pci/aspm, rename to
pci_update_aspm_saved_state() since it updates only LNKCTL, update only
ASPMC and CLKREQ_EN in LNKCTL]
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
Reviewed-by: David E. Box <david.e.box@linux.intel.com>
This commit is contained in:
Vidya Sagar 2024-02-23 13:36:24 -06:00 committed by Bjorn Helgaas
parent 64dbb2d707
commit 6d42666752

View File

@ -301,16 +301,42 @@ static int policy_to_clkpm_state(struct pcie_link_state *link)
return 0;
}
static void pci_update_aspm_saved_state(struct pci_dev *dev)
{
struct pci_cap_saved_state *save_state;
u16 *cap, lnkctl, aspm_ctl;
save_state = pci_find_saved_cap(dev, PCI_CAP_ID_EXP);
if (!save_state)
return;
pcie_capability_read_word(dev, PCI_EXP_LNKCTL, &lnkctl);
/*
* Update ASPM and CLKREQ bits of LNKCTL in save_state. We only
* write PCI_EXP_LNKCTL_CCC during enumeration, so it shouldn't
* change after being captured in save_state.
*/
aspm_ctl = lnkctl & (PCI_EXP_LNKCTL_ASPMC | PCI_EXP_LNKCTL_CLKREQ_EN);
lnkctl &= ~(PCI_EXP_LNKCTL_ASPMC | PCI_EXP_LNKCTL_CLKREQ_EN);
/* Depends on pci_save_pcie_state(): cap[1] is LNKCTL */
cap = (u16 *)&save_state->cap.data[0];
cap[1] = lnkctl | aspm_ctl;
}
static void pcie_set_clkpm_nocheck(struct pcie_link_state *link, int enable)
{
struct pci_dev *child;
struct pci_bus *linkbus = link->pdev->subordinate;
u32 val = enable ? PCI_EXP_LNKCTL_CLKREQ_EN : 0;
list_for_each_entry(child, &linkbus->devices, bus_list)
list_for_each_entry(child, &linkbus->devices, bus_list) {
pcie_capability_clear_and_set_word(child, PCI_EXP_LNKCTL,
PCI_EXP_LNKCTL_CLKREQ_EN,
val);
pci_update_aspm_saved_state(child);
}
link->clkpm_enabled = !!enable;
}
@ -929,6 +955,12 @@ static void pcie_config_aspm_link(struct pcie_link_state *link, u32 state)
pcie_config_aspm_dev(parent, upstream);
link->aspm_enabled = state;
/* Update latest ASPM configuration in saved context */
pci_save_aspm_l1ss_state(link->downstream);
pci_update_aspm_saved_state(link->downstream);
pci_save_aspm_l1ss_state(parent);
pci_update_aspm_saved_state(parent);
}
static void pcie_config_aspm_path(struct pcie_link_state *link)