Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

PCI: PM: Skip devices in D0 for suspend-to-idle

Commit d491f2b75237 ("PCI: PM: Avoid possible suspend-to-idle issue")
attempted to avoid a problem with devices whose drivers want them to
stay in D0 over suspend-to-idle and resume, but it did not go as far
as it should with that.

Namely, first of all, the power state of a PCI bridge with a
downstream device in D0 must be D0 (based on the PCI PM spec r1.2,
sec 6, table 6-1, if the bridge is not in D0, there can be no PCI
transactions on its secondary bus), but that is not actively enforced
during system-wide PM transitions, so use the skip_bus_pm flag
introduced by commit d491f2b75237 for that.

Second, the configuration of devices left in D0 (whatever the reason)
during suspend-to-idle need not be changed and attempting to put them
into D0 again by force is pointless, so explicitly avoid doing that.

Fixes: d491f2b75237 ("PCI: PM: Avoid possible suspend-to-idle issue")
Reported-by: Kai-Heng Feng <kai.heng.feng@canonical.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Tested-by: Kai-Heng Feng <kai.heng.feng@canonical.com>

+35 -12
+35 -12
drivers/pci/pci-driver.c
··· 524 524 pci_power_up(pci_dev); 525 525 pci_restore_state(pci_dev); 526 526 pci_pme_restore(pci_dev); 527 - pci_fixup_device(pci_fixup_resume_early, pci_dev); 528 527 } 529 528 530 529 /* ··· 830 831 831 832 if (pci_dev->skip_bus_pm) { 832 833 /* 833 - * The function is running for the second time in a row without 834 + * Either the device is a bridge with a child in D0 below it, or 835 + * the function is running for the second time in a row without 834 836 * going through full resume, which is possible only during 835 - * suspend-to-idle in a spurious wakeup case. Moreover, the 836 - * device was originally left in D0, so its power state should 837 - * not be changed here and the device register values saved 838 - * originally should be restored on resume again. 837 + * suspend-to-idle in a spurious wakeup case. The device should 838 + * be in D0 at this point, but if it is a bridge, it may be 839 + * necessary to save its state. 839 840 */ 840 - pci_dev->state_saved = true; 841 - } else if (pci_dev->state_saved) { 842 - if (pci_dev->current_state == PCI_D0) 843 - pci_dev->skip_bus_pm = true; 844 - } else { 841 + if (!pci_dev->state_saved) 842 + pci_save_state(pci_dev); 843 + } else if (!pci_dev->state_saved) { 845 844 pci_save_state(pci_dev); 846 845 if (pci_power_manageable(pci_dev)) 847 846 pci_prepare_to_sleep(pci_dev); ··· 847 850 848 851 dev_dbg(dev, "PCI PM: Suspend power state: %s\n", 849 852 pci_power_name(pci_dev->current_state)); 853 + 854 + if (pci_dev->current_state == PCI_D0) { 855 + pci_dev->skip_bus_pm = true; 856 + /* 857 + * Per PCI PM r1.2, table 6-1, a bridge must be in D0 if any 858 + * downstream device is in D0, so avoid changing the power state 859 + * of the parent bridge by setting the skip_bus_pm flag for it. 860 + */ 861 + if (pci_dev->bus->self) 862 + pci_dev->bus->self->skip_bus_pm = true; 863 + } 864 + 865 + if (pci_dev->skip_bus_pm && !pm_suspend_via_firmware()) { 866 + dev_dbg(dev, "PCI PM: Skipped\n"); 867 + goto Fixup; 868 + } 850 869 851 870 pci_pm_set_unknown_state(pci_dev); 852 871 ··· 911 898 if (dev_pm_smart_suspend_and_suspended(dev)) 912 899 pm_runtime_set_active(dev); 913 900 914 - pci_pm_default_resume_early(pci_dev); 901 + /* 902 + * In the suspend-to-idle case, devices left in D0 during suspend will 903 + * stay in D0, so it is not necessary to restore or update their 904 + * configuration here and attempting to put them into D0 again may 905 + * confuse some firmware, so avoid doing that. 906 + */ 907 + if (!pci_dev->skip_bus_pm || pm_suspend_via_firmware()) 908 + pci_pm_default_resume_early(pci_dev); 909 + 910 + pci_fixup_device(pci_fixup_resume_early, pci_dev); 915 911 916 912 if (pci_has_legacy_pm_support(pci_dev)) 917 913 return pci_legacy_resume_early(dev); ··· 1216 1194 } 1217 1195 1218 1196 pci_pm_default_resume_early(pci_dev); 1197 + pci_fixup_device(pci_fixup_resume_early, pci_dev); 1219 1198 1220 1199 if (pci_has_legacy_pm_support(pci_dev)) 1221 1200 return pci_legacy_resume_early(dev);