ACPI: PM: Introduce "poweroff" callbacks for ACPI PM domain and LPSS
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>
Mon, 1 Jul 2019 10:54:29 +0000 (12:54 +0200)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Tue, 2 Jul 2019 22:13:24 +0000 (00:13 +0200)
In general, it is not correct to call pm_generic_suspend(),
pm_generic_suspend_late() and pm_generic_suspend_noirq() during the
hibernation's "poweroff" transition, because device drivers may
provide special callbacks to be invoked then and the wrappers in
question cause system suspend callbacks to be run.  Unfortunately,
that happens in the ACPI PM domain and ACPI LPSS.

To address this potential issue, introduce "poweroff" callbacks
for the ACPI PM and LPSS that will use pm_generic_poweroff(),
pm_generic_poweroff_late() and pm_generic_poweroff_noirq() as
appropriate.

Fixes: 05087360fd7a (ACPI / PM: Take SMART_SUSPEND driver flag into account)
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
drivers/acpi/acpi_lpss.c
drivers/acpi/device_pm.c
include/linux/acpi.h

index 8ea836857691cfefe40b87cd0eaf41d01e4dd016..a7396e18f1686e26d90a72100b71cd778b6553dd 100644 (file)
@@ -1064,6 +1064,13 @@ static int acpi_lpss_suspend_noirq(struct device *dev)
        int ret;
 
        if (pdata->dev_desc->resume_from_noirq) {
+               /*
+                * The driver's ->suspend_late callback will be invoked by
+                * acpi_lpss_do_suspend_late(), with the assumption that the
+                * driver really wanted to run that code in ->suspend_noirq, but
+                * it could not run after acpi_dev_suspend() and the driver
+                * expected the latter to be called in the "late" phase.
+                */
                ret = acpi_lpss_do_suspend_late(dev);
                if (ret)
                        return ret;
@@ -1150,6 +1157,43 @@ static int acpi_lpss_restore_noirq(struct device *dev)
        /* This is analogous to what happens in acpi_lpss_resume_noirq(). */
        return acpi_lpss_do_restore_early(dev);
 }
+
+static int acpi_lpss_do_poweroff_late(struct device *dev)
+{
+       int ret = pm_generic_poweroff_late(dev);
+
+       return ret ? ret : acpi_lpss_suspend(dev, device_may_wakeup(dev));
+}
+
+static int acpi_lpss_poweroff_late(struct device *dev)
+{
+       struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
+
+       if (dev_pm_smart_suspend_and_suspended(dev))
+               return 0;
+
+       if (pdata->dev_desc->resume_from_noirq)
+               return 0;
+
+       return acpi_lpss_do_poweroff_late(dev);
+}
+
+static int acpi_lpss_poweroff_noirq(struct device *dev)
+{
+       struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
+
+       if (dev_pm_smart_suspend_and_suspended(dev))
+               return 0;
+
+       if (pdata->dev_desc->resume_from_noirq) {
+               /* This is analogous to the acpi_lpss_suspend_noirq() case. */
+               int ret = acpi_lpss_do_poweroff_late(dev);
+               if (ret)
+                       return ret;
+       }
+
+       return pm_generic_poweroff_noirq(dev);
+}
 #endif /* CONFIG_PM_SLEEP */
 
 static int acpi_lpss_runtime_suspend(struct device *dev)
@@ -1183,9 +1227,9 @@ static struct dev_pm_domain acpi_lpss_pm_domain = {
                .resume_noirq = acpi_lpss_resume_noirq,
                .resume_early = acpi_lpss_resume_early,
                .freeze = acpi_subsys_freeze,
-               .poweroff = acpi_subsys_suspend,
-               .poweroff_late = acpi_lpss_suspend_late,
-               .poweroff_noirq = acpi_lpss_suspend_noirq,
+               .poweroff = acpi_subsys_poweroff,
+               .poweroff_late = acpi_lpss_poweroff_late,
+               .poweroff_noirq = acpi_lpss_poweroff_noirq,
                .restore_noirq = acpi_lpss_restore_noirq,
                .restore_early = acpi_lpss_restore_early,
 #endif
index 52fc9042a10702ff08bf2d25a6e86a41725bc391..6a9d41c44b708e2b19265928ae033326a2883c80 100644 (file)
@@ -1133,6 +1133,58 @@ int acpi_subsys_restore_early(struct device *dev)
        return ret ? ret : pm_generic_restore_early(dev);
 }
 EXPORT_SYMBOL_GPL(acpi_subsys_restore_early);
