diff options
| author | Pavel Machek <pavel@ucw.cz> | 2002-05-21 01:57:49 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@penguin.transmeta.com> | 2002-05-21 01:57:49 -0700 |
| commit | 542f96a52f55f50dae8b82e11dac096b094202f0 (patch) | |
| tree | 4b15d74e79d2d137a36f37b93b9494ddeedaeeef /include | |
| parent | 79998decd54d550b61538a9248dc49b8db9a01e5 (diff) | |
[PATCH] suspend-to-{RAM,disk}
Here's suspend-to-{RAM,disk} combined patch for
2.5.17. Suspend-to-disk is pretty stable and was tested in
2.4-ac. Suspend-to-RAM is little more experimental, but works for me,
and is certainly better than disk-eating version currently in kernel.
Major parts are: process stopper, S3 specific code, S4 specific
code.
Diffstat (limited to 'include')
| -rw-r--r-- | include/asm-generic/bitops.h | 6 | ||||
| -rw-r--r-- | include/asm-i386/bitops.h | 6 | ||||
| -rw-r--r-- | include/asm-i386/suspend.h | 304 | ||||
| -rw-r--r-- | include/linux/bitops.h | 43 | ||||
| -rw-r--r-- | include/linux/init.h | 3 | ||||
| -rw-r--r-- | include/linux/page-flags.h | 7 | ||||
| -rw-r--r-- | include/linux/reboot.h | 9 | ||||
| -rw-r--r-- | include/linux/sched.h | 5 | ||||
| -rw-r--r-- | include/linux/suspend.h | 65 | ||||
| -rw-r--r-- | include/linux/tqueue.h | 2 |
10 files changed, 448 insertions, 2 deletions
diff --git a/include/asm-generic/bitops.h b/include/asm-generic/bitops.h index 1428fd95b987..23ac5227b702 100644 --- a/include/asm-generic/bitops.h +++ b/include/asm-generic/bitops.h @@ -51,6 +51,12 @@ extern __inline__ int test_bit(int nr, long * addr) return ((mask & *addr) != 0); } +/* + * fls: find last bit set. + */ + +#define fls(x) generic_fls(x) + #ifdef __KERNEL__ /* diff --git a/include/asm-i386/bitops.h b/include/asm-i386/bitops.h index 4e1b7c04df41..4cf9d29ef4ad 100644 --- a/include/asm-i386/bitops.h +++ b/include/asm-i386/bitops.h @@ -414,6 +414,12 @@ static __inline__ unsigned long __ffs(unsigned long word) return word; } +/* + * fls: find last bit set. + */ + +#define fls(x) generic_fls(x) + #ifdef __KERNEL__ /* diff --git a/include/asm-i386/suspend.h b/include/asm-i386/suspend.h new file mode 100644 index 000000000000..0bf90bb1c806 --- /dev/null +++ b/include/asm-i386/suspend.h @@ -0,0 +1,304 @@ +#ifndef __ASM_I386_SUSPEND_H +#define __ASM_I386_SUSPEND_H +#endif + +/* + * Copyright 2001-2002 Pavel Machek <pavel@suse.cz> + * Based on code + * Copyright 2001 Patrick Mochel <mochel@osdl.org> + */ +#if defined(SUSPEND_C) || defined(ACPI_C) +#include <asm/desc.h> +#include <asm/i387.h> + +static inline void +arch_prepare_suspend(void) +{ + if (!cpu_has_pse) + panic("pse required"); +} + +/* image of the saved processor state */ +struct saved_context { + u32 eax, ebx, ecx, edx; + u32 esp, ebp, esi, edi; + u16 es, fs, gs, ss; + u32 cr0, cr2, cr3, cr4; + u16 gdt_pad; + u16 gdt_limit; + u32 gdt_base; + u16 idt_pad; + u16 idt_limit; + u32 idt_base; + u16 ldt; + u16 tss; + u32 tr; + u32 safety; + u32 return_address; + u32 eflags; +} __attribute__((packed)); + +static struct saved_context saved_context; + +#define loaddebug(thread,register) \ + __asm__("movl %0,%%db" #register \ + : /* no output */ \ + :"r" ((thread)->debugreg[register])) + + +/* + * save_processor_context + * + * Save the state of the processor before we go to sleep. + * + * return_stack is the value of the stack pointer (%esp) as the caller sees it. + * A good way could not be found to obtain it from here (don't want to make _too_ + * many assumptions about the layout of the stack this far down.) Also, the + * handy little __builtin_frame_pointer(level) where level > 0, is blatantly + * buggy - it returns the value of the stack at the proper location, not the + * location, like it should (as of gcc 2.91.66) + * + * Note that the context and timing of this function is pretty critical. + * With a minimal amount of things going on in the caller and in here, gcc + * does a good job of being just a dumb compiler. Watch the assembly output + * if anything changes, though, and make sure everything is going in the right + * place. + */ +static inline void save_processor_context (void) +{ + kernel_fpu_begin(); + + /* + * descriptor tables + */ + asm volatile ("sgdt (%0)" : "=m" (saved_context.gdt_limit)); + asm volatile ("sidt (%0)" : "=m" (saved_context.idt_limit)); + asm volatile ("sldt (%0)" : "=m" (saved_context.ldt)); + asm volatile ("str (%0)" : "=m" (saved_context.tr)); + + /* + * save the general registers. + * note that gcc has constructs to specify output of certain registers, + * but they're not used here, because it assumes that you want to modify + * those registers, so it tries to be smart and save them beforehand. + * It's really not necessary, and kinda fishy (check the assembly output), + * so it's avoided. + */ + asm volatile ("movl %%esp, (%0)" : "=m" (saved_context.esp)); + asm volatile ("movl %%eax, (%0)" : "=m" (saved_context.eax)); + asm volatile ("movl %%ebx, (%0)" : "=m" (saved_context.ebx)); + asm volatile ("movl %%ecx, (%0)" : "=m" (saved_context.ecx)); + asm volatile ("movl %%edx, (%0)" : "=m" (saved_context.edx)); + asm volatile ("movl %%ebp, (%0)" : "=m" (saved_context.ebp)); + asm volatile ("movl %%esi, (%0)" : "=m" (saved_context.esi)); + asm volatile ("movl %%edi, (%0)" : "=m" (saved_context.edi)); + + /* + * segment registers + */ + asm volatile ("movw %%es, %0" : "=r" (saved_context.es)); + asm volatile ("movw %%fs, %0" : "=r" (saved_context.fs)); + asm volatile ("movw %%gs, %0" : "=r" (saved_context.gs)); + asm volatile ("movw %%ss, %0" : "=r" (saved_context.ss)); + + /* + * control registers + */ + asm volatile ("movl %%cr0, %0" : "=r" (saved_context.cr0)); + asm volatile ("movl %%cr2, %0" : "=r" (saved_context.cr2)); + asm volatile ("movl %%cr3, %0" : "=r" (saved_context.cr3)); + asm volatile ("movl %%cr4, %0" : "=r" (saved_context.cr4)); + + /* + * eflags + */ + asm volatile ("pushfl ; popl (%0)" : "=m" (saved_context.eflags)); +} + +static void fix_processor_context(void) +{ + int nr = smp_processor_id(); + struct tss_struct * t = &init_tss[nr]; + + set_tss_desc(nr,t); /* This just modifies memory; should not be neccessary. But... This is neccessary, because 386 hardware has concept of busy tsc or some similar stupidity. */ + gdt_table[__TSS(nr)].b &= 0xfffffdff; + + load_TR(nr); /* This does ltr */ + + load_LDT(¤t->mm->context); /* This does lldt */ + + /* + * Now maybe reload the debug registers + */ + if (current->thread.debugreg[7]){ + loaddebug(¤t->thread, 0); + loaddebug(¤t->thread, 1); + loaddebug(¤t->thread, 2); + loaddebug(¤t->thread, 3); + /* no 4 and 5 */ + loaddebug(¤t->thread, 6); + loaddebug(¤t->thread, 7); + } + +} + +static void +do_fpu_end(void) +{ + /* restore FPU regs if necessary */ + /* Do it out of line so that gcc does not move cr0 load to some stupid place */ + kernel_fpu_end(); +} + +/* + * restore_processor_context + * + * Restore the processor context as it was before we went to sleep + * - descriptor tables + * - control registers + * - segment registers + * - flags + * + * Note that it is critical that this function is declared inline. + * It was separated out from restore_state to make that function + * a little clearer, but it needs to be inlined because we won't have a + * stack when we get here (so we can't push a return address). + */ +static inline void restore_processor_context (void) +{ + /* + * first restore %ds, so we can access our data properly + */ + asm volatile (".align 4"); + asm volatile ("movw %0, %%ds" :: "r" ((u16)__KERNEL_DS)); + + + /* + * control registers + */ + asm volatile ("movl %0, %%cr4" :: "r" (saved_context.cr4)); + asm volatile ("movl %0, %%cr3" :: "r" (saved_context.cr3)); + asm volatile ("movl %0, %%cr2" :: "r" (saved_context.cr2)); + asm volatile ("movl %0, %%cr0" :: "r" (saved_context.cr0)); + + /* + * segment registers + */ + asm volatile ("movw %0, %%es" :: "r" (saved_context.es)); + asm volatile ("movw %0, %%fs" :: "r" (saved_context.fs)); + asm volatile ("movw %0, %%gs" :: "r" (saved_context.gs)); + asm volatile ("movw %0, %%ss" :: "r" (saved_context.ss)); + + /* + * the other general registers + * + * note that even though gcc has constructs to specify memory + * input into certain registers, it will try to be too smart + * and save them at the beginning of the function. This is esp. + * bad since we don't have a stack set up when we enter, and we + * want to preserve the values on exit. So, we set them manually. + */ + asm volatile ("movl %0, %%esp" :: "m" (saved_context.esp)); + asm volatile ("movl %0, %%ebp" :: "m" (saved_context.ebp)); + asm volatile ("movl %0, %%eax" :: "m" (saved_context.eax)); + asm volatile ("movl %0, %%ebx" :: "m" (saved_context.ebx)); + asm volatile ("movl %0, %%ecx" :: "m" (saved_context.ecx)); + asm volatile ("movl %0, %%edx" :: "m" (saved_context.edx)); + asm volatile ("movl %0, %%esi" :: "m" (saved_context.esi)); + asm volatile ("movl %0, %%edi" :: "m" (saved_context.edi)); + + /* + * now restore the descriptor tables to their proper values + */ + asm volatile ("lgdt (%0)" :: "m" (saved_context.gdt_limit)); + asm volatile ("lidt (%0)" :: "m" (saved_context.idt_limit)); + asm volatile ("lldt (%0)" :: "m" (saved_context.ldt)); + +#if 0 + asm volatile ("ltr (%0)" :: "m" (saved_context.tr)); +#endif + + fix_processor_context(); + + /* + * the flags + */ + asm volatile ("pushl %0 ; popfl" :: "m" (saved_context.eflags)); + + do_fpu_end(); +} + +#endif +#ifdef SUSPEND_C +#if 1 +/* Local variables for do_magic */ +static int loop __nosavedata = 0; +static int loop2 __nosavedata = 0; + +/* + * (KG): Since we affect stack here, we make this function as flat and easy + * as possible in order to not provoke gcc to use local variables on the stack. + * Note that on resume, all (expect nosave) variables will have the state from + * the time of writing (suspend_save_image) and the registers (including the + * stack pointer, but excluding the instruction pointer) will be loaded with + * the values saved at save_processor_context() time. + */ +static void do_magic(int resume) +{ + /* DANGER WILL ROBINSON! + * + * If this function is too difficult for gcc to optimize, it will crash and burn! + * see above. + * + * DO NOT TOUCH. + */ + + if (!resume) { + do_magic_suspend_1(); + save_processor_context(); /* We need to capture registers and memory at "same time" */ + do_magic_suspend_2(); /* If everything goes okay, this function does not return */ + return; + } + + /* We want to run from swapper_pg_dir, since swapper_pg_dir is stored in constant + * place in memory + */ + + __asm__( "movl %%ecx,%%cr3\n" ::"c"(__pa(swapper_pg_dir))); + +/* + * Final function for resuming: after copying the pages to their original + * position, it restores the register state. + */ + + do_magic_resume_1(); + + /* Critical section here: noone should touch memory from now */ + /* This works, because nr_copy_pages, pagedir_nosave, loop and loop2 are nosavedata */ + for (loop=0; loop < nr_copy_pages; loop++) { + /* You may not call something (like copy_page) here: + We may absolutely not use stack at this point */ + for (loop2=0; loop2 < PAGE_SIZE; loop2++) { + *(((char *)((pagedir_nosave+loop)->orig_address))+loop2) = + *(((char *)((pagedir_nosave+loop)->address))+loop2); + __flush_tlb(); + } + } +/* FIXME: What about page tables? Writing data pages may toggle + accessed/dirty bits in our page tables. That should be no problems + with 4MB page tables. That's why we require have_pse. */ + +/* Danger: previous loop probably destroyed our current stack. Better hope it did not use + any stack space, itself. + + When this function is entered at resume time, we move stack to _old_ place. + This is means that this function must use no stack and no local variables in registers. +*/ + restore_processor_context(); +/* Ahah, we now run with our old stack, and with registers copied from suspend time */ + + do_magic_resume_2(); +} +#endif +#endif + diff --git a/include/linux/bitops.h b/include/linux/bitops.h index b155b779696b..39f3b34a75d5 100644 --- a/include/linux/bitops.h +++ b/include/linux/bitops.h @@ -1,6 +1,6 @@ #ifndef _LINUX_BITOPS_H #define _LINUX_BITOPS_H - +#include <asm/bitops.h> /* * ffs: find first bit set. This is defined the same way as @@ -38,6 +38,47 @@ static inline int generic_ffs(int x) } /* + * fls: find last bit set. + */ + +extern __inline__ int generic_fls(int x) +{ + int r = 32; + + if (!x) + return 0; + if (!(x & 0xffff0000)) { + x <<= 16; + r -= 16; + } + if (!(x & 0xff000000)) { + x <<= 8; + r -= 8; + } + if (!(x & 0xf0000000)) { + x <<= 4; + r -= 4; + } + if (!(x & 0xc0000000)) { + x <<= 2; + r -= 2; + } + if (!(x & 0x80000000)) { + x <<= 1; + r -= 1; + } + return r; +} + +extern __inline__ int get_bitmask_order(unsigned int count) +{ + int order; + + order = fls(count); + return order; /* We could be slightly more clever with -1 here... */ +} + +/* * hweightN: returns the hamming weight (i.e. the number * of bits set) of a N-bit word */ diff --git a/include/linux/init.h b/include/linux/init.h index 142ec2f3aa68..ca7e75f37883 100644 --- a/include/linux/init.h +++ b/include/linux/init.h @@ -169,6 +169,9 @@ typedef void (*__cleanup_module_func_t)(void); #endif +/* Data marked not to be saved by software_suspend() */ +#define __nosavedata __attribute__ ((__section__ (".data.nosave"))) + #ifdef CONFIG_HOTPLUG #define __devinit #define __devinitdata diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h index 52b7117c4f64..23d6e6208432 100644 --- a/include/linux/page-flags.h +++ b/include/linux/page-flags.h @@ -64,6 +64,7 @@ #define PG_private 12 /* Has something at ->private */ #define PG_writeback 13 /* Page is under writeback */ +#define PG_nosave 15 /* Used for system suspend/resume */ /* * Global page accounting. One instance per CPU. @@ -207,6 +208,12 @@ extern void get_page_state(struct page_state *ret); ret; \ }) +#define PageNosave(page) test_bit(PG_nosave, &(page)->flags) +#define SetPageNosave(page) set_bit(PG_nosave, &(page)->flags) +#define TestSetPageNosave(page) test_and_set_bit(PG_nosave, &(page)->flags) +#define ClearPageNosave(page) clear_bit(PG_nosave, &(page)->flags) +#define TestClearPageNosave(page) test_and_clear_bit(PG_nosave, &(page)->flags) + /* * The PageSwapCache predicate doesn't use a PG_flag at this time, * but it may again do so one day. diff --git a/include/linux/reboot.h b/include/linux/reboot.h index 5f128a952550..4c8b4276c957 100644 --- a/include/linux/reboot.h +++ b/include/linux/reboot.h @@ -20,6 +20,7 @@ * CAD_OFF Ctrl-Alt-Del sequence sends SIGINT to init task. * POWER_OFF Stop OS and remove all power from system, if possible. * RESTART2 Restart system using given command string. + * SW_SUSPEND Suspend system using Software Suspend if compiled in */ #define LINUX_REBOOT_CMD_RESTART 0x01234567 @@ -28,6 +29,7 @@ #define LINUX_REBOOT_CMD_CAD_OFF 0x00000000 #define LINUX_REBOOT_CMD_POWER_OFF 0x4321FEDC #define LINUX_REBOOT_CMD_RESTART2 0xA1B2C3D4 +#define LINUX_REBOOT_CMD_SW_SUSPEND 0xD000FCE2 #ifdef __KERNEL__ @@ -46,6 +48,13 @@ extern void machine_restart(char *cmd); extern void machine_halt(void); extern void machine_power_off(void); +/* + * Architecture-independent suspend facility + */ + +extern void software_suspend(void); +extern unsigned char software_suspend_enabled; + #endif #endif /* _LINUX_REBOOT_H */ diff --git a/include/linux/sched.h b/include/linux/sched.h index 2ad9171a66b7..067c93387b77 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -388,6 +388,11 @@ do { if (atomic_dec_and_test(&(tsk)->usage)) __put_task_struct(tsk); } while(0) #define PF_FLUSHER 0x00004000 /* responsible for disk writeback */ #define PF_RADIX_TREE 0x00008000 /* debug: performing radix tree alloc */ +#define PF_FREEZE 0x00010000 /* this task should be frozen for suspend */ +#define PF_IOTHREAD 0x00020000 /* this thread is needed for doing I/O to swap */ +#define PF_KERNTHREAD 0x00040000 /* this thread is a kernel thread that cannot be sent signals to */ +#define PF_FROZEN 0x00080000 /* frozen for system suspend */ + /* * Ptrace flags */ diff --git a/include/linux/suspend.h b/include/linux/suspend.h new file mode 100644 index 000000000000..6de78341c4fc --- /dev/null +++ b/include/linux/suspend.h @@ -0,0 +1,65 @@ +#ifndef _LINUX_SWSUSP_H +#define _LINUX_SWSUSP_H + +#include <asm/suspend.h> +#include <linux/swap.h> +#include <linux/notifier.h> +#include <linux/config.h> + +extern unsigned char software_suspend_enabled; + +#define NORESUME 1 +#define RESUME_SPECIFIED 2 + +#ifdef CONFIG_SOFTWARE_SUSPEND +/* page backup entry */ +typedef struct pbe { + unsigned long address; /* address of the copy */ + unsigned long orig_address; /* original address of page */ + swp_entry_t swap_address; + swp_entry_t dummy; /* we need scratch space at + * end of page (see link, diskpage) + */ +} suspend_pagedir_t; + +#define SWAP_FILENAME_MAXLENGTH 32 + +struct suspend_header { + __u32 version_code; + unsigned long num_physpages; + char machine[8]; + char version[20]; + int num_cpus; + int page_size; + unsigned long suspend_pagedir; + unsigned int num_pbes; + struct swap_location { + char filename[SWAP_FILENAME_MAXLENGTH]; + } swap_location[MAX_SWAPFILES]; +}; + +#define SUSPEND_PD_PAGES(x) (((x)*sizeof(struct pbe))/PAGE_SIZE+1) + +extern struct tq_struct suspend_tq; + +/* mm/vmscan.c */ +extern int shrink_mem(void); + +/* kernel/suspend.c */ +extern void software_suspend(void); +extern void software_resume(void); +extern int resume_setup(char *str); + +extern int register_suspend_notifier(struct notifier_block *); +extern int unregister_suspend_notifier(struct notifier_block *); +extern void refrigerator(unsigned long); + +#else +#define software_suspend() do { } while(0) +#define software_resume() do { } while(0) +#define register_suspend_notifier(a) do { } while(0) +#define unregister_suspend_notifier(a) do { } while(0) +#define refrigerator(a) do { BUG(); } while(0) +#endif + +#endif /* _LINUX_SWSUSP_H */ diff --git a/include/linux/tqueue.h b/include/linux/tqueue.h index 5c518e91a8be..516108dcdbbf 100644 --- a/include/linux/tqueue.h +++ b/include/linux/tqueue.h @@ -66,7 +66,7 @@ typedef struct list_head task_queue; #define DECLARE_TASK_QUEUE(q) LIST_HEAD(q) #define TQ_ACTIVE(q) (!list_empty(&q)) -extern task_queue tq_timer, tq_immediate, tq_disk; +extern task_queue tq_timer, tq_immediate, tq_disk, tq_bdflush; /* * To implement your own list of active bottom halfs, use the following |
