forked from Minki/linux
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
This commit is contained in:
commit
643161ace2
@ -96,6 +96,12 @@ struct dev_pm_ops {
|
|||||||
int (*thaw)(struct device *dev);
|
int (*thaw)(struct device *dev);
|
||||||
int (*poweroff)(struct device *dev);
|
int (*poweroff)(struct device *dev);
|
||||||
int (*restore)(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 (*suspend_noirq)(struct device *dev);
|
||||||
int (*resume_noirq)(struct device *dev);
|
int (*resume_noirq)(struct device *dev);
|
||||||
int (*freeze_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:
|
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
|
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
|
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,
|
appropriate low-power state, depending on the bus type the device is on,
|
||||||
and they may enable wakeup events.
|
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
|
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 callback method is running. The methods should save the values of
|
||||||
the device's registers that weren't saved previously and finally put the
|
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:
|
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
|
1. The resume_noirq callback methods should perform any actions needed
|
||||||
before the driver's interrupt handlers are invoked. This generally
|
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
|
device driver's ->pm.resume_noirq() method to perform device-specific
|
||||||
actions.
|
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
|
state, so that it can perform normal I/O. This generally involves
|
||||||
undoing the actions of the suspend phase.
|
undoing the actions of the suspend phase.
|
||||||
|
|
||||||
3. The complete phase uses only a bus callback. The method should undo the
|
4. The complete phase should undo the actions of the prepare phase. Note,
|
||||||
actions of the prepare phase. Note, however, that new children may be
|
however, that new children may be registered below the device as soon as
|
||||||
registered below the device as soon as the resume callbacks occur; it's
|
the resume callbacks occur; it's not necessary to wait until the
|
||||||
not necessary to wait until the complete phase.
|
complete phase.
|
||||||
|
|
||||||
At the end of these phases, drivers should be as functional as they were before
|
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
|
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
|
devices (thaw), write the image to permanent storage, and finally shut down the
|
||||||
system (poweroff). The phases used to accomplish this are:
|
system (poweroff). The phases used to accomplish this are:
|
||||||
|
|
||||||
prepare, freeze, freeze_noirq, thaw_noirq, thaw, complete,
|
prepare, freeze, freeze_late, freeze_noirq, thaw_noirq, thaw_early,
|
||||||
prepare, poweroff, poweroff_noirq
|
thaw, complete, prepare, poweroff, poweroff_late, poweroff_noirq
|
||||||
|
|
||||||
1. The prepare phase is discussed in the "Entering System Suspend" section
|
1. The prepare phase is discussed in the "Entering System Suspend" section
|
||||||
above.
|
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
|
save time it's best not to do so. Also, the device should not be
|
||||||
prepared to generate wakeup events.
|
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
|
above, except again that the device should not be put in a low-power
|
||||||
state and should not be allowed to generate wakeup events.
|
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
|
the contents of memory should remain undisturbed while this happens, so that the
|
||||||
image forms an atomic snapshot of the system state.
|
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
|
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.
|
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
|
methods should bring the device back to an operating state, so that it
|
||||||
can be used for saving the image if necessary.
|
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.
|
above.
|
||||||
|
|
||||||
At this point the system image is saved, and the devices then need to be
|
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
|
before putting the system into the standby or memory sleep state, and the phases
|
||||||
are similar.
|
are similar.
|
||||||
|
|
||||||
7. The prepare phase is discussed above.
|
9. The prepare phase is discussed above.
|
||||||
|
|
||||||
8. The poweroff phase is analogous to the suspend phase.
|
10. The poweroff phase is analogous to the suspend phase.
|
||||||
|
|
||||||
9. The poweroff_noirq phase is analogous to the suspend_noirq phase.
|
11. The poweroff_late phase is analogous to the suspend_late phase.
|
||||||
|
|
||||||
The poweroff and poweroff_noirq callbacks should do essentially the same things
|
12. The poweroff_noirq phase is analogous to the suspend_noirq phase.
|
||||||
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
|
The poweroff, poweroff_late and poweroff_noirq callbacks should do essentially
|
||||||
already have been stored during the freeze or freeze_noirq phases.
|
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
|
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
|
functionality. The operation is much like waking up from the memory sleep
|
||||||
state, although it involves different phases:
|
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.
|
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 complete phase is discussed above.
|
3. The restore phase is analogous to the resume phase.
|
||||||
|
|
||||||
The main difference from resume[_noirq] is that restore[_noirq] must assume the
|
4. The complete phase is discussed above.
|
||||||
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
|
The main difference from resume[_early|_noirq] is that restore[_early|_noirq]
|
||||||
from the freeze and freeze_noirq phases. The device may even need to be reset
|
must assume the device has been accessed and reconfigured by the boot loader or
|
||||||
and completely re-initialized. In many cases this difference doesn't matter, so
|
the boot kernel. Consequently the state of the device may be different from the
|
||||||
the resume[_noirq] and restore[_norq] method pointers can be set to the same
|
state remembered from the freeze, freeze_late and freeze_noirq phases. The
|
||||||
routines. Nevertheless, different callback pointers are used in case there is a
|
device may even need to be reset and completely re-initialized. In many cases
|
||||||
situation where it actually matters.
|
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
|
Device Power Management Domains
|
||||||
|
@ -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
|
order to clear the PF_FROZEN flag for each frozen task. Then, the tasks that
|
||||||
have been frozen leave __refrigerator() and continue running.
|
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?
|
III. Which kernel threads are freezable?
|
||||||
|
|
||||||
Kernel threads are not freezable by default. However, a kernel thread may clear
|
Kernel threads are not freezable by default. However, a kernel thread may clear
|
||||||
|
@ -1234,8 +1234,7 @@ static int suspend(int vetoable)
|
|||||||
struct apm_user *as;
|
struct apm_user *as;
|
||||||
|
|
||||||
dpm_suspend_start(PMSG_SUSPEND);
|
dpm_suspend_start(PMSG_SUSPEND);
|
||||||
|
dpm_suspend_end(PMSG_SUSPEND);
|
||||||
dpm_suspend_noirq(PMSG_SUSPEND);
|
|
||||||
|
|
||||||
local_irq_disable();
|
local_irq_disable();
|
||||||
syscore_suspend();
|
syscore_suspend();
|
||||||
@ -1259,9 +1258,9 @@ static int suspend(int vetoable)
|
|||||||
syscore_resume();
|
syscore_resume();
|
||||||
local_irq_enable();
|
local_irq_enable();
|
||||||
|
|
||||||
dpm_resume_noirq(PMSG_RESUME);
|
dpm_resume_start(PMSG_RESUME);
|
||||||
|
|
||||||
dpm_resume_end(PMSG_RESUME);
|
dpm_resume_end(PMSG_RESUME);
|
||||||
|
|
||||||
queue_event(APM_NORMAL_RESUME, NULL);
|
queue_event(APM_NORMAL_RESUME, NULL);
|
||||||
spin_lock(&user_list_lock);
|
spin_lock(&user_list_lock);
|
||||||
for (as = user_list; as != NULL; as = as->next) {
|
for (as = user_list; as != NULL; as = as->next) {
|
||||||
@ -1277,7 +1276,7 @@ static void standby(void)
|
|||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
dpm_suspend_noirq(PMSG_SUSPEND);
|
dpm_suspend_end(PMSG_SUSPEND);
|
||||||
|
|
||||||
local_irq_disable();
|
local_irq_disable();
|
||||||
syscore_suspend();
|
syscore_suspend();
|
||||||
@ -1291,7 +1290,7 @@ static void standby(void)
|
|||||||
syscore_resume();
|
syscore_resume();
|
||||||
local_irq_enable();
|
local_irq_enable();
|
||||||
|
|
||||||
dpm_resume_noirq(PMSG_RESUME);
|
dpm_resume_start(PMSG_RESUME);
|
||||||
}
|
}
|
||||||
|
|
||||||
static apm_event_t get_event(void)
|
static apm_event_t get_event(void)
|
||||||
|
@ -91,68 +91,39 @@ int pm_generic_prepare(struct device *dev)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* __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.
|
|
||||||
*/
|
|
||||||
static int __pm_generic_call(struct device *dev, int event, bool noirq)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pm_generic_suspend_noirq - Generic suspend_noirq callback for subsystems.
|
* pm_generic_suspend_noirq - Generic suspend_noirq callback for subsystems.
|
||||||
* @dev: Device to suspend.
|
* @dev: Device to suspend.
|
||||||
*/
|
*/
|
||||||
int pm_generic_suspend_noirq(struct device *dev)
|
int pm_generic_suspend_noirq(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_noirq ? pm->suspend_noirq(dev) : 0;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(pm_generic_suspend_noirq);
|
EXPORT_SYMBOL_GPL(pm_generic_suspend_noirq);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pm_generic_suspend_late - Generic suspend_late callback for subsystems.
|
||||||
|
* @dev: Device to suspend.
|
||||||
|
*/
|
||||||
|
int pm_generic_suspend_late(struct device *dev)
|
||||||
|
{
|
||||||
|
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_late);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pm_generic_suspend - Generic suspend callback for subsystems.
|
* pm_generic_suspend - Generic suspend callback for subsystems.
|
||||||
* @dev: Device to suspend.
|
* @dev: Device to suspend.
|
||||||
*/
|
*/
|
||||||
int pm_generic_suspend(struct device *dev)
|
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);
|
EXPORT_SYMBOL_GPL(pm_generic_suspend);
|
||||||
|
|
||||||
@ -162,17 +133,33 @@ EXPORT_SYMBOL_GPL(pm_generic_suspend);
|
|||||||
*/
|
*/
|
||||||
int pm_generic_freeze_noirq(struct device *dev)
|
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);
|
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.
|
* pm_generic_freeze - Generic freeze callback for subsystems.
|
||||||
* @dev: Device to freeze.
|
* @dev: Device to freeze.
|
||||||
*/
|
*/
|
||||||
int pm_generic_freeze(struct device *dev)
|
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);
|
EXPORT_SYMBOL_GPL(pm_generic_freeze);
|
||||||
|
|
||||||
@ -182,17 +169,33 @@ EXPORT_SYMBOL_GPL(pm_generic_freeze);
|
|||||||
*/
|
*/
|
||||||
int pm_generic_poweroff_noirq(struct device *dev)
|
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);
|
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.
|
* pm_generic_poweroff - Generic poweroff callback for subsystems.
|
||||||
* @dev: Device to handle.
|
* @dev: Device to handle.
|
||||||
*/
|
*/
|
||||||
int pm_generic_poweroff(struct device *dev)
|
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);
|
EXPORT_SYMBOL_GPL(pm_generic_poweroff);
|
||||||
|
|
||||||
@ -202,17 +205,33 @@ EXPORT_SYMBOL_GPL(pm_generic_poweroff);
|
|||||||
*/
|
*/
|
||||||
int pm_generic_thaw_noirq(struct device *dev)
|
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);
|
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.
|
* pm_generic_thaw - Generic thaw callback for subsystems.
|
||||||
* @dev: Device to thaw.
|
* @dev: Device to thaw.
|
||||||
*/
|
*/
|
||||||
int pm_generic_thaw(struct device *dev)
|
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);
|
EXPORT_SYMBOL_GPL(pm_generic_thaw);
|
||||||
|
|
||||||
@ -222,17 +241,33 @@ EXPORT_SYMBOL_GPL(pm_generic_thaw);
|
|||||||
*/
|
*/
|
||||||
int pm_generic_resume_noirq(struct device *dev)
|
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);
|
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.
|
* pm_generic_resume - Generic resume callback for subsystems.
|
||||||
* @dev: Device to resume.
|
* @dev: Device to resume.
|
||||||
*/
|
*/
|
||||||
int pm_generic_resume(struct device *dev)
|
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);
|
EXPORT_SYMBOL_GPL(pm_generic_resume);
|
||||||
|
|
||||||
@ -242,17 +277,33 @@ EXPORT_SYMBOL_GPL(pm_generic_resume);
|
|||||||
*/
|
*/
|
||||||
int pm_generic_restore_noirq(struct device *dev)
|
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);
|
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.
|
* pm_generic_restore - Generic restore callback for subsystems.
|
||||||
* @dev: Device to restore.
|
* @dev: Device to restore.
|
||||||
*/
|
*/
|
||||||
int pm_generic_restore(struct device *dev)
|
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);
|
EXPORT_SYMBOL_GPL(pm_generic_restore);
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ typedef int (*pm_callback_t)(struct device *);
|
|||||||
LIST_HEAD(dpm_list);
|
LIST_HEAD(dpm_list);
|
||||||
LIST_HEAD(dpm_prepared_list);
|
LIST_HEAD(dpm_prepared_list);
|
||||||
LIST_HEAD(dpm_suspended_list);
|
LIST_HEAD(dpm_suspended_list);
|
||||||
|
LIST_HEAD(dpm_late_early_list);
|
||||||
LIST_HEAD(dpm_noirq_list);
|
LIST_HEAD(dpm_noirq_list);
|
||||||
|
|
||||||
struct suspend_stats suspend_stats;
|
struct suspend_stats suspend_stats;
|
||||||
@ -245,6 +246,40 @@ static pm_callback_t pm_op(const struct dev_pm_ops *ops, pm_message_t state)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* pm_noirq_op - Return the PM operation appropriate for given PM event.
|
||||||
* @ops: PM operations to choose from.
|
* @ops: PM operations to choose from.
|
||||||
@ -374,21 +409,21 @@ static int device_resume_noirq(struct device *dev, pm_message_t state)
|
|||||||
TRACE_RESUME(0);
|
TRACE_RESUME(0);
|
||||||
|
|
||||||
if (dev->pm_domain) {
|
if (dev->pm_domain) {
|
||||||
info = "EARLY power domain ";
|
info = "noirq power domain ";
|
||||||
callback = pm_noirq_op(&dev->pm_domain->ops, state);
|
callback = pm_noirq_op(&dev->pm_domain->ops, state);
|
||||||
} else if (dev->type && dev->type->pm) {
|
} else if (dev->type && dev->type->pm) {
|
||||||
info = "EARLY type ";
|
info = "noirq type ";
|
||||||
callback = pm_noirq_op(dev->type->pm, state);
|
callback = pm_noirq_op(dev->type->pm, state);
|
||||||
} else if (dev->class && dev->class->pm) {
|
} else if (dev->class && dev->class->pm) {
|
||||||
info = "EARLY class ";
|
info = "noirq class ";
|
||||||
callback = pm_noirq_op(dev->class->pm, state);
|
callback = pm_noirq_op(dev->class->pm, state);
|
||||||
} else if (dev->bus && dev->bus->pm) {
|
} else if (dev->bus && dev->bus->pm) {
|
||||||
info = "EARLY bus ";
|
info = "noirq bus ";
|
||||||
callback = pm_noirq_op(dev->bus->pm, state);
|
callback = pm_noirq_op(dev->bus->pm, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!callback && dev->driver && dev->driver->pm) {
|
if (!callback && dev->driver && dev->driver->pm) {
|
||||||
info = "EARLY driver ";
|
info = "noirq driver ";
|
||||||
callback = pm_noirq_op(dev->driver->pm, state);
|
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.
|
* @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.
|
* 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();
|
ktime_t starttime = ktime_get();
|
||||||
|
|
||||||
@ -415,7 +450,7 @@ void dpm_resume_noirq(pm_message_t state)
|
|||||||
int error;
|
int error;
|
||||||
|
|
||||||
get_device(dev);
|
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);
|
mutex_unlock(&dpm_list_mtx);
|
||||||
|
|
||||||
error = device_resume_noirq(dev, state);
|
error = device_resume_noirq(dev, state);
|
||||||
@ -423,6 +458,80 @@ void dpm_resume_noirq(pm_message_t state)
|
|||||||
suspend_stats.failed_resume_noirq++;
|
suspend_stats.failed_resume_noirq++;
|
||||||
dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);
|
dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);
|
||||||
dpm_save_failed_dev(dev_name(dev));
|
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);
|
pm_dev_err(dev, state, " early", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,9 +540,18 @@ void dpm_resume_noirq(pm_message_t state)
|
|||||||
}
|
}
|
||||||
mutex_unlock(&dpm_list_mtx);
|
mutex_unlock(&dpm_list_mtx);
|
||||||
dpm_show_time(starttime, state, "early");
|
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.
|
* 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;
|
char *info = NULL;
|
||||||
|
|
||||||
if (dev->pm_domain) {
|
if (dev->pm_domain) {
|
||||||
info = "LATE power domain ";
|
info = "noirq power domain ";
|
||||||
callback = pm_noirq_op(&dev->pm_domain->ops, state);
|
callback = pm_noirq_op(&dev->pm_domain->ops, state);
|
||||||
} else if (dev->type && dev->type->pm) {
|
} else if (dev->type && dev->type->pm) {
|
||||||
info = "LATE type ";
|
info = "noirq type ";
|
||||||
callback = pm_noirq_op(dev->type->pm, state);
|
callback = pm_noirq_op(dev->type->pm, state);
|
||||||
} else if (dev->class && dev->class->pm) {
|
} else if (dev->class && dev->class->pm) {
|
||||||
info = "LATE class ";
|
info = "noirq class ";
|
||||||
callback = pm_noirq_op(dev->class->pm, state);
|
callback = pm_noirq_op(dev->class->pm, state);
|
||||||
} else if (dev->bus && dev->bus->pm) {
|
} else if (dev->bus && dev->bus->pm) {
|
||||||
info = "LATE bus ";
|
info = "noirq bus ";
|
||||||
callback = pm_noirq_op(dev->bus->pm, state);
|
callback = pm_noirq_op(dev->bus->pm, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!callback && dev->driver && dev->driver->pm) {
|
if (!callback && dev->driver && dev->driver->pm) {
|
||||||
info = "LATE driver ";
|
info = "noirq driver ";
|
||||||
callback = pm_noirq_op(dev->driver->pm, state);
|
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.
|
* @state: PM transition of the system being carried out.
|
||||||
*
|
*
|
||||||
* Prevent device drivers from receiving interrupts and call the "noirq" suspend
|
* Prevent device drivers from receiving interrupts and call the "noirq" suspend
|
||||||
* handlers for all non-sysdev devices.
|
* 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();
|
ktime_t starttime = ktime_get();
|
||||||
int error = 0;
|
int error = 0;
|
||||||
|
|
||||||
suspend_device_irqs();
|
suspend_device_irqs();
|
||||||
mutex_lock(&dpm_list_mtx);
|
mutex_lock(&dpm_list_mtx);
|
||||||
while (!list_empty(&dpm_suspended_list)) {
|
while (!list_empty(&dpm_late_early_list)) {
|
||||||
struct device *dev = to_device(dpm_suspended_list.prev);
|
struct device *dev = to_device(dpm_late_early_list.prev);
|
||||||
|
|
||||||
get_device(dev);
|
get_device(dev);
|
||||||
mutex_unlock(&dpm_list_mtx);
|
mutex_unlock(&dpm_list_mtx);
|
||||||
@ -761,7 +879,7 @@ int dpm_suspend_noirq(pm_message_t state)
|
|||||||
|
|
||||||
mutex_lock(&dpm_list_mtx);
|
mutex_lock(&dpm_list_mtx);
|
||||||
if (error) {
|
if (error) {
|
||||||
pm_dev_err(dev, state, " late", error);
|
pm_dev_err(dev, state, " noirq", error);
|
||||||
suspend_stats.failed_suspend_noirq++;
|
suspend_stats.failed_suspend_noirq++;
|
||||||
dpm_save_failed_step(SUSPEND_SUSPEND_NOIRQ);
|
dpm_save_failed_step(SUSPEND_SUSPEND_NOIRQ);
|
||||||
dpm_save_failed_dev(dev_name(dev));
|
dpm_save_failed_dev(dev_name(dev));
|
||||||
@ -776,10 +894,95 @@ int dpm_suspend_noirq(pm_message_t state)
|
|||||||
if (error)
|
if (error)
|
||||||
dpm_resume_noirq(resume_event(state));
|
dpm_resume_noirq(resume_event(state));
|
||||||
else
|
else
|
||||||
dpm_show_time(starttime, state, "late");
|
dpm_show_time(starttime, state, "noirq");
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(dpm_suspend_noirq);
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* legacy_suspend - Execute a legacy (bus or class) suspend callback for device.
|
||||||
|
@ -52,6 +52,23 @@ static void pm_wakeup_timer_fn(unsigned long data);
|
|||||||
|
|
||||||
static LIST_HEAD(wakeup_sources);
|
static LIST_HEAD(wakeup_sources);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wakeup_source_prepare - Prepare a new wakeup source for initialization.
|
||||||
|
* @ws: Wakeup source to prepare.
|
||||||
|
* @name: Pointer to the name of the new wakeup source.
|
||||||
|
*
|
||||||
|
* Callers must ensure that the @name string won't be freed when @ws is still in
|
||||||
|
* use.
|
||||||
|
*/
|
||||||
|
void wakeup_source_prepare(struct wakeup_source *ws, const char *name)
|
||||||
|
{
|
||||||
|
if (ws) {
|
||||||
|
memset(ws, 0, sizeof(*ws));
|
||||||
|
ws->name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(wakeup_source_prepare);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* wakeup_source_create - Create a struct wakeup_source object.
|
* wakeup_source_create - Create a struct wakeup_source object.
|
||||||
* @name: Name of the new wakeup source.
|
* @name: Name of the new wakeup source.
|
||||||
@ -60,37 +77,44 @@ struct wakeup_source *wakeup_source_create(const char *name)
|
|||||||
{
|
{
|
||||||
struct wakeup_source *ws;
|
struct wakeup_source *ws;
|
||||||
|
|
||||||
ws = kzalloc(sizeof(*ws), GFP_KERNEL);
|
ws = kmalloc(sizeof(*ws), GFP_KERNEL);
|
||||||
if (!ws)
|
if (!ws)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
spin_lock_init(&ws->lock);
|
wakeup_source_prepare(ws, name ? kstrdup(name, GFP_KERNEL) : NULL);
|
||||||
if (name)
|
|
||||||
ws->name = kstrdup(name, GFP_KERNEL);
|
|
||||||
|
|
||||||
return ws;
|
return ws;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(wakeup_source_create);
|
EXPORT_SYMBOL_GPL(wakeup_source_create);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wakeup_source_drop - Prepare a struct wakeup_source object for destruction.
|
||||||
|
* @ws: Wakeup source to prepare for destruction.
|
||||||
|
*
|
||||||
|
* Callers must ensure that __pm_stay_awake() or __pm_wakeup_event() will never
|
||||||
|
* be run in parallel with this function for the same wakeup source object.
|
||||||
|
*/
|
||||||
|
void wakeup_source_drop(struct wakeup_source *ws)
|
||||||
|
{
|
||||||
|
if (!ws)
|
||||||
|
return;
|
||||||
|
|
||||||
|
del_timer_sync(&ws->timer);
|
||||||
|
__pm_relax(ws);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(wakeup_source_drop);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* wakeup_source_destroy - Destroy a struct wakeup_source object.
|
* wakeup_source_destroy - Destroy a struct wakeup_source object.
|
||||||
* @ws: Wakeup source to destroy.
|
* @ws: Wakeup source to destroy.
|
||||||
|
*
|
||||||
|
* Use only for wakeup source objects created with wakeup_source_create().
|
||||||
*/
|
*/
|
||||||
void wakeup_source_destroy(struct wakeup_source *ws)
|
void wakeup_source_destroy(struct wakeup_source *ws)
|
||||||
{
|
{
|
||||||
if (!ws)
|
if (!ws)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
spin_lock_irq(&ws->lock);
|
wakeup_source_drop(ws);
|
||||||
while (ws->active) {
|
|
||||||
spin_unlock_irq(&ws->lock);
|
|
||||||
|
|
||||||
schedule_timeout_interruptible(msecs_to_jiffies(TIMEOUT));
|
|
||||||
|
|
||||||
spin_lock_irq(&ws->lock);
|
|
||||||
}
|
|
||||||
spin_unlock_irq(&ws->lock);
|
|
||||||
|
|
||||||
kfree(ws->name);
|
kfree(ws->name);
|
||||||
kfree(ws);
|
kfree(ws);
|
||||||
}
|
}
|
||||||
@ -105,6 +129,7 @@ void wakeup_source_add(struct wakeup_source *ws)
|
|||||||
if (WARN_ON(!ws))
|
if (WARN_ON(!ws))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
spin_lock_init(&ws->lock);
|
||||||
setup_timer(&ws->timer, pm_wakeup_timer_fn, (unsigned long)ws);
|
setup_timer(&ws->timer, pm_wakeup_timer_fn, (unsigned long)ws);
|
||||||
ws->active = false;
|
ws->active = false;
|
||||||
|
|
||||||
@ -152,8 +177,10 @@ EXPORT_SYMBOL_GPL(wakeup_source_register);
|
|||||||
*/
|
*/
|
||||||
void wakeup_source_unregister(struct wakeup_source *ws)
|
void wakeup_source_unregister(struct wakeup_source *ws)
|
||||||
{
|
{
|
||||||
wakeup_source_remove(ws);
|
if (ws) {
|
||||||
wakeup_source_destroy(ws);
|
wakeup_source_remove(ws);
|
||||||
|
wakeup_source_destroy(ws);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(wakeup_source_unregister);
|
EXPORT_SYMBOL_GPL(wakeup_source_unregister);
|
||||||
|
|
||||||
@ -349,7 +376,6 @@ static void wakeup_source_activate(struct wakeup_source *ws)
|
|||||||
{
|
{
|
||||||
ws->active = true;
|
ws->active = true;
|
||||||
ws->active_count++;
|
ws->active_count++;
|
||||||
ws->timer_expires = jiffies;
|
|
||||||
ws->last_time = ktime_get();
|
ws->last_time = ktime_get();
|
||||||
|
|
||||||
/* Increment the counter of events in progress. */
|
/* Increment the counter of events in progress. */
|
||||||
@ -370,9 +396,14 @@ void __pm_stay_awake(struct wakeup_source *ws)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
spin_lock_irqsave(&ws->lock, flags);
|
spin_lock_irqsave(&ws->lock, flags);
|
||||||
|
|
||||||
ws->event_count++;
|
ws->event_count++;
|
||||||
if (!ws->active)
|
if (!ws->active)
|
||||||
wakeup_source_activate(ws);
|
wakeup_source_activate(ws);
|
||||||
|
|
||||||
|
del_timer(&ws->timer);
|
||||||
|
ws->timer_expires = 0;
|
||||||
|
|
||||||
spin_unlock_irqrestore(&ws->lock, flags);
|
spin_unlock_irqrestore(&ws->lock, flags);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(__pm_stay_awake);
|
EXPORT_SYMBOL_GPL(__pm_stay_awake);
|
||||||
@ -438,6 +469,7 @@ static void wakeup_source_deactivate(struct wakeup_source *ws)
|
|||||||
ws->max_time = duration;
|
ws->max_time = duration;
|
||||||
|
|
||||||
del_timer(&ws->timer);
|
del_timer(&ws->timer);
|
||||||
|
ws->timer_expires = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Increment the counter of registered wakeup events and decrement the
|
* Increment the counter of registered wakeup events and decrement the
|
||||||
@ -492,11 +524,22 @@ EXPORT_SYMBOL_GPL(pm_relax);
|
|||||||
* pm_wakeup_timer_fn - Delayed finalization of a wakeup event.
|
* pm_wakeup_timer_fn - Delayed finalization of a wakeup event.
|
||||||
* @data: Address of the wakeup source object associated with the event source.
|
* @data: Address of the wakeup source object associated with the event source.
|
||||||
*
|
*
|
||||||
* Call __pm_relax() for the wakeup source whose address is stored in @data.
|
* Call wakeup_source_deactivate() for the wakeup source whose address is stored
|
||||||
|
* in @data if it is currently active and its timer has not been canceled and
|
||||||
|
* the expiration time of the timer is not in future.
|
||||||
*/
|
*/
|
||||||
static void pm_wakeup_timer_fn(unsigned long data)
|
static void pm_wakeup_timer_fn(unsigned long data)
|
||||||
{
|
{
|
||||||
__pm_relax((struct wakeup_source *)data);
|
struct wakeup_source *ws = (struct wakeup_source *)data;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&ws->lock, flags);
|
||||||
|
|
||||||
|
if (ws->active && ws->timer_expires
|
||||||
|
&& time_after_eq(jiffies, ws->timer_expires))
|
||||||
|
wakeup_source_deactivate(ws);
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&ws->lock, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -534,7 +577,7 @@ void __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec)
|
|||||||
if (!expires)
|
if (!expires)
|
||||||
expires = 1;
|
expires = 1;
|
||||||
|
|
||||||
if (time_after(expires, ws->timer_expires)) {
|
if (!ws->timer_expires || time_after(expires, ws->timer_expires)) {
|
||||||
mod_timer(&ws->timer, expires);
|
mod_timer(&ws->timer, expires);
|
||||||
ws->timer_expires = expires;
|
ws->timer_expires = expires;
|
||||||
}
|
}
|
||||||
|
@ -129,9 +129,9 @@ static void do_suspend(void)
|
|||||||
printk(KERN_DEBUG "suspending xenstore...\n");
|
printk(KERN_DEBUG "suspending xenstore...\n");
|
||||||
xs_suspend();
|
xs_suspend();
|
||||||
|
|
||||||
err = dpm_suspend_noirq(PMSG_FREEZE);
|
err = dpm_suspend_end(PMSG_FREEZE);
|
||||||
if (err) {
|
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;
|
goto out_resume;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +149,7 @@ static void do_suspend(void)
|
|||||||
|
|
||||||
err = stop_machine(xen_suspend, &si, cpumask_of(0));
|
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) {
|
if (err) {
|
||||||
printk(KERN_ERR "failed to start xen_suspend: %d\n", err);
|
printk(KERN_ERR "failed to start xen_suspend: %d\n", err);
|
||||||
|
@ -110,6 +110,10 @@ typedef struct pm_message {
|
|||||||
* Subsystem-level @suspend() is executed for all devices after invoking
|
* Subsystem-level @suspend() is executed for all devices after invoking
|
||||||
* subsystem-level @prepare() for all of them.
|
* 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
|
* @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
|
* contents of main memory were preserved. The exact action to perform
|
||||||
* depends on the device's subsystem, but generally the driver is expected
|
* 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() is executed for all devices after invoking
|
||||||
* subsystem-level @resume_noirq() for all of them.
|
* 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.
|
* @freeze: Hibernation-specific, executed before creating a hibernation image.
|
||||||
* Analogous to @suspend(), but it should not enable the device to signal
|
* Analogous to @suspend(), but it should not enable the device to signal
|
||||||
* wakeup events or change its power state. The majority of subsystems
|
* 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 @freeze() is executed for all devices after invoking
|
||||||
* subsystem-level @prepare() for all of them.
|
* 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
|
* @thaw: Hibernation-specific, executed after creating a hibernation image OR
|
||||||
* if the creation of an image has failed. Also executed after a failing
|
* 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.
|
* 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
|
* subsystem-level @thaw_noirq() for all of them. It also may be executed
|
||||||
* directly after @freeze() in case of a transition error.
|
* 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.
|
* @poweroff: Hibernation-specific, executed after saving a hibernation image.
|
||||||
* Analogous to @suspend(), but it need not save the device's settings in
|
* Analogous to @suspend(), but it need not save the device's settings in
|
||||||
* memory.
|
* memory.
|
||||||
* Subsystem-level @poweroff() is executed for all devices after invoking
|
* Subsystem-level @poweroff() is executed for all devices after invoking
|
||||||
* subsystem-level @prepare() for all of them.
|
* 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
|
* @restore: Hibernation-specific, executed after restoring the contents of main
|
||||||
* memory from a hibernation image, analogous to @resume().
|
* 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
|
* @suspend_noirq: Complete the actions started by @suspend(). Carry out any
|
||||||
* additional operations required for suspending the device that might be
|
* additional operations required for suspending the device that might be
|
||||||
* racing with its driver's interrupt handler, which is guaranteed not to
|
* 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
|
* @suspend_noirq() has returned successfully. If the device can generate
|
||||||
* system wakeup signals and is enabled to wake up the system, it should be
|
* 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
|
* 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
|
* and device's subsystem, @suspend() or @suspend_late() may be allowed to
|
||||||
* the low-power state and configure it to generate wakeup signals, in
|
* put the device into the low-power state and configure it to generate
|
||||||
* which case it generally is not necessary to define @suspend_noirq().
|
* 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
|
* @resume_noirq: Prepare for the execution of @resume() by carrying out any
|
||||||
* operations required for resuming the device that might be racing with
|
* 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
|
* additional operations required for freezing the device that might be
|
||||||
* racing with its driver's interrupt handler, which is guaranteed not to
|
* racing with its driver's interrupt handler, which is guaranteed not to
|
||||||
* run while @freeze_noirq() is being executed.
|
* run while @freeze_noirq() is being executed.
|
||||||
* The power state of the device should not be changed by either @freeze()
|
* 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
|
* or @freeze_late(), or @freeze_noirq() and it should not be configured to
|
||||||
* wakeup by any of these callbacks.
|
* signal system wakeup by any of these callbacks.
|
||||||
*
|
*
|
||||||
* @thaw_noirq: Prepare for the execution of @thaw() by carrying out any
|
* @thaw_noirq: Prepare for the execution of @thaw() by carrying out any
|
||||||
* operations required for thawing the device that might be racing with its
|
* 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 (*thaw)(struct device *dev);
|
||||||
int (*poweroff)(struct device *dev);
|
int (*poweroff)(struct device *dev);
|
||||||
int (*restore)(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 (*suspend_noirq)(struct device *dev);
|
||||||
int (*resume_noirq)(struct device *dev);
|
int (*resume_noirq)(struct device *dev);
|
||||||
int (*freeze_noirq)(struct device *dev);
|
int (*freeze_noirq)(struct device *dev);
|
||||||
@ -293,6 +320,15 @@ const struct dev_pm_ops name = { \
|
|||||||
/*
|
/*
|
||||||
* Use this for defining a set of PM operations to be used in all situations
|
* Use this for defining a set of PM operations to be used in all situations
|
||||||
* (sustem suspend, hibernation or runtime PM).
|
* (sustem suspend, hibernation or runtime PM).
|
||||||
|
* NOTE: In general, system suspend callbacks, .suspend() and .resume(), should
|
||||||
|
* be different from the corresponding runtime PM callbacks, .runtime_suspend(),
|
||||||
|
* and .runtime_resume(), because .runtime_suspend() always works on an already
|
||||||
|
* quiescent device, while .suspend() should assume that the device may be doing
|
||||||
|
* something when it is called (it should ensure that the device will be
|
||||||
|
* quiescent after it has returned). Therefore it's better to point the "late"
|
||||||
|
* suspend and "early" resume callback pointers, .suspend_late() and
|
||||||
|
* .resume_early(), to the same routines as .runtime_suspend() and
|
||||||
|
* .runtime_resume(), respectively (and analogously for hibernation).
|
||||||
*/
|
*/
|
||||||
#define UNIVERSAL_DEV_PM_OPS(name, suspend_fn, resume_fn, idle_fn) \
|
#define UNIVERSAL_DEV_PM_OPS(name, suspend_fn, resume_fn, idle_fn) \
|
||||||
const struct dev_pm_ops name = { \
|
const struct dev_pm_ops name = { \
|
||||||
@ -584,13 +620,13 @@ struct dev_pm_domain {
|
|||||||
|
|
||||||
#ifdef CONFIG_PM_SLEEP
|
#ifdef CONFIG_PM_SLEEP
|
||||||
extern void device_pm_lock(void);
|
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_end(pm_message_t state);
|
||||||
extern void dpm_resume(pm_message_t state);
|
extern void dpm_resume(pm_message_t state);
|
||||||
extern void dpm_complete(pm_message_t state);
|
extern void dpm_complete(pm_message_t state);
|
||||||
|
|
||||||
extern void device_pm_unlock(void);
|
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_start(pm_message_t state);
|
||||||
extern int dpm_suspend(pm_message_t state);
|
extern int dpm_suspend(pm_message_t state);
|
||||||
extern int dpm_prepare(pm_message_t state);
|
extern int dpm_prepare(pm_message_t state);
|
||||||
@ -605,17 +641,23 @@ extern void __suspend_report_result(const char *function, void *fn, int ret);
|
|||||||
extern int device_pm_wait_for_dev(struct device *sub, struct device *dev);
|
extern int device_pm_wait_for_dev(struct device *sub, struct device *dev);
|
||||||
|
|
||||||
extern int pm_generic_prepare(struct device *dev);
|
extern int pm_generic_prepare(struct device *dev);
|
||||||
|
extern int pm_generic_suspend_late(struct device *dev);
|
||||||
extern int pm_generic_suspend_noirq(struct device *dev);
|
extern int pm_generic_suspend_noirq(struct device *dev);
|
||||||
extern int pm_generic_suspend(struct device *dev);
|
extern int pm_generic_suspend(struct device *dev);
|
||||||
|
extern int pm_generic_resume_early(struct device *dev);
|
||||||
extern int pm_generic_resume_noirq(struct device *dev);
|
extern int pm_generic_resume_noirq(struct device *dev);
|
||||||
extern int pm_generic_resume(struct device *dev);
|
extern int pm_generic_resume(struct device *dev);
|
||||||
extern int pm_generic_freeze_noirq(struct device *dev);
|
extern int pm_generic_freeze_noirq(struct device *dev);
|
||||||
|
extern int pm_generic_freeze_late(struct device *dev);
|
||||||
extern int pm_generic_freeze(struct device *dev);
|
extern int pm_generic_freeze(struct device *dev);
|
||||||
extern int pm_generic_thaw_noirq(struct device *dev);
|
extern int pm_generic_thaw_noirq(struct device *dev);
|
||||||
|
extern int pm_generic_thaw_early(struct device *dev);
|
||||||
extern int pm_generic_thaw(struct device *dev);
|
extern int pm_generic_thaw(struct device *dev);
|
||||||
extern int pm_generic_restore_noirq(struct device *dev);
|
extern int pm_generic_restore_noirq(struct device *dev);
|
||||||
|
extern int pm_generic_restore_early(struct device *dev);
|
||||||
extern int pm_generic_restore(struct device *dev);
|
extern int pm_generic_restore(struct device *dev);
|
||||||
extern int pm_generic_poweroff_noirq(struct device *dev);
|
extern int pm_generic_poweroff_noirq(struct device *dev);
|
||||||
|
extern int pm_generic_poweroff_late(struct device *dev);
|
||||||
extern int pm_generic_poweroff(struct device *dev);
|
extern int pm_generic_poweroff(struct device *dev);
|
||||||
extern void pm_generic_complete(struct device *dev);
|
extern void pm_generic_complete(struct device *dev);
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
* @active: Status of the wakeup source.
|
* @active: Status of the wakeup source.
|
||||||
*/
|
*/
|
||||||
struct wakeup_source {
|
struct wakeup_source {
|
||||||
char *name;
|
const char *name;
|
||||||
struct list_head entry;
|
struct list_head entry;
|
||||||
spinlock_t lock;
|
spinlock_t lock;
|
||||||
struct timer_list timer;
|
struct timer_list timer;
|
||||||
@ -73,7 +73,9 @@ static inline bool device_may_wakeup(struct device *dev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* drivers/base/power/wakeup.c */
|
/* drivers/base/power/wakeup.c */
|
||||||
|
extern void wakeup_source_prepare(struct wakeup_source *ws, const char *name);
|
||||||
extern struct wakeup_source *wakeup_source_create(const char *name);
|
extern struct wakeup_source *wakeup_source_create(const char *name);
|
||||||
|
extern void wakeup_source_drop(struct wakeup_source *ws);
|
||||||
extern void wakeup_source_destroy(struct wakeup_source *ws);
|
extern void wakeup_source_destroy(struct wakeup_source *ws);
|
||||||
extern void wakeup_source_add(struct wakeup_source *ws);
|
extern void wakeup_source_add(struct wakeup_source *ws);
|
||||||
extern void wakeup_source_remove(struct wakeup_source *ws);
|
extern void wakeup_source_remove(struct wakeup_source *ws);
|
||||||
@ -103,11 +105,16 @@ static inline bool device_can_wakeup(struct device *dev)
|
|||||||
return dev->power.can_wakeup;
|
return dev->power.can_wakeup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void wakeup_source_prepare(struct wakeup_source *ws,
|
||||||
|
const char *name) {}
|
||||||
|
|
||||||
static inline struct wakeup_source *wakeup_source_create(const char *name)
|
static inline struct wakeup_source *wakeup_source_create(const char *name)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void wakeup_source_drop(struct wakeup_source *ws) {}
|
||||||
|
|
||||||
static inline void wakeup_source_destroy(struct wakeup_source *ws) {}
|
static inline void wakeup_source_destroy(struct wakeup_source *ws) {}
|
||||||
|
|
||||||
static inline void wakeup_source_add(struct wakeup_source *ws) {}
|
static inline void wakeup_source_add(struct wakeup_source *ws) {}
|
||||||
@ -165,4 +172,17 @@ static inline void pm_wakeup_event(struct device *dev, unsigned int msec) {}
|
|||||||
|
|
||||||
#endif /* !CONFIG_PM_SLEEP */
|
#endif /* !CONFIG_PM_SLEEP */
|
||||||
|
|
||||||
|
static inline void wakeup_source_init(struct wakeup_source *ws,
|
||||||
|
const char *name)
|
||||||
|
{
|
||||||
|
wakeup_source_prepare(ws, name);
|
||||||
|
wakeup_source_add(ws);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void wakeup_source_trash(struct wakeup_source *ws)
|
||||||
|
{
|
||||||
|
wakeup_source_remove(ws);
|
||||||
|
wakeup_source_drop(ws);
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* _LINUX_PM_WAKEUP_H */
|
#endif /* _LINUX_PM_WAKEUP_H */
|
||||||
|
@ -42,8 +42,10 @@ enum suspend_stat_step {
|
|||||||
SUSPEND_FREEZE = 1,
|
SUSPEND_FREEZE = 1,
|
||||||
SUSPEND_PREPARE,
|
SUSPEND_PREPARE,
|
||||||
SUSPEND_SUSPEND,
|
SUSPEND_SUSPEND,
|
||||||
|
SUSPEND_SUSPEND_LATE,
|
||||||
SUSPEND_SUSPEND_NOIRQ,
|
SUSPEND_SUSPEND_NOIRQ,
|
||||||
SUSPEND_RESUME_NOIRQ,
|
SUSPEND_RESUME_NOIRQ,
|
||||||
|
SUSPEND_RESUME_EARLY,
|
||||||
SUSPEND_RESUME
|
SUSPEND_RESUME
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -53,8 +55,10 @@ struct suspend_stats {
|
|||||||
int failed_freeze;
|
int failed_freeze;
|
||||||
int failed_prepare;
|
int failed_prepare;
|
||||||
int failed_suspend;
|
int failed_suspend;
|
||||||
|
int failed_suspend_late;
|
||||||
int failed_suspend_noirq;
|
int failed_suspend_noirq;
|
||||||
int failed_resume;
|
int failed_resume;
|
||||||
|
int failed_resume_early;
|
||||||
int failed_resume_noirq;
|
int failed_resume_noirq;
|
||||||
#define REC_FAILED_NUM 2
|
#define REC_FAILED_NUM 2
|
||||||
int last_failed_dev;
|
int last_failed_dev;
|
||||||
|
@ -424,7 +424,7 @@ void daemonize(const char *name, ...)
|
|||||||
*/
|
*/
|
||||||
exit_mm(current);
|
exit_mm(current);
|
||||||
/*
|
/*
|
||||||
* We don't want to have TIF_FREEZE set if the system-wide hibernation
|
* We don't want to get frozen, in case system-wide hibernation
|
||||||
* or suspend transition begins right now.
|
* or suspend transition begins right now.
|
||||||
*/
|
*/
|
||||||
current->flags |= (PF_NOFREEZE | PF_KTHREAD);
|
current->flags |= (PF_NOFREEZE | PF_KTHREAD);
|
||||||
|
@ -99,9 +99,9 @@ static void fake_signal_wake_up(struct task_struct *p)
|
|||||||
* freeze_task - send a freeze request to given task
|
* freeze_task - send a freeze request to given task
|
||||||
* @p: task to send the request to
|
* @p: task to send the request to
|
||||||
*
|
*
|
||||||
* If @p is freezing, the freeze request is sent by setting %TIF_FREEZE
|
* If @p is freezing, the freeze request is sent either by sending a fake
|
||||||
* flag and either sending a fake signal to it or waking it up, depending
|
* signal (if it's not a kernel thread) or waking it up (if it's a kernel
|
||||||
* on whether it has %PF_FREEZER_NOSIG set.
|
* thread).
|
||||||
*
|
*
|
||||||
* RETURNS:
|
* RETURNS:
|
||||||
* %false, if @p is not freezing or already frozen; %true, otherwise
|
* %false, if @p is not freezing or already frozen; %true, otherwise
|
||||||
|
@ -1546,13 +1546,13 @@ int kernel_kexec(void)
|
|||||||
if (error)
|
if (error)
|
||||||
goto Resume_console;
|
goto Resume_console;
|
||||||
/* At this point, dpm_suspend_start() has been called,
|
/* At this point, dpm_suspend_start() has been called,
|
||||||
* but *not* dpm_suspend_noirq(). We *must* call
|
* but *not* dpm_suspend_end(). We *must* call
|
||||||
* dpm_suspend_noirq() now. Otherwise, drivers for
|
* dpm_suspend_end() now. Otherwise, drivers for
|
||||||
* some devices (e.g. interrupt controllers) become
|
* some devices (e.g. interrupt controllers) become
|
||||||
* desynchronized with the actual state of the
|
* desynchronized with the actual state of the
|
||||||
* hardware at resume time, and evil weirdness ensues.
|
* hardware at resume time, and evil weirdness ensues.
|
||||||
*/
|
*/
|
||||||
error = dpm_suspend_noirq(PMSG_FREEZE);
|
error = dpm_suspend_end(PMSG_FREEZE);
|
||||||
if (error)
|
if (error)
|
||||||
goto Resume_devices;
|
goto Resume_devices;
|
||||||
error = disable_nonboot_cpus();
|
error = disable_nonboot_cpus();
|
||||||
@ -1579,7 +1579,7 @@ int kernel_kexec(void)
|
|||||||
local_irq_enable();
|
local_irq_enable();
|
||||||
Enable_cpus:
|
Enable_cpus:
|
||||||
enable_nonboot_cpus();
|
enable_nonboot_cpus();
|
||||||
dpm_resume_noirq(PMSG_RESTORE);
|
dpm_resume_start(PMSG_RESTORE);
|
||||||
Resume_devices:
|
Resume_devices:
|
||||||
dpm_resume_end(PMSG_RESTORE);
|
dpm_resume_end(PMSG_RESTORE);
|
||||||
Resume_console:
|
Resume_console:
|
||||||
|
@ -245,8 +245,8 @@ void swsusp_show_speed(struct timeval *start, struct timeval *stop,
|
|||||||
* create_image - Create a hibernation image.
|
* create_image - Create a hibernation image.
|
||||||
* @platform_mode: Whether or not to use the platform driver.
|
* @platform_mode: Whether or not to use the platform driver.
|
||||||
*
|
*
|
||||||
* Execute device drivers' .freeze_noirq() callbacks, create a hibernation image
|
* Execute device drivers' "late" and "noirq" freeze callbacks, create a
|
||||||
* and execute the drivers' .thaw_noirq() callbacks.
|
* hibernation image and run the drivers' "noirq" and "early" thaw callbacks.
|
||||||
*
|
*
|
||||||
* Control reappears in this routine after the subsequent restore.
|
* Control reappears in this routine after the subsequent restore.
|
||||||
*/
|
*/
|
||||||
@ -254,7 +254,7 @@ static int create_image(int platform_mode)
|
|||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
error = dpm_suspend_noirq(PMSG_FREEZE);
|
error = dpm_suspend_end(PMSG_FREEZE);
|
||||||
if (error) {
|
if (error) {
|
||||||
printk(KERN_ERR "PM: Some devices failed to power down, "
|
printk(KERN_ERR "PM: Some devices failed to power down, "
|
||||||
"aborting hibernation\n");
|
"aborting hibernation\n");
|
||||||
@ -306,7 +306,7 @@ static int create_image(int platform_mode)
|
|||||||
Platform_finish:
|
Platform_finish:
|
||||||
platform_finish(platform_mode);
|
platform_finish(platform_mode);
|
||||||
|
|
||||||
dpm_resume_noirq(in_suspend ?
|
dpm_resume_start(in_suspend ?
|
||||||
(error ? PMSG_RECOVER : PMSG_THAW) : PMSG_RESTORE);
|
(error ? PMSG_RECOVER : PMSG_THAW) : PMSG_RESTORE);
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
@ -343,13 +343,13 @@ int hibernation_snapshot(int platform_mode)
|
|||||||
* successful freezer test.
|
* successful freezer test.
|
||||||
*/
|
*/
|
||||||
freezer_test_done = true;
|
freezer_test_done = true;
|
||||||
goto Cleanup;
|
goto Thaw;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = dpm_prepare(PMSG_FREEZE);
|
error = dpm_prepare(PMSG_FREEZE);
|
||||||
if (error) {
|
if (error) {
|
||||||
dpm_complete(PMSG_RECOVER);
|
dpm_complete(PMSG_RECOVER);
|
||||||
goto Cleanup;
|
goto Thaw;
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend_console();
|
suspend_console();
|
||||||
@ -385,6 +385,8 @@ int hibernation_snapshot(int platform_mode)
|
|||||||
platform_end(platform_mode);
|
platform_end(platform_mode);
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
|
Thaw:
|
||||||
|
thaw_kernel_threads();
|
||||||
Cleanup:
|
Cleanup:
|
||||||
swsusp_free();
|
swsusp_free();
|
||||||
goto Close;
|
goto Close;
|
||||||
@ -394,16 +396,16 @@ int hibernation_snapshot(int platform_mode)
|
|||||||
* resume_target_kernel - Restore system state from a hibernation image.
|
* resume_target_kernel - Restore system state from a hibernation image.
|
||||||
* @platform_mode: Whether or not to use the platform driver.
|
* @platform_mode: Whether or not to use the platform driver.
|
||||||
*
|
*
|
||||||
* Execute device drivers' .freeze_noirq() callbacks, restore the contents of
|
* Execute device drivers' "noirq" and "late" freeze callbacks, restore the
|
||||||
* highmem that have not been restored yet from the image and run the low-level
|
* contents of highmem that have not been restored yet from the image and run
|
||||||
* code that will restore the remaining contents of memory and switch to the
|
* the low-level code that will restore the remaining contents of memory and
|
||||||
* just restored target kernel.
|
* switch to the just restored target kernel.
|
||||||
*/
|
*/
|
||||||
static int resume_target_kernel(bool platform_mode)
|
static int resume_target_kernel(bool platform_mode)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
error = dpm_suspend_noirq(PMSG_QUIESCE);
|
error = dpm_suspend_end(PMSG_QUIESCE);
|
||||||
if (error) {
|
if (error) {
|
||||||
printk(KERN_ERR "PM: Some devices failed to power down, "
|
printk(KERN_ERR "PM: Some devices failed to power down, "
|
||||||
"aborting resume\n");
|
"aborting resume\n");
|
||||||
@ -460,7 +462,7 @@ static int resume_target_kernel(bool platform_mode)
|
|||||||
Cleanup:
|
Cleanup:
|
||||||
platform_restore_cleanup(platform_mode);
|
platform_restore_cleanup(platform_mode);
|
||||||
|
|
||||||
dpm_resume_noirq(PMSG_RECOVER);
|
dpm_resume_start(PMSG_RECOVER);
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
@ -518,7 +520,7 @@ int hibernation_platform_enter(void)
|
|||||||
goto Resume_devices;
|
goto Resume_devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = dpm_suspend_noirq(PMSG_HIBERNATE);
|
error = dpm_suspend_end(PMSG_HIBERNATE);
|
||||||
if (error)
|
if (error)
|
||||||
goto Resume_devices;
|
goto Resume_devices;
|
||||||
|
|
||||||
@ -549,7 +551,7 @@ int hibernation_platform_enter(void)
|
|||||||
Platform_finish:
|
Platform_finish:
|
||||||
hibernation_ops->finish();
|
hibernation_ops->finish();
|
||||||
|
|
||||||
dpm_resume_noirq(PMSG_RESTORE);
|
dpm_resume_start(PMSG_RESTORE);
|
||||||
|
|
||||||
Resume_devices:
|
Resume_devices:
|
||||||
entering_platform_hibernation = false;
|
entering_platform_hibernation = false;
|
||||||
@ -616,7 +618,7 @@ int hibernate(void)
|
|||||||
/* Allocate memory management structures */
|
/* Allocate memory management structures */
|
||||||
error = create_basic_memory_bitmaps();
|
error = create_basic_memory_bitmaps();
|
||||||
if (error)
|
if (error)
|
||||||
goto Exit;
|
goto Enable_umh;
|
||||||
|
|
||||||
printk(KERN_INFO "PM: Syncing filesystems ... ");
|
printk(KERN_INFO "PM: Syncing filesystems ... ");
|
||||||
sys_sync();
|
sys_sync();
|
||||||
@ -624,15 +626,11 @@ int hibernate(void)
|
|||||||
|
|
||||||
error = freeze_processes();
|
error = freeze_processes();
|
||||||
if (error)
|
if (error)
|
||||||
goto Finish;
|
goto Free_bitmaps;
|
||||||
|
|
||||||
error = hibernation_snapshot(hibernation_mode == HIBERNATION_PLATFORM);
|
error = hibernation_snapshot(hibernation_mode == HIBERNATION_PLATFORM);
|
||||||
if (error)
|
if (error || freezer_test_done)
|
||||||
goto Thaw;
|
goto Thaw;
|
||||||
if (freezer_test_done) {
|
|
||||||
freezer_test_done = false;
|
|
||||||
goto Thaw;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_suspend) {
|
if (in_suspend) {
|
||||||
unsigned int flags = 0;
|
unsigned int flags = 0;
|
||||||
@ -657,8 +655,13 @@ int hibernate(void)
|
|||||||
|
|
||||||
Thaw:
|
Thaw:
|
||||||
thaw_processes();
|
thaw_processes();
|
||||||
Finish:
|
|
||||||
|
/* Don't bother checking whether freezer_test_done is true */
|
||||||
|
freezer_test_done = false;
|
||||||
|
|
||||||
|
Free_bitmaps:
|
||||||
free_basic_memory_bitmaps();
|
free_basic_memory_bitmaps();
|
||||||
|
Enable_umh:
|
||||||
usermodehelper_enable();
|
usermodehelper_enable();
|
||||||
Exit:
|
Exit:
|
||||||
pm_notifier_call_chain(PM_POST_HIBERNATION);
|
pm_notifier_call_chain(PM_POST_HIBERNATION);
|
||||||
|
@ -165,16 +165,20 @@ static int suspend_stats_show(struct seq_file *s, void *unused)
|
|||||||
last_errno %= REC_FAILED_NUM;
|
last_errno %= REC_FAILED_NUM;
|
||||||
last_step = suspend_stats.last_failed_step + REC_FAILED_NUM - 1;
|
last_step = suspend_stats.last_failed_step + REC_FAILED_NUM - 1;
|
||||||
last_step %= REC_FAILED_NUM;
|
last_step %= REC_FAILED_NUM;
|
||||||
seq_printf(s, "%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%s: %d\n%s: %d\n%s: %d\n%s: %d\n",
|
||||||
"success", suspend_stats.success,
|
"success", suspend_stats.success,
|
||||||
"fail", suspend_stats.fail,
|
"fail", suspend_stats.fail,
|
||||||
"failed_freeze", suspend_stats.failed_freeze,
|
"failed_freeze", suspend_stats.failed_freeze,
|
||||||
"failed_prepare", suspend_stats.failed_prepare,
|
"failed_prepare", suspend_stats.failed_prepare,
|
||||||
"failed_suspend", suspend_stats.failed_suspend,
|
"failed_suspend", suspend_stats.failed_suspend,
|
||||||
|
"failed_suspend_late",
|
||||||
|
suspend_stats.failed_suspend_late,
|
||||||
"failed_suspend_noirq",
|
"failed_suspend_noirq",
|
||||||
suspend_stats.failed_suspend_noirq,
|
suspend_stats.failed_suspend_noirq,
|
||||||
"failed_resume", suspend_stats.failed_resume,
|
"failed_resume", suspend_stats.failed_resume,
|
||||||
|
"failed_resume_early",
|
||||||
|
suspend_stats.failed_resume_early,
|
||||||
"failed_resume_noirq",
|
"failed_resume_noirq",
|
||||||
suspend_stats.failed_resume_noirq);
|
suspend_stats.failed_resume_noirq);
|
||||||
seq_printf(s, "failures:\n last_failed_dev:\t%-s\n",
|
seq_printf(s, "failures:\n last_failed_dev:\t%-s\n",
|
||||||
@ -287,16 +291,10 @@ static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|||||||
|
|
||||||
#ifdef CONFIG_SUSPEND
|
#ifdef CONFIG_SUSPEND
|
||||||
for (s = &pm_states[state]; state < PM_SUSPEND_MAX; s++, state++) {
|
for (s = &pm_states[state]; state < PM_SUSPEND_MAX; s++, state++) {
|
||||||
if (*s && len == strlen(*s) && !strncmp(buf, *s, len))
|
if (*s && len == strlen(*s) && !strncmp(buf, *s, len)) {
|
||||||
|
error = pm_suspend(state);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (state < PM_SUSPEND_MAX && *s) {
|
|
||||||
error = enter_state(state);
|
|
||||||
if (error) {
|
|
||||||
suspend_stats.fail++;
|
|
||||||
dpm_save_failed_errno(error);
|
|
||||||
} else
|
|
||||||
suspend_stats.success++;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -177,13 +177,11 @@ extern const char *const pm_states[];
|
|||||||
|
|
||||||
extern bool valid_state(suspend_state_t state);
|
extern bool valid_state(suspend_state_t state);
|
||||||
extern int suspend_devices_and_enter(suspend_state_t state);
|
extern int suspend_devices_and_enter(suspend_state_t state);
|
||||||
extern int enter_state(suspend_state_t state);
|
|
||||||
#else /* !CONFIG_SUSPEND */
|
#else /* !CONFIG_SUSPEND */
|
||||||
static inline int suspend_devices_and_enter(suspend_state_t state)
|
static inline int suspend_devices_and_enter(suspend_state_t state)
|
||||||
{
|
{
|
||||||
return -ENOSYS;
|
return -ENOSYS;
|
||||||
}
|
}
|
||||||
static inline int enter_state(suspend_state_t state) { return -ENOSYS; }
|
|
||||||
static inline bool valid_state(suspend_state_t state) { return false; }
|
static inline bool valid_state(suspend_state_t state) { return false; }
|
||||||
#endif /* !CONFIG_SUSPEND */
|
#endif /* !CONFIG_SUSPEND */
|
||||||
|
|
||||||
@ -234,16 +232,14 @@ static inline int suspend_freeze_processes(void)
|
|||||||
int error;
|
int error;
|
||||||
|
|
||||||
error = freeze_processes();
|
error = freeze_processes();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* freeze_processes() automatically thaws every task if freezing
|
* freeze_processes() automatically thaws every task if freezing
|
||||||
* fails. So we need not do anything extra upon error.
|
* fails. So we need not do anything extra upon error.
|
||||||
*/
|
*/
|
||||||
if (error)
|
if (error)
|
||||||
goto Finish;
|
return error;
|
||||||
|
|
||||||
error = freeze_kernel_threads();
|
error = freeze_kernel_threads();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* freeze_kernel_threads() thaws only kernel threads upon freezing
|
* freeze_kernel_threads() thaws only kernel threads upon freezing
|
||||||
* failure. So we have to thaw the userspace tasks ourselves.
|
* failure. So we have to thaw the userspace tasks ourselves.
|
||||||
@ -251,7 +247,6 @@ static inline int suspend_freeze_processes(void)
|
|||||||
if (error)
|
if (error)
|
||||||
thaw_processes();
|
thaw_processes();
|
||||||
|
|
||||||
Finish:
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,11 +53,9 @@ static int try_to_freeze_tasks(bool user_only)
|
|||||||
* It is "frozen enough". If the task does wake
|
* It is "frozen enough". If the task does wake
|
||||||
* up, it will immediately call try_to_freeze.
|
* up, it will immediately call try_to_freeze.
|
||||||
*
|
*
|
||||||
* Because freeze_task() goes through p's
|
* Because freeze_task() goes through p's scheduler lock, it's
|
||||||
* scheduler lock after setting TIF_FREEZE, it's
|
* guaranteed that TASK_STOPPED/TRACED -> TASK_RUNNING
|
||||||
* guaranteed that either we see TASK_RUNNING or
|
* transition can't race with task state testing here.
|
||||||
* try_to_stop() after schedule() in ptrace/signal
|
|
||||||
* stop sees TIF_FREEZE.
|
|
||||||
*/
|
*/
|
||||||
if (!task_is_stopped_or_traced(p) &&
|
if (!task_is_stopped_or_traced(p) &&
|
||||||
!freezer_should_skip(p))
|
!freezer_should_skip(p))
|
||||||
@ -98,13 +96,15 @@ static int try_to_freeze_tasks(bool user_only)
|
|||||||
elapsed_csecs / 100, elapsed_csecs % 100,
|
elapsed_csecs / 100, elapsed_csecs % 100,
|
||||||
todo - wq_busy, wq_busy);
|
todo - wq_busy, wq_busy);
|
||||||
|
|
||||||
read_lock(&tasklist_lock);
|
if (!wakeup) {
|
||||||
do_each_thread(g, p) {
|
read_lock(&tasklist_lock);
|
||||||
if (!wakeup && !freezer_should_skip(p) &&
|
do_each_thread(g, p) {
|
||||||
p != current && freezing(p) && !frozen(p))
|
if (p != current && !freezer_should_skip(p)
|
||||||
sched_show_task(p);
|
&& freezing(p) && !frozen(p))
|
||||||
} while_each_thread(g, p);
|
sched_show_task(p);
|
||||||
read_unlock(&tasklist_lock);
|
} while_each_thread(g, p);
|
||||||
|
read_unlock(&tasklist_lock);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
printk("(elapsed %d.%02d seconds) ", elapsed_csecs / 100,
|
printk("(elapsed %d.%02d seconds) ", elapsed_csecs / 100,
|
||||||
elapsed_csecs % 100);
|
elapsed_csecs % 100);
|
||||||
|
@ -711,9 +711,10 @@ static void mark_nosave_pages(struct memory_bitmap *bm)
|
|||||||
list_for_each_entry(region, &nosave_regions, list) {
|
list_for_each_entry(region, &nosave_regions, list) {
|
||||||
unsigned long pfn;
|
unsigned long pfn;
|
||||||
|
|
||||||
pr_debug("PM: Marking nosave pages: %016lx - %016lx\n",
|
pr_debug("PM: Marking nosave pages: [mem %#010llx-%#010llx]\n",
|
||||||
region->start_pfn << PAGE_SHIFT,
|
(unsigned long long) region->start_pfn << PAGE_SHIFT,
|
||||||
region->end_pfn << PAGE_SHIFT);
|
((unsigned long long) region->end_pfn << PAGE_SHIFT)
|
||||||
|
- 1);
|
||||||
|
|
||||||
for (pfn = region->start_pfn; pfn < region->end_pfn; pfn++)
|
for (pfn = region->start_pfn; pfn < region->end_pfn; pfn++)
|
||||||
if (pfn_valid(pfn)) {
|
if (pfn_valid(pfn)) {
|
||||||
|
@ -37,8 +37,8 @@ const char *const pm_states[PM_SUSPEND_MAX] = {
|
|||||||
static const struct platform_suspend_ops *suspend_ops;
|
static const struct platform_suspend_ops *suspend_ops;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* suspend_set_ops - Set the global suspend method table.
|
* suspend_set_ops - Set the global suspend method table.
|
||||||
* @ops: Pointer to ops structure.
|
* @ops: Suspend operations to use.
|
||||||
*/
|
*/
|
||||||
void suspend_set_ops(const struct platform_suspend_ops *ops)
|
void suspend_set_ops(const struct platform_suspend_ops *ops)
|
||||||
{
|
{
|
||||||
@ -58,11 +58,11 @@ bool valid_state(suspend_state_t state)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* suspend_valid_only_mem - generic memory-only valid callback
|
* suspend_valid_only_mem - Generic memory-only valid callback.
|
||||||
*
|
*
|
||||||
* Platform drivers that implement mem suspend only and only need
|
* Platform drivers that implement mem suspend only and only need to check for
|
||||||
* to check for that in their .valid callback can use this instead
|
* that in their .valid() callback can use this instead of rolling their own
|
||||||
* of rolling their own .valid callback.
|
* .valid() callback.
|
||||||
*/
|
*/
|
||||||
int suspend_valid_only_mem(suspend_state_t state)
|
int suspend_valid_only_mem(suspend_state_t state)
|
||||||
{
|
{
|
||||||
@ -83,10 +83,11 @@ static int suspend_test(int level)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* suspend_prepare - Do prep work before entering low-power state.
|
* suspend_prepare - Prepare for entering system sleep state.
|
||||||
*
|
*
|
||||||
* This is common code that is called for each state that we're entering.
|
* Common code run for every system sleep state that can be entered (except for
|
||||||
* Run suspend notifiers, allocate a console and stop all processes.
|
* hibernation). Run suspend notifiers, allocate the "suspend" console and
|
||||||
|
* freeze processes.
|
||||||
*/
|
*/
|
||||||
static int suspend_prepare(void)
|
static int suspend_prepare(void)
|
||||||
{
|
{
|
||||||
@ -131,9 +132,9 @@ void __attribute__ ((weak)) arch_suspend_enable_irqs(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* suspend_enter - enter the desired system sleep state.
|
* suspend_enter - Make the system enter the given sleep state.
|
||||||
* @state: State to enter
|
* @state: System sleep state to enter.
|
||||||
* @wakeup: Returns information that suspend should not be entered again.
|
* @wakeup: Returns information that the sleep state should not be re-entered.
|
||||||
*
|
*
|
||||||
* This function should be called after devices have been suspended.
|
* This function should be called after devices have been suspended.
|
||||||
*/
|
*/
|
||||||
@ -147,7 +148,7 @@ static int suspend_enter(suspend_state_t state, bool *wakeup)
|
|||||||
goto Platform_finish;
|
goto Platform_finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = dpm_suspend_noirq(PMSG_SUSPEND);
|
error = dpm_suspend_end(PMSG_SUSPEND);
|
||||||
if (error) {
|
if (error) {
|
||||||
printk(KERN_ERR "PM: Some devices failed to power down\n");
|
printk(KERN_ERR "PM: Some devices failed to power down\n");
|
||||||
goto Platform_finish;
|
goto Platform_finish;
|
||||||
@ -189,7 +190,7 @@ static int suspend_enter(suspend_state_t state, bool *wakeup)
|
|||||||
if (suspend_ops->wake)
|
if (suspend_ops->wake)
|
||||||
suspend_ops->wake();
|
suspend_ops->wake();
|
||||||
|
|
||||||
dpm_resume_noirq(PMSG_RESUME);
|
dpm_resume_start(PMSG_RESUME);
|
||||||
|
|
||||||
Platform_finish:
|
Platform_finish:
|
||||||
if (suspend_ops->finish)
|
if (suspend_ops->finish)
|
||||||
@ -199,9 +200,8 @@ static int suspend_enter(suspend_state_t state, bool *wakeup)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* suspend_devices_and_enter - suspend devices and enter the desired system
|
* suspend_devices_and_enter - Suspend devices and enter system sleep state.
|
||||||
* sleep state.
|
* @state: System sleep state to enter.
|
||||||
* @state: state to enter
|
|
||||||
*/
|
*/
|
||||||
int suspend_devices_and_enter(suspend_state_t state)
|
int suspend_devices_and_enter(suspend_state_t state)
|
||||||
{
|
{
|
||||||
@ -251,10 +251,10 @@ int suspend_devices_and_enter(suspend_state_t state)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* suspend_finish - Do final work before exiting suspend sequence.
|
* suspend_finish - Clean up before finishing the suspend sequence.
|
||||||
*
|
*
|
||||||
* Call platform code to clean up, restart processes, and free the
|
* Call platform code to clean up, restart processes, and free the console that
|
||||||
* console that we've allocated. This is not called for suspend-to-disk.
|
* we've allocated. This routine is not called for hibernation.
|
||||||
*/
|
*/
|
||||||
static void suspend_finish(void)
|
static void suspend_finish(void)
|
||||||
{
|
{
|
||||||
@ -265,16 +265,14 @@ static void suspend_finish(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* enter_state - Do common work of entering low-power state.
|
* enter_state - Do common work needed to enter system sleep state.
|
||||||
* @state: pm_state structure for state we're entering.
|
* @state: System sleep state to enter.
|
||||||
*
|
*
|
||||||
* Make sure we're the only ones trying to enter a sleep state. Fail
|
* Make sure that no one else is trying to put the system into a sleep state.
|
||||||
* if someone has beat us to it, since we don't want anything weird to
|
* Fail if that's not the case. Otherwise, prepare for system suspend, make the
|
||||||
* happen when we wake up.
|
* system enter the given sleep state and clean up after wakeup.
|
||||||
* Then, do the setup for suspend, enter the state, and cleaup (after
|
|
||||||
* we've woken up).
|
|
||||||
*/
|
*/
|
||||||
int enter_state(suspend_state_t state)
|
static int enter_state(suspend_state_t state)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
@ -310,24 +308,26 @@ int enter_state(suspend_state_t state)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pm_suspend - Externally visible function for suspending system.
|
* pm_suspend - Externally visible function for suspending the system.
|
||||||
* @state: Enumerated value of state to enter.
|
* @state: System sleep state to enter.
|
||||||
*
|
*
|
||||||
* Determine whether or not value is within range, get state
|
* Check if the value of @state represents one of the supported states,
|
||||||
* structure, and enter (above).
|
* execute enter_state() and update system suspend statistics.
|
||||||
*/
|
*/
|
||||||
int pm_suspend(suspend_state_t state)
|
int pm_suspend(suspend_state_t state)
|
||||||
{
|
{
|
||||||
int ret;
|
int error;
|
||||||
if (state > PM_SUSPEND_ON && state < PM_SUSPEND_MAX) {
|
|
||||||
ret = enter_state(state);
|
if (state <= PM_SUSPEND_ON || state >= PM_SUSPEND_MAX)
|
||||||
if (ret) {
|
return -EINVAL;
|
||||||
suspend_stats.fail++;
|
|
||||||
dpm_save_failed_errno(ret);
|
error = enter_state(state);
|
||||||
} else
|
if (error) {
|
||||||
suspend_stats.success++;
|
suspend_stats.fail++;
|
||||||
return ret;
|
dpm_save_failed_errno(error);
|
||||||
|
} else {
|
||||||
|
suspend_stats.success++;
|
||||||
}
|
}
|
||||||
return -EINVAL;
|
return error;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(pm_suspend);
|
EXPORT_SYMBOL(pm_suspend);
|
||||||
|
@ -249,16 +249,10 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
|
|||||||
}
|
}
|
||||||
pm_restore_gfp_mask();
|
pm_restore_gfp_mask();
|
||||||
error = hibernation_snapshot(data->platform_support);
|
error = hibernation_snapshot(data->platform_support);
|
||||||
if (error) {
|
if (!error) {
|
||||||
thaw_kernel_threads();
|
|
||||||
} else {
|
|
||||||
error = put_user(in_suspend, (int __user *)arg);
|
error = put_user(in_suspend, (int __user *)arg);
|
||||||
if (!error && !freezer_test_done)
|
data->ready = !freezer_test_done && !error;
|
||||||
data->ready = 1;
|
freezer_test_done = false;
|
||||||
if (freezer_test_done) {
|
|
||||||
freezer_test_done = false;
|
|
||||||
thaw_kernel_threads();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user