This patch provide the kexec based implementation of hibernation image operation. Now, only jumping between original kernel and kexeced kernel is supported, real image write/read/check will be provided in next patches. Signed-off-by: Huang Ying <ying.huang@xxxxxxxxx> arch/i386/kernel/Makefile | 1 arch/i386/kernel/kexec_jump.S | 73 ++++++++++++++++++++++++++ arch/i386/kernel/machine_kexec.c | 58 +++++++++++++++++++++ include/asm-i386/kexec.h | 4 + include/linux/kexec.h | 8 ++ kernel/kexec.c | 25 +++++++++ kernel/ksysfs.c | 12 ++++ kernel/power/Kconfig | 8 ++ kernel/power/Makefile | 1 kernel/power/kexec.c | 106 +++++++++++++++++++++++++++++++++++++++ 10 files changed, 296 insertions(+) Index: linux-2.6/arch/i386/kernel/Makefile =================================================================== --- linux-2.6.orig/arch/i386/kernel/Makefile 2007-07-09 21:51:45.000000000 +0000 +++ linux-2.6/arch/i386/kernel/Makefile 2007-07-09 21:53:46.000000000 +0000 @@ -27,6 +27,7 @@ obj-$(CONFIG_X86_REBOOTFIXUPS) += reboot_fixups.o obj-$(CONFIG_KEXEC) += machine_kexec.o relocate_kernel.o crash.o obj-$(CONFIG_CRASH_DUMP) += crash_dump.o +obj-$(CONFIG_KEXEC_HIBERNATION) += kexec_jump.o obj-$(CONFIG_X86_NUMAQ) += numaq.o obj-$(CONFIG_X86_SUMMIT_NUMA) += summit.o obj-$(CONFIG_KPROBES) += kprobes.o Index: linux-2.6/arch/i386/kernel/machine_kexec.c =================================================================== --- linux-2.6.orig/arch/i386/kernel/machine_kexec.c 2007-07-09 21:51:45.000000000 +0000 +++ linux-2.6/arch/i386/kernel/machine_kexec.c 2007-07-10 09:17:23.000000000 +0000 @@ -10,6 +10,7 @@ #include <linux/kexec.h> #include <linux/delay.h> #include <linux/init.h> +#include <linux/highmem.h> #include <asm/pgtable.h> #include <asm/pgalloc.h> #include <asm/tlbflush.h> @@ -169,3 +170,60 @@ return 0; } early_param("crashkernel", parse_crashkernel); + +#ifdef CONFIG_KEXEC_HIBERNATION +int kexec_jump(int jump_type) +{ + asmlinkage int (*real_jump)(int jmp, void *buf); + unsigned long jump_buf_pfn; + void *jump_buf; + + if (jump_type == KEXEC_JUMP_TYPE_EXEC && !kexec_crash_image) + return -EINVAL; + jump_buf_pfn = kexec_get_jump_buf_pfn(0); + if (!jump_buf_pfn) + return -EINVAL; + jump_buf = kmap_atomic_pfn(jump_buf_pfn, KM_PTE0); + memcpy(jump_buf + PAGE_SIZE/2, kexec_real_jump, PAGE_SIZE/2); + real_jump = jump_buf + PAGE_SIZE/2; + + if (!real_jump(jump_type == KEXEC_JUMP_TYPE_EXEC, jump_buf)) + machine_kexec(kexec_crash_image); + kunmap_atomic(jump_buf, KM_PTE0); + return 0; +} + +static unsigned long kexec_backup_addr = ~0UL; + +/* kexec_backup= specifies the location of backuped 0~640k memory of + * crashed kernel. + */ +static int __init parse_kexec_backup(char *arg) +{ + if (!arg) + return -EINVAL; + + kexec_backup_addr = memparse(arg, &arg); + return 0; +} +early_param("kexec_backup", parse_kexec_backup); + +void kexec_restore_backup(void) +{ + void *vaddr; + void *vaddr_backup; + unsigned long paddr; + + if (kexec_backup_addr == ~0UL) + return; + + for (paddr = 0; paddr < 640 * 1024; paddr += PAGE_SIZE) { + vaddr = kmap_atomic_pfn(paddr >> PAGE_SHIFT, KM_PTE0); + vaddr_backup = kmap_atomic_pfn((paddr+kexec_backup_addr) >> PAGE_SHIFT, + KM_PTE1); + memcpy(vaddr, vaddr_backup, PAGE_SIZE); + kunmap_atomic(vaddr, KM_PTE0); + kunmap_atomic(vaddr_backup, KM_PTE1); + } +} +#endif /* CONFIG_KEXEC_HIBERNATION */ Index: linux-2.6/include/asm-i386/kexec.h =================================================================== --- linux-2.6.orig/include/asm-i386/kexec.h 2007-07-09 21:51:45.000000000 +0000 +++ linux-2.6/include/asm-i386/kexec.h 2007-07-09 21:53:46.000000000 +0000 @@ -94,6 +94,10 @@ unsigned long start_address, unsigned int has_pae) ATTRIB_NORET; +#ifdef CONFIG_KEXEC_HIBERNATION +extern asmlinkage int kexec_real_jump(int save_only, void *buf); +#endif + #endif /* __ASSEMBLY__ */ #endif /* _I386_KEXEC_H */ Index: linux-2.6/include/linux/kexec.h =================================================================== --- linux-2.6.orig/include/linux/kexec.h 2007-07-09 21:51:45.000000000 +0000 +++ linux-2.6/include/linux/kexec.h 2007-07-09 21:53:46.000000000 +0000 @@ -161,4 +161,12 @@ static inline void crash_kexec(struct pt_regs *regs) { } static inline int kexec_should_crash(struct task_struct *p) { return 0; } #endif /* CONFIG_KEXEC */ + +#ifdef CONFIG_KEXEC_HIBERNATION +#define KEXEC_JUMP_TYPE_EXEC 0 +#define KEXEC_JUMP_TYPE_JUMP 1 +extern int kexec_jump(int jump_type); +extern unsigned long kexec_get_jump_buf_pfn(int alloc); +extern void kexec_restore_backup(void); +#endif #endif /* LINUX_KEXEC_H */ Index: linux-2.6/kernel/kexec.c =================================================================== --- linux-2.6.orig/kernel/kexec.c 2007-07-09 21:51:45.000000000 +0000 +++ linux-2.6/kernel/kexec.c 2007-07-09 21:53:46.000000000 +0000 @@ -1135,3 +1135,28 @@ return 0; } module_init(crash_notes_memory_init) + +#ifdef CONFIG_KEXEC_HIBERNATION +static unsigned long kexec_jump_buf_pfn; + +static int __init parse_kexec_jump_buf_pfn(char *arg) +{ + if (!arg) + return -EINVAL; + + kexec_jump_buf_pfn = memparse(arg, &arg); + return 0; +} +early_param("kexec_jump_buf_pfn", parse_kexec_jump_buf_pfn); + +unsigned long kexec_get_jump_buf_pfn(int alloc) +{ + struct page *jump_buf_page; + + if (!kexec_jump_buf_pfn && alloc) { + jump_buf_page = alloc_page(GFP_KERNEL); + kexec_jump_buf_pfn = page_to_pfn(jump_buf_page); + } + return kexec_jump_buf_pfn; +} +#endif /* CONFIG_KEXEC_HIBERNATION */ Index: linux-2.6/kernel/ksysfs.c =================================================================== --- linux-2.6.orig/kernel/ksysfs.c 2007-07-09 21:51:45.000000000 +0000 +++ linux-2.6/kernel/ksysfs.c 2007-07-09 21:53:46.000000000 +0000 @@ -60,6 +60,15 @@ return sprintf(page, "%d\n", !!kexec_crash_image); } KERNEL_ATTR_RO(kexec_crash_loaded); + +#ifdef CONFIG_KEXEC_HIBERNATION +static ssize_t kexec_jump_buf_pfn_show(struct kset *kset, char *page) +{ + return sprintf(page, "0x%lx\n", kexec_get_jump_buf_pfn(1)); +} + +KERNEL_ATTR_RO(kexec_jump_buf_pfn); +#endif #endif /* CONFIG_KEXEC */ decl_subsys(kernel, NULL, NULL); @@ -73,6 +82,9 @@ #ifdef CONFIG_KEXEC &kexec_loaded_attr.attr, &kexec_crash_loaded_attr.attr, +#ifdef CONFIG_KEXEC_HIBERNATION + &kexec_jump_buf_pfn_attr.attr, +#endif #endif NULL }; Index: linux-2.6/kernel/power/Kconfig =================================================================== --- linux-2.6.orig/kernel/power/Kconfig 2007-07-09 21:51:45.000000000 +0000 +++ linux-2.6/kernel/power/Kconfig 2007-07-09 21:53:46.000000000 +0000 @@ -115,6 +115,14 @@ For more information take a look at <file:Documentation/power/swsusp.txt>. +config KEXEC_HIBERNATION + bool "Kexec based software suspend (hibernation) (EXPERIMENTAL)" + depends on EXPERIMENTAL + depends on SOFTWARE_SUSPEND && X86_32 && KEXEC + ---help--- + Writing the hibernation image through booting another kernel with + kexec. + config PM_STD_PARTITION string "Default resume partition" depends on SOFTWARE_SUSPEND Index: linux-2.6/kernel/power/Makefile =================================================================== --- linux-2.6.orig/kernel/power/Makefile 2007-07-09 21:51:45.000000000 +0000 +++ linux-2.6/kernel/power/Makefile 2007-07-09 21:53:46.000000000 +0000 @@ -6,5 +6,6 @@ obj-y := main.o process.o console.o obj-$(CONFIG_PM_LEGACY) += pm.o obj-$(CONFIG_SOFTWARE_SUSPEND) += swsusp.o disk.o snapshot.o swap.o user.o +obj-$(CONFIG_KEXEC_HIBERNATION) += kexec.o obj-$(CONFIG_MAGIC_SYSRQ) += poweroff.o Index: linux-2.6/arch/i386/kernel/kexec_jump.S =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6/arch/i386/kernel/kexec_jump.S 2007-07-09 22:13:35.000000000 +0000 @@ -0,0 +1,73 @@ +/* + * kexec_jump.S - Jump between normal kernel and hibernation kernel + * Copyright (C) 2007 Huang Ying <ying.huang@xxxxxxxxx> + * + * This source code is licensed under the GNU General Public License, + * Version 2. See the file COPYING for more details. + */ + +#include <linux/linkage.h> +#include <asm/page.h> +#include <asm/kexec.h> + +/* + * Must be relocatable PIC code callable as a C function + */ +#define HALF_PAGE_ALIGNED (1 << (PAGE_SHIFT-1)) + +#define EBX 0x0 +#define ESI 0x4 +#define EDI 0x8 +#define EBP 0xc +#define ESP 0x10 +#define CR0 0x14 +#define CR3 0x18 +#define CR4 0x1c +#define FLAG 0x20 +#define RET 0x24 + + .text + .align HALF_PAGE_ALIGNED + .globl kexec_real_jump +kexec_real_jump: + movl 4(%esp), %ecx + movl 8(%esp), %edx + cmpl $1, %ecx + jnz 1f + movl %ebx, EBX(%edx) + movl %esi, ESI(%edx) + movl %edi, EDI(%edx) + movl %ebp, EBP(%edx) + movl %esp, ESP(%edx) + movl %cr0, %eax + movl %eax, CR0(%edx) + movl %cr3, %eax + movl %eax, CR3(%edx) + movl %cr4, %eax + movl %eax, CR4(%edx) + pushf + popl %eax + movl %eax, FLAG(%edx) + movl (%esp), %eax + movl %eax, RET(%edx) + mov $0, %eax + ret +1: + movl EBX(%edx), %ebx + movl ESI(%edx), %esi + movl EDI(%edx), %edi + movl EBP(%edx), %ebp + movl FLAG(%edx), %eax + pushl %eax + popf + movl ESP(%edx), %esp + movl CR4(%edx), %eax + movl %eax, %cr4 + movl CR3(%edx), %eax + movl %eax, %cr3 + movl CR0(%edx), %eax + movl %eax, %cr0 + movl RET(%edx), %eax + movl %eax, (%esp) + mov $1, %eax + ret Index: linux-2.6/kernel/power/kexec.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6/kernel/power/kexec.c 2007-07-10 09:16:47.000000000 +0000 @@ -0,0 +1,106 @@ +/* + * kernel/power/kexec.c - Kexec based Suspend-to-disk support. + * + * Copyright (C) 2007 Huang Ying <ying.huang@xxxxxxxxx> + * + * This file is released under the GPLv2. + * + */ + +#include <linux/suspend.h> +#include <linux/pm.h> +#include <linux/kexec.h> + +#include "power.h" + + +static int kexec_suspend(void) +{ + int error; + + 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) { + printk(KERN_ERR "Some devices failed to power down, aborting suspend\n"); + goto Enable_irqs; + } + + save_processor_state(); + error = kexec_jump(KEXEC_JUMP_TYPE_EXEC); + restore_processor_state(); + + /* NOTE: device_power_up() is just a resume() for devices + * that suspended with irqs off ... no overall powerup. + */ + device_power_up(); + Enable_irqs: + in_suspend = 0; + local_irq_enable(); + return error; +} + +static int kexec_write(void) +{ + return 0; +} + +static int kexec_check(void) +{ + return 0; +} + +static int kexec_read(void) +{ + return 0; +} + +static void kexec_close(void) +{ +} + +static int kexec_resume(void) +{ + int error = 0; + + local_irq_disable(); + /* NOTE: device_power_down() is just a suspend() with irqs off; + * it has no special "power things down" semantics + */ + if (device_power_down(PMSG_PRETHAW)) + printk(KERN_ERR "Some devices failed to power down, very bad\n"); + /* We'll ignore saved state, but this gets preempt count (etc) right */ + save_processor_state(); + kexec_restore_backup(); + error = kexec_jump(KEXEC_JUMP_TYPE_JUMP); + swsusp_free(); + restore_processor_state(); + touch_softlockup_watchdog(); + device_power_up(); + local_irq_enable(); + return error; +} + +static struct hibernation_image_ops kexec_hibernation_image_ops = +{ + .name = "kexec", + .suspend = kexec_suspend, + .resume = kexec_resume, + .write = kexec_write, + .check = kexec_check, + .read = kexec_read, + .close = kexec_close, +}; + +static int __init kexec_hibernation_init(void) +{ + hibernation_add_image_ops(&kexec_hibernation_image_ops); + return 0; +} + +subsys_initcall(kexec_hibernation_init); _______________________________________________ linux-pm mailing list linux-pm@xxxxxxxxxxxxxxxxxxxxxxxxxx https://lists.linux-foundation.org/mailman/listinfo/linux-pm