From 0d4b54c6fee87ff60b0bc1007ca487449698468d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 18 Nov 2017 15:31:49 +0100 Subject: PM / core: Add LEAVE_SUSPENDED driver flag Define and document a new driver flag, DPM_FLAG_LEAVE_SUSPENDED, to instruct the PM core and middle-layer (bus type, PM domain, etc.) code that it is desirable to leave the device in runtime suspend after system-wide transitions to the working state (for example, the device may be slow to resume and it may be better to avoid resuming it right away). Generally, the middle-layer code involved in the handling of the device is expected to indicate to the PM core whether or not the device may be left in suspend with the help of the device's power.may_skip_resume status bit. That has to happen in the "noirq" phase of the preceding system suspend (or analogous) transition. The middle layer is then responsible for handling the device as appropriate in its "noirq" resume callback which is executed regardless of whether or not the device may be left suspended, but the other resume callbacks (except for ->complete) will be skipped automatically by the core if the device really can be left in suspend. The additional power.must_resume status bit introduced for the implementation of this mechanisn is used internally by the PM core to track the requirement to resume the device (which may depend on its children etc). Signed-off-by: Rafael J. Wysocki Acked-by: Greg Kroah-Hartman Reviewed-by: Ulf Hansson --- drivers/base/power/main.c | 73 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 5 deletions(-) (limited to 'drivers/base/power/main.c') diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index db2f04415927..73ec6796d9e1 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -525,6 +525,18 @@ static void dpm_watchdog_clear(struct dpm_watchdog *wd) /*------------------------- Resume routines -------------------------*/ +/** + * dev_pm_may_skip_resume - System-wide device resume optimization check. + * @dev: Target device. + * + * Checks whether or not the device may be left in suspend after a system-wide + * transition to the working state. + */ +bool dev_pm_may_skip_resume(struct device *dev) +{ + return !dev->power.must_resume && pm_transition.event != PM_EVENT_RESTORE; +} + /** * device_resume_noirq - Execute a "noirq resume" callback for given device. * @dev: Device to handle. @@ -573,6 +585,19 @@ static int device_resume_noirq(struct device *dev, pm_message_t state, bool asyn error = dpm_run_callback(callback, dev, state, info); dev->power.is_noirq_suspended = false; + if (dev_pm_may_skip_resume(dev)) { + /* + * The device is going to be left in suspend, but it might not + * have been in runtime suspend before the system suspended, so + * its runtime PM status needs to be updated to avoid confusing + * the runtime PM framework when runtime PM is enabled for the + * device again. + */ + pm_runtime_set_suspended(dev); + dev->power.is_late_suspended = false; + dev->power.is_suspended = false; + } + Out: complete_all(&dev->power.completion); TRACE_RESUME(error); @@ -1074,6 +1099,22 @@ static pm_message_t resume_event(pm_message_t sleep_state) return PMSG_ON; } +static void dpm_superior_set_must_resume(struct device *dev) +{ + struct device_link *link; + int idx; + + if (dev->parent) + dev->parent->power.must_resume = true; + + idx = device_links_read_lock(); + + list_for_each_entry_rcu(link, &dev->links.suppliers, c_node) + link->supplier->power.must_resume = true; + + device_links_read_unlock(idx); +} + /** * __device_suspend_noirq - Execute a "noirq suspend" callback for given device. * @dev: Device to handle. @@ -1125,10 +1166,28 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a } error = dpm_run_callback(callback, dev, state, info); - if (!error) - dev->power.is_noirq_suspended = true; - else + if (error) { async_error = error; + goto Complete; + } + + 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; + } else { + dev->power.must_resume = true; + } + + if (dev->power.must_resume) + dpm_superior_set_must_resume(dev); Complete: complete_all(&dev->power.completion); @@ -1485,6 +1544,9 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) dev->power.direct_complete = false; } + dev->power.may_skip_resume = false; + dev->power.must_resume = false; + dpm_watchdog_set(&wd, dev); device_lock(dev); @@ -1650,8 +1712,9 @@ static int device_prepare(struct device *dev, pm_message_t state) if (dev->power.syscore) return 0; - WARN_ON(dev_pm_test_driver_flags(dev, DPM_FLAG_SMART_SUSPEND) && - !pm_runtime_enabled(dev)); + WARN_ON(!pm_runtime_enabled(dev) && + dev_pm_test_driver_flags(dev, DPM_FLAG_SMART_SUSPEND | + DPM_FLAG_LEAVE_SUSPENDED)); /* * If a device's parent goes into runtime suspend at the wrong time, -- cgit v1.2.3 From 1172ee31259b51a9b2d83b05f01161fd5938b15d Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 13 Nov 2017 16:46:41 +0100 Subject: PM / core: Re-factor some code dealing with parents in __device_suspend() Let's make the code a bit more readable by moving some of the code, which deals with adjustments for parent devices in __device_suspend(), into its own function. Signed-off-by: Ulf Hansson Reviewed-by: Geert Uytterhoeven Signed-off-by: Rafael J. Wysocki --- drivers/base/power/main.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) (limited to 'drivers/base/power/main.c') diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 73ec6796d9e1..c0d5f4a3611d 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -1479,6 +1479,22 @@ static int legacy_suspend(struct device *dev, pm_message_t state, return error; } +static void dpm_propagate_to_parent(struct device *dev) +{ + struct device *parent = dev->parent; + + if (!parent) + return; + + spin_lock_irq(&parent->power.lock); + + parent->power.direct_complete = false; + if (dev->power.wakeup_path && !parent->power.ignore_children) + parent->power.wakeup_path = true; + + spin_unlock_irq(&parent->power.lock); +} + static void dpm_clear_suppliers_direct_complete(struct device *dev) { struct device_link *link; @@ -1590,19 +1606,8 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) End: if (!error) { - struct device *parent = dev->parent; - dev->power.is_suspended = true; - if (parent) { - spin_lock_irq(&parent->power.lock); - - dev->parent->power.direct_complete = false; - if (dev->power.wakeup_path - && !dev->parent->power.ignore_children) - dev->parent->power.wakeup_path = true; - - spin_unlock_irq(&parent->power.lock); - } + dpm_propagate_to_parent(dev); dpm_clear_suppliers_direct_complete(dev); } -- cgit v1.2.3 From 7e6a70a57800014743ecfae7023c379388eff121 Mon Sep 17 00:00:00 2001 From: Sergey Senozhatsky Date: Fri, 8 Dec 2017 11:56:10 +0900 Subject: PM / core: remove unneeded kallsyms include The file was converted from print_fn_descriptor_symbol() to %pF some time ago (c80cfb0406c01bb "vsprintf: use new vsprintf symbolic function pointer format"). kallsyms does not seem to be needed anymore. Signed-off-by: Sergey Senozhatsky Signed-off-by: Rafael J. Wysocki --- drivers/base/power/main.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/base/power/main.c') diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index c0d5f4a3611d..d8aa88baf9c1 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -18,7 +18,6 @@ */ #include -#include #include #include #include -- cgit v1.2.3 From 34fb8f0ba9ceea88e116688f9f53e3802c38aafb Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 10 Dec 2017 00:56:50 +0100 Subject: PM / core: Use dev_pm_skip_next_resume_phases() internally Make the PM core call dev_pm_skip_next_resume_phases() to skip the "early resume" and "resume" phases of system-wide transitions to the working state for a given device instead of clearing the relevant status bits for it directly. No intentional changes in functionality. Signed-off-by: Rafael J. Wysocki Reviewed-by: Geert Uytterhoeven Reviewed-by: Ulf Hansson --- drivers/base/power/main.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/base/power/main.c') diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index d8aa88baf9c1..cd48b1c69167 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -593,8 +593,7 @@ static int device_resume_noirq(struct device *dev, pm_message_t state, bool asyn * device again. */ pm_runtime_set_suspended(dev); - dev->power.is_late_suspended = false; - dev->power.is_suspended = false; + dev_pm_skip_next_resume_phases(dev); } Out: -- cgit v1.2.3 From 4fa3061a6856cc72f3f984702145bb30f16ee40e Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 10 Dec 2017 00:58:18 +0100 Subject: PM / core: Add helpers for subsystem callback selection Add helper routines to find and return a suitable subsystem callback during the "noirq" phases of system suspend/resume (or analogous) transitions as well as during the "late" phase of system suspend and the "early" phase of system resume (or analogous) transitions. The helpers will be called from additional sites going forward. Signed-off-by: Rafael J. Wysocki Reviewed-by: Ulf Hansson Reviewed-by: Geert Uytterhoeven --- drivers/base/power/main.c | 188 +++++++++++++++++++++++++++++++--------------- 1 file changed, 128 insertions(+), 60 deletions(-) (limited to 'drivers/base/power/main.c') diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 6e8cc5de93fd..3c5fdf155c91 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -551,6 +551,35 @@ bool dev_pm_may_skip_resume(struct device *dev) return !dev->power.must_resume && pm_transition.event != PM_EVENT_RESTORE; } +static pm_callback_t dpm_subsys_resume_noirq_cb(struct device *dev, + pm_message_t state, + const char **info_p) +{ + pm_callback_t callback; + const char *info; + + if (dev->pm_domain) { + info = "noirq power domain "; + callback = pm_noirq_op(&dev->pm_domain->ops, state); + } else if (dev->type && dev->type->pm) { + info = "noirq type "; + callback = pm_noirq_op(dev->type->pm, state); + } else if (dev->class && dev->class->pm) { + info = "noirq class "; + callback = pm_noirq_op(dev->class->pm, state); + } else if (dev->bus && dev->bus->pm) { + info = "noirq bus "; + callback = pm_noirq_op(dev->bus->pm, state); + } else { + return NULL; + } + + if (info_p) + *info_p = info; + + return callback; +} + /** * device_resume_noirq - Execute a "noirq resume" callback for given device. * @dev: Device to handle. @@ -562,8 +591,8 @@ bool dev_pm_may_skip_resume(struct device *dev) */ static int device_resume_noirq(struct device *dev, pm_message_t state, bool async) { - pm_callback_t callback = NULL; - const char *info = NULL; + pm_callback_t callback; + const char *info; int error = 0; TRACE_DEVICE(dev); @@ -577,19 +606,7 @@ static int device_resume_noirq(struct device *dev, pm_message_t state, bool asyn dpm_wait_for_superior(dev, async); - if (dev->pm_domain) { - info = "noirq power domain "; - callback = pm_noirq_op(&dev->pm_domain->ops, state); - } else if (dev->type && dev->type->pm) { - info = "noirq type "; - callback = pm_noirq_op(dev->type->pm, state); - } else if (dev->class && dev->class->pm) { - info = "noirq class "; - callback = pm_noirq_op(dev->class->pm, state); - } else if (dev->bus && dev->bus->pm) { - info = "noirq bus "; - callback = pm_noirq_op(dev->bus->pm, state); - } + callback = dpm_subsys_resume_noirq_cb(dev, state, &info); if (!callback && dev->driver && dev->driver->pm) { info = "noirq driver "; @@ -704,6 +721,35 @@ void dpm_resume_noirq(pm_message_t state) dpm_noirq_end(); } +static pm_callback_t dpm_subsys_resume_early_cb(struct device *dev, + pm_message_t state, + const char **info_p) +{ + pm_callback_t callback; + const char *info; + + if (dev->pm_domain) { + info = "early power domain "; + callback = pm_late_early_op(&dev->pm_domain->ops, state); + } else if (dev->type && dev->type->pm) { + info = "early type "; + callback = pm_late_early_op(dev->type->pm, state); + } else if (dev->class && dev->class->pm) { + info = "early class "; + callback = pm_late_early_op(dev->class->pm, state); + } else if (dev->bus && dev->bus->pm) { + info = "early bus "; + callback = pm_late_early_op(dev->bus->pm, state); + } else { + return NULL; + } + + if (info_p) + *info_p = info; + + return callback; +} + /** * device_resume_early - Execute an "early resume" callback for given device. * @dev: Device to handle. @@ -714,8 +760,8 @@ void dpm_resume_noirq(pm_message_t state) */ static int device_resume_early(struct device *dev, pm_message_t state, bool async) { - pm_callback_t callback = NULL; - const char *info = NULL; + pm_callback_t callback; + const char *info; int error = 0; TRACE_DEVICE(dev); @@ -729,19 +775,7 @@ static int device_resume_early(struct device *dev, pm_message_t state, bool asyn dpm_wait_for_superior(dev, async); - if (dev->pm_domain) { - info = "early power domain "; - callback = pm_late_early_op(&dev->pm_domain->ops, state); - } else if (dev->type && dev->type->pm) { - info = "early type "; - callback = pm_late_early_op(dev->type->pm, state); - } else if (dev->class && dev->class->pm) { - info = "early class "; - callback = pm_late_early_op(dev->class->pm, state); - } else if (dev->bus && dev->bus->pm) { - info = "early bus "; - callback = pm_late_early_op(dev->bus->pm, state); - } + callback = dpm_subsys_resume_early_cb(dev, state, &info); if (!callback && dev->driver && dev->driver->pm) { info = "early driver "; @@ -1128,6 +1162,35 @@ static void dpm_superior_set_must_resume(struct device *dev) device_links_read_unlock(idx); } +static pm_callback_t dpm_subsys_suspend_noirq_cb(struct device *dev, + pm_message_t state, + const char **info_p) +{ + pm_callback_t callback; + const char *info; + + if (dev->pm_domain) { + info = "noirq power domain "; + callback = pm_noirq_op(&dev->pm_domain->ops, state); + } else if (dev->type && dev->type->pm) { + info = "noirq type "; + callback = pm_noirq_op(dev->type->pm, state); + } else if (dev->class && dev->class->pm) { + info = "noirq class "; + callback = pm_noirq_op(dev->class->pm, state); + } else if (dev->bus && dev->bus->pm) { + info = "noirq bus "; + callback = pm_noirq_op(dev->bus->pm, state); + } else { + return NULL; + } + + if (info_p) + *info_p = info; + + return callback; +} + /** * __device_suspend_noirq - Execute a "noirq suspend" callback for given device. * @dev: Device to handle. @@ -1139,8 +1202,8 @@ static void dpm_superior_set_must_resume(struct device *dev) */ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool async) { - pm_callback_t callback = NULL; - const char *info = NULL; + pm_callback_t callback; + const char *info; int error = 0; TRACE_DEVICE(dev); @@ -1159,19 +1222,7 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a if (dev->power.syscore || dev->power.direct_complete) goto Complete; - if (dev->pm_domain) { - info = "noirq power domain "; - callback = pm_noirq_op(&dev->pm_domain->ops, state); - } else if (dev->type && dev->type->pm) { - info = "noirq type "; - callback = pm_noirq_op(dev->type->pm, state); - } else if (dev->class && dev->class->pm) { - info = "noirq class "; - callback = pm_noirq_op(dev->class->pm, state); - } else if (dev->bus && dev->bus->pm) { - info = "noirq bus "; - callback = pm_noirq_op(dev->bus->pm, state); - } + callback = dpm_subsys_suspend_noirq_cb(dev, state, &info); if (!callback && dev->driver && dev->driver->pm) { info = "noirq driver "; @@ -1306,6 +1357,35 @@ int dpm_suspend_noirq(pm_message_t state) return ret; } +static pm_callback_t dpm_subsys_suspend_late_cb(struct device *dev, + pm_message_t state, + const char **info_p) +{ + pm_callback_t callback; + const char *info; + + if (dev->pm_domain) { + info = "late power domain "; + callback = pm_late_early_op(&dev->pm_domain->ops, state); + } else if (dev->type && dev->type->pm) { + info = "late type "; + callback = pm_late_early_op(dev->type->pm, state); + } else if (dev->class && dev->class->pm) { + info = "late class "; + callback = pm_late_early_op(dev->class->pm, state); + } else if (dev->bus && dev->bus->pm) { + info = "late bus "; + callback = pm_late_early_op(dev->bus->pm, state); + } else { + return NULL; + } + + if (info_p) + *info_p = info; + + return callback; +} + /** * __device_suspend_late - Execute a "late suspend" callback for given device. * @dev: Device to handle. @@ -1316,8 +1396,8 @@ int dpm_suspend_noirq(pm_message_t state) */ static int __device_suspend_late(struct device *dev, pm_message_t state, bool async) { - pm_callback_t callback = NULL; - const char *info = NULL; + pm_callback_t callback; + const char *info; int error = 0; TRACE_DEVICE(dev); @@ -1338,19 +1418,7 @@ static int __device_suspend_late(struct device *dev, pm_message_t state, bool as if (dev->power.syscore || dev->power.direct_complete) goto Complete; - if (dev->pm_domain) { - info = "late power domain "; - callback = pm_late_early_op(&dev->pm_domain->ops, state); - } else if (dev->type && dev->type->pm) { - info = "late type "; - callback = pm_late_early_op(dev->type->pm, state); - } else if (dev->class && dev->class->pm) { - info = "late class "; - callback = pm_late_early_op(dev->class->pm, state); - } else if (dev->bus && dev->bus->pm) { - info = "late bus "; - callback = pm_late_early_op(dev->bus->pm, state); - } + callback = dpm_subsys_suspend_late_cb(dev, state, &info); if (!callback && dev->driver && dev->driver->pm) { info = "late driver "; -- cgit v1.2.3 From 75e94645fc3b1007eacb4c7863059f8e8d098cda Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 10 Dec 2017 01:00:45 +0100 Subject: 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 --- Documentation/driver-api/pm/devices.rst | 18 +++---- drivers/base/power/main.c | 85 ++++++++++++++++++++++++++++++--- 2 files changed, 88 insertions(+), 15 deletions(-) (limited to 'drivers/base/power/main.c') diff --git a/Documentation/driver-api/pm/devices.rst b/Documentation/driver-api/pm/devices.rst index b0fe63c91f8d..07026811dcae 100644 --- a/Documentation/driver-api/pm/devices.rst +++ b/Documentation/driver-api/pm/devices.rst @@ -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 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 -after that point until the system-wide transition is over. If that happens, the -driver's system-wide resume callbacks, if present, may still be invoked during -the subsequent system-wide resume transition and the device's runtime power -management status may be set to "active" before enabling runtime PM for it, -so the driver must be prepared to cope with the invocation of its system-wide -resume callbacks back-to-back with its ``->runtime_suspend`` one (without the -intervening ``->runtime_resume`` and so on) and the final state of the device -must reflect the "active" status for runtime PM in that case. +after that point until the system-wide transition is over (the PM core itself +does that for devices whose "noirq", "late" and "early" system-wide PM callbacks +are executed directly by it). If that happens, the driver's system-wide resume +callbacks, if present, may still be invoked during the subsequent system-wide +resume transition and the device's runtime power management status may be set +to "active" before enabling runtime PM for it, so the driver must be prepared to +cope with the invocation of its system-wide resume callbacks back-to-back with +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 the full-power state, as explained in :file:`Documentation/power/runtime_pm.txt`. diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 3c5fdf155c91..154f7b4db8d0 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -539,6 +539,24 @@ void dev_pm_skip_next_resume_phases(struct device *dev) 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: Target device. @@ -580,6 +598,14 @@ static pm_callback_t dpm_subsys_resume_noirq_cb(struct device *dev, 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. * @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); 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 "; callback = pm_noirq_op(dev->driver->pm, state); } +Run: error = dpm_run_callback(callback, dev, state, info); + +Skip: dev->power.is_noirq_suspended = false; 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); } - Out: +Out: complete_all(&dev->power.completion); TRACE_RESUME(error); return error; @@ -1223,18 +1276,26 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a goto Complete; 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 "; callback = pm_noirq_op(dev->driver->pm, state); } +Run: error = dpm_run_callback(callback, dev, state, info); if (error) { async_error = error; goto Complete; } +Skip: dev->power.is_noirq_suspended = true; 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; 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 "; callback = pm_late_early_op(dev->driver->pm, state); } +Run: error = dpm_run_callback(callback, dev, state, info); - if (!error) - dev->power.is_late_suspended = true; - else + if (error) { async_error = error; + goto Complete; + } + +Skip: + dev->power.is_late_suspended = true; Complete: TRACE_SUSPEND(error); -- cgit v1.2.3 From 32bfa56ac158c1ebcc82df2518860f824be5e5be Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 10 Dec 2017 01:02:13 +0100 Subject: PM / core: Direct DPM_FLAG_LEAVE_SUSPENDED handling Make the PM core handle DPM_FLAG_LEAVE_SUSPENDED directly for devices whose "noirq", "late" and "early" driver callbacks are invoked directly by it. Namely, make it skip all of the system-wide resume callbacks for such devices with DPM_FLAG_LEAVE_SUSPENDED set if they are in runtime suspend during the "noirq" phase of system-wide suspend (or analogous) transitions or the system transition under way is a proper suspend (rather than anything related to hibernation) and the device's wakeup settings are compatible with runtime PM (that is, the device cannot generate wakeup signals at all or it is allowed to wake up the system from sleep). Signed-off-by: Rafael J. Wysocki --- Documentation/driver-api/pm/devices.rst | 9 ++++++ drivers/base/power/main.c | 51 ++++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 11 deletions(-) (limited to 'drivers/base/power/main.c') 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; } -- cgit v1.2.3 From 8512220c5782d3e469cf8127a612a6c8f521e2dc Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Tue, 2 Jan 2018 17:08:50 +0100 Subject: PM / core: Assign the wakeup_path status flag in __device_prepare() The PM core in the device_prepare() phase, resets the wakeup_path status flag to the value of device_may_wakeup(). This means if a ->prepare() or a ->suspend() callback for the device would update the device's wakeup setting, this doesn't become reflected in the wakeup_path status flag. In general this isn't a problem, because wakeup settings are not supposed to be changed (via for example calling device_set_wakeup_enable()) during any system wide suspend/resume phase. Nevertheless there are some users, which can be considered as legacy, that don't conform to this behaviour. These legacy cases should be corrected, however until that is done, let's address the issue from the PM core, by moving the assignment of the wakeup_path status flag to the __device_suspend() phase and after the ->suspend() callback has been invoked. Signed-off-by: Ulf Hansson Signed-off-by: Rafael J. Wysocki --- drivers/base/power/main.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers/base/power/main.c') diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 70398e7b3569..ebcec7e677ba 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -1788,6 +1788,9 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) End: if (!error) { dev->power.is_suspended = true; + if (device_may_wakeup(dev)) + dev->power.wakeup_path = true; + dpm_propagate_to_parent(dev); dpm_clear_suppliers_direct_complete(dev); } @@ -1912,7 +1915,7 @@ static int device_prepare(struct device *dev, pm_message_t state) device_lock(dev); - dev->power.wakeup_path = device_may_wakeup(dev); + dev->power.wakeup_path = false; if (dev->power.no_pm_callbacks) { ret = 1; /* Let device go direct_complete */ -- cgit v1.2.3 From c23bd3877bc21d830fa650570fc1a88bea82ecd2 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Tue, 9 Jan 2018 10:03:39 +0100 Subject: PM / core: Re-structure code for clearing the direct_complete flag To make the code more consistent, let's clear the parent's direct_complete flag along with clearing it for suppliers, instead of as currently, when propagating the wakeup_path flag to parents. While changing this, let's take the opportunity to rename the affected internal functions, to make them self-explanatory. Like this: dpm_clear_suppliers_direct_complete -> dpm_clear_superiors_direct_complete dpm_propagate_to_parent -> dpm_propagate_wakeup_to_parent Signed-off-by: Ulf Hansson Signed-off-by: Rafael J. Wysocki --- drivers/base/power/main.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'drivers/base/power/main.c') diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index ebcec7e677ba..720e36ec84ac 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -1660,7 +1660,7 @@ static int legacy_suspend(struct device *dev, pm_message_t state, return error; } -static void dpm_propagate_to_parent(struct device *dev) +static void dpm_propagate_wakeup_to_parent(struct device *dev) { struct device *parent = dev->parent; @@ -1669,18 +1669,23 @@ static void dpm_propagate_to_parent(struct device *dev) spin_lock_irq(&parent->power.lock); - parent->power.direct_complete = false; if (dev->power.wakeup_path && !parent->power.ignore_children) parent->power.wakeup_path = true; spin_unlock_irq(&parent->power.lock); } -static void dpm_clear_suppliers_direct_complete(struct device *dev) +static void dpm_clear_superiors_direct_complete(struct device *dev) { struct device_link *link; int idx; + if (dev->parent) { + spin_lock_irq(&dev->parent->power.lock); + dev->parent->power.direct_complete = false; + spin_unlock_irq(&dev->parent->power.lock); + } + idx = device_links_read_lock(); list_for_each_entry_rcu(link, &dev->links.suppliers, c_node) { @@ -1791,8 +1796,8 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) if (device_may_wakeup(dev)) dev->power.wakeup_path = true; - dpm_propagate_to_parent(dev); - dpm_clear_suppliers_direct_complete(dev); + dpm_propagate_wakeup_to_parent(dev); + dpm_clear_superiors_direct_complete(dev); } device_unlock(dev); -- cgit v1.2.3 From 0a99d767a9b0aae6e0fd983c889c793e4c91684c Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Tue, 9 Jan 2018 10:03:40 +0100 Subject: PM / core: Propagate wakeup_path status flag in __device_suspend_late() Currently the wakeup_path status flag becomes propagated from a child device to its parent device at __device_suspend(). This allows a driver dealing with a parent device to act on the flag from its ->suspend() callback. However, in situations when the wakeup_path status flag needs to be set from a ->suspend_late() callback, its value doesn't get propagated to the parent by the PM core. Let's address this limitation, by also propagating the flag at __device_suspend_late(). Signed-off-by: Ulf Hansson Signed-off-by: Rafael J. Wysocki --- drivers/base/power/main.c | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) (limited to 'drivers/base/power/main.c') diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 720e36ec84ac..02a497e7c785 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -1447,6 +1447,21 @@ int dpm_suspend_noirq(pm_message_t state) return ret; } +static void dpm_propagate_wakeup_to_parent(struct device *dev) +{ + struct device *parent = dev->parent; + + if (!parent) + return; + + spin_lock_irq(&parent->power.lock); + + if (dev->power.wakeup_path && !parent->power.ignore_children) + parent->power.wakeup_path = true; + + spin_unlock_irq(&parent->power.lock); +} + static pm_callback_t dpm_subsys_suspend_late_cb(struct device *dev, pm_message_t state, const char **info_p) @@ -1527,6 +1542,7 @@ Run: async_error = error; goto Complete; } + dpm_propagate_wakeup_to_parent(dev); Skip: dev->power.is_late_suspended = true; @@ -1660,21 +1676,6 @@ static int legacy_suspend(struct device *dev, pm_message_t state, return error; } -static void dpm_propagate_wakeup_to_parent(struct device *dev) -{ - struct device *parent = dev->parent; - - if (!parent) - return; - - spin_lock_irq(&parent->power.lock); - - if (dev->power.wakeup_path && !parent->power.ignore_children) - parent->power.wakeup_path = true; - - spin_unlock_irq(&parent->power.lock); -} - static void dpm_clear_superiors_direct_complete(struct device *dev) { struct device_link *link; -- cgit v1.2.3