summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/driver-api/pm/devices.rst9
-rw-r--r--drivers/base/power/main.c51
2 files changed, 49 insertions, 11 deletions
diff --git a/Documentation/driver-api/pm/devices.rst b/Documentation/driver-api/pm/devices.rst
index 07026811dcae..1128705a5731 100644
--- a/Documentation/driver-api/pm/devices.rst
+++ b/Documentation/driver-api/pm/devices.rst
@@ -816,3 +816,12 @@ appropriate in its "noirq" resume callback, which is executed regardless of
whether or not the device is left suspended, but the other resume callbacks
(except for ``->complete``) will be skipped automatically by the PM core if the
device really can be left in suspend.
+
+For devices whose "noirq", "late" and "early" driver callbacks are invoked
+directly by the PM core, all of the system-wide resume callbacks are skipped if
+``DPM_FLAG_LEAVE_SUSPENDED`` is set and the device is in runtime suspend during
+the ``suspend_noirq`` (or analogous) phase or the transition under way is a
+proper system suspend (rather than anything related to hibernation) and the
+device's wakeup settings are suitable for runtime PM (that is, it cannot
+generate wakeup signals at all or it is allowed to wake up the system from
+sleep).
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index 154f7b4db8d0..70398e7b3569 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -619,6 +619,7 @@ static int device_resume_noirq(struct device *dev, pm_message_t state, bool asyn
{
pm_callback_t callback;
const char *info;
+ bool skip_resume;
int error = 0;
TRACE_DEVICE(dev);
@@ -632,10 +633,15 @@ static int device_resume_noirq(struct device *dev, pm_message_t state, bool asyn
dpm_wait_for_superior(dev, async);
+ skip_resume = dev_pm_may_skip_resume(dev);
+
callback = dpm_subsys_resume_noirq_cb(dev, state, &info);
if (callback)
goto Run;
+ if (skip_resume)
+ goto Skip;
+
if (dev_pm_smart_suspend_and_suspended(dev)) {
pm_message_t suspend_msg = suspend_event(state);
@@ -650,7 +656,7 @@ static int device_resume_noirq(struct device *dev, pm_message_t state, bool asyn
if (!dpm_subsys_suspend_late_cb(dev, suspend_msg, NULL) &&
!dpm_subsys_suspend_noirq_cb(dev, suspend_msg, NULL)) {
if (state.event == PM_EVENT_THAW) {
- dev_pm_skip_next_resume_phases(dev);
+ skip_resume = true;
goto Skip;
} else {
pm_runtime_set_active(dev);
@@ -669,7 +675,7 @@ Run:
Skip:
dev->power.is_noirq_suspended = false;
- if (dev_pm_may_skip_resume(dev)) {
+ if (skip_resume) {
/*
* The device is going to be left in suspend, but it might not
* have been in runtime suspend before the system suspended, so
@@ -1244,6 +1250,32 @@ static pm_callback_t dpm_subsys_suspend_noirq_cb(struct device *dev,
return callback;
}
+static bool device_must_resume(struct device *dev, pm_message_t state,
+ bool no_subsys_suspend_noirq)
+{
+ pm_message_t resume_msg = resume_event(state);
+
+ /*
+ * If all of the device driver's "noirq", "late" and "early" callbacks
+ * are invoked directly by the core, the decision to allow the device to
+ * stay in suspend can be based on its current runtime PM status and its
+ * wakeup settings.
+ */
+ if (no_subsys_suspend_noirq &&
+ !dpm_subsys_suspend_late_cb(dev, state, NULL) &&
+ !dpm_subsys_resume_early_cb(dev, resume_msg, NULL) &&
+ !dpm_subsys_resume_noirq_cb(dev, resume_msg, NULL))
+ return !pm_runtime_status_suspended(dev) &&
+ (resume_msg.event != PM_EVENT_RESUME ||
+ (device_can_wakeup(dev) && !device_may_wakeup(dev)));
+
+ /*
+ * The only safe strategy here is to require that if the device may not
+ * be left in suspend, resume callbacks must be invoked for it.
+ */
+ return !dev->power.may_skip_resume;
+}
+
/**
* __device_suspend_noirq - Execute a "noirq suspend" callback for given device.
* @dev: Device to handle.
@@ -1257,6 +1289,7 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a
{
pm_callback_t callback;
const char *info;
+ bool no_subsys_cb = false;
int error = 0;
TRACE_DEVICE(dev);
@@ -1279,8 +1312,9 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a
if (callback)
goto Run;
- if (dev_pm_smart_suspend_and_suspended(dev) &&
- !dpm_subsys_suspend_late_cb(dev, state, NULL))
+ no_subsys_cb = !dpm_subsys_suspend_late_cb(dev, state, NULL);
+
+ if (dev_pm_smart_suspend_and_suspended(dev) && no_subsys_cb)
goto Skip;
if (dev->driver && dev->driver->pm) {
@@ -1299,14 +1333,9 @@ Skip:
dev->power.is_noirq_suspended = true;
if (dev_pm_test_driver_flags(dev, DPM_FLAG_LEAVE_SUSPENDED)) {
- /*
- * The only safe strategy here is to require that if the device
- * may not be left in suspend, resume callbacks must be invoked
- * for it.
- */
dev->power.must_resume = dev->power.must_resume ||
- !dev->power.may_skip_resume ||
- atomic_read(&dev->power.usage_count) > 1;
+ atomic_read(&dev->power.usage_count) > 1 ||
+ device_must_resume(dev, state, no_subsys_cb);
} else {
dev->power.must_resume = true;
}