kexec based hibernation -v7 : kexec hibernate/resume
Subsystems (5)
PATCH 3/4 kexec based hibernation -v7 : kexec hibernate/resume
This patch implements kexec based hibernate/resume. This is based on the facility provided by kexec_jump. The states save/restore code of ordinary kexec_jump is overridden by hibernate/resume specific code. The ACPI methods are called at specified environment to conform the ACPI specification. A new reboot command is added to go to ACPI S4 state from user space.
--- include/linux/kexec.h | 4 include/linux/reboot.h | 1 include/linux/suspend.h | 1 kernel/power/disk.c | 244 +++++++++++++++++++++++++++++++++++++++++++++++- kernel/sys.c | 5 5 files changed, 251 insertions(+), 4 deletions(-)
kernel/power/disk.c +241 -3
@@ -21,6 +21,7 @@#include <linux/console.h> #include <linux/cpu.h> #include <linux/freezer.h> +#include <linux/kexec.h> #include "power.h"
@@ -365,13 +366,13 @@ int hibernation_platform_enter(void) }/** -* power_down - Shut the machine down for hibernation. +* hibernate_power_down - Shut the machine down for hibernation. * * Use the platform driver, if configured so; otherwise try * to power off or reboot. */ -static void power_down (void ) +void hibernate_power_down (void ) {switch (hibernation_mode) {case HIBERNATION_TEST :
@@ -461,7 +462,7 @@ int hibernate(void) error =swsusp_write (flags);swsusp_free ();if (!error) -power_down (); +hibernate_power_down (); }else {pr_debug ("PM: Image restored successfully.\n" );swsusp_free ();
@@ -478,6 +479,243 @@ int hibernate(void)return error; } +#ifdef CONFIG_KEXEC +static int kexec_snapshot (struct notifier_block *nb, +unsigned long cmd,void *arg) +{ +int error; +int platform_mode = (hibernation_mode ==HIBERNATION_PLATFORM ); + +if (cmd !=KJUMP_CMD_HIBERNATE_WRITE_IMAGE ) +return NOTIFY_DONE ; + +pm_prepare_console (); + + error =pm_notifier_call_chain (PM_HIBERNATION_PREPARE ); +if (error) + goto Exit; + + error =freeze_processes (); +if (error) { + error = -EBUSY ; + goto Exit; + } + +if (hibernation_test (TEST_FREEZER ) || +hibernation_testmode (HIBERNATION_TESTPROC )) { + error = -EAGAIN ; + goto Resume_process; + } + + error =platform_start (platform_mode); +if (error) + goto Resume_process; + +suspend_console (); + error =device_suspend (PMSG_FREEZE ); +if (error) + goto Resume_console; + +if (hibernation_test (TEST_DEVICES )) { + error = -EAGAIN ; + goto Resume_devices; + } + + error =platform_pre_snapshot (platform_mode); +if (error) + goto Resume_devices; + +if (hibernation_test (TEST_PLATFORM )) { + error = -EAGAIN ; + goto Resume_devices; + } + + error =disable_nonboot_cpus (); +if (error) + goto Resume_devices; + +if (hibernation_test (TEST_CPUS ) || +hibernation_testmode (HIBERNATION_TEST )) { + error = -EAGAIN ; + goto Enable_cpus; + } + +local_irq_disable (); +/* At this point, device_suspend() has been called, but *not* +* device_power_down(). We *must* 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) + goto Enable_irqs; + +if (hibernation_test (TEST_CORE )) { + error = -EAGAIN ; + goto Power_up; + } + +return NOTIFY_STOP ; + + Power_up: +device_power_up (); + Enable_irqs: +local_irq_enable (); + Enable_cpus: +enable_nonboot_cpus (); + Resume_devices: +platform_finish (platform_mode); +device_resume (); + Resume_console: +resume_console (); + Resume_process: +thaw_processes (); + Exit: +pm_notifier_call_chain (PM_POST_HIBERNATION ); +pm_restore_console (); +return notifier_from_errno (error); +} + +static int kexec_prepare_write_image (struct notifier_block *nb, +unsigned long cmd,void *arg) +{ +int platform_mode = (hibernation_mode ==HIBERNATION_PLATFORM ); + +if (cmd !=KJUMP_CMD_HIBERNATE_WRITE_IMAGE ) +return NOTIFY_DONE ; + +device_power_up (); +local_irq_enable (); +enable_nonboot_cpus (); +platform_finish (platform_mode); +device_resume (); +resume_console (); +thaw_processes (); +pm_restore_console (); +return NOTIFY_STOP ; +} + +static int kexec_prepare_resume (struct notifier_block *nb, +unsigned long cmd,void *arg) +{ +int error; +int platform_mode = (hibernation_mode ==HIBERNATION_PLATFORM ); + +if (cmd !=KJUMP_CMD_HIBERNATE_RESUME ) +return NOTIFY_DONE ; + +pm_prepare_console (); + error =pm_notifier_call_chain (PM_RESTORE_PREPARE ); +if (error) + goto Exit; + + error =freeze_processes (); +if (error) { + error = -EBUSY ; + goto Exit; + } + +suspend_console (); + error =device_suspend (PMSG_PRETHAW ); +if (error) + goto Resume_console; + + error =platform_pre_restore (platform_mode); +if (error) + goto Resume_devices; + + error =disable_nonboot_cpus (); +if (error) + goto Resume_devices; +local_irq_disable (); +/* At this point, device_suspend() has been called, but *not* +* device_power_down(). We *must* 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_PRETHAW ); +if (error) + goto Enable_irqs; + +return NOTIFY_STOP ; + + Enable_irqs: +local_irq_enable (); +enable_nonboot_cpus (); + Resume_devices: +platform_restore_cleanup (platform_mode); +device_resume (); + Resume_console: +resume_console (); +thaw_processes (); + Exit: +pm_notifier_call_chain (PM_POST_RESTORE ); +pm_restore_console (); +return notifier_from_errno (error); +} + +static int kexec_resume (struct notifier_block *nb, +unsigned long cmd,void *arg) +{ +int platform_mode = (hibernation_mode ==HIBERNATION_PLATFORM ); + +if (cmd !=KJUMP_CMD_HIBERNATE_RESUME ) +return NOTIFY_DONE ; + +platform_leave (platform_mode); +device_power_up (); +local_irq_enable (); +enable_nonboot_cpus (); +platform_finish (platform_mode); +device_resume (); +resume_console (); +thaw_processes (); +pm_notifier_call_chain (PM_POST_HIBERNATION ); +pm_restore_console (); +return NOTIFY_STOP ; +} + +static struct notifier_block nb_kexec_snapshot = { + .notifier_call = kexec_snapshot, +}; + +static struct notifier_block nb_kexec_prepare_write_image = { + .notifier_call = kexec_prepare_write_image, +}; + +static struct notifier_block nb_kexec_prepare_resume = { + .notifier_call = kexec_prepare_resume, +}; + +static struct notifier_block nb_kexec_resume = { + .notifier_call = kexec_resume, +}; + +static int kexec_hibernation_register (void ) +{ +int ret; + + ret =blocking_notifier_chain_register (&kjump_chain_pre, + &nb_kexec_snapshot); +if (ret) +return ret; + ret =blocking_notifier_chain_register (&kjump_chain_post, + &nb_kexec_prepare_write_image); +if (ret) +return ret; + ret =blocking_notifier_chain_register (&kjump_chain_pre, + &nb_kexec_prepare_resume); +if (ret) +return ret; + ret =blocking_notifier_chain_register (&kjump_chain_post, + &nb_kexec_resume); +return ret; +} + +late_initcall (kexec_hibernation_register); +#endif /** * software_resume - Resume from a saved image.
include/linux/suspend.h +1 -0
@@ -198,6 +198,7 @@ extern unsigned long get_safe_page(gfp_textern void hibernation_set_ops (struct platform_hibernation_ops *ops);extern int hibernate (void ); +extern void hibernate_power_down (void );#else /* CONFIG_HIBERNATION */ static inline int swsusp_page_is_forbidden (struct page *p) {return 0 ; }static inline void swsusp_set_page_free (struct page *p) {}
kernel/sys.c +5 -0
@@ -442,6 +442,11 @@ asmlinkage long sys_reboot(int magic1, iunlock_kernel (); return ret; } + +case LINUX_REBOOT_CMD_HIBERNATE_POWER_DOWN : +hibernate_power_down (); +unlock_kernel (); +return -EINVAL ;#endif default :
include/linux/reboot.h +1 -0
@@ -33,6 +33,7 @@#define LINUX_REBOOT_CMD_RESTART2 0xA1B2C3D4#define LINUX_REBOOT_CMD_SW_SUSPEND 0xD000FCE2#define LINUX_REBOOT_CMD_KEXEC 0x45584543 +#define LINUX_REBOOT_CMD_HIBERNATE_POWER_DOWN 0xE4390DF3#ifdef __KERNEL__
include/linux/kexec.h +3 -1
@@ -127,7 +127,9 @@ extern int kexec_vcall(struct kimage *im unsigned int argc, va_list args);extern int kexec_jump (struct kimage *image,unsigned long *cmd_ret,unsigned long cmd); -#define KJUMP_CMD_NONE 0 +#define KJUMP_CMD_NONE 0x0 +#define KJUMP_CMD_HIBERNATE_WRITE_IMAGE 0x6b630001 +#define KJUMP_CMD_HIBERNATE_RESUME 0x6b630002#ifdef CONFIG_COMPAT extern asmlinkage long compat_sys_kexec_load(unsigned long entry, unsigned long nr_segments,
_______________________________________________
kexec mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/kexec
Quoted 4 lines · Ying
This patch implements kexec based hibernate/resume. This is based on the facility provided by
kexec_jump. The states save/restore code of ordinarykexec_jumpis overridden by hibernate/resume specific code.
Can you explain in more details how this works?
Quoted 3 lines
The ACPI methods are called at specified environment to conform the ACPI specification. A new reboot command is added to go to ACPI S4 state from user space.
Well, I still don't like the amount of duplicated code introduced by this patch.
Also, IMO it should be using the mutual exclusion mechanisms used by the existing hibernation code, ie. pm_mutex and the snapshot_device_available atomic variable.
Quoted 328 lines
Signed-off-by Huang Ying <[email protected]>--- include/linux/kexec.h | 4 include/linux/reboot.h | 1 include/linux/suspend.h | 1 kernel/power/disk.c | 244 +++++++++++++++++++++++++++++++++++++++++++++++- kernel/sys.c | 5 5 files changed, 251 insertions(+), 4 deletions(-)kernel/power/disk.c +241
-3@@ -21,6 +21,7 @@#include <linux/console.h> #include <linux/cpu.h> #include <linux/freezer.h> +#include <linux/kexec.h> #include "power.h" @@ -365,13 +366,13 @@ int hibernation_platform_enter(void) }/** -* power_down - Shut the machine down for hibernation. +* hibernate_power_down - Shut the machine down for hibernation. * * Use the platform driver, if configured so; otherwise try * to power off or reboot. */ -static void power_down (void ) +void hibernate_power_down (void ) {switch (hibernation_mode) {case HIBERNATION_TEST :@@ -461,7 +462,7 @@ int hibernate(void) error =swsusp_write (flags);swsusp_free ();if (!error) -power_down (); +hibernate_power_down (); }else {pr_debug ("PM: Image restored successfully.\n" );swsusp_free ();@@ -478,6 +479,243 @@ int hibernate(void)return error; } +#ifdef CONFIG_KEXEC +static int kexec_snapshot (struct notifier_block *nb, +unsigned long cmd,void *arg) +{ +int error; +int platform_mode = (hibernation_mode ==HIBERNATION_PLATFORM ); + +if (cmd !=KJUMP_CMD_HIBERNATE_WRITE_IMAGE ) +return NOTIFY_DONE ; + +pm_prepare_console (); + + error =pm_notifier_call_chain (PM_HIBERNATION_PREPARE ); +if (error) + goto Exit; + + error =freeze_processes (); +if (error) { + error = -EBUSY ; + goto Exit; + } + +if (hibernation_test (TEST_FREEZER ) || +hibernation_testmode (HIBERNATION_TESTPROC )) { + error = -EAGAIN ; + goto Resume_process; + } + + error =platform_start (platform_mode); +if (error) + goto Resume_process; + +suspend_console (); + error =device_suspend (PMSG_FREEZE ); +if (error) + goto Resume_console; + +if (hibernation_test (TEST_DEVICES )) { + error = -EAGAIN ; + goto Resume_devices; + } + + error =platform_pre_snapshot (platform_mode); +if (error) + goto Resume_devices; + +if (hibernation_test (TEST_PLATFORM )) { + error = -EAGAIN ; + goto Resume_devices; + } + + error =disable_nonboot_cpus (); +if (error) + goto Resume_devices; + +if (hibernation_test (TEST_CPUS ) || +hibernation_testmode (HIBERNATION_TEST )) { + error = -EAGAIN ; + goto Enable_cpus; + } + +local_irq_disable (); +/* At this point, device_suspend() has been called, but *not* +* device_power_down(). We *must* 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) + goto Enable_irqs; + +if (hibernation_test (TEST_CORE )) { + error = -EAGAIN ; + goto Power_up; + } + +return NOTIFY_STOP ; + + Power_up: +device_power_up (); + Enable_irqs: +local_irq_enable (); + Enable_cpus: +enable_nonboot_cpus (); + Resume_devices: +platform_finish (platform_mode); +device_resume (); + Resume_console: +resume_console (); + Resume_process: +thaw_processes (); + Exit: +pm_notifier_call_chain (PM_POST_HIBERNATION ); +pm_restore_console (); +return notifier_from_errno (error); +} + +static int kexec_prepare_write_image (struct notifier_block *nb, +unsigned long cmd,void *arg) +{ +int platform_mode = (hibernation_mode ==HIBERNATION_PLATFORM ); + +if (cmd !=KJUMP_CMD_HIBERNATE_WRITE_IMAGE ) +return NOTIFY_DONE ; + +device_power_up (); +local_irq_enable (); +enable_nonboot_cpus (); +platform_finish (platform_mode); +device_resume (); +resume_console (); +thaw_processes (); +pm_restore_console (); +return NOTIFY_STOP ; +} + +static int kexec_prepare_resume (struct notifier_block *nb, +unsigned long cmd,void *arg) +{ +int error; +int platform_mode = (hibernation_mode ==HIBERNATION_PLATFORM ); + +if (cmd !=KJUMP_CMD_HIBERNATE_RESUME ) +return NOTIFY_DONE ; + +pm_prepare_console (); + error =pm_notifier_call_chain (PM_RESTORE_PREPARE ); +if (error) + goto Exit; + + error =freeze_processes (); +if (error) { + error = -EBUSY ; + goto Exit; + } + +suspend_console (); + error =device_suspend (PMSG_PRETHAW ); +if (error) + goto Resume_console; + + error =platform_pre_restore (platform_mode); +if (error) + goto Resume_devices; + + error =disable_nonboot_cpus (); +if (error) + goto Resume_devices; +local_irq_disable (); +/* At this point, device_suspend() has been called, but *not* +* device_power_down(). We *must* 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_PRETHAW ); +if (error) + goto Enable_irqs; + +return NOTIFY_STOP ; + + Enable_irqs: +local_irq_enable (); +enable_nonboot_cpus (); + Resume_devices: +platform_restore_cleanup (platform_mode); +device_resume (); + Resume_console: +resume_console (); +thaw_processes (); + Exit: +pm_notifier_call_chain (PM_POST_RESTORE ); +pm_restore_console (); +return notifier_from_errno (error); +} + +static int kexec_resume (struct notifier_block *nb, +unsigned long cmd,void *arg) +{ +int platform_mode = (hibernation_mode ==HIBERNATION_PLATFORM ); + +if (cmd !=KJUMP_CMD_HIBERNATE_RESUME ) +return NOTIFY_DONE ; + +platform_leave (platform_mode); +device_power_up (); +local_irq_enable (); +enable_nonboot_cpus (); +platform_finish (platform_mode); +device_resume (); +resume_console (); +thaw_processes (); +pm_notifier_call_chain (PM_POST_HIBERNATION ); +pm_restore_console (); +return NOTIFY_STOP ; +} + +static struct notifier_block nb_kexec_snapshot = { + .notifier_call = kexec_snapshot, +}; + +static struct notifier_block nb_kexec_prepare_write_image = { + .notifier_call = kexec_prepare_write_image, +}; + +static struct notifier_block nb_kexec_prepare_resume = { + .notifier_call = kexec_prepare_resume, +}; + +static struct notifier_block nb_kexec_resume = { + .notifier_call = kexec_resume, +}; + +static int kexec_hibernation_register (void ) +{ +int ret; + + ret =blocking_notifier_chain_register (&kjump_chain_pre, + &nb_kexec_snapshot); +if (ret) +return ret; + ret =blocking_notifier_chain_register (&kjump_chain_post, + &nb_kexec_prepare_write_image); +if (ret) +return ret; + ret =blocking_notifier_chain_register (&kjump_chain_pre, + &nb_kexec_prepare_resume); +if (ret) +return ret; + ret =blocking_notifier_chain_register (&kjump_chain_post, + &nb_kexec_resume); +return ret; +} + +late_initcall (kexec_hibernation_register); +#endif /** * software_resume - Resume from a saved image. include/linux/suspend.h +1
-0@@ -198,6 +198,7 @@ extern unsigned long get_safe_page(gfp_textern void hibernation_set_ops (struct platform_hibernation_ops *ops);extern int hibernate (void ); +extern void hibernate_power_down (void );#else /* CONFIG_HIBERNATION */ static inline int swsusp_page_is_forbidden (struct page *p) {return 0 ; }static inline void swsusp_set_page_free (struct page *p) {}kernel/sys.c +5
-0@@ -442,6 +442,11 @@ asmlinkage long sys_reboot(int magic1, iunlock_kernel (); return ret; } + +case LINUX_REBOOT_CMD_HIBERNATE_POWER_DOWN : +hibernate_power_down (); +unlock_kernel (); +return -EINVAL ;#endif default :include/linux/reboot.h +1
-0@@ -33,6 +33,7 @@#define LINUX_REBOOT_CMD_RESTART2 0xA1B2C3D4#define LINUX_REBOOT_CMD_SW_SUSPEND 0xD000FCE2#define LINUX_REBOOT_CMD_KEXEC 0x45584543 +#define LINUX_REBOOT_CMD_HIBERNATE_POWER_DOWN 0xE4390DF3#ifdef __KERNEL__
include/linux/kexec.h extern int kexec_vcall(struct kimage *im · line 135
Quoted 10 lines
include/linux/kexec.h +3
-1@@ -127,7 +127,9 @@ extern int kexec_vcall(struct kimage *im unsigned int argc, va_list args);extern int kexec_jump (struct kimage *image,unsigned long *cmd_ret,unsigned long cmd); -#define KJUMP_CMD_NONE 0 +#define KJUMP_CMD_NONE 0x0 +#define KJUMP_CMD_HIBERNATE_WRITE_IMAGE 0x6b630001 +#define KJUMP_CMD_HIBERNATE_RESUME 0x6b630002#ifdef CONFIG_COMPAT extern asmlinkage long compat_sys_kexec_load(unsigned long entry, unsigned long nr_segments,
_______________________________________________
kexec mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/kexec
Quoted 7 lines · Rafael J. Wysocki
Quoted 4 lines · Ying
This patch implements kexec based hibernate/resume. This is based on the facility provided by
kexec_jump. The states save/restore code of ordinarykexec_jumpis overridden by hibernate/resume specific code.Can you explain in more details how this works?
Two blocking notifier chain named kjump_chain_pre and kjump_chain_post are defined, the basic procedure of kexec jump is as follow:
call functions in kjump_chain_pre
jump to peer kernel
call functions in kjump_chain_post
A command is the first parameter of functions in chain. If A command is processed in a function, the function will execute and stop the chain (return NOTIFY_STOP), otherwise it will do nothing (return NOTIFY_DONE). If no function has interest in the command, the default behavior will be executed (kexec_vcall_pre, kexec_vcall_post).
So for each command the procedure is as follow:
KJUMP_CMD_HIBERNATE_WRITE_IMAGE:
[chain] kexec_snapshot
jump to kexeced kernel
[chain] kexec_prepare_write_image /* in kexeced kernel */
KJUMP_HIBERNATE_RESUME:
[chain] kexec_prepare_resume /* in kexeced kernel */ jump to kexec kernel
[chain] kexec_resume
Quoted 5 lines
Quoted 3 lines
The ACPI methods are called at specified environment to conform the ACPI specification. A new reboot command is added to go to ACPI S4 state from user space.
Well, I still don't like the amount of duplicated code introduced by this patch.
Yes, there are too many duplicated code. They should be merged. But I want to delay the merging until the kexec based hibernation code goes more stable.
Quoted 3 lines
Also, IMO it should be using the mutual exclusion mechanisms used by the existing hibernation code, ie.
pm_mutexand thesnapshot_device_availableatomic variable.
Now the kexec_lock is used as a mutex between kexec related operations. It seems reasonable to use pm_mutex and maybe snapshot_device_available to eliminate potential conflict between kexec based hibernation and u/swsusp.
Best Regards,
Huang Ying
Quoted 7 lines · Rafael J. Wysocki
Quoted 4 lines · Ying
This patch implements kexec based hibernate/resume. This is based on the facility provided by
kexec_jump. The states save/restore code of ordinarykexec_jumpis overridden by hibernate/resume specific code.Can you explain in more details how this works?
Two blocking notifier chain named kjump_chain_pre and kjump_chain_post are defined, the basic procedure of kexec jump is as follow:
call functions in kjump_chain_pre
jump to peer kernel
call functions in kjump_chain_post
A command is the first parameter of functions in chain. If A command is processed in a function, the function will execute and stop the chain (return NOTIFY_STOP), otherwise it will do nothing (return NOTIFY_DONE). If no function has interest in the command, the default behavior will be executed (kexec_vcall_pre, kexec_vcall_post).
So for each command the procedure is as follow:
KJUMP_CMD_HIBERNATE_WRITE_IMAGE:
[chain] kexec_snapshot
jump to kexeced kernel
[chain] kexec_prepare_write_image /* in kexeced kernel */
KJUMP_HIBERNATE_RESUME:
[chain] kexec_prepare_resume /* in kexeced kernel */ jump to kexec kernel
[chain] kexec_resume
Quoted 5 lines
Quoted 3 lines
The ACPI methods are called at specified environment to conform the ACPI specification. A new reboot command is added to go to ACPI S4 state from user space.
Well, I still don't like the amount of duplicated code introduced by this patch.
Yes, there are too many duplicated code. They should be merged. But I want to delay the merging until the kexec based hibernation code goes more stable.
Quoted 3 lines
Also, IMO it should be using the mutual exclusion mechanisms used by the existing hibernation code, ie.
pm_mutexand thesnapshot_device_availableatomic variable.
Now the kexec_lock is used as a mutex between kexec related operations. It seems reasonable to use pm_mutex and maybe snapshot_device_available to eliminate potential conflict between kexec based hibernation and u/swsusp.
Best Regards,
Huang Ying
_______________________________________________
kexec mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/kexec
Quoted 4 lines · Ying
This patch implements kexec based hibernate/resume. This is based on the facility provided by
kexec_jump. The states save/restore code of ordinarykexec_jumpis overridden by hibernate/resume specific code.
Can you explain in more details how this works?
Quoted 3 lines
The ACPI methods are called at specified environment to conform the ACPI specification. A new reboot command is added to go to ACPI S4 state from user space.
Well, I still don't like the amount of duplicated code introduced by this patch.
Also, IMO it should be using the mutual exclusion mechanisms used by the existing hibernation code, ie. pm_mutex and the snapshot_device_available atomic variable.
Quoted 338 lines
Signed-off-by Huang Ying <[email protected]>--- include/linux/kexec.h | 4 include/linux/reboot.h | 1 include/linux/suspend.h | 1 kernel/power/disk.c | 244 +++++++++++++++++++++++++++++++++++++++++++++++- kernel/sys.c | 5 5 files changed, 251 insertions(+), 4 deletions(-)kernel/power/disk.c +241
-3@@ -21,6 +21,7 @@#include <linux/console.h> #include <linux/cpu.h> #include <linux/freezer.h> +#include <linux/kexec.h> #include "power.h" @@ -365,13 +366,13 @@ int hibernation_platform_enter(void) }/** -* power_down - Shut the machine down for hibernation. +* hibernate_power_down - Shut the machine down for hibernation. * * Use the platform driver, if configured so; otherwise try * to power off or reboot. */ -static void power_down (void ) +void hibernate_power_down (void ) {switch (hibernation_mode) {case HIBERNATION_TEST :@@ -461,7 +462,7 @@ int hibernate(void) error =swsusp_write (flags);swsusp_free ();if (!error) -power_down (); +hibernate_power_down (); }else {pr_debug ("PM: Image restored successfully.\n" );swsusp_free ();@@ -478,6 +479,243 @@ int hibernate(void)return error; } +#ifdef CONFIG_KEXEC +static int kexec_snapshot (struct notifier_block *nb, +unsigned long cmd,void *arg) +{ +int error; +int platform_mode = (hibernation_mode ==HIBERNATION_PLATFORM ); + +if (cmd !=KJUMP_CMD_HIBERNATE_WRITE_IMAGE ) +return NOTIFY_DONE ; + +pm_prepare_console (); + + error =pm_notifier_call_chain (PM_HIBERNATION_PREPARE ); +if (error) + goto Exit; + + error =freeze_processes (); +if (error) { + error = -EBUSY ; + goto Exit; + } + +if (hibernation_test (TEST_FREEZER ) || +hibernation_testmode (HIBERNATION_TESTPROC )) { + error = -EAGAIN ; + goto Resume_process; + } + + error =platform_start (platform_mode); +if (error) + goto Resume_process; + +suspend_console (); + error =device_suspend (PMSG_FREEZE ); +if (error) + goto Resume_console; + +if (hibernation_test (TEST_DEVICES )) { + error = -EAGAIN ; + goto Resume_devices; + } + + error =platform_pre_snapshot (platform_mode); +if (error) + goto Resume_devices; + +if (hibernation_test (TEST_PLATFORM )) { + error = -EAGAIN ; + goto Resume_devices; + } + + error =disable_nonboot_cpus (); +if (error) + goto Resume_devices; + +if (hibernation_test (TEST_CPUS ) || +hibernation_testmode (HIBERNATION_TEST )) { + error = -EAGAIN ; + goto Enable_cpus; + } + +local_irq_disable (); +/* At this point, device_suspend() has been called, but *not* +* device_power_down(). We *must* 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) + goto Enable_irqs; + +if (hibernation_test (TEST_CORE )) { + error = -EAGAIN ; + goto Power_up; + } + +return NOTIFY_STOP ; + + Power_up: +device_power_up (); + Enable_irqs: +local_irq_enable (); + Enable_cpus: +enable_nonboot_cpus (); + Resume_devices: +platform_finish (platform_mode); +device_resume (); + Resume_console: +resume_console (); + Resume_process: +thaw_processes (); + Exit: +pm_notifier_call_chain (PM_POST_HIBERNATION ); +pm_restore_console (); +return notifier_from_errno (error); +} + +static int kexec_prepare_write_image (struct notifier_block *nb, +unsigned long cmd,void *arg) +{ +int platform_mode = (hibernation_mode ==HIBERNATION_PLATFORM ); + +if (cmd !=KJUMP_CMD_HIBERNATE_WRITE_IMAGE ) +return NOTIFY_DONE ; + +device_power_up (); +local_irq_enable (); +enable_nonboot_cpus (); +platform_finish (platform_mode); +device_resume (); +resume_console (); +thaw_processes (); +pm_restore_console (); +return NOTIFY_STOP ; +} + +static int kexec_prepare_resume (struct notifier_block *nb, +unsigned long cmd,void *arg) +{ +int error; +int platform_mode = (hibernation_mode ==HIBERNATION_PLATFORM ); + +if (cmd !=KJUMP_CMD_HIBERNATE_RESUME ) +return NOTIFY_DONE ; + +pm_prepare_console (); + error =pm_notifier_call_chain (PM_RESTORE_PREPARE ); +if (error) + goto Exit; + + error =freeze_processes (); +if (error) { + error = -EBUSY ; + goto Exit; + } + +suspend_console (); + error =device_suspend (PMSG_PRETHAW ); +if (error) + goto Resume_console; + + error =platform_pre_restore (platform_mode); +if (error) + goto Resume_devices; + + error =disable_nonboot_cpus (); +if (error) + goto Resume_devices; +local_irq_disable (); +/* At this point, device_suspend() has been called, but *not* +* device_power_down(). We *must* 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_PRETHAW ); +if (error) + goto Enable_irqs; + +return NOTIFY_STOP ; + + Enable_irqs: +local_irq_enable (); +enable_nonboot_cpus (); + Resume_devices: +platform_restore_cleanup (platform_mode); +device_resume (); + Resume_console: +resume_console (); +thaw_processes (); + Exit: +pm_notifier_call_chain (PM_POST_RESTORE ); +pm_restore_console (); +return notifier_from_errno (error); +} + +static int kexec_resume (struct notifier_block *nb, +unsigned long cmd,void *arg) +{ +int platform_mode = (hibernation_mode ==HIBERNATION_PLATFORM ); + +if (cmd !=KJUMP_CMD_HIBERNATE_RESUME ) +return NOTIFY_DONE ; + +platform_leave (platform_mode); +device_power_up (); +local_irq_enable (); +enable_nonboot_cpus (); +platform_finish (platform_mode); +device_resume (); +resume_console (); +thaw_processes (); +pm_notifier_call_chain (PM_POST_HIBERNATION ); +pm_restore_console (); +return NOTIFY_STOP ; +} + +static struct notifier_block nb_kexec_snapshot = { + .notifier_call = kexec_snapshot, +}; + +static struct notifier_block nb_kexec_prepare_write_image = { + .notifier_call = kexec_prepare_write_image, +}; + +static struct notifier_block nb_kexec_prepare_resume = { + .notifier_call = kexec_prepare_resume, +}; + +static struct notifier_block nb_kexec_resume = { + .notifier_call = kexec_resume, +}; + +static int kexec_hibernation_register (void ) +{ +int ret; + + ret =blocking_notifier_chain_register (&kjump_chain_pre, + &nb_kexec_snapshot); +if (ret) +return ret; + ret =blocking_notifier_chain_register (&kjump_chain_post, + &nb_kexec_prepare_write_image); +if (ret) +return ret; + ret =blocking_notifier_chain_register (&kjump_chain_pre, + &nb_kexec_prepare_resume); +if (ret) +return ret; + ret =blocking_notifier_chain_register (&kjump_chain_post, + &nb_kexec_resume); +return ret; +} + +late_initcall (kexec_hibernation_register); +#endif /** * software_resume - Resume from a saved image. include/linux/suspend.h +1
-0@@ -198,6 +198,7 @@ extern unsigned long get_safe_page(gfp_textern void hibernation_set_ops (struct platform_hibernation_ops *ops);extern int hibernate (void ); +extern void hibernate_power_down (void );#else /* CONFIG_HIBERNATION */ static inline int swsusp_page_is_forbidden (struct page *p) {return 0 ; }static inline void swsusp_set_page_free (struct page *p) {}kernel/sys.c +5
-0@@ -442,6 +442,11 @@ asmlinkage long sys_reboot(int magic1, iunlock_kernel (); return ret; } + +case LINUX_REBOOT_CMD_HIBERNATE_POWER_DOWN : +hibernate_power_down (); +unlock_kernel (); +return -EINVAL ;#endif default :include/linux/reboot.h +1
-0@@ -33,6 +33,7 @@#define LINUX_REBOOT_CMD_RESTART2 0xA1B2C3D4#define LINUX_REBOOT_CMD_SW_SUSPEND 0xD000FCE2#define LINUX_REBOOT_CMD_KEXEC 0x45584543 +#define LINUX_REBOOT_CMD_HIBERNATE_POWER_DOWN 0xE4390DF3#ifdef __KERNEL__include/linux/kexec.h +3
-1@@ -127,7 +127,9 @@ extern int kexec_vcall(struct kimage *im unsigned int argc, va_list args);extern int kexec_jump (struct kimage *image,unsigned long *cmd_ret,unsigned long cmd); -#define KJUMP_CMD_NONE 0 +#define KJUMP_CMD_NONE 0x0 +#define KJUMP_CMD_HIBERNATE_WRITE_IMAGE 0x6b630001 +#define KJUMP_CMD_HIBERNATE_RESUME 0x6b630002#ifdef CONFIG_COMPAT extern asmlinkage long compat_sys_kexec_load(unsigned long entry, unsigned long nr_segments,