diff options
| author | Bjorn Helgaas <bhelgaas@google.com> | 2026-06-23 17:32:01 -0500 |
|---|---|---|
| committer | Bjorn Helgaas <bhelgaas@google.com> | 2026-06-23 17:32:01 -0500 |
| commit | 3a81c660546352577a35280898d9aa20d8a91e3d (patch) | |
| tree | 371ceaf017cedc7749dcc0ee78e7a32580f63311 | |
| parent | 5ea9159447890425c37455e14f47eb243fcb3dd5 (diff) | |
| parent | 748a927e081bffc9a9be17b4d96088515e1c5f01 (diff) | |
| download | linux-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.c | 3 | ||||
| -rw-r--r-- | drivers/pci/controller/dwc/pcie-qcom.c | 13 | ||||
| -rw-r--r-- | drivers/pci/iov.c | 6 | ||||
| -rw-r--r-- | drivers/pci/pci-driver.c | 10 | ||||
| -rw-r--r-- | drivers/pci/pci.c | 35 | ||||
| -rw-r--r-- | drivers/pci/rebar.c | 6 | ||||
| -rw-r--r-- | include/linux/pci.h | 9 |
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 */ |
