diff options
Diffstat (limited to 'kernel/power/disk.c')
-rw-r--r-- | kernel/power/disk.c | 208 |
1 files changed, 157 insertions, 51 deletions
diff --git a/kernel/power/disk.c b/kernel/power/disk.c index b138b431e271..859a8e59773a 100644 --- a/kernel/power/disk.c +++ b/kernel/power/disk.c @@ -26,7 +26,7 @@ static int noresume = 0; -char resume_file[256] = CONFIG_PM_STD_PARTITION; +static char resume_file[256] = CONFIG_PM_STD_PARTITION; dev_t swsusp_resume_device; sector_t swsusp_resume_block; @@ -54,8 +54,8 @@ static struct platform_hibernation_ops *hibernation_ops; void hibernation_set_ops(struct platform_hibernation_ops *ops) { - if (ops && !(ops->start && ops->pre_snapshot && ops->finish - && ops->prepare && ops->enter && ops->pre_restore + if (ops && !(ops->begin && ops->end && ops->pre_snapshot + && ops->prepare && ops->finish && ops->enter && ops->pre_restore && ops->restore_cleanup)) { WARN_ON(1); return; @@ -70,15 +70,55 @@ void hibernation_set_ops(struct platform_hibernation_ops *ops) mutex_unlock(&pm_mutex); } +#ifdef CONFIG_PM_DEBUG +static void hibernation_debug_sleep(void) +{ + printk(KERN_INFO "hibernation debug: Waiting for 5 seconds.\n"); + mdelay(5000); +} + +static int hibernation_testmode(int mode) +{ + if (hibernation_mode == mode) { + hibernation_debug_sleep(); + return 1; + } + return 0; +} + +static int hibernation_test(int level) +{ + if (pm_test_level == level) { + hibernation_debug_sleep(); + return 1; + } + return 0; +} +#else /* !CONFIG_PM_DEBUG */ +static int hibernation_testmode(int mode) { return 0; } +static int hibernation_test(int level) { return 0; } +#endif /* !CONFIG_PM_DEBUG */ + /** - * platform_start - tell the platform driver that we're starting + * platform_begin - tell the platform driver that we're starting * hibernation */ -static int platform_start(int platform_mode) +static int platform_begin(int platform_mode) { return (platform_mode && hibernation_ops) ? - hibernation_ops->start() : 0; + hibernation_ops->begin() : 0; +} + +/** + * platform_end - tell the platform driver that we've entered the + * working state + */ + +static void platform_end(int platform_mode) +{ + if (platform_mode && hibernation_ops) + hibernation_ops->end(); } /** @@ -145,7 +185,7 @@ static void platform_restore_cleanup(int platform_mode) * reappears in this routine after a restore. */ -int create_image(int platform_mode) +static int create_image(int platform_mode) { int error; @@ -162,19 +202,25 @@ int create_image(int platform_mode) */ error = device_power_down(PMSG_FREEZE); if (error) { - printk(KERN_ERR "Some devices failed to power down, " - KERN_ERR "aborting suspend\n"); + printk(KERN_ERR "PM: Some devices failed to power down, " + "aborting hibernation\n"); goto Enable_irqs; } + if (hibernation_test(TEST_CORE)) + goto Power_up; + + in_suspend = 1; save_processor_state(); error = swsusp_arch_suspend(); if (error) - printk(KERN_ERR "Error %d while creating the image\n", error); + printk(KERN_ERR "PM: Error %d creating hibernation image\n", + error); /* Restore control flow magically appears here */ restore_processor_state(); if (!in_suspend) platform_leave(platform_mode); + Power_up: /* NOTE: device_power_up() is just a resume() for devices * that suspended with irqs off ... no overall powerup. */ @@ -202,36 +248,90 @@ int hibernation_snapshot(int platform_mode) if (error) return error; - error = platform_start(platform_mode); + error = platform_begin(platform_mode); if (error) - return error; + goto Close; suspend_console(); error = device_suspend(PMSG_FREEZE); if (error) goto Resume_console; - error = platform_pre_snapshot(platform_mode); - if (error) + if (hibernation_test(TEST_DEVICES)) goto Resume_devices; + error = platform_pre_snapshot(platform_mode); + if (error || hibernation_test(TEST_PLATFORM)) + goto Finish; + error = disable_nonboot_cpus(); if (!error) { - if (hibernation_mode != HIBERNATION_TEST) { - in_suspend = 1; - error = create_image(platform_mode); - /* Control returns here after successful restore */ - } else { - printk("swsusp debug: Waiting for 5 seconds.\n"); - mdelay(5000); - } + if (hibernation_test(TEST_CPUS)) + goto Enable_cpus; + + if (hibernation_testmode(HIBERNATION_TEST)) + goto Enable_cpus; + + error = create_image(platform_mode); + /* Control returns here after successful restore */ } + Enable_cpus: enable_nonboot_cpus(); - Resume_devices: + Finish: platform_finish(platform_mode); + Resume_devices: device_resume(); Resume_console: resume_console(); + Close: + platform_end(platform_mode); + return error; +} + +/** + * resume_target_kernel - prepare devices that need to be suspended with + * interrupts off, restore the contents of highmem that have not been + * restored yet from the image and run the low level code that will restore + * the remaining contents of memory and switch to the just restored target + * kernel. + */ + +static int resume_target_kernel(void) +{ + int error; + + local_irq_disable(); + error = device_power_down(PMSG_PRETHAW); + if (error) { + printk(KERN_ERR "PM: Some devices failed to power down, " + "aborting resume\n"); + goto Enable_irqs; + } + /* We'll ignore saved state, but this gets preempt count (etc) right */ + save_processor_state(); + error = restore_highmem(); + if (!error) { + error = swsusp_arch_resume(); + /* + * The code below is only ever reached in case of a failure. + * Otherwise execution continues at place where + * swsusp_arch_suspend() was called + */ + BUG_ON(!error); + /* This call to restore_highmem() undos the previous one */ + restore_highmem(); + } + /* + * The only reason why swsusp_arch_resume() can fail is memory being + * very tight, so we have to free it as soon as we can to avoid + * subsequent failures + */ + swsusp_free(); + restore_processor_state(); + touch_softlockup_watchdog(); + device_power_up(); + Enable_irqs: + local_irq_enable(); return error; } @@ -258,7 +358,7 @@ int hibernation_restore(int platform_mode) if (!error) { error = disable_nonboot_cpus(); if (!error) - error = swsusp_resume(); + error = resume_target_kernel(); enable_nonboot_cpus(); } platform_restore_cleanup(platform_mode); @@ -286,9 +386,9 @@ int hibernation_platform_enter(void) * 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(); + error = hibernation_ops->begin(); if (error) - return error; + goto Close; suspend_console(); error = device_suspend(PMSG_SUSPEND); @@ -322,6 +422,8 @@ int hibernation_platform_enter(void) device_resume(); Resume_console: resume_console(); + Close: + hibernation_ops->end(); return error; } @@ -352,24 +454,17 @@ static void power_down(void) * Valid image is on the disk, if we continue we risk serious data * corruption after resume. */ - printk(KERN_CRIT "Please power me down manually\n"); + printk(KERN_CRIT "PM: Please power down manually\n"); while(1); } -static void unprepare_processes(void) -{ - thaw_processes(); - pm_restore_console(); -} - static int prepare_processes(void) { int error = 0; - pm_prepare_console(); if (freeze_processes()) { error = -EBUSY; - unprepare_processes(); + thaw_processes(); } return error; } @@ -389,6 +484,7 @@ int hibernate(void) goto Unlock; } + pm_prepare_console(); error = pm_notifier_call_chain(PM_HIBERNATION_PREPARE); if (error) goto Exit; @@ -398,7 +494,7 @@ int hibernate(void) if (error) goto Exit; - printk("Syncing filesystems ... "); + printk(KERN_INFO "PM: Syncing filesystems ... "); sys_sync(); printk("done.\n"); @@ -406,11 +502,12 @@ int hibernate(void) if (error) goto Finish; - if (hibernation_mode == HIBERNATION_TESTPROC) { - printk("swsusp debug: Waiting for 5 seconds.\n"); - mdelay(5000); + if (hibernation_test(TEST_FREEZER)) goto Thaw; - } + + if (hibernation_testmode(HIBERNATION_TESTPROC)) + goto Thaw; + error = hibernation_snapshot(hibernation_mode == HIBERNATION_PLATFORM); if (in_suspend && !error) { unsigned int flags = 0; @@ -427,11 +524,12 @@ int hibernate(void) swsusp_free(); } Thaw: - unprepare_processes(); + thaw_processes(); Finish: free_basic_memory_bitmaps(); Exit: pm_notifier_call_chain(PM_POST_HIBERNATION); + pm_restore_console(); atomic_inc(&snapshot_device_available); Unlock: mutex_unlock(&pm_mutex); @@ -473,22 +571,23 @@ static int software_resume(void) return -ENOENT; } swsusp_resume_device = name_to_dev_t(resume_file); - pr_debug("swsusp: Resume From Partition %s\n", resume_file); + pr_debug("PM: Resume from partition %s\n", resume_file); } else { - pr_debug("swsusp: Resume From Partition %d:%d\n", - MAJOR(swsusp_resume_device), MINOR(swsusp_resume_device)); + pr_debug("PM: Resume from partition %d:%d\n", + MAJOR(swsusp_resume_device), + MINOR(swsusp_resume_device)); } if (noresume) { /** - * FIXME: If noresume is specified, we need to find the partition - * and reset it back to normal swap space. + * FIXME: If noresume is specified, we need to find the + * partition and reset it back to normal swap space. */ mutex_unlock(&pm_mutex); return 0; } - pr_debug("PM: Checking swsusp image.\n"); + pr_debug("PM: Checking hibernation image.\n"); error = swsusp_check(); if (error) goto Unlock; @@ -499,6 +598,11 @@ static int software_resume(void) goto Unlock; } + pm_prepare_console(); + error = pm_notifier_call_chain(PM_RESTORE_PREPARE); + if (error) + goto Finish; + error = create_basic_memory_bitmaps(); if (error) goto Finish; @@ -510,7 +614,7 @@ static int software_resume(void) goto Done; } - pr_debug("PM: Reading swsusp image.\n"); + pr_debug("PM: Reading hibernation image.\n"); error = swsusp_read(&flags); if (!error) @@ -518,10 +622,12 @@ static int software_resume(void) printk(KERN_ERR "PM: Restore failed, recovering.\n"); swsusp_free(); - unprepare_processes(); + thaw_processes(); Done: free_basic_memory_bitmaps(); Finish: + pm_notifier_call_chain(PM_POST_RESTORE); + pm_restore_console(); atomic_inc(&snapshot_device_available); /* For success case, the suspend path will release the lock */ Unlock: @@ -636,7 +742,7 @@ static ssize_t disk_store(struct kobject *kobj, struct kobj_attribute *attr, error = -EINVAL; if (!error) - pr_debug("PM: suspend-to-disk mode set to '%s'\n", + pr_debug("PM: Hibernation mode set to '%s'\n", hibernation_modes[mode]); mutex_unlock(&pm_mutex); return error ? error : n; @@ -668,7 +774,7 @@ static ssize_t resume_store(struct kobject *kobj, struct kobj_attribute *attr, mutex_lock(&pm_mutex); swsusp_resume_device = res; mutex_unlock(&pm_mutex); - printk("Attempting manual resume\n"); + printk(KERN_INFO "PM: Starting manual resume from disk\n"); noresume = 0; software_resume(); ret = n; |