summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjorn Helgaas <bhelgaas@google.com>2026-06-23 17:32:01 -0500
committerBjorn Helgaas <bhelgaas@google.com>2026-06-23 17:32:01 -0500
commit3a81c660546352577a35280898d9aa20d8a91e3d (patch)
tree371ceaf017cedc7749dcc0ee78e7a32580f63311
parent5ea9159447890425c37455e14f47eb243fcb3dd5 (diff)
parent748a927e081bffc9a9be17b4d96088515e1c5f01 (diff)
downloadlinux-next-3a81c660546352577a35280898d9aa20d8a91e3d.tar.gz
linux-next-3a81c660546352577a35280898d9aa20d8a91e3d.zip
Merge branch 'pci/pm'
- Set power state to 'unknown' for all devices, not just those with drivers, during suspend (Lukas Wunner) - Skip restoring Resizable BARs and VF Resizable BARs if device doesn't respond to config reads, to avoid invalid array accesses (Marco Nenciarini) - Add pci_suspend_retains_context() so drivers can tell whether devices may be reset while resuming from suspend due to platform issues; use this in nvme to avoid issues on Qcom RCs (Manivannan Sadhasivam) * pci/pm: nvme-pci: Use pci_suspend_retains_context() during suspend PCI: qcom: Indicate broken L1SS exit during resume from system suspend PCI: Indicate context lost if L1SS exit is broken during resume from system suspend PCI: Add pci_suspend_retains_context() to check if device state is preserved during suspend PCI/IOV: Skip VF Resizable BAR restore on read error PCI: Skip Resizable BAR restore on read error PCI: Stop setting cached power state to 'unknown' on unbind
-rw-r--r--drivers/nvme/host/pci.c3
-rw-r--r--drivers/pci/controller/dwc/pcie-qcom.c13
-rw-r--r--drivers/pci/iov.c6
-rw-r--r--drivers/pci/pci-driver.c10
-rw-r--r--drivers/pci/pci.c35
-rw-r--r--drivers/pci/rebar.c6
-rw-r--r--include/linux/pci.h9
7 files changed, 73 insertions, 9 deletions
diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c
index db5fc9bf6627..a6664983ce5d 100644
--- a/drivers/nvme/host/pci.c
+++ b/drivers/nvme/host/pci.c
@@ -3915,6 +3915,7 @@ static int nvme_suspend(struct device *dev)
* use host managed nvme power settings for lowest idle power if
* possible. This should have quicker resume latency than a full device
* shutdown. But if the firmware is involved after the suspend or the
+ * platform has any limitation in waking from low power states or the
* device does not support any non-default power states, shut down the
* device fully.
*
@@ -3923,7 +3924,7 @@ static int nvme_suspend(struct device *dev)
* down, so as to allow the platform to achieve its minimum low-power
* state (which may not be possible if the link is up).
*/
- if (pm_suspend_via_firmware() || !ctrl->npss ||
+ if (!pci_suspend_retains_context(pdev) || !ctrl->npss ||
!pcie_aspm_enabled(pdev) ||
(ndev->ctrl.quirks & NVME_QUIRK_SIMPLE_SUSPEND))
return nvme_disable_prepare_reset(ndev, true);
diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index af6bf5cce65b..a0b59b4ef1d2 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -1368,6 +1368,19 @@ static void qcom_pcie_host_post_init(struct dw_pcie_rp *pp)
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
struct qcom_pcie *pcie = to_qcom_pcie(pci);
+ /*
+ * During system suspend, the Qcom RC driver may turn off the
+ * analog circuitry of PHY and remove controller votes to save
+ * power. If the link is in L1SS and the endpoint asserts CLKREQ#
+ * to exit L1SS, the time required to wake the system and restore
+ * the PHY/REFCLK may exceed the L1SS exit timing (L10_REFCLK_ON +
+ * T_COMMONMODE), resulting in Link Down (LDn) and a reset of the
+ * endpoint. Set this flag to indicate this limitation to client
+ * drivers so that they can avoid relying on device state being
+ * preserved during system suspend.
+ */
+ pp->bridge->broken_l1ss_resume = true;
+
if (pcie->cfg->ops->host_post_init)
pcie->cfg->ops->host_post_init(pcie);
}
diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c
index 91ac4e37ecb9..08df9bace13d 100644
--- a/drivers/pci/iov.c
+++ b/drivers/pci/iov.c
@@ -938,12 +938,18 @@ static void sriov_restore_vf_rebar_state(struct pci_dev *dev)
return;
pci_read_config_dword(dev, pos + PCI_VF_REBAR_CTRL, &ctrl);
+ if (PCI_POSSIBLE_ERROR(ctrl))
+ return;
+
nbars = FIELD_GET(PCI_VF_REBAR_CTRL_NBAR_MASK, ctrl);
for (i = 0; i < nbars; i++, pos += 8) {
int bar_idx, size;
pci_read_config_dword(dev, pos + PCI_VF_REBAR_CTRL, &ctrl);
+ if (PCI_POSSIBLE_ERROR(ctrl))
+ return;
+
bar_idx = FIELD_GET(PCI_VF_REBAR_CTRL_BAR_IDX, ctrl);
size = pci_rebar_bytes_to_size(dev->sriov->barsz[bar_idx]);
ctrl &= ~PCI_VF_REBAR_CTRL_BAR_SIZE;
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index d10ece0889f0..2bfefd8db526 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -513,13 +513,6 @@ static void pci_device_remove(struct device *dev)
pm_runtime_put_sync(dev);
/*
- * If the device is still on, set the power state as "unknown",
- * since it might change by the next time we load the driver.
- */
- if (pci_dev->current_state == PCI_D0)
- pci_dev->current_state = PCI_UNKNOWN;
-
- /*
* We would love to complain here if pci_dev->is_enabled is set, that
* the driver should have called pci_disable_device(), but the
* unfortunate fact is there are too many odd BIOS and bridge setups
@@ -893,7 +886,7 @@ static int pci_pm_suspend_noirq(struct device *dev)
if (!pm) {
pci_save_state(pci_dev);
- goto Fixup;
+ goto set_unknown;
}
if (pm->suspend_noirq) {
@@ -945,6 +938,7 @@ static int pci_pm_suspend_noirq(struct device *dev)
goto Fixup;
}
+set_unknown:
pci_pm_set_unknown_state(pci_dev);
/*
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index ebbb5ec0a9df..8ec24562c94c 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -33,6 +33,7 @@
#include <asm/dma.h>
#include <linux/aer.h>
#include <linux/bitfield.h>
+#include <linux/suspend.h>
#include "pci.h"
DEFINE_MUTEX(pci_slot_mutex);
@@ -2886,6 +2887,40 @@ void pci_config_pm_runtime_put(struct pci_dev *pdev)
pm_runtime_put_sync(parent);
}
+/**
+ * pci_suspend_retains_context - Check if the platform can retain the device
+ * context during system suspend
+ * @pdev: PCI device to check
+ *
+ * Return: true if the platform can guarantee to retain the device context,
+ * false otherwise.
+ */
+bool pci_suspend_retains_context(struct pci_dev *pdev)
+{
+ struct pci_host_bridge *bridge = pci_find_host_bridge(pdev->bus);
+
+ /*
+ * If the platform firmware (like ACPI) is involved at the end of
+ * system suspend, device context may not be retained.
+ */
+ if (pm_suspend_via_firmware())
+ return false;
+
+ /*
+ * Some host bridges power off the PHY to enter deep low-power
+ * modes during system suspend. Exiting L1SS from this condition
+ * may violate timing requirements and result in Link Down (LDn),
+ * which causes a reset of the device. On such platforms, the
+ * endpoint must be prepared for context loss.
+ */
+ if (bridge && bridge->broken_l1ss_resume)
+ return false;
+
+ /* Assume that the context is retained by default */
+ return true;
+}
+EXPORT_SYMBOL_GPL(pci_suspend_retains_context);
+
static const struct dmi_system_id bridge_d3_blacklist[] = {
#ifdef CONFIG_X86
{
diff --git a/drivers/pci/rebar.c b/drivers/pci/rebar.c
index 39f8cf3b70d5..11965947c4cb 100644
--- a/drivers/pci/rebar.c
+++ b/drivers/pci/rebar.c
@@ -231,6 +231,9 @@ void pci_restore_rebar_state(struct pci_dev *pdev)
return;
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
+ if (PCI_POSSIBLE_ERROR(ctrl))
+ return;
+
nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl);
for (i = 0; i < nbars; i++, pos += 8) {
@@ -238,6 +241,9 @@ void pci_restore_rebar_state(struct pci_dev *pdev)
int bar_idx, size;
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
+ if (PCI_POSSIBLE_ERROR(ctrl))
+ return;
+
bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX;
res = pci_resource_n(pdev, bar_idx);
size = pci_rebar_bytes_to_size(resource_size(res));
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 1e6802017d6b..78e44bdb4aff 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -660,6 +660,8 @@ struct pci_host_bridge {
unsigned int preserve_config:1; /* Preserve FW resource setup */
unsigned int size_windows:1; /* Enable root bus sizing */
unsigned int msi_domain:1; /* Bridge wants MSI domain */
+ unsigned int broken_l1ss_resume:1; /* Resuming from L1SS during
+ system suspend is broken */
/* Resource alignment requirements */
resource_size_t (*align_resource)(struct pci_dev *dev,
@@ -2086,6 +2088,8 @@ pci_release_mem_regions(struct pci_dev *pdev)
pci_select_bars(pdev, IORESOURCE_MEM));
}
+bool pci_suspend_retains_context(struct pci_dev *pdev);
+
#else /* CONFIG_PCI is not enabled */
static inline void pci_set_flags(int flags) { }
@@ -2244,6 +2248,11 @@ pci_alloc_irq_vectors(struct pci_dev *dev, unsigned int min_vecs,
static inline void pci_free_irq_vectors(struct pci_dev *dev)
{
}
+
+static inline bool pci_suspend_retains_context(struct pci_dev *pdev)
+{
+ return true;
+}
#endif /* CONFIG_PCI */
/* Include architecture-dependent settings and functions */