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
This commit is contained in:
Bjorn Helgaas 2026-06-23 17:32:01 -05:00
commit 3a81c66054
7 changed files with 73 additions and 9 deletions

View File

@ -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);

View File

@ -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);
}

View File

@ -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;

View File

@ -512,13 +512,6 @@ static void pci_device_remove(struct device *dev)
/* Undo the runtime PM settings in local_pci_probe() */
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
@ -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);
/*

View File

@ -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
{

View File

@ -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));

View File

@ -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 */