提交 cf579dfb 编写于 作者: R Rafael J. Wysocki

PM / Sleep: Introduce "late suspend" and "early resume" of devices

The current device suspend/resume phases during system-wide power
transitions appear to be insufficient for some platforms that want
to use the same callback routines for saving device states and
related operations during runtime suspend/resume as well as during
system suspend/resume.  In principle, they could point their
.suspend_noirq() and .resume_noirq() to the same callback routines
as their .runtime_suspend() and .runtime_resume(), respectively,
but at least some of them require device interrupts to be enabled
while the code in those routines is running.

It also makes sense to have device suspend-resume callbacks that will
be executed with runtime PM disabled and with device interrupts
enabled in case someone needs to run some special code in that
context during system-wide power transitions.

Apart from this, .suspend_noirq() and .resume_noirq() were introduced
as a workaround for drivers using shared interrupts and failing to
prevent their interrupt handlers from accessing suspended hardware.
It appears to be better not to use them for other porposes, or we may
have to deal with some serious confusion (which seems to be happening
already).

For the above reasons, introduce new device suspend/resume phases,
"late suspend" and "early resume" (and analogously for hibernation)
whose callback will be executed with runtime PM disabled and with
device interrupts enabled and whose callback pointers generally may
point to runtime suspend/resume routines.
Signed-off-by: NRafael J. Wysocki <rjw@sisk.pl>
Reviewed-by: NMark Brown <broonie@opensource.wolfsonmicro.com>
Reviewed-by: NKevin Hilman <khilman@ti.com>
上级 181e9bde
...@@ -96,6 +96,12 @@ struct dev_pm_ops { ...@@ -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 ...@@ -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: ...@@ -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 ...@@ -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: ...@@ -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 ...@@ -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: ...@@ -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 ...@@ -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 ...@@ -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.
10. The poweroff phase is analogous to the suspend phase.
8. The poweroff phase is analogous to the suspend phase. 11. The poweroff_late phase is analogous to the suspend_late phase.
9. The poweroff_noirq phase is analogous to the suspend_noirq phase. 12. The poweroff_noirq phase is analogous to the suspend_noirq phase.
The poweroff and poweroff_noirq callbacks should do essentially the same things The poweroff, poweroff_late and poweroff_noirq callbacks should do essentially
as the suspend and suspend_noirq callbacks. The only notable difference is that the same things as the suspend, suspend_late and suspend_noirq callbacks,
they need not store the device register values, because the registers should respectively. The only notable difference is that they need not store the
already have been stored during the freeze or freeze_noirq phases. 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 ...@@ -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 restore phase is analogous to the resume phase.
3. The complete phase is discussed above. 4. The complete phase is discussed above.
The main difference from resume[_noirq] is that restore[_noirq] must assume the The main difference from resume[_early|_noirq] is that restore[_early|_noirq]
device has been accessed and reconfigured by the boot loader or the boot kernel. must assume the device has been accessed and reconfigured by the boot loader or
Consequently the state of the device may be different from the state remembered the boot kernel. Consequently the state of the device may be different from the
from the freeze and freeze_noirq phases. The device may even need to be reset state remembered from the freeze, freeze_late and freeze_noirq phases. The
and completely re-initialized. In many cases this difference doesn't matter, so device may even need to be reset and completely re-initialized. In many cases
the resume[_noirq] and restore[_norq] method pointers can be set to the same this difference doesn't matter, so the resume[_early|_noirq] and
routines. Nevertheless, different callback pointers are used in case there is a restore[_early|_norq] method pointers can be set to the same routines.
situation where it actually matters. 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
......
...@@ -1234,8 +1234,7 @@ static int suspend(int vetoable) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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)
......
...@@ -47,6 +47,7 @@ typedef int (*pm_callback_t)(struct device *); ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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));
...@@ -775,11 +893,96 @@ int dpm_suspend_noirq(pm_message_t state) ...@@ -775,11 +893,96 @@ int dpm_suspend_noirq(pm_message_t state)
mutex_unlock(&dpm_list_mtx); mutex_unlock(&dpm_list_mtx);
if (error) if (error)
dpm_resume_noirq(resume_event(state)); dpm_resume_noirq(resume_event(state));
else
dpm_show_time(starttime, state, "noirq");
return error;
}
/**
* device_suspend_late - Execute a "late suspend" callback for given device.
* @dev: Device to handle.
* @state: PM transition of the system being carried out.
*
* Runtime PM is disabled for @dev while this function is being executed.
*/
static int device_suspend_late(struct device *dev, pm_message_t state)
{
pm_callback_t callback = NULL;
char *info = NULL;
if (dev->pm_domain) {
info = "late power domain ";
callback = pm_late_early_op(&dev->pm_domain->ops, state);
} else if (dev->type && dev->type->pm) {
info = "late type ";
callback = pm_late_early_op(dev->type->pm, state);
} else if (dev->class && dev->class->pm) {
info = "late class ";
callback = pm_late_early_op(dev->class->pm, state);
} else if (dev->bus && dev->bus->pm) {
info = "late bus ";
callback = pm_late_early_op(dev->bus->pm, state);
}
if (!callback && dev->driver && dev->driver->pm) {
info = "late driver ";
callback = pm_late_early_op(dev->driver->pm, state);
}
return dpm_run_callback(callback, dev, state, info);
}
/**
* dpm_suspend_late - Execute "late suspend" callbacks for all devices.
* @state: PM transition of the system being carried out.
*/
static int dpm_suspend_late(pm_message_t state)
{
ktime_t starttime = ktime_get();
int error = 0;
mutex_lock(&dpm_list_mtx);
while (!list_empty(&dpm_suspended_list)) {
struct device *dev = to_device(dpm_suspended_list.prev);
get_device(dev);
mutex_unlock(&dpm_list_mtx);
error = device_suspend_late(dev, state);
mutex_lock(&dpm_list_mtx);
if (error) {
pm_dev_err(dev, state, " late", error);
suspend_stats.failed_suspend_late++;
dpm_save_failed_step(SUSPEND_SUSPEND_LATE);
dpm_save_failed_dev(dev_name(dev));
put_device(dev);
break;
}
if (!list_empty(&dev->power.entry))
list_move(&dev->power.entry, &dpm_late_early_list);
put_device(dev);
}
mutex_unlock(&dpm_list_mtx);
if (error)
dpm_resume_early(resume_event(state));
else else
dpm_show_time(starttime, state, "late"); dpm_show_time(starttime, state, "late");
return error; return error;
} }
EXPORT_SYMBOL_GPL(dpm_suspend_noirq);
/**
* dpm_suspend_end - Execute "late" and "noirq" device suspend callbacks.
* @state: PM transition of the system being carried out.
*/
int dpm_suspend_end(pm_message_t state)
{
int error = dpm_suspend_late(state);
return error ? : dpm_suspend_noirq(state);
}
EXPORT_SYMBOL_GPL(dpm_suspend_end);
/** /**
* legacy_suspend - Execute a legacy (bus or class) suspend callback for device. * legacy_suspend - Execute a legacy (bus or class) suspend callback for device.
......
...@@ -129,9 +129,9 @@ static void do_suspend(void) ...@@ -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) ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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);
...@@ -584,13 +611,13 @@ struct dev_pm_domain { ...@@ -584,13 +611,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);
......
...@@ -42,8 +42,10 @@ enum suspend_stat_step { ...@@ -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 { ...@@ -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;
......
...@@ -1546,13 +1546,13 @@ int kernel_kexec(void) ...@@ -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) ...@@ -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, ...@@ -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) ...@@ -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) ...@@ -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;
...@@ -394,16 +394,16 @@ int hibernation_snapshot(int platform_mode) ...@@ -394,16 +394,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 +460,7 @@ static int resume_target_kernel(bool platform_mode) ...@@ -460,7 +460,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 +518,7 @@ int hibernation_platform_enter(void) ...@@ -518,7 +518,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 +549,7 @@ int hibernation_platform_enter(void) ...@@ -549,7 +549,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;
......
...@@ -165,16 +165,20 @@ static int suspend_stats_show(struct seq_file *s, void *unused) ...@@ -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",
......
...@@ -147,7 +147,7 @@ static int suspend_enter(suspend_state_t state, bool *wakeup) ...@@ -147,7 +147,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 +189,7 @@ static int suspend_enter(suspend_state_t state, bool *wakeup) ...@@ -189,7 +189,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)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册