aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2024-04-23 13:39:51 -0700
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2024-04-23 13:39:51 -0700
commit4e2b6cae1e6d3cebabe3071fa5f11e3d04c8b147 (patch)
tree5ff2374f37f69b34724f0d720afcb8aada1f9573
parent3f9c33333f6c3d4550b0b7dc607976bc416fa82d (diff)
downloadstable-queue-4e2b6cae1e6d3cebabe3071fa5f11e3d04c8b147.tar.gz
6.6-stable patches
added patches: pci-aspm-fix-deadlock-when-enabling-aspm.patch
-rw-r--r--queue-6.6/pci-aspm-fix-deadlock-when-enabling-aspm.patch388
-rw-r--r--queue-6.6/series1
2 files changed, 389 insertions, 0 deletions
diff --git a/queue-6.6/pci-aspm-fix-deadlock-when-enabling-aspm.patch b/queue-6.6/pci-aspm-fix-deadlock-when-enabling-aspm.patch
new file mode 100644
index 0000000000..a0434c097f
--- /dev/null
+++ b/queue-6.6/pci-aspm-fix-deadlock-when-enabling-aspm.patch
@@ -0,0 +1,388 @@
+From 1e560864159d002b453da42bd2c13a1805515a20 Mon Sep 17 00:00:00 2001
+From: Johan Hovold <johan+linaro@kernel.org>
+Date: Tue, 30 Jan 2024 11:02:43 +0100
+Subject: PCI/ASPM: Fix deadlock when enabling ASPM
+
+From: Johan Hovold <johan+linaro@kernel.org>
+
+commit 1e560864159d002b453da42bd2c13a1805515a20 upstream.
+
+A last minute revert in 6.7-final introduced a potential deadlock when
+enabling ASPM during probe of Qualcomm PCIe controllers as reported by
+lockdep:
+
+ ============================================
+ WARNING: possible recursive locking detected
+ 6.7.0 #40 Not tainted
+ --------------------------------------------
+ kworker/u16:5/90 is trying to acquire lock:
+ ffffacfa78ced000 (pci_bus_sem){++++}-{3:3}, at: pcie_aspm_pm_state_change+0x58/0xdc
+
+ but task is already holding lock:
+ ffffacfa78ced000 (pci_bus_sem){++++}-{3:3}, at: pci_walk_bus+0x34/0xbc
+
+ other info that might help us debug this:
+ Possible unsafe locking scenario:
+
+ CPU0
+ ----
+ lock(pci_bus_sem);
+ lock(pci_bus_sem);
+
+ *** DEADLOCK ***
+
+ Call trace:
+ print_deadlock_bug+0x25c/0x348
+ __lock_acquire+0x10a4/0x2064
+ lock_acquire+0x1e8/0x318
+ down_read+0x60/0x184
+ pcie_aspm_pm_state_change+0x58/0xdc
+ pci_set_full_power_state+0xa8/0x114
+ pci_set_power_state+0xc4/0x120
+ qcom_pcie_enable_aspm+0x1c/0x3c [pcie_qcom]
+ pci_walk_bus+0x64/0xbc
+ qcom_pcie_host_post_init_2_7_0+0x28/0x34 [pcie_qcom]
+
+The deadlock can easily be reproduced on machines like the Lenovo ThinkPad
+X13s by adding a delay to increase the race window during asynchronous
+probe where another thread can take a write lock.
+
+Add a new pci_set_power_state_locked() and associated helper functions that
+can be called with the PCI bus semaphore held to avoid taking the read lock
+twice.
+
+Link: https://lore.kernel.org/r/ZZu0qx2cmn7IwTyQ@hovoldconsulting.com
+Link: https://lore.kernel.org/r/20240130100243.11011-1-johan+linaro@kernel.org
+Fixes: f93e71aea6c6 ("Revert "PCI/ASPM: Remove pcie_aspm_pm_state_change()"")
+Signed-off-by: Johan Hovold <johan+linaro@kernel.org>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Cc: <stable@vger.kernel.org> # 6.7
+[bhelgaas: backported to v6.6.y, which contains 8cc22ba3f77c ("Revert
+ "PCI/ASPM: Remove pcie_aspm_pm_state_change()""), a backport of
+ f93e71aea6c6. This omits the drivers/pci/controller/dwc/pcie-qcom.c hunk
+ that updates qcom_pcie_enable_aspm(), which was added by 9f4f3dfad8cf
+ ("PCI: qcom: Enable ASPM for platforms supporting 1.9.0 ops"), which is not
+ present in v6.6.28.]
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/bus.c | 49 +++++++++++++++++++-----------
+ drivers/pci/pci.c | 78 ++++++++++++++++++++++++++++++++----------------
+ drivers/pci/pci.h | 4 +-
+ drivers/pci/pcie/aspm.c | 13 +++++---
+ include/linux/pci.h | 5 +++
+ 5 files changed, 100 insertions(+), 49 deletions(-)
+
+--- a/drivers/pci/bus.c
++++ b/drivers/pci/bus.c
+@@ -386,21 +386,8 @@ void pci_bus_add_devices(const struct pc
+ }
+ EXPORT_SYMBOL(pci_bus_add_devices);
+
+-/** pci_walk_bus - walk devices on/under bus, calling callback.
+- * @top bus whose devices should be walked
+- * @cb callback to be called for each device found
+- * @userdata arbitrary pointer to be passed to callback.
+- *
+- * Walk the given bus, including any bridged devices
+- * on buses under this bus. Call the provided callback
+- * on each device found.
+- *
+- * We check the return of @cb each time. If it returns anything
+- * other than 0, we break out.
+- *
+- */
+-void pci_walk_bus(struct pci_bus *top, int (*cb)(struct pci_dev *, void *),
+- void *userdata)
++static void __pci_walk_bus(struct pci_bus *top, int (*cb)(struct pci_dev *, void *),
++ void *userdata, bool locked)
+ {
+ struct pci_dev *dev;
+ struct pci_bus *bus;
+@@ -408,7 +395,8 @@ void pci_walk_bus(struct pci_bus *top, i
+ int retval;
+
+ bus = top;
+- down_read(&pci_bus_sem);
++ if (!locked)
++ down_read(&pci_bus_sem);
+ next = top->devices.next;
+ for (;;) {
+ if (next == &bus->devices) {
+@@ -431,10 +419,37 @@ void pci_walk_bus(struct pci_bus *top, i
+ if (retval)
+ break;
+ }
+- up_read(&pci_bus_sem);
++ if (!locked)
++ up_read(&pci_bus_sem);
++}
++
++/**
++ * pci_walk_bus - walk devices on/under bus, calling callback.
++ * @top: bus whose devices should be walked
++ * @cb: callback to be called for each device found
++ * @userdata: arbitrary pointer to be passed to callback
++ *
++ * Walk the given bus, including any bridged devices
++ * on buses under this bus. Call the provided callback
++ * on each device found.
++ *
++ * We check the return of @cb each time. If it returns anything
++ * other than 0, we break out.
++ */
++void pci_walk_bus(struct pci_bus *top, int (*cb)(struct pci_dev *, void *), void *userdata)
++{
++ __pci_walk_bus(top, cb, userdata, false);
+ }
+ EXPORT_SYMBOL_GPL(pci_walk_bus);
+
++void pci_walk_bus_locked(struct pci_bus *top, int (*cb)(struct pci_dev *, void *), void *userdata)
++{
++ lockdep_assert_held(&pci_bus_sem);
++
++ __pci_walk_bus(top, cb, userdata, true);
++}
++EXPORT_SYMBOL_GPL(pci_walk_bus_locked);
++
+ struct pci_bus *pci_bus_get(struct pci_bus *bus)
+ {
+ if (bus)
+--- a/drivers/pci/pci.c
++++ b/drivers/pci/pci.c
+@@ -1291,6 +1291,7 @@ end:
+ /**
+ * pci_set_full_power_state - Put a PCI device into D0 and update its state
+ * @dev: PCI device to power up
++ * @locked: whether pci_bus_sem is held
+ *
+ * Call pci_power_up() to put @dev into D0, read from its PCI_PM_CTRL register
+ * to confirm the state change, restore its BARs if they might be lost and
+@@ -1300,7 +1301,7 @@ end:
+ * to D0, it is more efficient to use pci_power_up() directly instead of this
+ * function.
+ */
+-static int pci_set_full_power_state(struct pci_dev *dev)
++static int pci_set_full_power_state(struct pci_dev *dev, bool locked)
+ {
+ u16 pmcsr;
+ int ret;
+@@ -1336,7 +1337,7 @@ static int pci_set_full_power_state(stru
+ }
+
+ if (dev->bus->self)
+- pcie_aspm_pm_state_change(dev->bus->self);
++ pcie_aspm_pm_state_change(dev->bus->self, locked);
+
+ return 0;
+ }
+@@ -1365,10 +1366,22 @@ void pci_bus_set_current_state(struct pc
+ pci_walk_bus(bus, __pci_dev_set_current_state, &state);
+ }
+
++static void __pci_bus_set_current_state(struct pci_bus *bus, pci_power_t state, bool locked)
++{
++ if (!bus)
++ return;
++
++ if (locked)
++ pci_walk_bus_locked(bus, __pci_dev_set_current_state, &state);
++ else
++ pci_walk_bus(bus, __pci_dev_set_current_state, &state);
++}
++
+ /**
+ * pci_set_low_power_state - Put a PCI device into a low-power state.
+ * @dev: PCI device to handle.
+ * @state: PCI power state (D1, D2, D3hot) to put the device into.
++ * @locked: whether pci_bus_sem is held
+ *
+ * Use the device's PCI_PM_CTRL register to put it into a low-power state.
+ *
+@@ -1379,7 +1392,7 @@ void pci_bus_set_current_state(struct pc
+ * 0 if device already is in the requested state.
+ * 0 if device's power state has been successfully changed.
+ */
+-static int pci_set_low_power_state(struct pci_dev *dev, pci_power_t state)
++static int pci_set_low_power_state(struct pci_dev *dev, pci_power_t state, bool locked)
+ {
+ u16 pmcsr;
+
+@@ -1433,29 +1446,12 @@ static int pci_set_low_power_state(struc
+ pci_power_name(state));
+
+ if (dev->bus->self)
+- pcie_aspm_pm_state_change(dev->bus->self);
++ pcie_aspm_pm_state_change(dev->bus->self, locked);
+
+ return 0;
+ }
+
+-/**
+- * pci_set_power_state - Set the power state of a PCI device
+- * @dev: PCI device to handle.
+- * @state: PCI power state (D0, D1, D2, D3hot) to put the device into.
+- *
+- * Transition a device to a new power state, using the platform firmware and/or
+- * the device's PCI PM registers.
+- *
+- * RETURN VALUE:
+- * -EINVAL if the requested state is invalid.
+- * -EIO if device does not support PCI PM or its PM capabilities register has a
+- * wrong version, or device doesn't support the requested state.
+- * 0 if the transition is to D1 or D2 but D1 and D2 are not supported.
+- * 0 if device already is in the requested state.
+- * 0 if the transition is to D3 but D3 is not supported.
+- * 0 if device's power state has been successfully changed.
+- */
+-int pci_set_power_state(struct pci_dev *dev, pci_power_t state)
++static int __pci_set_power_state(struct pci_dev *dev, pci_power_t state, bool locked)
+ {
+ int error;
+
+@@ -1479,7 +1475,7 @@ int pci_set_power_state(struct pci_dev *
+ return 0;
+
+ if (state == PCI_D0)
+- return pci_set_full_power_state(dev);
++ return pci_set_full_power_state(dev, locked);
+
+ /*
+ * This device is quirked not to be put into D3, so don't put it in
+@@ -1493,16 +1489,16 @@ int pci_set_power_state(struct pci_dev *
+ * To put the device in D3cold, put it into D3hot in the native
+ * way, then put it into D3cold using platform ops.
+ */
+- error = pci_set_low_power_state(dev, PCI_D3hot);
++ error = pci_set_low_power_state(dev, PCI_D3hot, locked);
+
+ if (pci_platform_power_transition(dev, PCI_D3cold))
+ return error;
+
+ /* Powering off a bridge may power off the whole hierarchy */
+ if (dev->current_state == PCI_D3cold)
+- pci_bus_set_current_state(dev->subordinate, PCI_D3cold);
++ __pci_bus_set_current_state(dev->subordinate, PCI_D3cold, locked);
+ } else {
+- error = pci_set_low_power_state(dev, state);
++ error = pci_set_low_power_state(dev, state, locked);
+
+ if (pci_platform_power_transition(dev, state))
+ return error;
+@@ -1510,8 +1506,38 @@ int pci_set_power_state(struct pci_dev *
+
+ return 0;
+ }
++
++/**
++ * pci_set_power_state - Set the power state of a PCI device
++ * @dev: PCI device to handle.
++ * @state: PCI power state (D0, D1, D2, D3hot) to put the device into.
++ *
++ * Transition a device to a new power state, using the platform firmware and/or
++ * the device's PCI PM registers.
++ *
++ * RETURN VALUE:
++ * -EINVAL if the requested state is invalid.
++ * -EIO if device does not support PCI PM or its PM capabilities register has a
++ * wrong version, or device doesn't support the requested state.
++ * 0 if the transition is to D1 or D2 but D1 and D2 are not supported.
++ * 0 if device already is in the requested state.
++ * 0 if the transition is to D3 but D3 is not supported.
++ * 0 if device's power state has been successfully changed.
++ */
++int pci_set_power_state(struct pci_dev *dev, pci_power_t state)
++{
++ return __pci_set_power_state(dev, state, false);
++}
+ EXPORT_SYMBOL(pci_set_power_state);
+
++int pci_set_power_state_locked(struct pci_dev *dev, pci_power_t state)
++{
++ lockdep_assert_held(&pci_bus_sem);
++
++ return __pci_set_power_state(dev, state, true);
++}
++EXPORT_SYMBOL(pci_set_power_state_locked);
++
+ #define PCI_EXP_SAVE_REGS 7
+
+ static struct pci_cap_saved_state *_pci_find_saved_cap(struct pci_dev *pci_dev,
+--- a/drivers/pci/pci.h
++++ b/drivers/pci/pci.h
+@@ -561,12 +561,12 @@ int pcie_retrain_link(struct pci_dev *pd
+ #ifdef CONFIG_PCIEASPM
+ void pcie_aspm_init_link_state(struct pci_dev *pdev);
+ void pcie_aspm_exit_link_state(struct pci_dev *pdev);
+-void pcie_aspm_pm_state_change(struct pci_dev *pdev);
++void pcie_aspm_pm_state_change(struct pci_dev *pdev, bool locked);
+ void pcie_aspm_powersave_config_link(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_pm_state_change(struct pci_dev *pdev) { }
++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) { }
+ #endif
+
+--- a/drivers/pci/pcie/aspm.c
++++ b/drivers/pci/pcie/aspm.c
+@@ -1001,8 +1001,11 @@ void pcie_aspm_exit_link_state(struct pc
+ up_read(&pci_bus_sem);
+ }
+
+-/* @pdev: the root port or switch downstream port */
+-void pcie_aspm_pm_state_change(struct pci_dev *pdev)
++/*
++ * @pdev: the root port or switch downstream port
++ * @locked: whether pci_bus_sem is held
++ */
++void pcie_aspm_pm_state_change(struct pci_dev *pdev, bool locked)
+ {
+ struct pcie_link_state *link = pdev->link_state;
+
+@@ -1012,12 +1015,14 @@ void pcie_aspm_pm_state_change(struct pc
+ * Devices changed PM state, we should recheck if latency
+ * meets all functions' requirement
+ */
+- down_read(&pci_bus_sem);
++ if (!locked)
++ down_read(&pci_bus_sem);
+ mutex_lock(&aspm_lock);
+ pcie_update_aspm_capable(link->root);
+ pcie_config_aspm_path(link);
+ mutex_unlock(&aspm_lock);
+- up_read(&pci_bus_sem);
++ if (!locked)
++ up_read(&pci_bus_sem);
+ }
+
+ void pcie_aspm_powersave_config_link(struct pci_dev *pdev)
+--- a/include/linux/pci.h
++++ b/include/linux/pci.h
+@@ -1391,6 +1391,7 @@ int pci_load_and_free_saved_state(struct
+ struct pci_saved_state **state);
+ int pci_platform_power_transition(struct pci_dev *dev, pci_power_t state);
+ int pci_set_power_state(struct pci_dev *dev, pci_power_t state);
++int pci_set_power_state_locked(struct pci_dev *dev, pci_power_t state);
+ pci_power_t pci_choose_state(struct pci_dev *dev, pm_message_t state);
+ bool pci_pme_capable(struct pci_dev *dev, pci_power_t state);
+ void pci_pme_active(struct pci_dev *dev, bool enable);
+@@ -1594,6 +1595,8 @@ int pci_scan_bridge(struct pci_bus *bus,
+
+ void pci_walk_bus(struct pci_bus *top, int (*cb)(struct pci_dev *, void *),
+ void *userdata);
++void pci_walk_bus_locked(struct pci_bus *top, int (*cb)(struct pci_dev *, void *),
++ void *userdata);
+ int pci_cfg_space_size(struct pci_dev *dev);
+ unsigned char pci_bus_max_busnr(struct pci_bus *bus);
+ void pci_setup_bridge(struct pci_bus *bus);
+@@ -1990,6 +1993,8 @@ static inline int pci_save_state(struct
+ static inline void pci_restore_state(struct pci_dev *dev) { }
+ static inline int pci_set_power_state(struct pci_dev *dev, pci_power_t state)
+ { return 0; }
++static inline int pci_set_power_state_locked(struct pci_dev *dev, pci_power_t state)
++{ return 0; }
+ static inline int pci_wake_from_d3(struct pci_dev *dev, bool enable)
+ { return 0; }
+ static inline pci_power_t pci_choose_state(struct pci_dev *dev,
diff --git a/queue-6.6/series b/queue-6.6/series
index ceac98733c..3d5b9d1b9c 100644
--- a/queue-6.6/series
+++ b/queue-6.6/series
@@ -155,3 +155,4 @@ ksmbd-fix-slab-out-of-bounds-in-smb2_allocate_rsp_buf.patch
ksmbd-validate-request-buffer-size-in-smb2_allocate_rsp_buf.patch
ksmbd-clear-rename_noreplace-before-calling-vfs_rename.patch
ksmbd-common-use-struct_group_attr-instead-of-struct_group-for-network_open_info.patch
+pci-aspm-fix-deadlock-when-enabling-aspm.patch