aboutsummaryrefslogtreecommitdiff
path: root/kernel/power/disk.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/power/disk.c')
-rw-r--r--kernel/power/disk.c156
1 files changed, 130 insertions, 26 deletions
diff --git a/kernel/power/disk.c b/kernel/power/disk.c
index eb72255b5c8..8b15f777010 100644
--- a/kernel/power/disk.c
+++ b/kernel/power/disk.c
@@ -45,17 +45,18 @@ enum {
static int hibernation_mode = HIBERNATION_SHUTDOWN;
-static struct hibernation_ops *hibernation_ops;
+static struct platform_hibernation_ops *hibernation_ops;
/**
* hibernation_set_ops - set the global hibernate operations
* @ops: the hibernation operations to use in subsequent hibernation transitions
*/
-void hibernation_set_ops(struct hibernation_ops *ops)
+void hibernation_set_ops(struct platform_hibernation_ops *ops)
{
- if (ops && !(ops->prepare && ops->enter && ops->finish
- && ops->pre_restore && ops->restore_cleanup)) {
+ if (ops && !(ops->start && ops->pre_snapshot && ops->finish
+ && ops->prepare && ops->enter && ops->pre_restore
+ && ops->restore_cleanup)) {
WARN_ON(1);
return;
}
@@ -69,16 +70,37 @@ void hibernation_set_ops(struct hibernation_ops *ops)
mutex_unlock(&pm_mutex);
}
+/**
+ * platform_start - tell the platform driver that we're starting
+ * hibernation
+ */
+
+static int platform_start(int platform_mode)
+{
+ return (platform_mode && hibernation_ops) ?
+ hibernation_ops->start() : 0;
+}
/**
- * platform_prepare - prepare the machine for hibernation using the
+ * platform_pre_snapshot - prepare the machine for hibernation using the
* platform driver if so configured and return an error code if it fails
*/
-static int platform_prepare(int platform_mode)
+static int platform_pre_snapshot(int platform_mode)
{
return (platform_mode && hibernation_ops) ?
- hibernation_ops->prepare() : 0;
+ hibernation_ops->pre_snapshot() : 0;
+}
+
+/**
+ * platform_leave - prepare the machine for switching to the normal mode
+ * of operation using the platform driver (called with interrupts disabled)
+ */
+
+static void platform_leave(int platform_mode)
+{
+ if (platform_mode && hibernation_ops)
+ hibernation_ops->leave();
}
/**
@@ -118,6 +140,51 @@ static void platform_restore_cleanup(int platform_mode)
}
/**
+ * create_image - freeze devices that need to be frozen with interrupts
+ * off, create the hibernation image and thaw those devices. Control
+ * reappears in this routine after a restore.
+ */
+
+int create_image(int platform_mode)
+{
+ int error;
+
+ error = arch_prepare_suspend();
+ if (error)
+ return error;
+
+ local_irq_disable();
+ /* At this point, device_suspend() has been called, but *not*
+ * device_power_down(). We *must* call device_power_down() now.
+ * Otherwise, drivers for some devices (e.g. interrupt controllers)
+ * become desynchronized with the actual state of the hardware
+ * at resume time, and evil weirdness ensues.
+ */
+ error = device_power_down(PMSG_FREEZE);
+ if (error) {
+ printk(KERN_ERR "Some devices failed to power down, "
+ KERN_ERR "aborting suspend\n");
+ goto Enable_irqs;
+ }
+
+ save_processor_state();
+ error = swsusp_arch_suspend();
+ if (error)
+ printk(KERN_ERR "Error %d while creating the image\n", error);
+ /* Restore control flow magically appears here */
+ restore_processor_state();
+ if (!in_suspend)
+ platform_leave(platform_mode);
+ /* NOTE: device_power_up() is just a resume() for devices
+ * that suspended with irqs off ... no overall powerup.
+ */
+ device_power_up();
+ Enable_irqs:
+ local_irq_enable();
+ return error;
+}
+
+/**
* hibernation_snapshot - quiesce devices and create the hibernation
* snapshot image.
* @platform_mode - if set, use the platform driver, if available, to
@@ -135,12 +202,16 @@ int hibernation_snapshot(int platform_mode)
if (error)
return error;
+ error = platform_start(platform_mode);
+ if (error)
+ return error;
+
suspend_console();
error = device_suspend(PMSG_FREEZE);
if (error)
goto Resume_console;
- error = platform_prepare(platform_mode);
+ error = platform_pre_snapshot(platform_mode);
if (error)
goto Resume_devices;
@@ -148,7 +219,7 @@ int hibernation_snapshot(int platform_mode)
if (!error) {
if (hibernation_mode != HIBERNATION_TEST) {
in_suspend = 1;
- error = swsusp_suspend();
+ error = create_image(platform_mode);
/* Control returns here after successful restore */
} else {
printk("swsusp debug: Waiting for 5 seconds.\n");
@@ -207,21 +278,50 @@ int hibernation_platform_enter(void)
{
int error;
- if (hibernation_ops) {
- kernel_shutdown_prepare(SYSTEM_SUSPEND_DISK);
- /*
- * We have cancelled the power transition by running
- * hibernation_ops->finish() before saving the image, so we
- * should let the firmware know that we're going to enter the
- * sleep state after all
- */
- error = hibernation_ops->prepare();
- sysdev_shutdown();
- if (!error)
- error = hibernation_ops->enter();
- } else {
- error = -ENOSYS;
+ if (!hibernation_ops)
+ return -ENOSYS;
+
+ /*
+ * We have cancelled the power transition by running
+ * hibernation_ops->finish() before saving the image, so we should let
+ * the firmware know that we're going to enter the sleep state after all
+ */
+ error = hibernation_ops->start();
+ if (error)
+ return error;
+
+ suspend_console();
+ error = device_suspend(PMSG_SUSPEND);
+ if (error)
+ goto Resume_console;
+
+ error = hibernation_ops->prepare();
+ if (error)
+ goto Resume_devices;
+
+ error = disable_nonboot_cpus();
+ if (error)
+ goto Finish;
+
+ local_irq_disable();
+ error = device_power_down(PMSG_SUSPEND);
+ if (!error) {
+ hibernation_ops->enter();
+ /* We should never get here */
+ while (1);
}
+ local_irq_enable();
+
+ /*
+ * We don't need to reenable the nonboot CPUs or resume consoles, since
+ * the system is going to be halted anyway.
+ */
+ Finish:
+ hibernation_ops->finish();
+ Resume_devices:
+ device_resume();
+ Resume_console:
+ resume_console();
return error;
}
@@ -238,14 +338,14 @@ static void power_down(void)
case HIBERNATION_TEST:
case HIBERNATION_TESTPROC:
break;
- case HIBERNATION_SHUTDOWN:
- kernel_power_off();
- break;
case HIBERNATION_REBOOT:
kernel_restart(NULL);
break;
case HIBERNATION_PLATFORM:
hibernation_platform_enter();
+ case HIBERNATION_SHUTDOWN:
+ kernel_power_off();
+ break;
}
kernel_halt();
/*
@@ -298,6 +398,10 @@ int hibernate(void)
if (error)
goto Exit;
+ printk("Syncing filesystems ... ");
+ sys_sync();
+ printk("done.\n");
+
error = prepare_processes();
if (error)
goto Finish;