| From dca3a5c3507d066f750ba2ce7ee8f8f695d75162 Mon Sep 17 00:00:00 2001 |
| From: "Rafael J. Wysocki" <rjw@sisk.pl> |
| Date: Sun, 29 Jan 2012 20:38:29 +0100 |
| Subject: PM / Sleep: Introduce "late suspend" and "early resume" of devices |
| |
| The current device suspend/resume phases during system-wide power |
| transitions appear to be insufficient for some platforms that want |
| to use the same callback routines for saving device states and |
| related operations during runtime suspend/resume as well as during |
| system suspend/resume. In principle, they could point their |
| .suspend_noirq() and .resume_noirq() to the same callback routines |
| as their .runtime_suspend() and .runtime_resume(), respectively, |
| but at least some of them require device interrupts to be enabled |
| while the code in those routines is running. |
| |
| It also makes sense to have device suspend-resume callbacks that will |
| be executed with runtime PM disabled and with device interrupts |
| enabled in case someone needs to run some special code in that |
| context during system-wide power transitions. |
| |
| Apart from this, .suspend_noirq() and .resume_noirq() were introduced |
| as a workaround for drivers using shared interrupts and failing to |
| prevent their interrupt handlers from accessing suspended hardware. |
| It appears to be better not to use them for other porposes, or we may |
| have to deal with some serious confusion (which seems to be happening |
| already). |
| |
| For the above reasons, introduce new device suspend/resume phases, |
| "late suspend" and "early resume" (and analogously for hibernation) |
| whose callback will be executed with runtime PM disabled and with |
| device interrupts enabled and whose callback pointers generally may |
| point to runtime suspend/resume routines. |
| |
| Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl> |
| Reviewed-by: Mark Brown <broonie@opensource.wolfsonmicro.com> |
| Reviewed-by: Kevin Hilman <khilman@ti.com> |
| (cherry picked from commit cf579dfb82550e34de7ccf3ef090d8b834ccd3a9) |
| |
| Signed-off-by: Simon Horman <horms@verge.net.au> |
| --- |
| Documentation/power/devices.txt | 93 +++++++++------ |
| arch/x86/kernel/apm_32.c | 11 - |
| drivers/base/power/main.c | 247 ++++++++++++++++++++++++++++++++++++---- |
| drivers/xen/manage.c | 6 |
| include/linux/pm.h | 43 +++++- |
| include/linux/suspend.h | 4 |
| kernel/kexec.c | 8 - |
| kernel/power/hibernate.c | 24 +-- |
| kernel/power/main.c | 8 - |
| kernel/power/suspend.c | 4 |
| 10 files changed, 357 insertions(+), 91 deletions(-) |
| |
| --- 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 |
| 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 sle |
| 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 ever |
| 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 a |
| 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 create |
| 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 shutdow |
| 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 r |
| 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 |
| --- a/arch/x86/kernel/apm_32.c |
| +++ b/arch/x86/kernel/apm_32.c |
| @@ -1236,8 +1236,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(); |
| @@ -1261,9 +1260,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) { |
| @@ -1279,7 +1278,7 @@ static void standby(void) |
| { |
| int err; |
| |
| - dpm_suspend_noirq(PMSG_SUSPEND); |
| + dpm_suspend_end(PMSG_SUSPEND); |
| |
| local_irq_disable(); |
| syscore_suspend(); |
| @@ -1293,7 +1292,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) |
| --- a/drivers/base/power/main.c |
| +++ b/drivers/base/power/main.c |
| @@ -47,6 +47,7 @@ typedef int (*pm_callback_t)(struct devi |
| 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 |
| } |
| |
| /** |
| + * 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 de |
| 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 de |
| } |
| |
| /** |
| - * 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 d |
| 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 d |
| } |
| |
| /** |
| - * 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 "; |
| + 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); |
| + } |
| + |
| + if (!callback && dev->driver && dev->driver->pm) { |
| + info = "late driver "; |
| + callback = pm_late_early_op(dev->driver->pm, state); |
| + } |
| + |
| + return dpm_run_callback(callback, dev, state, info); |
| +} |
| + |
| +/** |
| + * dpm_suspend_late - Execute "late suspend" callbacks for all devices. |
| + * @state: PM transition of the system being carried out. |
| + */ |
| +static int dpm_suspend_late(pm_message_t state) |
| +{ |
| + ktime_t starttime = ktime_get(); |
| + int error = 0; |
| + |
| + mutex_lock(&dpm_list_mtx); |
| + while (!list_empty(&dpm_suspended_list)) { |
| + struct device *dev = to_device(dpm_suspended_list.prev); |
| + |
| + get_device(dev); |
| + mutex_unlock(&dpm_list_mtx); |
| + |
| + error = device_suspend_late(dev, state); |
| + |
| + mutex_lock(&dpm_list_mtx); |
| + if (error) { |
| + pm_dev_err(dev, state, " late", error); |
| + suspend_stats.failed_suspend_late++; |
| + dpm_save_failed_step(SUSPEND_SUSPEND_LATE); |
| + dpm_save_failed_dev(dev_name(dev)); |
| + put_device(dev); |
| + break; |
| + } |
| + if (!list_empty(&dev->power.entry)) |
| + list_move(&dev->power.entry, &dpm_late_early_list); |
| + put_device(dev); |
| + } |
| + mutex_unlock(&dpm_list_mtx); |
| + if (error) |
| + dpm_resume_early(resume_event(state)); |
| + else |
| dpm_show_time(starttime, state, "late"); |
| + |
| return error; |
| } |
| -EXPORT_SYMBOL_GPL(dpm_suspend_noirq); |
| + |
| +/** |
| + * dpm_suspend_end - Execute "late" and "noirq" device suspend callbacks. |
| + * @state: PM transition of the system being carried out. |
| + */ |
| +int dpm_suspend_end(pm_message_t state) |
| +{ |
| + int error = dpm_suspend_late(state); |
| + |
| + return error ? : dpm_suspend_noirq(state); |
| +} |
| +EXPORT_SYMBOL_GPL(dpm_suspend_end); |
| |
| /** |
| * legacy_suspend - Execute a legacy (bus or class) suspend callback for device. |
| --- a/drivers/xen/manage.c |
| +++ b/drivers/xen/manage.c |
| @@ -128,9 +128,9 @@ static void do_suspend(void) |
| printk(KERN_DEBUG "suspending xenstore...\n"); |
| xs_suspend(); |
| |
| - err = dpm_suspend_noirq(PMSG_FREEZE); |
| + err = dpm_suspend_end(PMSG_FREEZE); |
| if (err) { |
| - printk(KERN_ERR "dpm_suspend_noirq failed: %d\n", err); |
| + printk(KERN_ERR "dpm_suspend_end failed: %d\n", err); |
| goto out_resume; |
| } |
| |
| @@ -148,7 +148,7 @@ static void do_suspend(void) |
| |
| err = stop_machine(xen_suspend, &si, cpumask_of(0)); |
| |
| - dpm_resume_noirq(si.cancelled ? PMSG_THAW : PMSG_RESTORE); |
| + dpm_resume_start(si.cancelled ? PMSG_THAW : PMSG_RESTORE); |
| |
| if (err) { |
| printk(KERN_ERR "failed to start xen_suspend: %d\n", err); |
| --- a/include/linux/pm.h |
| +++ b/include/linux/pm.h |
| @@ -110,6 +110,10 @@ typedef struct pm_message { |
| * Subsystem-level @suspend() is executed for all devices after invoking |
| * subsystem-level @prepare() for all of them. |
| * |
| + * @suspend_late: Continue operations started by @suspend(). For a number of |
| + * devices @suspend_late() may point to the same callback routine as the |
| + * runtime suspend callback. |
| + * |
| * @resume: Executed after waking the system up from a sleep state in which the |
| * contents of main memory were preserved. The exact action to perform |
| * depends on the device's subsystem, but generally the driver is expected |
| @@ -122,6 +126,10 @@ typedef struct pm_message { |
| * Subsystem-level @resume() is executed for all devices after invoking |
| * subsystem-level @resume_noirq() for all of them. |
| * |
| + * @resume_early: Prepare to execute @resume(). For a number of devices |
| + * @resume_early() may point to the same callback routine as the runtime |
| + * resume callback. |
| + * |
| * @freeze: Hibernation-specific, executed before creating a hibernation image. |
| * Analogous to @suspend(), but it should not enable the device to signal |
| * wakeup events or change its power state. The majority of subsystems |
| @@ -131,6 +139,10 @@ typedef struct pm_message { |
| * Subsystem-level @freeze() is executed for all devices after invoking |
| * subsystem-level @prepare() for all of them. |
| * |
| + * @freeze_late: Continue operations started by @freeze(). Analogous to |
| + * @suspend_late(), but it should not enable the device to signal wakeup |
| + * events or change its power state. |
| + * |
| * @thaw: Hibernation-specific, executed after creating a hibernation image OR |
| * if the creation of an image has failed. Also executed after a failing |
| * attempt to restore the contents of main memory from such an image. |
| @@ -140,15 +152,23 @@ typedef struct pm_message { |
| * subsystem-level @thaw_noirq() for all of them. It also may be executed |
| * directly after @freeze() in case of a transition error. |
| * |
| + * @thaw_early: Prepare to execute @thaw(). Undo the changes made by the |
| + * preceding @freeze_late(). |
| + * |
| * @poweroff: Hibernation-specific, executed after saving a hibernation image. |
| * Analogous to @suspend(), but it need not save the device's settings in |
| * memory. |
| * Subsystem-level @poweroff() is executed for all devices after invoking |
| * subsystem-level @prepare() for all of them. |
| * |
| + * @poweroff_late: Continue operations started by @poweroff(). Analogous to |
| + * @suspend_late(), but it need not save the device's settings in memory. |
| + * |
| * @restore: Hibernation-specific, executed after restoring the contents of main |
| * memory from a hibernation image, analogous to @resume(). |
| * |
| + * @restore_early: Prepare to execute @restore(), analogous to @resume_early(). |
| + * |
| * @suspend_noirq: Complete the actions started by @suspend(). Carry out any |
| * additional operations required for suspending the device that might be |
| * racing with its driver's interrupt handler, which is guaranteed not to |
| @@ -158,9 +178,10 @@ typedef struct pm_message { |
| * @suspend_noirq() has returned successfully. If the device can generate |
| * system wakeup signals and is enabled to wake up the system, it should be |
| * configured to do so at that time. However, depending on the platform |
| - * and device's subsystem, @suspend() may be allowed to put the device into |
| - * the low-power state and configure it to generate wakeup signals, in |
| - * which case it generally is not necessary to define @suspend_noirq(). |
| + * and device's subsystem, @suspend() or @suspend_late() may be allowed to |
| + * put the device into the low-power state and configure it to generate |
| + * wakeup signals, in which case it generally is not necessary to define |
| + * @suspend_noirq(). |
| * |
| * @resume_noirq: Prepare for the execution of @resume() by carrying out any |
| * operations required for resuming the device that might be racing with |
| @@ -171,9 +192,9 @@ typedef struct pm_message { |
| * additional operations required for freezing the device that might be |
| * racing with its driver's interrupt handler, which is guaranteed not to |
| * run while @freeze_noirq() is being executed. |
| - * The power state of the device should not be changed by either @freeze() |
| - * or @freeze_noirq() and it should not be configured to signal system |
| - * wakeup by any of these callbacks. |
| + * The power state of the device should not be changed by either @freeze(), |
| + * or @freeze_late(), or @freeze_noirq() and it should not be configured to |
| + * signal system wakeup by any of these callbacks. |
| * |
| * @thaw_noirq: Prepare for the execution of @thaw() by carrying out any |
| * operations required for thawing the device that might be racing with its |
| @@ -249,6 +270,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); |
| @@ -584,13 +611,13 @@ struct dev_pm_domain { |
| |
| #ifdef CONFIG_PM_SLEEP |
| extern void device_pm_lock(void); |
| -extern void dpm_resume_noirq(pm_message_t state); |
| +extern void dpm_resume_start(pm_message_t state); |
| extern void dpm_resume_end(pm_message_t state); |
| extern void dpm_resume(pm_message_t state); |
| extern void dpm_complete(pm_message_t state); |
| |
| extern void device_pm_unlock(void); |
| -extern int dpm_suspend_noirq(pm_message_t state); |
| +extern int dpm_suspend_end(pm_message_t state); |
| extern int dpm_suspend_start(pm_message_t state); |
| extern int dpm_suspend(pm_message_t state); |
| extern int dpm_prepare(pm_message_t state); |
| --- a/include/linux/suspend.h |
| +++ b/include/linux/suspend.h |
| @@ -41,8 +41,10 @@ enum suspend_stat_step { |
| SUSPEND_FREEZE = 1, |
| SUSPEND_PREPARE, |
| SUSPEND_SUSPEND, |
| + SUSPEND_SUSPEND_LATE, |
| SUSPEND_SUSPEND_NOIRQ, |
| SUSPEND_RESUME_NOIRQ, |
| + SUSPEND_RESUME_EARLY, |
| SUSPEND_RESUME |
| }; |
| |
| @@ -52,8 +54,10 @@ struct suspend_stats { |
| int failed_freeze; |
| int failed_prepare; |
| int failed_suspend; |
| + int failed_suspend_late; |
| int failed_suspend_noirq; |
| int failed_resume; |
| + int failed_resume_early; |
| int failed_resume_noirq; |
| #define REC_FAILED_NUM 2 |
| int last_failed_dev; |
| --- a/kernel/kexec.c |
| +++ b/kernel/kexec.c |
| @@ -1518,13 +1518,13 @@ int kernel_kexec(void) |
| if (error) |
| goto Resume_console; |
| /* At this point, dpm_suspend_start() has been called, |
| - * but *not* dpm_suspend_noirq(). We *must* call |
| - * dpm_suspend_noirq() now. Otherwise, drivers for |
| + * but *not* dpm_suspend_end(). We *must* call |
| + * dpm_suspend_end() now. Otherwise, drivers for |
| * some devices (e.g. interrupt controllers) become |
| * desynchronized with the actual state of the |
| * hardware at resume time, and evil weirdness ensues. |
| */ |
| - error = dpm_suspend_noirq(PMSG_FREEZE); |
| + error = dpm_suspend_end(PMSG_FREEZE); |
| if (error) |
| goto Resume_devices; |
| error = disable_nonboot_cpus(); |
| @@ -1551,7 +1551,7 @@ int kernel_kexec(void) |
| local_irq_enable(); |
| Enable_cpus: |
| enable_nonboot_cpus(); |
| - dpm_resume_noirq(PMSG_RESTORE); |
| + dpm_resume_start(PMSG_RESTORE); |
| Resume_devices: |
| dpm_resume_end(PMSG_RESTORE); |
| Resume_console: |
| --- a/kernel/power/hibernate.c |
| +++ b/kernel/power/hibernate.c |
| @@ -245,8 +245,8 @@ void swsusp_show_speed(struct timeval *s |
| * create_image - Create a hibernation image. |
| * @platform_mode: Whether or not to use the platform driver. |
| * |
| - * Execute device drivers' .freeze_noirq() callbacks, create a hibernation image |
| - * and execute the drivers' .thaw_noirq() callbacks. |
| + * Execute device drivers' "late" and "noirq" freeze callbacks, create a |
| + * hibernation image and run the drivers' "noirq" and "early" thaw callbacks. |
| * |
| * Control reappears in this routine after the subsequent restore. |
| */ |
| @@ -254,7 +254,7 @@ static int create_image(int platform_mod |
| { |
| int error; |
| |
| - error = dpm_suspend_noirq(PMSG_FREEZE); |
| + error = dpm_suspend_end(PMSG_FREEZE); |
| if (error) { |
| printk(KERN_ERR "PM: Some devices failed to power down, " |
| "aborting hibernation\n"); |
| @@ -306,7 +306,7 @@ static int create_image(int platform_mod |
| Platform_finish: |
| platform_finish(platform_mode); |
| |
| - dpm_resume_noirq(in_suspend ? |
| + dpm_resume_start(in_suspend ? |
| (error ? PMSG_RECOVER : PMSG_THAW) : PMSG_RESTORE); |
| |
| return error; |
| @@ -400,16 +400,16 @@ int hibernation_snapshot(int platform_mo |
| * resume_target_kernel - Restore system state from a hibernation image. |
| * @platform_mode: Whether or not to use the platform driver. |
| * |
| - * Execute device drivers' .freeze_noirq() callbacks, restore the contents of |
| - * highmem that have not been restored yet from the image and run the low-level |
| - * code that will restore the remaining contents of memory and switch to the |
| - * just restored target kernel. |
| + * Execute device drivers' "noirq" and "late" freeze callbacks, restore the |
| + * contents of highmem that have not been restored yet from the image and run |
| + * the low-level code that will restore the remaining contents of memory and |
| + * switch to the just restored target kernel. |
| */ |
| static int resume_target_kernel(bool platform_mode) |
| { |
| int error; |
| |
| - error = dpm_suspend_noirq(PMSG_QUIESCE); |
| + error = dpm_suspend_end(PMSG_QUIESCE); |
| if (error) { |
| printk(KERN_ERR "PM: Some devices failed to power down, " |
| "aborting resume\n"); |
| @@ -466,7 +466,7 @@ static int resume_target_kernel(bool pla |
| Cleanup: |
| platform_restore_cleanup(platform_mode); |
| |
| - dpm_resume_noirq(PMSG_RECOVER); |
| + dpm_resume_start(PMSG_RECOVER); |
| |
| return error; |
| } |
| @@ -527,7 +527,7 @@ int hibernation_platform_enter(void) |
| goto Resume_devices; |
| } |
| |
| - error = dpm_suspend_noirq(PMSG_HIBERNATE); |
| + error = dpm_suspend_end(PMSG_HIBERNATE); |
| if (error) |
| goto Resume_devices; |
| |
| @@ -558,7 +558,7 @@ int hibernation_platform_enter(void) |
| Platform_finish: |
| hibernation_ops->finish(); |
| |
| - dpm_resume_noirq(PMSG_RESTORE); |
| + dpm_resume_start(PMSG_RESTORE); |
| |
| Resume_devices: |
| entering_platform_hibernation = false; |
| --- a/kernel/power/main.c |
| +++ b/kernel/power/main.c |
| @@ -165,16 +165,20 @@ static int suspend_stats_show(struct seq |
| last_errno %= REC_FAILED_NUM; |
| last_step = suspend_stats.last_failed_step + REC_FAILED_NUM - 1; |
| last_step %= REC_FAILED_NUM; |
| - seq_printf(s, "%s: %d\n%s: %d\n%s: %d\n%s: %d\n" |
| - "%s: %d\n%s: %d\n%s: %d\n%s: %d\n", |
| + seq_printf(s, "%s: %d\n%s: %d\n%s: %d\n%s: %d\n%s: %d\n" |
| + "%s: %d\n%s: %d\n%s: %d\n%s: %d\n%s: %d\n", |
| "success", suspend_stats.success, |
| "fail", suspend_stats.fail, |
| "failed_freeze", suspend_stats.failed_freeze, |
| "failed_prepare", suspend_stats.failed_prepare, |
| "failed_suspend", suspend_stats.failed_suspend, |
| + "failed_suspend_late", |
| + suspend_stats.failed_suspend_late, |
| "failed_suspend_noirq", |
| suspend_stats.failed_suspend_noirq, |
| "failed_resume", suspend_stats.failed_resume, |
| + "failed_resume_early", |
| + suspend_stats.failed_resume_early, |
| "failed_resume_noirq", |
| suspend_stats.failed_resume_noirq); |
| seq_printf(s, "failures:\n last_failed_dev:\t%-s\n", |
| --- a/kernel/power/suspend.c |
| +++ b/kernel/power/suspend.c |
| @@ -148,7 +148,7 @@ static int suspend_enter(suspend_state_t |
| goto Platform_finish; |
| } |
| |
| - error = dpm_suspend_noirq(PMSG_SUSPEND); |
| + error = dpm_suspend_end(PMSG_SUSPEND); |
| if (error) { |
| printk(KERN_ERR "PM: Some devices failed to power down\n"); |
| goto Platform_finish; |
| @@ -190,7 +190,7 @@ static int suspend_enter(suspend_state_t |
| if (suspend_ops->wake) |
| suspend_ops->wake(); |
| |
| - dpm_resume_noirq(PMSG_RESUME); |
| + dpm_resume_start(PMSG_RESUME); |
| |
| Platform_finish: |
| if (suspend_ops->finish) |