PM / core: Direct DPM_FLAG_SMART_SUSPEND optimization
Make the PM core avoid invoking the "late" and "noirq" system-wide suspend (or analogous) callbacks provided by device drivers directly for devices with DPM_FLAG_SMART_SUSPEND set that are in runtime suspend during the "late" and "noirq" phases of system-wide suspend (or analogous) transitions. That is only done for devices without any middle-layer "late" and "noirq" suspend callbacks (to avoid confusing the middle layer if there is one). The underlying observation is that runtime PM is disabled for devices during the "late" and "noirq" system-wide suspend phases, so if they remain in runtime suspend from the "late" phase forward, it doesn't make sense to invoke the "late" and "noirq" callbacks provided by the drivers for them (arguably, the device is already suspended and in the right state). Thus, if the remaining driver suspend callbacks are to be invoked directly by the core, they can be skipped. This change really makes it possible for, say, platform device drivers to re-use runtime PM suspend and resume callbacks by pointing ->suspend_late and ->resume_early, respectively (and possibly the analogous hibernation-related callback pointers too), to them without adding any extra "is the device already suspended?" type of checks to the callback routines, as long as they will be invoked directly by the core. Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
This commit is contained in:
parent
4fa3061a68
commit
75e94645fc
|
@ -777,14 +777,16 @@ The driver can indicate that by setting ``DPM_FLAG_SMART_SUSPEND`` in
|
||||||
runtime suspend at the beginning of the ``suspend_late`` phase of system-wide
|
runtime suspend at the beginning of the ``suspend_late`` phase of system-wide
|
||||||
suspend (or in the ``poweroff_late`` phase of hibernation), when runtime PM
|
suspend (or in the ``poweroff_late`` phase of hibernation), when runtime PM
|
||||||
has been disabled for it, under the assumption that its state should not change
|
has been disabled for it, under the assumption that its state should not change
|
||||||
after that point until the system-wide transition is over. If that happens, the
|
after that point until the system-wide transition is over (the PM core itself
|
||||||
driver's system-wide resume callbacks, if present, may still be invoked during
|
does that for devices whose "noirq", "late" and "early" system-wide PM callbacks
|
||||||
the subsequent system-wide resume transition and the device's runtime power
|
are executed directly by it). If that happens, the driver's system-wide resume
|
||||||
management status may be set to "active" before enabling runtime PM for it,
|
callbacks, if present, may still be invoked during the subsequent system-wide
|
||||||
so the driver must be prepared to cope with the invocation of its system-wide
|
resume transition and the device's runtime power management status may be set
|
||||||
resume callbacks back-to-back with its ``->runtime_suspend`` one (without the
|
to "active" before enabling runtime PM for it, so the driver must be prepared to
|
||||||
intervening ``->runtime_resume`` and so on) and the final state of the device
|
cope with the invocation of its system-wide resume callbacks back-to-back with
|
||||||
must reflect the "active" status for runtime PM in that case.
|
its ``->runtime_suspend`` one (without the intervening ``->runtime_resume`` and
|
||||||
|
so on) and the final state of the device must reflect the "active" runtime PM
|
||||||
|
status in that case.
|
||||||
|
|
||||||
During system-wide resume from a sleep state it's easiest to put devices into
|
During system-wide resume from a sleep state it's easiest to put devices into
|
||||||
the full-power state, as explained in :file:`Documentation/power/runtime_pm.txt`.
|
the full-power state, as explained in :file:`Documentation/power/runtime_pm.txt`.
|
||||||
|
|
|
@ -539,6 +539,24 @@ void dev_pm_skip_next_resume_phases(struct device *dev)
|
||||||
dev->power.is_suspended = false;
|
dev->power.is_suspended = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* suspend_event - Return a "suspend" message for given "resume" one.
|
||||||
|
* @resume_msg: PM message representing a system-wide resume transition.
|
||||||
|
*/
|
||||||
|
static pm_message_t suspend_event(pm_message_t resume_msg)
|
||||||
|
{
|
||||||
|
switch (resume_msg.event) {
|
||||||
|
case PM_EVENT_RESUME:
|
||||||
|
return PMSG_SUSPEND;
|
||||||
|
case PM_EVENT_THAW:
|
||||||
|
case PM_EVENT_RESTORE:
|
||||||
|
return PMSG_FREEZE;
|
||||||
|
case PM_EVENT_RECOVER:
|
||||||
|
return PMSG_HIBERNATE;
|
||||||
|
}
|
||||||
|
return PMSG_ON;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dev_pm_may_skip_resume - System-wide device resume optimization check.
|
* dev_pm_may_skip_resume - System-wide device resume optimization check.
|
||||||
* @dev: Target device.
|
* @dev: Target device.
|
||||||
|
@ -580,6 +598,14 @@ static pm_callback_t dpm_subsys_resume_noirq_cb(struct device *dev,
|
||||||
return callback;
|
return callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static pm_callback_t dpm_subsys_suspend_noirq_cb(struct device *dev,
|
||||||
|
pm_message_t state,
|
||||||
|
const char **info_p);
|
||||||
|
|
||||||
|
static pm_callback_t dpm_subsys_suspend_late_cb(struct device *dev,
|
||||||
|
pm_message_t state,
|
||||||
|
const char **info_p);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* device_resume_noirq - Execute a "noirq resume" callback for given device.
|
* device_resume_noirq - Execute a "noirq resume" callback for given device.
|
||||||
* @dev: Device to handle.
|
* @dev: Device to handle.
|
||||||
|
@ -607,13 +633,40 @@ static int device_resume_noirq(struct device *dev, pm_message_t state, bool asyn
|
||||||
dpm_wait_for_superior(dev, async);
|
dpm_wait_for_superior(dev, async);
|
||||||
|
|
||||||
callback = dpm_subsys_resume_noirq_cb(dev, state, &info);
|
callback = dpm_subsys_resume_noirq_cb(dev, state, &info);
|
||||||
|
if (callback)
|
||||||
|
goto Run;
|
||||||
|
|
||||||
if (!callback && dev->driver && dev->driver->pm) {
|
if (dev_pm_smart_suspend_and_suspended(dev)) {
|
||||||
|
pm_message_t suspend_msg = suspend_event(state);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If "freeze" callbacks have been skipped during a transition
|
||||||
|
* related to hibernation, the subsequent "thaw" callbacks must
|
||||||
|
* be skipped too or bad things may happen. Otherwise, resume
|
||||||
|
* callbacks are going to be run for the device, so its runtime
|
||||||
|
* PM status must be changed to reflect the new state after the
|
||||||
|
* transition under way.
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
goto Skip;
|
||||||
|
} else {
|
||||||
|
pm_runtime_set_active(dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev->driver && dev->driver->pm) {
|
||||||
info = "noirq driver ";
|
info = "noirq driver ";
|
||||||
callback = pm_noirq_op(dev->driver->pm, state);
|
callback = pm_noirq_op(dev->driver->pm, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Run:
|
||||||
error = dpm_run_callback(callback, dev, state, info);
|
error = dpm_run_callback(callback, dev, state, info);
|
||||||
|
|
||||||
|
Skip:
|
||||||
dev->power.is_noirq_suspended = false;
|
dev->power.is_noirq_suspended = false;
|
||||||
|
|
||||||
if (dev_pm_may_skip_resume(dev)) {
|
if (dev_pm_may_skip_resume(dev)) {
|
||||||
|
@ -628,7 +681,7 @@ static int device_resume_noirq(struct device *dev, pm_message_t state, bool asyn
|
||||||
dev_pm_skip_next_resume_phases(dev);
|
dev_pm_skip_next_resume_phases(dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
Out:
|
Out:
|
||||||
complete_all(&dev->power.completion);
|
complete_all(&dev->power.completion);
|
||||||
TRACE_RESUME(error);
|
TRACE_RESUME(error);
|
||||||
return error;
|
return error;
|
||||||
|
@ -1223,18 +1276,26 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a
|
||||||
goto Complete;
|
goto Complete;
|
||||||
|
|
||||||
callback = dpm_subsys_suspend_noirq_cb(dev, state, &info);
|
callback = dpm_subsys_suspend_noirq_cb(dev, state, &info);
|
||||||
|
if (callback)
|
||||||
|
goto Run;
|
||||||
|
|
||||||
if (!callback && dev->driver && dev->driver->pm) {
|
if (dev_pm_smart_suspend_and_suspended(dev) &&
|
||||||
|
!dpm_subsys_suspend_late_cb(dev, state, NULL))
|
||||||
|
goto Skip;
|
||||||
|
|
||||||
|
if (dev->driver && dev->driver->pm) {
|
||||||
info = "noirq driver ";
|
info = "noirq driver ";
|
||||||
callback = pm_noirq_op(dev->driver->pm, state);
|
callback = pm_noirq_op(dev->driver->pm, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Run:
|
||||||
error = dpm_run_callback(callback, dev, state, info);
|
error = dpm_run_callback(callback, dev, state, info);
|
||||||
if (error) {
|
if (error) {
|
||||||
async_error = error;
|
async_error = error;
|
||||||
goto Complete;
|
goto Complete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Skip:
|
||||||
dev->power.is_noirq_suspended = true;
|
dev->power.is_noirq_suspended = true;
|
||||||
|
|
||||||
if (dev_pm_test_driver_flags(dev, DPM_FLAG_LEAVE_SUSPENDED)) {
|
if (dev_pm_test_driver_flags(dev, DPM_FLAG_LEAVE_SUSPENDED)) {
|
||||||
|
@ -1419,17 +1480,27 @@ static int __device_suspend_late(struct device *dev, pm_message_t state, bool as
|
||||||
goto Complete;
|
goto Complete;
|
||||||
|
|
||||||
callback = dpm_subsys_suspend_late_cb(dev, state, &info);
|
callback = dpm_subsys_suspend_late_cb(dev, state, &info);
|
||||||
|
if (callback)
|
||||||
|
goto Run;
|
||||||
|
|
||||||
if (!callback && dev->driver && dev->driver->pm) {
|
if (dev_pm_smart_suspend_and_suspended(dev) &&
|
||||||
|
!dpm_subsys_suspend_noirq_cb(dev, state, NULL))
|
||||||
|
goto Skip;
|
||||||
|
|
||||||
|
if (dev->driver && dev->driver->pm) {
|
||||||
info = "late driver ";
|
info = "late driver ";
|
||||||
callback = pm_late_early_op(dev->driver->pm, state);
|
callback = pm_late_early_op(dev->driver->pm, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Run:
|
||||||
error = dpm_run_callback(callback, dev, state, info);
|
error = dpm_run_callback(callback, dev, state, info);
|
||||||
if (!error)
|
if (error) {
|
||||||
dev->power.is_late_suspended = true;
|
|
||||||
else
|
|
||||||
async_error = error;
|
async_error = error;
|
||||||
|
goto Complete;
|
||||||
|
}
|
||||||
|
|
||||||
|
Skip:
|
||||||
|
dev->power.is_late_suspended = true;
|
||||||
|
|
||||||
Complete:
|
Complete:
|
||||||
TRACE_SUSPEND(error);
|
TRACE_SUSPEND(error);
|
||||||
|
|
Loading…
Reference in New Issue