PCI/ASPM: Use RMW accessors for changing LNKCTL

[ Upstream commit e09060b3b6 ]

Don't assume that the device is fully under the control of ASPM and use RMW
capability accessors which do proper locking to avoid losing concurrent
updates to the register values.

If configuration fails in pcie_aspm_configure_common_clock(), the
function attempts to restore the old PCI_EXP_LNKCTL_CCC settings. Store
only the old PCI_EXP_LNKCTL_CCC bit for the relevant devices rather
than the content of the whole LNKCTL registers. It aligns better with
how pcie_lnkctl_clear_and_set() expects its parameter and makes the
code more obvious to understand.

Suggested-by: Lukas Wunner <lukas@wunner.de>
Fixes: 2a42d9dba7 ("PCIe: ASPM: Break out of endless loop waiting for PCI config bits to switch")
Fixes: 7d715a6c1a ("PCI: add PCI Express ASPM support")
Link: https://lore.kernel.org/r/20230717120503.15276-5-ilpo.jarvinen@linux.intel.com
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Acked-by: "Rafael J. Wysocki" <rafael@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
Ilpo Järvinen 2023-07-17 15:04:56 +03:00 коммит произвёл Greg Kroah-Hartman
Родитель f46fa8ab8d
Коммит 66ef144dbd
1 изменённых файлов: 13 добавлений и 17 удалений

Просмотреть файл

@ -249,7 +249,7 @@ static int pcie_retrain_link(struct pcie_link_state *link)
static void pcie_aspm_configure_common_clock(struct pcie_link_state *link) static void pcie_aspm_configure_common_clock(struct pcie_link_state *link)
{ {
int same_clock = 1; int same_clock = 1;
u16 reg16, parent_reg, child_reg[8]; u16 reg16, ccc, parent_old_ccc, child_old_ccc[8];
struct pci_dev *child, *parent = link->pdev; struct pci_dev *child, *parent = link->pdev;
struct pci_bus *linkbus = parent->subordinate; struct pci_bus *linkbus = parent->subordinate;
/* /*
@ -271,6 +271,7 @@ static void pcie_aspm_configure_common_clock(struct pcie_link_state *link)
/* Port might be already in common clock mode */ /* Port might be already in common clock mode */
pcie_capability_read_word(parent, PCI_EXP_LNKCTL, &reg16); pcie_capability_read_word(parent, PCI_EXP_LNKCTL, &reg16);
parent_old_ccc = reg16 & PCI_EXP_LNKCTL_CCC;
if (same_clock && (reg16 & PCI_EXP_LNKCTL_CCC)) { if (same_clock && (reg16 & PCI_EXP_LNKCTL_CCC)) {
bool consistent = true; bool consistent = true;
@ -287,34 +288,29 @@ static void pcie_aspm_configure_common_clock(struct pcie_link_state *link)
pci_info(parent, "ASPM: current common clock configuration is inconsistent, reconfiguring\n"); pci_info(parent, "ASPM: current common clock configuration is inconsistent, reconfiguring\n");
} }
ccc = same_clock ? PCI_EXP_LNKCTL_CCC : 0;
/* Configure downstream component, all functions */ /* Configure downstream component, all functions */
list_for_each_entry(child, &linkbus->devices, bus_list) { list_for_each_entry(child, &linkbus->devices, bus_list) {
pcie_capability_read_word(child, PCI_EXP_LNKCTL, &reg16); pcie_capability_read_word(child, PCI_EXP_LNKCTL, &reg16);
child_reg[PCI_FUNC(child->devfn)] = reg16; child_old_ccc[PCI_FUNC(child->devfn)] = reg16 & PCI_EXP_LNKCTL_CCC;
if (same_clock) pcie_capability_clear_and_set_word(child, PCI_EXP_LNKCTL,
reg16 |= PCI_EXP_LNKCTL_CCC; PCI_EXP_LNKCTL_CCC, ccc);
else
reg16 &= ~PCI_EXP_LNKCTL_CCC;
pcie_capability_write_word(child, PCI_EXP_LNKCTL, reg16);
} }
/* Configure upstream component */ /* Configure upstream component */
pcie_capability_read_word(parent, PCI_EXP_LNKCTL, &reg16); pcie_capability_clear_and_set_word(parent, PCI_EXP_LNKCTL,
parent_reg = reg16; PCI_EXP_LNKCTL_CCC, ccc);
if (same_clock)
reg16 |= PCI_EXP_LNKCTL_CCC;
else
reg16 &= ~PCI_EXP_LNKCTL_CCC;
pcie_capability_write_word(parent, PCI_EXP_LNKCTL, reg16);
if (pcie_retrain_link(link)) { if (pcie_retrain_link(link)) {
/* Training failed. Restore common clock configurations */ /* Training failed. Restore common clock configurations */
pci_err(parent, "ASPM: Could not configure common clock\n"); pci_err(parent, "ASPM: Could not configure common clock\n");
list_for_each_entry(child, &linkbus->devices, bus_list) list_for_each_entry(child, &linkbus->devices, bus_list)
pcie_capability_write_word(child, PCI_EXP_LNKCTL, pcie_capability_clear_and_set_word(child, PCI_EXP_LNKCTL,
child_reg[PCI_FUNC(child->devfn)]); PCI_EXP_LNKCTL_CCC,
pcie_capability_write_word(parent, PCI_EXP_LNKCTL, parent_reg); child_old_ccc[PCI_FUNC(child->devfn)]);
pcie_capability_clear_and_set_word(parent, PCI_EXP_LNKCTL,
PCI_EXP_LNKCTL_CCC, parent_old_ccc);
} }
} }