aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRafael J. Wysocki <rjw@sisk.pl>2012-03-04 23:11:14 +0100
committerRafael J. Wysocki <rjw@sisk.pl>2012-03-04 23:11:14 +0100
commit643161ace2a7624fd0106ede12ae43bcbbfc1de0 (patch)
tree56fb5d4af5c5e46da8cfe3c613a84f1402a60d41
parent743c5bc210f45b728a246da65fd1a3160566d34d (diff)
parent37f08be11be9a7d9351fb1b9b408259519a126f3 (diff)
Merge branch 'pm-sleep'
* pm-sleep: PM / Freezer: Remove references to TIF_FREEZE in comments PM / Sleep: Add more wakeup source initialization routines PM / Hibernate: Enable usermodehelpers in hibernate() error path PM / Sleep: Make __pm_stay_awake() delete wakeup source timers PM / Sleep: Fix race conditions related to wakeup source timer function PM / Sleep: Fix possible infinite loop during wakeup source destruction PM / Hibernate: print physical addresses consistently with other parts of kernel PM: Add comment describing relationships between PM callbacks to pm.h PM / Sleep: Drop suspend_stats_update() PM / Sleep: Make enter_state() in kernel/power/suspend.c static PM / Sleep: Unify kerneldoc comments in kernel/power/suspend.c PM / Sleep: Remove unnecessary label from suspend_freeze_processes() PM / Sleep: Do not check wakeup too often in try_to_freeze_tasks() PM / Sleep: Initialize wakeup source locks in wakeup_source_add() PM / Hibernate: Refactor and simplify freezer_test_done PM / Hibernate: Thaw kernel threads in hibernation_snapshot() in error/test path PM / Freezer / Docs: Document the beauty of freeze/thaw semantics PM / Suspend: Avoid code duplication in suspend statistics update PM / Sleep: Introduce generic callbacks for new device PM phases PM / Sleep: Introduce "late suspend" and "early resume" of devices
-rw-r--r--Documentation/power/devices.txt93
-rw-r--r--Documentation/power/freezing-of-tasks.txt21
-rw-r--r--arch/x86/kernel/apm_32.c11
-rw-r--r--drivers/base/power/generic_ops.c157
-rw-r--r--drivers/base/power/main.c247
-rw-r--r--drivers/base/power/wakeup.c85
-rw-r--r--drivers/xen/manage.c6
-rw-r--r--include/linux/pm.h58
-rw-r--r--include/linux/pm_wakeup.h22
-rw-r--r--include/linux/suspend.h4
-rw-r--r--kernel/exit.c2
-rw-r--r--kernel/freezer.c6
-rw-r--r--kernel/kexec.c8
-rw-r--r--kernel/power/hibernate.c47
-rw-r--r--kernel/power/main.c20
-rw-r--r--kernel/power/power.h7
-rw-r--r--kernel/power/process.c24
-rw-r--r--kernel/power/snapshot.c7
-rw-r--r--kernel/power/suspend.c84
-rw-r--r--kernel/power/user.c12
20 files changed, 662 insertions, 259 deletions
diff --git a/Documentation/power/devices.txt b/Documentation/power/devices.txt
index 20af7def23c8..872815cd41d3 100644
--- a/Documentation/power/devices.txt
+++ b/Documentation/power/devices.txt
@@ -96,6 +96,12 @@ struct dev_pm_ops {
int (*thaw)(struct device *dev);
int (*poweroff)(struct device *dev);
int (*restore)(struct device *dev);
+ int (*suspend_late)(struct device *dev);
+ int (*resume_early)(struct device *dev);
+ int (*freeze_late)(struct device *dev);
+ int (*thaw_early)(struct device *dev);
+ int (*poweroff_late)(struct device *dev);
+ int (*restore_early)(struct device *dev);
int (*suspend_noirq)(struct device *dev);
int (*resume_noirq)(struct device *dev);
int (*freeze_noirq)(struct device *dev);
@@ -305,7 +311,7 @@ Entering System Suspend
-----------------------
When the system goes into the standby or memory sleep state, the phases are:
- prepare, suspend, suspend_noirq.
+ prepare, suspend, suspend_late, suspend_noirq.
1. The prepare phase is meant to prevent races by preventing new devices
from being registered; the PM core would never know that all the
@@ -324,7 +330,12 @@ When the system goes into the standby or memory sleep state, the phases are:
appropriate low-power state, depending on the bus type the device is on,
and they may enable wakeup events.
- 3. The suspend_noirq phase occurs after IRQ handlers have been disabled,
+ 3 For a number of devices it is convenient to split suspend into the
+ "quiesce device" and "save device state" phases, in which cases
+ suspend_late is meant to do the latter. It is always executed after
+ runtime power management has been disabled for all devices.
+
+ 4. The suspend_noirq phase occurs after IRQ handlers have been disabled,
which means that the driver's interrupt handler will not be called while
the callback method is running. The methods should save the values of
the device's registers that weren't saved previously and finally put the
@@ -359,7 +370,7 @@ Leaving System Suspend
----------------------
When resuming from standby or memory sleep, the phases are:
- resume_noirq, resume, complete.
+ resume_noirq, resume_early, resume, complete.
1. The resume_noirq callback methods should perform any actions needed
before the driver's interrupt handlers are invoked. This generally
@@ -375,14 +386,18 @@ When resuming from standby or memory sleep, the phases are:
device driver's ->pm.resume_noirq() method to perform device-specific
actions.
- 2. The resume methods should bring the the device back to its operating
+ 2. The resume_early methods should prepare devices for the execution of
+ the resume methods. This generally involves undoing the actions of the
+ preceding suspend_late phase.
+
+ 3 The resume methods should bring the the device back to its operating
state, so that it can perform normal I/O. This generally involves
undoing the actions of the suspend phase.
- 3. The complete phase uses only a bus callback. The method should undo the
- actions of the prepare phase. Note, however, that new children may be
- registered below the device as soon as the resume callbacks occur; it's
- not necessary to wait until the complete phase.
+ 4. The complete phase should undo the actions of the prepare phase. Note,
+ however, that new children may be registered below the device as soon as
+ the resume callbacks occur; it's not necessary to wait until the
+ complete phase.
At the end of these phases, drivers should be as functional as they were before
suspending: I/O can be performed using DMA and IRQs, and the relevant clocks are
@@ -429,8 +444,8 @@ an image of the system memory while everything is stable, reactivate all
devices (thaw), write the image to permanent storage, and finally shut down the
system (poweroff). The phases used to accomplish this are:
- prepare, freeze, freeze_noirq, thaw_noirq, thaw, complete,
- prepare, poweroff, poweroff_noirq
+ prepare, freeze, freeze_late, freeze_noirq, thaw_noirq, thaw_early,
+ thaw, complete, prepare, poweroff, poweroff_late, poweroff_noirq
1. The prepare phase is discussed in the "Entering System Suspend" section
above.
@@ -441,7 +456,11 @@ system (poweroff). The phases used to accomplish this are:
save time it's best not to do so. Also, the device should not be
prepared to generate wakeup events.
- 3. The freeze_noirq phase is analogous to the suspend_noirq phase discussed
+ 3. The freeze_late phase is analogous to the suspend_late phase described
+ above, except that the device should not be put in a low-power state and
+ should not be allowed to generate wakeup events by it.
+
+ 4. The freeze_noirq phase is analogous to the suspend_noirq phase discussed
above, except again that the device should not be put in a low-power
state and should not be allowed to generate wakeup events.
@@ -449,15 +468,19 @@ At this point the system image is created. All devices should be inactive and
the contents of memory should remain undisturbed while this happens, so that the
image forms an atomic snapshot of the system state.
- 4. The thaw_noirq phase is analogous to the resume_noirq phase discussed
+ 5. The thaw_noirq phase is analogous to the resume_noirq phase discussed
above. The main difference is that its methods can assume the device is
in the same state as at the end of the freeze_noirq phase.
- 5. The thaw phase is analogous to the resume phase discussed above. Its
+ 6. The thaw_early phase is analogous to the resume_early phase described
+ above. Its methods should undo the actions of the preceding
+ freeze_late, if necessary.
+
+ 7. The thaw phase is analogous to the resume phase discussed above. Its
methods should bring the device back to an operating state, so that it
can be used for saving the image if necessary.
- 6. The complete phase is discussed in the "Leaving System Suspend" section
+ 8. The complete phase is discussed in the "Leaving System Suspend" section
above.
At this point the system image is saved, and the devices then need to be
@@ -465,16 +488,19 @@ prepared for the upcoming system shutdown. This is much like suspending them
before putting the system into the standby or memory sleep state, and the phases
are similar.
- 7. The prepare phase is discussed above.
+ 9. The prepare phase is discussed above.
+
+ 10. The poweroff phase is analogous to the suspend phase.
- 8. The poweroff phase is analogous to the suspend phase.
+ 11. The poweroff_late phase is analogous to the suspend_late phase.
- 9. The poweroff_noirq phase is analogous to the suspend_noirq phase.
+ 12. The poweroff_noirq phase is analogous to the suspend_noirq phase.
-The poweroff and poweroff_noirq callbacks should do essentially the same things
-as the suspend and suspend_noirq callbacks. The only notable difference is that
-they need not store the device register values, because the registers should
-already have been stored during the freeze or freeze_noirq phases.
+The poweroff, poweroff_late and poweroff_noirq callbacks should do essentially
+the same things as the suspend, suspend_late and suspend_noirq callbacks,
+respectively. The only notable difference is that they need not store the
+device register values, because the registers should already have been stored
+during the freeze, freeze_late or freeze_noirq phases.
Leaving Hibernation
@@ -518,22 +544,25 @@ To achieve this, the image kernel must restore the devices' pre-hibernation
functionality. The operation is much like waking up from the memory sleep
state, although it involves different phases:
- restore_noirq, restore, complete
+ restore_noirq, restore_early, restore, complete
1. The restore_noirq phase is analogous to the resume_noirq phase.
- 2. The restore phase is analogous to the resume phase.
+ 2. The restore_early phase is analogous to the resume_early phase.
+
+ 3. The restore phase is analogous to the resume phase.
- 3. The complete phase is discussed above.
+ 4. The complete phase is discussed above.
-The main difference from resume[_noirq] is that restore[_noirq] must assume the
-device has been accessed and reconfigured by the boot loader or the boot kernel.
-Consequently the state of the device may be different from the state remembered
-from the freeze and freeze_noirq phases. The device may even need to be reset
-and completely re-initialized. In many cases this difference doesn't matter, so
-the resume[_noirq] and restore[_norq] method pointers can be set to the same
-routines. Nevertheless, different callback pointers are used in case there is a
-situation where it actually matters.
+The main difference from resume[_early|_noirq] is that restore[_early|_noirq]
+must assume the device has been accessed and reconfigured by the boot loader or
+the boot kernel. Consequently the state of the device may be different from the
+state remembered from the freeze, freeze_late and freeze_noirq phases. The
+device may even need to be reset and completely re-initialized. In many cases
+this difference doesn't matter, so the resume[_early|_noirq] and
+restore[_early|_norq] method pointers can be set to the same routines.
+Nevertheless, different callback pointers are used in case there is a situation
+where it actually does matter.
Device Power Management Domains
diff --git a/Documentation/power/freezing-of-tasks.txt b/Documentation/power/freezing-of-tasks.txt
index ebd7490ef1df..ec715cd78fbb 100644
--- a/Documentation/power/freezing-of-tasks.txt
+++ b/Documentation/power/freezing-of-tasks.txt
@@ -63,6 +63,27 @@ devices have been reinitialized, the function thaw_processes() is called in
order to clear the PF_FROZEN flag for each frozen task. Then, the tasks that
have been frozen leave __refrigerator() and continue running.
+
+Rationale behind the functions dealing with freezing and thawing of tasks:
+-------------------------------------------------------------------------
+
+freeze_processes():
+ - freezes only userspace tasks
+
+freeze_kernel_threads():
+ - freezes all tasks (including kernel threads) because we can't freeze
+ kernel threads without freezing userspace tasks
+
+thaw_kernel_threads():
+ - thaws only kernel threads; this is particularly useful if we need to do
+ anything special in between thawing of kernel threads and thawing of
+ userspace tasks, or if we want to postpone the thawing of userspace tasks
+
+thaw_processes():
+ - thaws all tasks (including kernel threads) because we can't thaw userspace
+ tasks without thawing kernel threads
+
+
III. Which kernel threads are freezable?
Kernel threads are not freezable by default. However, a kernel thread may clear
diff --git a/arch/x86/kernel/apm_32.c b/arch/x86/kernel/apm_32.c
index f76623cbe263..5d56931a15b3 100644
--- a/arch/x86/kernel/apm_32.c
+++ b/arch/x86/kernel/apm_32.c
@@ -1234,8 +1234,7 @@ static int suspend(int vetoable)
struct apm_user *as;
dpm_suspend_start(PMSG_SUSPEND);
-
- dpm_suspend_noirq(PMSG_SUSPEND);
+ dpm_suspend_end(PMSG_SUSPEND);
local_irq_disable();
syscore_suspend();
@@ -1259,9 +1258,9 @@ static int suspend(int vetoable)
syscore_resume();
local_irq_enable();
- dpm_resume_noirq(PMSG_RESUME);
-
+ dpm_resume_start(PMSG_RESUME);
dpm_resume_end(PMSG_RESUME);
+
queue_event(APM_NORMAL_RESUME, NULL);
spin_lock(&user_list_lock);
for (as = user_list; as != NULL; as = as->next) {
@@ -1277,7 +1276,7 @@ static void standby(void)
{
int err;
- dpm_suspend_noirq(PMSG_SUSPEND);
+ dpm_suspend_end(PMSG_SUSPEND);
local_irq_disable();
syscore_suspend();
@@ -1291,7 +1290,7 @@ static void standby(void)
syscore_resume();
local_irq_enable();
- dpm_resume_noirq(PMSG_RESUME);
+ dpm_resume_start(PMSG_RESUME);
}
static apm_event_t get_event(void)
diff --git a/drivers/base/power/generic_ops.c b/drivers/base/power/generic_ops.c
index 10bdd793f0bd..d03d290f31c2 100644
--- a/drivers/base/power/generic_ops.c
+++ b/drivers/base/power/generic_ops.c
@@ -92,59 +92,28 @@ int pm_generic_prepare(struct device *dev)
}
/**
- * __pm_generic_call - Generic suspend/freeze/poweroff/thaw subsystem callback.
- * @dev: Device to handle.
- * @event: PM transition of the system under way.
- * @bool: Whether or not this is the "noirq" stage.
- *
- * Execute the PM callback corresponding to @event provided by the driver of
- * @dev, if defined, and return its error code. Return 0 if the callback is
- * not present.
+ * pm_generic_suspend_noirq - Generic suspend_noirq callback for subsystems.
+ * @dev: Device to suspend.
*/
-static int __pm_generic_call(struct device *dev, int event, bool noirq)
+int pm_generic_suspend_noirq(struct device *dev)
{
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
- int (*callback)(struct device *);
-
- if (!pm)
- return 0;
-
- switch (event) {
- case PM_EVENT_SUSPEND:
- callback = noirq ? pm->suspend_noirq : pm->suspend;
- break;
- case PM_EVENT_FREEZE:
- callback = noirq ? pm->freeze_noirq : pm->freeze;
- break;
- case PM_EVENT_HIBERNATE:
- callback = noirq ? pm->poweroff_noirq : pm->poweroff;
- break;
- case PM_EVENT_RESUME:
- callback = noirq ? pm->resume_noirq : pm->resume;
- break;
- case PM_EVENT_THAW:
- callback = noirq ? pm->thaw_noirq : pm->thaw;
- break;
- case PM_EVENT_RESTORE:
- callback = noirq ? pm->restore_noirq : pm->restore;
- break;
- default:
- callback = NULL;
- break;
- }
- return callback ? callback(dev) : 0;
+ return pm && pm->suspend_noirq ? pm->suspend_noirq(dev) : 0;
}
+EXPORT_SYMBOL_GPL(pm_generic_suspend_noirq);
/**
- * pm_generic_suspend_noirq - Generic suspend_noirq callback for subsystems.
+ * pm_generic_suspend_late - Generic suspend_late callback for subsystems.
* @dev: Device to suspend.
*/
-int pm_generic_suspend_noirq(struct device *dev)
+int pm_generic_suspend_late(struct device *dev)
{
- return __pm_generic_call(dev, PM_EVENT_SUSPEND, true);
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->suspend_late ? pm->suspend_late(dev) : 0;
}
-EXPORT_SYMBOL_GPL(pm_generic_suspend_noirq);
+EXPORT_SYMBOL_GPL(pm_generic_suspend_late);
/**
* pm_generic_suspend - Generic suspend callback for subsystems.
@@ -152,7 +121,9 @@ EXPORT_SYMBOL_GPL(pm_generic_suspend_noirq);
*/
int pm_generic_suspend(struct device *dev)
{
- return __pm_generic_call(dev, PM_EVENT_SUSPEND, false);
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->suspend ? pm->suspend(dev) : 0;
}
EXPORT_SYMBOL_GPL(pm_generic_suspend);
@@ -162,17 +133,33 @@ EXPORT_SYMBOL_GPL(pm_generic_suspend);
*/
int pm_generic_freeze_noirq(struct device *dev)
{
- return __pm_generic_call(dev, PM_EVENT_FREEZE, true);
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->freeze_noirq ? pm->freeze_noirq(dev) : 0;
}
EXPORT_SYMBOL_GPL(pm_generic_freeze_noirq);
/**
+ * pm_generic_freeze_late - Generic freeze_late callback for subsystems.
+ * @dev: Device to freeze.
+ */
+int pm_generic_freeze_late(struct device *dev)
+{
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->freeze_late ? pm->freeze_late(dev) : 0;
+}
+EXPORT_SYMBOL_GPL(pm_generic_freeze_late);
+
+/**
* pm_generic_freeze - Generic freeze callback for subsystems.
* @dev: Device to freeze.
*/
int pm_generic_freeze(struct device *dev)
{
- return __pm_generic_call(dev, PM_EVENT_FREEZE, false);
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->freeze ? pm->freeze(dev) : 0;
}
EXPORT_SYMBOL_GPL(pm_generic_freeze);
@@ -182,17 +169,33 @@ EXPORT_SYMBOL_GPL(pm_generic_freeze);
*/
int pm_generic_poweroff_noirq(struct device *dev)
{
- return __pm_generic_call(dev, PM_EVENT_HIBERNATE, true);
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->poweroff_noirq ? pm->poweroff_noirq(dev) : 0;
}
EXPORT_SYMBOL_GPL(pm_generic_poweroff_noirq);
/**
+ * pm_generic_poweroff_late - Generic poweroff_late callback for subsystems.
+ * @dev: Device to handle.
+ */
+int pm_generic_poweroff_late(struct device *dev)
+{
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->poweroff_late ? pm->poweroff_late(dev) : 0;
+}
+EXPORT_SYMBOL_GPL(pm_generic_poweroff_late);
+
+/**
* pm_generic_poweroff - Generic poweroff callback for subsystems.
* @dev: Device to handle.
*/
int pm_generic_poweroff(struct device *dev)
{
- return __pm_generic_call(dev, PM_EVENT_HIBERNATE, false);
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->poweroff ? pm->poweroff(dev) : 0;
}
EXPORT_SYMBOL_GPL(pm_generic_poweroff);
@@ -202,17 +205,33 @@ EXPORT_SYMBOL_GPL(pm_generic_poweroff);
*/
int pm_generic_thaw_noirq(struct device *dev)
{
- return __pm_generic_call(dev, PM_EVENT_THAW, true);
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->thaw_noirq ? pm->thaw_noirq(dev) : 0;
}
EXPORT_SYMBOL_GPL(pm_generic_thaw_noirq);
/**
+ * pm_generic_thaw_early - Generic thaw_early callback for subsystems.
+ * @dev: Device to thaw.
+ */
+int pm_generic_thaw_early(struct device *dev)
+{
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->thaw_early ? pm->thaw_early(dev) : 0;
+}
+EXPORT_SYMBOL_GPL(pm_generic_thaw_early);
+
+/**
* pm_generic_thaw - Generic thaw callback for subsystems.
* @dev: Device to thaw.
*/
int pm_generic_thaw(struct device *dev)
{
- return __pm_generic_call(dev, PM_EVENT_THAW, false);
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->thaw ? pm->thaw(dev) : 0;
}
EXPORT_SYMBOL_GPL(pm_generic_thaw);
@@ -222,17 +241,33 @@ EXPORT_SYMBOL_GPL(pm_generic_thaw);
*/
int pm_generic_resume_noirq(struct device *dev)
{
- return __pm_generic_call(dev, PM_EVENT_RESUME, true);
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->resume_noirq ? pm->resume_noirq(dev) : 0;
}
EXPORT_SYMBOL_GPL(pm_generic_resume_noirq);
/**
+ * pm_generic_resume_early - Generic resume_early callback for subsystems.
+ * @dev: Device to resume.
+ */
+int pm_generic_resume_early(struct device *dev)
+{
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->resume_early ? pm->resume_early(dev) : 0;
+}
+EXPORT_SYMBOL_GPL(pm_generic_resume_early);
+
+/**
* pm_generic_resume - Generic resume callback for subsystems.
* @dev: Device to resume.
*/
int pm_generic_resume(struct device *dev)
{
- return __pm_generic_call(dev, PM_EVENT_RESUME, false);
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->resume ? pm->resume(dev) : 0;
}
EXPORT_SYMBOL_GPL(pm_generic_resume);
@@ -242,17 +277,33 @@ EXPORT_SYMBOL_GPL(pm_generic_resume);
*/
int pm_generic_restore_noirq(struct device *dev)
{
- return __pm_generic_call(dev, PM_EVENT_RESTORE, true);
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->restore_noirq ? pm->restore_noirq(dev) : 0;
}
EXPORT_SYMBOL_GPL(pm_generic_restore_noirq);
/**
+ * pm_generic_restore_early - Generic restore_early callback for subsystems.
+ * @dev: Device to resume.
+ */
+int pm_generic_restore_early(struct device *dev)
+{
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->restore_early ? pm->restore_early(dev) : 0;
+}
+EXPORT_SYMBOL_GPL(pm_generic_restore_early);
+
+/**
* pm_generic_restore - Generic restore callback for subsystems.
* @dev: Device to restore.
*/
int pm_generic_restore(struct device *dev)
{
- return __pm_generic_call(dev, PM_EVENT_RESTORE, false);
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ return pm && pm->restore ? pm->restore(dev) : 0;
}
EXPORT_SYMBOL_GPL(pm_generic_restore);
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index e2cc3d2e0ecc..b462c0e341cb 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -47,6 +47,7 @@ typedef int (*pm_callback_t)(struct device *);
LIST_HEAD(dpm_list);
LIST_HEAD(dpm_prepared_list);
LIST_HEAD(dpm_suspended_list);
+LIST_HEAD(dpm_late_early_list);
LIST_HEAD(dpm_noirq_list);
struct suspend_stats suspend_stats;
@@ -246,6 +247,40 @@ static pm_callback_t pm_op(const struct dev_pm_ops *ops, pm_message_t state)
}
/**
+ * pm_late_early_op - Return the PM operation appropriate for given PM event.
+ * @ops: PM operations to choose from.
+ * @state: PM transition of the system being carried out.
+ *
+ * Runtime PM is disabled for @dev while this function is being executed.
+ */
+static pm_callback_t pm_late_early_op(const struct dev_pm_ops *ops,
+ pm_message_t state)
+{
+ switch (state.event) {
+#ifdef CONFIG_SUSPEND
+ case PM_EVENT_SUSPEND:
+ return ops->suspend_late;
+ case PM_EVENT_RESUME:
+ return ops->resume_early;
+#endif /* CONFIG_SUSPEND */
+#ifdef CONFIG_HIBERNATE_CALLBACKS
+ case PM_EVENT_FREEZE:
+ case PM_EVENT_QUIESCE:
+ return ops->freeze_late;
+ case PM_EVENT_HIBERNATE:
+ return ops->poweroff_late;
+ case PM_EVENT_THAW:
+ case PM_EVENT_RECOVER:
+ return ops->thaw_early;
+ case PM_EVENT_RESTORE:
+ return ops->restore_early;
+#endif /* CONFIG_HIBERNATE_CALLBACKS */
+ }
+
+ return NULL;
+}
+
+/**
* pm_noirq_op - Return the PM operation appropriate for given PM event.
* @ops: PM operations to choose from.
* @state: PM transition of the system being carried out.
@@ -374,21 +409,21 @@ static int device_resume_noirq(struct device *dev, pm_message_t state)
TRACE_RESUME(0);
if (dev->pm_domain) {
- info = "EARLY power domain ";
+ info = "noirq power domain ";
callback = pm_noirq_op(&dev->pm_domain->ops, state);
} else if (dev->type && dev->type->pm) {
- info = "EARLY type ";
+ info = "noirq type ";
callback = pm_noirq_op(dev->type->pm, state);
} else if (dev->class && dev->class->pm) {
- info = "EARLY class ";
+ info = "noirq class ";
callback = pm_noirq_op(dev->class->pm, state);
} else if (dev->bus && dev->bus->pm) {
- info = "EARLY bus ";
+ info = "noirq bus ";
callback = pm_noirq_op(dev->bus->pm, state);
}
if (!callback && dev->driver && dev->driver->pm) {
- info = "EARLY driver ";
+ info = "noirq driver ";
callback = pm_noirq_op(dev->driver->pm, state);
}
@@ -399,13 +434,13 @@ static int device_resume_noirq(struct device *dev, pm_message_t state)
}
/**
- * dpm_resume_noirq - Execute "early resume" callbacks for non-sysdev devices.
+ * dpm_resume_noirq - Execute "noirq resume" callbacks for all devices.
* @state: PM transition of the system being carried out.
*
- * Call the "noirq" resume handlers for all devices marked as DPM_OFF_IRQ and
+ * Call the "noirq" resume handlers for all devices in dpm_noirq_list and
* enable device drivers to receive interrupts.
*/
-void dpm_resume_noirq(pm_message_t state)
+static void dpm_resume_noirq(pm_message_t state)
{
ktime_t starttime = ktime_get();
@@ -415,7 +450,7 @@ void dpm_resume_noirq(pm_message_t state)
int error;
get_device(dev);
- list_move_tail(&dev->power.entry, &dpm_suspended_list);
+ list_move_tail(&dev->power.entry, &dpm_late_early_list);
mutex_unlock(&dpm_list_mtx);
error = device_resume_noirq(dev, state);
@@ -423,6 +458,80 @@ void dpm_resume_noirq(pm_message_t state)
suspend_stats.failed_resume_noirq++;
dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);
dpm_save_failed_dev(dev_name(dev));
+ pm_dev_err(dev, state, " noirq", error);
+ }
+
+ mutex_lock(&dpm_list_mtx);
+ put_device(dev);
+ }
+ mutex_unlock(&dpm_list_mtx);
+ dpm_show_time(starttime, state, "noirq");
+ resume_device_irqs();
+}
+
+/**
+ * device_resume_early - Execute an "early resume" callback for given device.
+ * @dev: Device to handle.
+ * @state: PM transition of the system being carried out.
+ *
+ * Runtime PM is disabled for @dev while this function is being executed.
+ */
+static int device_resume_early(struct device *dev, pm_message_t state)
+{
+ pm_callback_t callback = NULL;
+ char *info = NULL;
+ int error = 0;
+
+ TRACE_DEVICE(dev);
+ TRACE_RESUME(0);
+
+ 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);
+ }
+
+ if (!callback && dev->driver && dev->driver->pm) {
+ info = "early driver ";
+ callback = pm_late_early_op(dev->driver->pm, state);
+ }
+
+ error = dpm_run_callback(callback, dev, state, info);
+
+ TRACE_RESUME(error);
+ return error;
+}
+
+/**
+ * dpm_resume_early - Execute "early resume" callbacks for all devices.
+ * @state: PM transition of the system being carried out.
+ */
+static void dpm_resume_early(pm_message_t state)
+{
+ ktime_t starttime = ktime_get();
+
+ mutex_lock(&dpm_list_mtx);
+ while (!list_empty(&dpm_late_early_list)) {
+ struct device *dev = to_device(dpm_late_early_list.next);
+ int error;
+
+ get_device(dev);
+ list_move_tail(&dev->power.entry, &dpm_suspended_list);
+ mutex_unlock(&dpm_list_mtx);
+
+ error = device_resume_early(dev, state);
+ if (error) {
+ suspend_stats.failed_resume_early++;
+ dpm_save_failed_step(SUSPEND_RESUME_EARLY);
+ dpm_save_failed_dev(dev_name(dev));
pm_dev_err(dev, state, " early", error);
}
@@ -431,9 +540,18 @@ void dpm_resume_noirq(pm_message_t state)
}
mutex_unlock(&dpm_list_mtx);
dpm_show_time(starttime, state, "early");
- resume_device_irqs();
}
-EXPORT_SYMBOL_GPL(dpm_resume_noirq);
+
+/**
+ * dpm_resume_start - Execute "noirq" and "early" device callbacks.
+ * @state: PM transition of the system being carried out.
+ */
+void dpm_resume_start(pm_message_t state)
+{
+ dpm_resume_noirq(state);
+ dpm_resume_early(state);
+}
+EXPORT_SYMBOL_GPL(dpm_resume_start);
/**
* device_resume - Execute "resume" callbacks for given device.
@@ -716,21 +834,21 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state)
char *info = NULL;
if (dev->pm_domain) {
- info = "LATE power domain ";
+ info = "noirq power domain ";
callback = pm_noirq_op(&dev->pm_domain->ops, state);
} else if (dev->type && dev->type->pm) {
- info = "LATE type ";
+ info = "noirq type ";
callback = pm_noirq_op(dev->type->pm, state);
} else if (dev->class && dev->class->pm) {
- info = "LATE class ";
+ info = "noirq class ";
callback = pm_noirq_op(dev->class->pm, state);
} else if (dev->bus && dev->bus->pm) {
- info = "LATE bus ";
+ info = "noirq bus ";
callback = pm_noirq_op(dev->bus->pm, state);
}
if (!callback && dev->driver && dev->driver->pm) {
- info = "LATE driver ";
+ info = "noirq driver ";
callback = pm_noirq_op(dev->driver->pm, state);
}
@@ -738,21 +856,21 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state)
}
/**
- * dpm_suspend_noirq - Execute "late suspend" callbacks for non-sysdev devices.
+ * dpm_suspend_noirq - Execute "noirq suspend" callbacks for all devices.
* @state: PM transition of the system being carried out.
*
* Prevent device drivers from receiving interrupts and call the "noirq" suspend
* handlers for all non-sysdev devices.
*/
-int dpm_suspend_noirq(pm_message_t state)
+static int dpm_suspend_noirq(pm_message_t state)
{
ktime_t starttime = ktime_get();
int error = 0;
suspend_device_irqs();
mutex_lock(&dpm_list_mtx);
- while (!list_empty(&dpm_suspended_list)) {
- struct device *dev = to_device(dpm_suspended_list.prev);
+ while (!list_empty(&dpm_late_early_list)) {
+ struct device *dev = to_device(dpm_late_early_list.prev);
get_device(dev);
mutex_unlock(&dpm_list_mtx);
@@ -761,7 +879,7 @@ int dpm_suspend_noirq(pm_message_t state)
mutex_lock(&dpm_list_mtx);
if (error) {
- pm_dev_err(dev, state, " late", error);
+ pm_dev_err(dev, state, " noirq", error);
suspend_stats.failed_suspend_noirq++;
dpm_save_failed_step(SUSPEND_SUSPEND_NOIRQ);
dpm_save_failed_dev(dev_name(dev));
@@ -776,10 +894,95 @@ int dpm_suspend_noirq(pm_message_t state)
if (error)
dpm_resume_noirq(resume_event(state));
else
+ dpm_show_time(starttime, state, "noirq");
+ return error;
+}
+
+/**
+ * device_suspend_late - Execute a "late suspend" callback for given device.
+ * @dev: Device to handle.
+ * @state: PM transition of the system being carried out.
+ *
+ * Runtime PM is disabled for @dev while this function is being executed.
+ */
+static int device_suspend_late(struct device *dev, pm_message_t state)
+{
+ pm_callback_t callback = NULL;
+ char *info = NULL;
+
+ 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 ";
+