new file mode 100644
@@ -0,0 +1,25 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+==================
+Driver Suspend API
+==================
+
+
+1. How Can A driver abort system suspend?
+-----------------------------------------
+
+Any driver can abort system-wide by invoking pm_system_wakeup()
+during the suspend flow.
+
+ie. from the drivers suspend callbacks:
+ .suspend()
+ .suspend_noirq()
+ .suspend_late()
+
+Alternatively, if CONFIG_PM_SLEEP_LEGACY_CALLBACK_ABORT=y is present in .config,
+then any non-zero return value from any device drivers callback:
+ .suspend()
+ .suspend_noirq()
+ .suspend_late()
+will abort the system-wide suspend flow.
+Note that CONFIG_PM_SLEEP_LEGACY_CALLBACK_ABORT=n, by default.
@@ -7,6 +7,7 @@ Power Management
.. toctree::
:maxdepth: 1
+ driver_api
apm-acpi
basic-pm-debugging
charger-manager
@@ -1244,7 +1244,6 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state, bool asy
Run:
error = dpm_run_callback(callback, dev, state, info);
if (error) {
- async_error = error;
dpm_save_failed_dev(dev_name(dev));
pm_dev_err(dev, state, async ? " async noirq" : " noirq", error);
goto Complete;
@@ -1270,7 +1269,12 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state, bool asy
Complete:
complete_all(&dev->power.completion);
TRACE_SUSPEND(error);
- return error;
+
+ if (IS_ENABLED(CONFIG_PM_SLEEP_LEGACY_CALLBACK_ABORT)) {
+ async_error = error;
+ return error;
+ }
+ return 0;
}
static void async_suspend_noirq(void *data, async_cookie_t cookie)
@@ -1424,7 +1428,6 @@ static int device_suspend_late(struct device *dev, pm_message_t state, bool asyn
Run:
error = dpm_run_callback(callback, dev, state, info);
if (error) {
- async_error = error;
dpm_save_failed_dev(dev_name(dev));
pm_dev_err(dev, state, async ? " async late" : " late", error);
goto Complete;
@@ -1437,7 +1440,12 @@ static int device_suspend_late(struct device *dev, pm_message_t state, bool asyn
Complete:
TRACE_SUSPEND(error);
complete_all(&dev->power.completion);
- return error;
+
+ if (IS_ENABLED(CONFIG_PM_SLEEP_LEGACY_CALLBACK_ABORT)) {
+ async_error = error;
+ return error;
+ }
+ return 0;
}
static void async_suspend_late(void *data, async_cookie_t cookie)
@@ -1681,7 +1689,7 @@ static int device_suspend(struct device *dev, pm_message_t state, bool async)
error = dpm_run_callback(callback, dev, state, info);
End:
- if (!error) {
+ if (!error || !IS_ENABLED(CONFIG_PM_SLEEP_LEGACY_CALLBACK_ABORT)) {
dev->power.is_suspended = true;
if (device_may_wakeup(dev))
dev->power.wakeup_path = true;
@@ -1695,14 +1703,17 @@ static int device_suspend(struct device *dev, pm_message_t state, bool async)
Complete:
if (error) {
- async_error = error;
dpm_save_failed_dev(dev_name(dev));
pm_dev_err(dev, state, async ? " async" : "", error);
}
complete_all(&dev->power.completion);
TRACE_SUSPEND(error);
- return error;
+ if (IS_ENABLED(CONFIG_PM_SLEEP_LEGACY_CALLBACK_ABORT)) {
+ async_error = error;
+ return error;
+ }
+ return 0;
}
static void async_suspend(void *data, async_cookie_t cookie)
@@ -141,6 +141,23 @@ config PM_SLEEP
depends on SUSPEND || HIBERNATE_CALLBACKS
select PM
+config PM_SLEEP_LEGACY_CALLBACK_ABORT
+ bool "Enable legacy callback abort via return value"
+ depends on PM_SLEEP
+ help
+ This option enables the legacy API for device .suspend() callbacks.
+ That API empowered any driver to abort system-wide suspend
+ by returning any non-zero value from its suspend calbacks:
+ (.suspend/.suspend_noirq/.suspend_late)
+ In practice, these aborts are almost always spurious and unwanted.
+
+ Disabling this option (default) ignores .suspend() callback return values,
+ though they are still traced and logged.
+
+ The proper way for a device driver to abort system-wide suspend is to
+ invoke pm_system_wakeup() during the suspend flow. This method is
+ valid, independent of this config option.
+
config PM_SLEEP_SMP
def_bool y
depends on SMP