+
+/**
+ * acpi_subsys_poweroff - Run the device driver's poweroff callback.
+ * @dev: Device to handle.
+ *
+ * Follow PCI and resume devices from runtime suspend before running their
+ * system poweroff callbacks, unless the driver can cope with runtime-suspended
+ * devices during system suspend and there are no ACPI-specific reasons for
+ * resuming them.
+ */
+int acpi_subsys_poweroff(struct device *dev)
+{
+       if (!dev_pm_test_driver_flags(dev, DPM_FLAG_SMART_SUSPEND) ||
+           acpi_dev_needs_resume(dev, ACPI_COMPANION(dev)))
+               pm_runtime_resume(dev);
+
+       return pm_generic_poweroff(dev);
+}
+EXPORT_SYMBOL_GPL(acpi_subsys_poweroff);
+
+/**
+ * acpi_subsys_poweroff_late - Run the device driver's poweroff callback.
+ * @dev: Device to handle.
+ *
+ * Carry out the generic late poweroff procedure for @dev and use ACPI to put
+ * it into a low-power state during system transition into a sleep state.
+ */
+static int acpi_subsys_poweroff_late(struct device *dev)
+{
+       int ret;
+
+       if (dev_pm_smart_suspend_and_suspended(dev))
+               return 0;
+
+       ret = pm_generic_poweroff_late(dev);
+       if (ret)
+               return ret;
+
+       return acpi_dev_suspend(dev, device_may_wakeup(dev));
+}
+
+/**
+ * acpi_subsys_poweroff_noirq - Run the driver's "noirq" poweroff callback.
+ * @dev: Device to suspend.
+ */
+static int acpi_subsys_poweroff_noirq(struct device *dev)
+{
+       if (dev_pm_smart_suspend_and_suspended(dev))
+               return 0;
+
+       return pm_generic_poweroff_noirq(dev);
+}
 #endif /* CONFIG_PM_SLEEP */
 
 static struct dev_pm_domain acpi_general_pm_domain = {
@@ -1148,9 +1200,9 @@ static struct dev_pm_domain acpi_general_pm_domain = {
                .resume_noirq = acpi_subsys_resume_noirq,
                .resume_early = acpi_subsys_resume_early,
                .freeze = acpi_subsys_freeze,
-               .poweroff = acpi_subsys_suspend,
-               .poweroff_late = acpi_subsys_suspend_late,
-               .poweroff_noirq = acpi_subsys_suspend_noirq,
+               .poweroff = acpi_subsys_poweroff,
+               .poweroff_late = acpi_subsys_poweroff_late,
+               .poweroff_noirq = acpi_subsys_poweroff_noirq,
                .restore_early = acpi_subsys_restore_early,
 #endif
        },
index ea74154409016df4da633e7cecc776f950b00506..22840633c28c044c72c79f2a5a587bd1bbfe4a14 100644 (file)
@@ -920,6 +920,7 @@ int acpi_subsys_suspend_late(struct device *dev);
 int acpi_subsys_suspend_noirq(struct device *dev);
 int acpi_subsys_suspend(struct device *dev);
 int acpi_subsys_freeze(struct device *dev);
+int acpi_subsys_poweroff(struct device *dev);
 #else
 static inline int acpi_dev_resume_early(struct device *dev) { return 0; }
 static inline int acpi_subsys_prepare(struct device *dev) { return 0; }
@@ -928,6 +929,7 @@ static inline int acpi_subsys_suspend_late(struct device *dev) { return 0; }
 static inline int acpi_subsys_suspend_noirq(struct device *dev) { return 0; }
 static inline int acpi_subsys_suspend(struct device *dev) { return 0; }
 static inline int acpi_subsys_freeze(struct device *dev) { return 0; }
+static inline int acpi_subsys_poweroff(struct device *dev) { return 0; }
 #endif
 
 #ifdef CONFIG_ACPI