Hi, Christoph Hellwig writes:
> - I have done some more work on the split patches, especially added > lots of .dpatch commentary. I'll re-merged that with your > changes tomorrow > - the changelog should be much more detailed, e.g. mentioning which > changes we've dropped that didn't go upstream. I'll write > something up on that. Very nice. I was kind of wondering what to do about the identical dpatch comments and default author addresses. I enclose a new version of modular-swsusp.dpatch. The header file that Kenshi's system complained about was simply missing from the patch. Just adding it from 2.6.6 looks alright to me. Regards, Jens. #! /bin/sh -e ## <PATCHNAME>.dpatch by <[EMAIL PROTECTED]> ## ## All lines beginning with `## DP:' are a description of the patch. ## DP: Makefiles, configure scripts and other build stuff adapted for ## DP: debian package creation . $(dirname $0)/DPATCH * Partially modularised software suspend --- 1.3/arch/i386/power/Makefile 2003-09-09 22:52:20 +02:00 +++ edited/arch/i386/power/Makefile 2004-06-16 15:29:58 +02:00 @@ -1,3 +1,8 @@ +swsusp-arch-y += swsusp_syms.o swsusp.o + obj-$(CONFIG_PM) += cpu.o obj-$(CONFIG_PM_DISK) += pmdisk.o -obj-$(CONFIG_SOFTWARE_SUSPEND) += swsusp.o + +ifneq ($(CONFIG_SOFTWARE_SUSPEND),) +obj-y += swsusp-arch.o +endif --- 1.14/arch/i386/power/pmdisk.S 2004-05-25 11:53:07 +02:00 +++ edited/arch/i386/power/pmdisk.S 2004-06-16 15:29:58 +02:00 @@ -25,8 +25,9 @@ movl %ecx,%cr3 movl pm_pagedir_nosave,%ebx - xorl %eax, %eax - xorl %edx, %edx + movl pmdisk_pages, %eax + leal -1(%eax), %edx + sall $4, %edx .p2align 4,,7 .L1455: movl 4(%ebx,%edx),%edi @@ -36,13 +37,11 @@ rep movsl - movl %cr3, %ecx; - movl %ecx, %cr3; # flush TLB + movl %cr3, %eax; + movl %eax, %cr3; # flush TLB - incl %eax - addl $16, %edx - cmpl pmdisk_pages,%eax - jb .L1455 + subl $16, %edx + jge .L1455 .p2align 4,,7 .L1453: movl saved_context_esp, %esp --- 1.14/arch/i386/power/swsusp.S 2004-05-25 11:53:07 +02:00 +++ edited/arch/i386/power/swsusp.S 2004-06-16 15:29:59 +02:00 @@ -40,8 +40,9 @@ movl %ecx,%cr3 call do_magic_resume_1 - movl $0,loop - cmpl $0,nr_copy_pages + movl nr_copy_pages,%eax + movl %eax,loop + cmpl $0,%eax je .L1453 .p2align 4,,7 .L1455: @@ -52,8 +53,8 @@ movl loop,%eax movl loop2,%edx sall $4,%eax - movl 4(%ecx,%eax),%ebx - movl (%ecx,%eax),%eax + movl -12(%ecx,%eax),%ebx + movl -16(%ecx,%eax),%eax movb (%edx,%eax),%al movb %al,(%edx,%ebx) movl %cr3, %eax; @@ -66,11 +67,11 @@ cmpl $4095,%eax jbe .L1459 movl loop,%eax - leal 1(%eax),%edx + leal -1(%eax),%edx movl %edx,loop movl %edx,%eax - cmpl nr_copy_pages,%eax - jb .L1455 + cmpl $0,%eax + jne .L1455 .p2align 4,,7 .L1453: movl $__USER_DS,%eax --- 1.36/arch/x86_64/kernel/Makefile 2004-05-10 12:25:59 +02:00 +++ edited/arch/x86_64/kernel/Makefile 2004-06-16 15:29:59 +02:00 @@ -19,7 +19,9 @@ obj-$(CONFIG_X86_LOCAL_APIC) += apic.o nmi.o obj-$(CONFIG_X86_IO_APIC) += io_apic.o mpparse.o obj-$(CONFIG_PM) += suspend.o -obj-$(CONFIG_SOFTWARE_SUSPEND) += suspend_asm.o +ifneq ($(CONFIG_SOFTWARE_SUSPEND),) +obj-y += swsusp-arch.o +endif obj-$(CONFIG_CPU_FREQ) += cpufreq/ obj-$(CONFIG_EARLY_PRINTK) += early_printk.o obj-$(CONFIG_GART_IOMMU) += pci-gart.o aperture.o @@ -36,3 +38,4 @@ topology-y += ../../i386/mach-default/topology.o swiotlb-$(CONFIG_SWIOTLB) += ../../ia64/lib/swiotlb.o microcode-$(subst m,y,$(CONFIG_MICROCODE)) += ../../i386/kernel/microcode.o +swsusp-arch-y += swsusp_syms.o suspend_asm.o --- 1.10/drivers/acpi/sleep/proc.c 2004-03-31 15:32:27 +02:00 +++ edited/drivers/acpi/sleep/proc.c 2004-06-16 15:30:00 +02:00 @@ -2,6 +2,7 @@ #include <linux/seq_file.h> #include <linux/suspend.h> #include <linux/bcd.h> +#include <linux/module.h> #include <asm/uaccess.h> #include <acpi/acpi_bus.h> @@ -67,12 +68,17 @@ goto Done; } state = simple_strtoul(str, NULL, 0); -#ifdef CONFIG_SOFTWARE_SUSPEND if (state == 4) { - software_suspend(); - goto Done; + down(&software_suspend_sem); + if (software_suspend_module && + try_module_get(software_suspend_module)) { + up(&software_suspend_sem); + software_suspend_hook(); + module_put(software_suspend_module); + goto Done; + } + up(&software_suspend_sem); } -#endif error = acpi_suspend(state); Done: return error ? error : count; --- 1.248/fs/buffer.c 2004-05-22 23:56:23 +02:00 +++ edited/fs/buffer.c 2004-06-16 15:30:03 +02:00 @@ -379,6 +379,8 @@ return 0; } +EXPORT_SYMBOL(sys_sync); + void emergency_sync(void) { pdflush_operation(do_sync, 0); --- 1.29/include/linux/suspend.h 2004-04-12 19:55:24 +02:00 +++ edited/include/linux/suspend.h 2004-06-16 15:30:03 +02:00 @@ -4,6 +4,7 @@ #ifdef CONFIG_X86 #include <asm/suspend.h> #endif +#include <asm/semaphore.h> #include <linux/swap.h> #include <linux/notifier.h> #include <linux/config.h> @@ -42,23 +43,10 @@ /* mm/page_alloc.c */ extern void drain_local_pages(void); -/* kernel/power/swsusp.c */ -extern int software_suspend(void); - extern unsigned int nr_copy_pages __nosavedata; extern suspend_pagedir_t *pagedir_nosave __nosavedata; -#else /* CONFIG_SOFTWARE_SUSPEND */ -static inline int software_suspend(void) -{ - printk("Warning: fake suspend called\n"); - return -EPERM; -} -#define software_resume() do { } while(0) -#endif /* CONFIG_SOFTWARE_SUSPEND */ - -#ifdef CONFIG_PM extern void refrigerator(unsigned long); extern int freeze_processes(void); extern void thaw_processes(void); @@ -86,5 +74,10 @@ asmlinkage void do_magic_resume_2(void); asmlinkage void do_magic_suspend_1(void); asmlinkage void do_magic_suspend_2(void); + +struct module; +extern struct module *software_suspend_module; +extern int (*software_suspend_hook)(void); +extern struct semaphore software_suspend_sem; #endif /* _LINUX_SWSUSP_H */ --- 1.87/kernel/sys.c 2004-06-01 17:16:26 +02:00 +++ edited/kernel/sys.c 2004-06-16 15:30:02 +02:00 @@ -27,6 +27,7 @@ #include <asm/uaccess.h> #include <asm/io.h> #include <asm/unistd.h> +#include <asm/semaphore.h> #ifndef SET_UNALIGN_CTL # define SET_UNALIGN_CTL(a,b) (-EINVAL) @@ -427,6 +428,14 @@ } +struct module *software_suspend_module; +int (*software_suspend_hook)(void); +DECLARE_MUTEX(software_suspend_sem); + +EXPORT_SYMBOL(software_suspend_module); +EXPORT_SYMBOL(software_suspend_hook); +EXPORT_SYMBOL(software_suspend_sem); + /* * Reboot system call: for obvious reasons only root may call it, * and even root needs to set up some magic numbers in the registers @@ -503,14 +512,20 @@ machine_restart(buffer); break; -#ifdef CONFIG_SOFTWARE_SUSPEND case LINUX_REBOOT_CMD_SW_SUSPEND: - { - int ret = software_suspend(); + down(&software_suspend_sem); + if (software_suspend_module && + try_module_get(software_suspend_module)) { + int ret; + + up(&software_suspend_sem); + ret = software_suspend_hook(); + module_put(software_suspend_module); unlock_kernel(); return ret; } -#endif + up(&software_suspend_sem); + /* fall through */ default: unlock_kernel(); --- 1.7/kernel/power/Kconfig 2004-04-12 19:55:34 +02:00 +++ edited/kernel/power/Kconfig 2004-06-16 15:30:00 +02:00 @@ -19,7 +19,7 @@ sending the processor to sleep and saving power. config SOFTWARE_SUSPEND - bool "Software Suspend (EXPERIMENTAL)" + tristate "Software Suspend (EXPERIMENTAL)" depends on EXPERIMENTAL && PM && SWAP ---help--- Enable the possibilty of suspendig machine. It doesn't need APM. --- 1.9/kernel/power/Makefile 2003-09-09 22:52:20 +02:00 +++ edited/kernel/power/Makefile 2004-06-16 15:30:01 +02:00 @@ -3,3 +3,7 @@ obj-$(CONFIG_PM_DISK) += disk.o pmdisk.o obj-$(CONFIG_MAGIC_SYSRQ) += poweroff.o + +ifneq ($(CONFIG_SOFTWARE_SUSPEND),) +obj-y += swsusp-core.o +endif --- 1.6/kernel/power/console.c 2004-02-04 06:28:11 +01:00 +++ edited/kernel/power/console.c 2004-06-16 15:30:01 +02:00 @@ -7,6 +7,7 @@ #include <linux/vt_kern.h> #include <linux/kbd_kern.h> #include <linux/console.h> +#include <linux/module.h> #include "power.h" static int new_loglevel = 10; @@ -43,6 +44,8 @@ return 0; } +EXPORT_SYMBOL(pm_prepare_console); + void pm_restore_console(void) { console_loglevel = orig_loglevel; @@ -53,3 +56,5 @@ #endif return; } + +EXPORT_SYMBOL(pm_restore_console); --- 1.8/kernel/power/process.c 2004-05-22 10:24:05 +02:00 +++ edited/kernel/power/process.c 2004-06-16 15:30:01 +02:00 @@ -53,6 +53,8 @@ current->state = save; } +EXPORT_SYMBOL(refrigerator); + /* 0 = success, else # of processes that we failed to stop */ int freeze_processes(void) { @@ -95,6 +97,8 @@ return 0; } +EXPORT_SYMBOL(freeze_processes); + void thaw_processes(void) { struct task_struct *g, *p; @@ -117,4 +121,4 @@ printk( " done\n" ); } -EXPORT_SYMBOL(refrigerator); +EXPORT_SYMBOL(thaw_processes); --- 1.81/kernel/power/swsusp.c 2004-05-22 10:24:07 +02:00 +++ edited/kernel/power/swsusp.c 2004-06-16 15:30:01 +02:00 @@ -39,548 +39,38 @@ #include <linux/module.h> #include <linux/mm.h> #include <linux/suspend.h> -#include <linux/smp_lock.h> -#include <linux/file.h> #include <linux/utsname.h> #include <linux/version.h> -#include <linux/delay.h> -#include <linux/reboot.h> -#include <linux/bitops.h> -#include <linux/vt_kern.h> -#include <linux/kbd_kern.h> -#include <linux/keyboard.h> -#include <linux/spinlock.h> -#include <linux/genhd.h> #include <linux/kernel.h> #include <linux/major.h> #include <linux/swap.h> #include <linux/pm.h> -#include <linux/device.h> -#include <linux/buffer_head.h> -#include <linux/swapops.h> -#include <linux/bootmem.h> #include <linux/syscalls.h> #include <linux/console.h> -#include <linux/highmem.h> - -#include <asm/uaccess.h> -#include <asm/mmu_context.h> -#include <asm/pgtable.h> -#include <asm/io.h> +#include <linux/init.h> +#include <linux/moduleparam.h> +#include <linux/kdev_t.h> +#include <linux/blkdev.h> +#include <linux/buffer_head.h> +#include <linux/swapops.h> +#include <linux/cpumask.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/string.h> +#include <linux/pagemap.h> #include "power.h" +#include "swsusp.h" unsigned char software_suspend_enabled = 0; -#define NORESUME 1 -#define RESUME_SPECIFIED 2 - -/* References to section boundaries */ -extern char __nosave_begin, __nosave_end; - -extern int is_head_of_free_region(struct page *); - -/* Locks */ -spinlock_t suspend_pagedir_lock __nosavedata = SPIN_LOCK_UNLOCKED; - -/* Variables to be preserved over suspend */ -static int pagedir_order_check; -static int nr_copy_pages_check; - -static int resume_status; -static char resume_file[256] = ""; /* For resume= kernel option */ -static dev_t resume_device; -/* Local variables that should not be affected by save */ -unsigned int nr_copy_pages __nosavedata = 0; - -/* Suspend pagedir is allocated before final copy, therefore it - must be freed after resume - - Warning: this is evil. There are actually two pagedirs at time of - resume. One is "pagedir_save", which is empty frame allocated at - time of suspend, that must be freed. Second is "pagedir_nosave", - allocated at time of resume, that travels through memory not to - collide with anything. - - Warning: this is even more evil than it seems. Pagedirs this file - talks about are completely different from page directories used by - MMU hardware. - */ -suspend_pagedir_t *pagedir_nosave __nosavedata = NULL; -static suspend_pagedir_t *pagedir_save; -static int pagedir_order __nosavedata = 0; - -struct link { - char dummy[PAGE_SIZE - sizeof(swp_entry_t)]; - swp_entry_t next; -}; - -union diskpage { - union swap_header swh; - struct link link; - struct suspend_header sh; -}; - -/* - * XXX: We try to keep some more pages free so that I/O operations succeed - * without paging. Might this be more? - */ -#define PAGES_FOR_IO 512 - -static const char name_suspend[] = "Suspend Machine: "; -static const char name_resume[] = "Resume Machine: "; - -/* - * Debug - */ -#define DEBUG_DEFAULT -#undef DEBUG_PROCESS -#undef DEBUG_SLOW -#define TEST_SWSUSP 0 /* Set to 1 to reboot instead of halt machine after suspension */ - -#ifdef DEBUG_DEFAULT -# define PRINTK(f, a...) printk(f, ## a) -#else -# define PRINTK(f, a...) do { } while(0) -#endif - -#ifdef DEBUG_SLOW -#define MDELAY(a) mdelay(a) -#else -#define MDELAY(a) do { } while(0) -#endif +static int noresume __initdata; +static char resume_file[256] __initdata; /* * Saving part... */ -static __inline__ int fill_suspend_header(struct suspend_header *sh) -{ - memset((char *)sh, 0, sizeof(*sh)); - - sh->version_code = LINUX_VERSION_CODE; - sh->num_physpages = num_physpages; - strncpy(sh->machine, system_utsname.machine, 8); - strncpy(sh->version, system_utsname.version, 20); - /* FIXME: Is this bogus? --RR */ - sh->num_cpus = num_online_cpus(); - sh->page_size = PAGE_SIZE; - sh->suspend_pagedir = pagedir_nosave; - BUG_ON (pagedir_save != pagedir_nosave); - sh->num_pbes = nr_copy_pages; - /* TODO: needed? mounted fs' last mounted date comparison - * [so they haven't been mounted since last suspend. - * Maybe it isn't.] [we'd need to do this for _all_ fs-es] - */ - return 0; -} - -/* We memorize in swapfile_used what swap devices are used for suspension */ -#define SWAPFILE_UNUSED 0 -#define SWAPFILE_SUSPEND 1 /* This is the suspending device */ -#define SWAPFILE_IGNORED 2 /* Those are other swap devices ignored for suspension */ - -static unsigned short swapfile_used[MAX_SWAPFILES]; -static unsigned short root_swap; -#define MARK_SWAP_SUSPEND 0 -#define MARK_SWAP_RESUME 2 - -static void mark_swapfiles(swp_entry_t prev, int mode) -{ - swp_entry_t entry; - union diskpage *cur; - struct page *page; - - if (root_swap == 0xFFFF) /* ignored */ - return; - - page = alloc_page(GFP_ATOMIC); - if (!page) - panic("Out of memory in mark_swapfiles"); - cur = page_address(page); - /* XXX: this is dirty hack to get first page of swap file */ - entry = swp_entry(root_swap, 0); - rw_swap_page_sync(READ, entry, page); - - if (mode == MARK_SWAP_RESUME) { - if (!memcmp("S1",cur->swh.magic.magic,2)) - memcpy(cur->swh.magic.magic,"SWAP-SPACE",10); - else if (!memcmp("S2",cur->swh.magic.magic,2)) - memcpy(cur->swh.magic.magic,"SWAPSPACE2",10); - else printk("%sUnable to find suspended-data signature (%.10s - misspelled?\n", - name_resume, cur->swh.magic.magic); - } else { - if ((!memcmp("SWAP-SPACE",cur->swh.magic.magic,10))) - memcpy(cur->swh.magic.magic,"S1SUSP....",10); - else if ((!memcmp("SWAPSPACE2",cur->swh.magic.magic,10))) - memcpy(cur->swh.magic.magic,"S2SUSP....",10); - else panic("\nSwapspace is not swapspace (%.10s)\n", cur->swh.magic.magic); - cur->link.next = prev; /* prev is the first/last swap page of the resume area */ - /* link.next lies *no more* in last 4/8 bytes of magic */ - } - rw_swap_page_sync(WRITE, entry, page); - __free_page(page); -} - - -/* - * Check whether the swap device is the specified resume - * device, irrespective of whether they are specified by - * identical names. - * - * (Thus, device inode aliasing is allowed. You can say /dev/hda4 - * instead of /dev/ide/host0/bus0/target0/lun0/part4 [if using devfs] - * and they'll be considered the same device. This is *necessary* for - * devfs, since the resume code can only recognize the form /dev/hda4, - * but the suspend code would see the long name.) - */ -static int is_resume_device(const struct swap_info_struct *swap_info) -{ - struct file *file = swap_info->swap_file; - struct inode *inode = file->f_dentry->d_inode; - - return S_ISBLK(inode->i_mode) && - resume_device == MKDEV(imajor(inode), iminor(inode)); -} - -static void read_swapfiles(void) /* This is called before saving image */ -{ - int i, len; - - len=strlen(resume_file); - root_swap = 0xFFFF; - - swap_list_lock(); - for(i=0; i<MAX_SWAPFILES; i++) { - if (swap_info[i].flags == 0) { - swapfile_used[i]=SWAPFILE_UNUSED; - } else { - if(!len) { - printk(KERN_WARNING "resume= option should be used to set suspend device" ); - if(root_swap == 0xFFFF) { - swapfile_used[i] = SWAPFILE_SUSPEND; - root_swap = i; - } else - swapfile_used[i] = SWAPFILE_IGNORED; - } else { - /* we ignore all swap devices that are not the resume_file */ - if (is_resume_device(&swap_info[i])) { - swapfile_used[i] = SWAPFILE_SUSPEND; - root_swap = i; - } else { - swapfile_used[i] = SWAPFILE_IGNORED; - } - } - } - } - swap_list_unlock(); -} - -static void lock_swapdevices(void) /* This is called after saving image so modification - will be lost after resume... and that's what we want. */ -{ - int i; - - swap_list_lock(); - for(i = 0; i< MAX_SWAPFILES; i++) - if(swapfile_used[i] == SWAPFILE_IGNORED) { - swap_info[i].flags ^= 0xFF; /* we make the device unusable. A new call to - lock_swapdevices can unlock the devices. */ - } - swap_list_unlock(); -} - -/** - * write_suspend_image - Write entire image to disk. - * - * After writing suspend signature to the disk, suspend may no - * longer fail: we have ready-to-run image in swap, and rollback - * would happen on next reboot -- corrupting data. - * - * Note: The buffer we allocate to use to write the suspend header is - * not freed; its not needed since the system is going down anyway - * (plus it causes an oops and I'm lazy^H^H^H^Htoo busy). - */ -static int write_suspend_image(void) -{ - int i; - swp_entry_t entry, prev = { 0 }; - int nr_pgdir_pages = SUSPEND_PD_PAGES(nr_copy_pages); - union diskpage *cur, *buffer = (union diskpage *)get_zeroed_page(GFP_ATOMIC); - unsigned long address; - struct page *page; - - if (!buffer) - return -ENOMEM; - - printk( "Writing data to swap (%d pages): ", nr_copy_pages ); - for (i=0; i<nr_copy_pages; i++) { - if (!(i%100)) - printk( "." ); - if (!(entry = get_swap_page()).val) - panic("\nNot enough swapspace when writing data" ); - - if (swapfile_used[swp_type(entry)] != SWAPFILE_SUSPEND) - panic("\nPage %d: not enough swapspace on suspend device", i ); - - address = (pagedir_nosave+i)->address; - page = virt_to_page(address); - rw_swap_page_sync(WRITE, entry, page); - (pagedir_nosave+i)->swap_address = entry; - } - printk( "|\n" ); - printk( "Writing pagedir (%d pages): ", nr_pgdir_pages); - for (i=0; i<nr_pgdir_pages; i++) { - cur = (union diskpage *)((char *) pagedir_nosave)+i; - BUG_ON ((char *) cur != (((char *) pagedir_nosave) + i*PAGE_SIZE)); - printk( "." ); - if (!(entry = get_swap_page()).val) { - printk(KERN_CRIT "Not enough swapspace when writing pgdir\n" ); - panic("Don't know how to recover"); - free_page((unsigned long) buffer); - return -ENOSPC; - } - - if(swapfile_used[swp_type(entry)] != SWAPFILE_SUSPEND) - panic("\nNot enough swapspace for pagedir on suspend device" ); - - BUG_ON (sizeof(swp_entry_t) != sizeof(long)); - BUG_ON (PAGE_SIZE % sizeof(struct pbe)); - - cur->link.next = prev; - page = virt_to_page((unsigned long)cur); - rw_swap_page_sync(WRITE, entry, page); - prev = entry; - } - printk("H"); - BUG_ON (sizeof(struct suspend_header) > PAGE_SIZE-sizeof(swp_entry_t)); - BUG_ON (sizeof(union diskpage) != PAGE_SIZE); - BUG_ON (sizeof(struct link) != PAGE_SIZE); - if (!(entry = get_swap_page()).val) - panic( "\nNot enough swapspace when writing header" ); - if (swapfile_used[swp_type(entry)] != SWAPFILE_SUSPEND) - panic("\nNot enough swapspace for header on suspend device" ); - - cur = (void *) buffer; - if (fill_suspend_header(&cur->sh)) - BUG(); /* Not a BUG_ON(): we want fill_suspend_header to be called, always */ - - cur->link.next = prev; - - page = virt_to_page((unsigned long)cur); - rw_swap_page_sync(WRITE, entry, page); - prev = entry; - - printk( "S" ); - mark_swapfiles(prev, MARK_SWAP_SUSPEND); - printk( "|\n" ); - - MDELAY(1000); - return 0; -} - -#ifdef CONFIG_HIGHMEM -struct highmem_page { - char *data; - struct page *page; - struct highmem_page *next; -}; - -struct highmem_page *highmem_copy = NULL; - -static int save_highmem_zone(struct zone *zone) -{ - unsigned long zone_pfn; - for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn) { - struct page *page; - struct highmem_page *save; - void *kaddr; - unsigned long pfn = zone_pfn + zone->zone_start_pfn; - int chunk_size; - - if (!(pfn%1000)) - printk("."); - if (!pfn_valid(pfn)) - continue; - page = pfn_to_page(pfn); - /* - * This condition results from rvmalloc() sans vmalloc_32() - * and architectural memory reservations. This should be - * corrected eventually when the cases giving rise to this - * are better understood. - */ - if (PageReserved(page)) { - printk("highmem reserved page?!\n"); - continue; - } - if ((chunk_size = is_head_of_free_region(page))) { - pfn += chunk_size - 1; - zone_pfn += chunk_size - 1; - continue; - } - save = kmalloc(sizeof(struct highmem_page), GFP_ATOMIC); - if (!save) - return -ENOMEM; - save->next = highmem_copy; - save->page = page; - save->data = (void *) get_zeroed_page(GFP_ATOMIC); - if (!save->data) { - kfree(save); - return -ENOMEM; - } - kaddr = kmap_atomic(page, KM_USER0); - memcpy(save->data, kaddr, PAGE_SIZE); - kunmap_atomic(kaddr, KM_USER0); - highmem_copy = save; - } - return 0; -} - -static int save_highmem(void) -{ - struct zone *zone; - int res = 0; - for_each_zone(zone) { - if (is_highmem(zone)) - res = save_highmem_zone(zone); - if (res) - return res; - } - return 0; -} - -static int restore_highmem(void) -{ - while (highmem_copy) { - struct highmem_page *save = highmem_copy; - void *kaddr; - highmem_copy = save->next; - - kaddr = kmap_atomic(save->page, KM_USER0); - memcpy(kaddr, save->data, PAGE_SIZE); - kunmap_atomic(kaddr, KM_USER0); - free_page((long) save->data); - kfree(save); - } - return 0; -} -#endif - -static int pfn_is_nosave(unsigned long pfn) -{ - unsigned long nosave_begin_pfn = __pa(&__nosave_begin) >> PAGE_SHIFT; - unsigned long nosave_end_pfn = PAGE_ALIGN(__pa(&__nosave_end)) >> PAGE_SHIFT; - return (pfn >= nosave_begin_pfn) && (pfn < nosave_end_pfn); -} - -/* if *pagedir_p != NULL it also copies the counted pages */ -static int count_and_copy_zone(struct zone *zone, struct pbe **pagedir_p) -{ - unsigned long zone_pfn, chunk_size, nr_copy_pages = 0; - struct pbe *pbe = *pagedir_p; - for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn) { - struct page *page; - unsigned long pfn = zone_pfn + zone->zone_start_pfn; - - if (!(pfn%1000)) - printk("."); - if (!pfn_valid(pfn)) - continue; - page = pfn_to_page(pfn); - BUG_ON(PageReserved(page) && PageNosave(page)); - if (PageNosave(page)) - continue; - if (PageReserved(page) && pfn_is_nosave(pfn)) { - PRINTK("[nosave pfn 0x%lx]", pfn); - continue; - } - if ((chunk_size = is_head_of_free_region(page))) { - pfn += chunk_size - 1; - zone_pfn += chunk_size - 1; - continue; - } - nr_copy_pages++; - if (!pbe) - continue; - pbe->orig_address = (long) page_address(page); - copy_page((void *)pbe->address, (void *)pbe->orig_address); - pbe++; - } - *pagedir_p = pbe; - return nr_copy_pages; -} - -static int count_and_copy_data_pages(struct pbe *pagedir_p) -{ - int nr_copy_pages = 0; - struct zone *zone; - for_each_zone(zone) { - if (!is_highmem(zone)) - nr_copy_pages += count_and_copy_zone(zone, &pagedir_p); - } - return nr_copy_pages; -} - -static void free_suspend_pagedir_zone(struct zone *zone, unsigned long pagedir) -{ - unsigned long zone_pfn, pagedir_end, pagedir_pfn, pagedir_end_pfn; - pagedir_end = pagedir + (PAGE_SIZE << pagedir_order); - pagedir_pfn = __pa(pagedir) >> PAGE_SHIFT; - pagedir_end_pfn = __pa(pagedir_end) >> PAGE_SHIFT; - for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn) { - struct page *page; - unsigned long pfn = zone_pfn + zone->zone_start_pfn; - if (!pfn_valid(pfn)) - continue; - page = pfn_to_page(pfn); - if (!TestClearPageNosave(page)) - continue; - else if (pfn >= pagedir_pfn && pfn < pagedir_end_pfn) - continue; - __free_page(page); - } -} - -static void free_suspend_pagedir(unsigned long this_pagedir) -{ - struct zone *zone; - for_each_zone(zone) { - if (!is_highmem(zone)) - free_suspend_pagedir_zone(zone, this_pagedir); - } - free_pages(this_pagedir, pagedir_order); -} - -static suspend_pagedir_t *create_suspend_pagedir(int nr_copy_pages) -{ - int i; - suspend_pagedir_t *pagedir; - struct pbe *p; - struct page *page; - - pagedir_order = get_bitmask_order(SUSPEND_PD_PAGES(nr_copy_pages)); - - p = pagedir = (suspend_pagedir_t *)__get_free_pages(GFP_ATOMIC | __GFP_COLD, pagedir_order); - if (!pagedir) - return NULL; - - page = virt_to_page(pagedir); - for(i=0; i < 1<<pagedir_order; i++) - SetPageNosave(page++); - - while(nr_copy_pages--) { - p->address = get_zeroed_page(GFP_ATOMIC | __GFP_COLD); - if (!p->address) { - free_suspend_pagedir((unsigned long) pagedir); - return NULL; - } - SetPageNosave(virt_to_page(p->address)); - p->orig_address = 0; - p++; - } - return pagedir; -} - static int prepare_suspend_processes(void) { sys_sync(); /* Syncing needs pdflushd, so do it before stopping processes */ @@ -605,216 +95,11 @@ printk("|\n"); } -static int suspend_prepare_image(void) -{ - struct sysinfo i; - unsigned int nr_needed_pages = 0; - - pagedir_nosave = NULL; - printk( "/critical section: "); -#ifdef CONFIG_HIGHMEM - printk( "handling highmem" ); - if (save_highmem()) { - printk(KERN_CRIT "%sNot enough free pages for highmem\n", name_suspend); - return -ENOMEM; - } - printk(", "); -#endif - - printk("counting pages to copy" ); - drain_local_pages(); - nr_copy_pages = count_and_copy_data_pages(NULL); - nr_needed_pages = nr_copy_pages + PAGES_FOR_IO; - - printk(" (pages needed: %d+%d=%d free: %d)\n",nr_copy_pages,PAGES_FOR_IO,nr_needed_pages,nr_free_pages()); - if(nr_free_pages() < nr_needed_pages) { - printk(KERN_CRIT "%sCouldn't get enough free pages, on %d pages short\n", - name_suspend, nr_needed_pages-nr_free_pages()); - root_swap = 0xFFFF; - return -ENOMEM; - } - si_swapinfo(&i); /* FIXME: si_swapinfo(&i) returns all swap devices information. - We should only consider resume_device. */ - if (i.freeswap < nr_needed_pages) { - printk(KERN_CRIT "%sThere's not enough swap space available, on %ld pages short\n", - name_suspend, nr_needed_pages-i.freeswap); - return -ENOSPC; - } - - PRINTK( "Alloc pagedir\n" ); - pagedir_save = pagedir_nosave = create_suspend_pagedir(nr_copy_pages); - if (!pagedir_nosave) { - /* Pagedir is big, one-chunk allocation. It is easily possible for this allocation to fail */ - printk(KERN_CRIT "%sCouldn't allocate continuous pagedir\n", name_suspend); - return -ENOMEM; - } - nr_copy_pages_check = nr_copy_pages; - pagedir_order_check = pagedir_order; - - drain_local_pages(); /* During allocating of suspend pagedir, new cold pages may appear. Kill them */ - if (nr_copy_pages != count_and_copy_data_pages(pagedir_nosave)) /* copy */ - BUG(); - - /* - * End of critical section. From now on, we can write to memory, - * but we should not touch disk. This specially means we must _not_ - * touch swap space! Except we must write out our image of course. - */ - - printk( "critical section/: done (%d pages copied)\n", nr_copy_pages ); - return 0; -} - -static void suspend_save_image(void) -{ - device_resume(); - - lock_swapdevices(); - write_suspend_image(); - lock_swapdevices(); /* This will unlock ignored swap devices since writing is finished */ - - /* It is important _NOT_ to umount filesystems at this point. We want - * them synced (in case something goes wrong) but we DO not want to mark - * filesystem clean: it is not. (And it does not matter, if we resume - * correctly, we'll mark system clean, anyway.) - */ -} - -static void suspend_power_down(void) -{ - extern int C_A_D; - C_A_D = 0; - printk(KERN_EMERG "%s%s Trying to power down.\n", name_suspend, TEST_SWSUSP ? "Disable TEST_SWSUSP. NOT ": ""); -#ifdef CONFIG_VT - PRINTK(KERN_EMERG "shift_state: %04x\n", shift_state); - mdelay(1000); - if (TEST_SWSUSP ^ (!!(shift_state & (1 << KG_CTRL)))) - machine_restart(NULL); - else -#endif - { - device_shutdown(); - machine_power_off(); - } - - printk(KERN_EMERG "%sProbably not capable for powerdown. System halted.\n", name_suspend); - machine_halt(); - while (1); - /* NOTREACHED */ -} - -/* - * Magic happens here - */ - -asmlinkage void do_magic_resume_1(void) -{ - barrier(); - mb(); - spin_lock_irq(&suspend_pagedir_lock); /* Done to disable interrupts */ - - device_power_down(4); - PRINTK( "Waiting for DMAs to settle down...\n"); - mdelay(1000); /* We do not want some readahead with DMA to corrupt our memory, right? - Do it with disabled interrupts for best effect. That way, if some - driver scheduled DMA, we have good chance for DMA to finish ;-). */ -} - -asmlinkage void do_magic_resume_2(void) -{ - BUG_ON (nr_copy_pages_check != nr_copy_pages); - BUG_ON (pagedir_order_check != pagedir_order); - - __flush_tlb_global(); /* Even mappings of "global" things (vmalloc) need to be fixed */ - - PRINTK( "Freeing prev allocated pagedir\n" ); - free_suspend_pagedir((unsigned long) pagedir_save); - -#ifdef CONFIG_HIGHMEM - printk( "Restoring highmem\n" ); - restore_highmem(); -#endif - printk("done, devices\n"); - - device_power_up(); - spin_unlock_irq(&suspend_pagedir_lock); - device_resume(); - - /* Fixme: this is too late; we should do this ASAP to avoid "infinite reboots" problem */ - PRINTK( "Fixing swap signatures... " ); - mark_swapfiles(((swp_entry_t) {0}), MARK_SWAP_RESUME); - PRINTK( "ok\n" ); - -#ifdef SUSPEND_CONSOLE - acquire_console_sem(); - update_screen(fg_console); - release_console_sem(); -#endif -} - -/* do_magic() is implemented in arch/?/kernel/suspend_asm.S, and basically does: - - if (!resume) { - do_magic_suspend_1(); - save_processor_state(); - SAVE_REGISTERS - do_magic_suspend_2(); - return; - } - GO_TO_SWAPPER_PAGE_TABLES - do_magic_resume_1(); - COPY_PAGES_BACK - RESTORE_REGISTERS - restore_processor_state(); - do_magic_resume_2(); - - */ - -asmlinkage void do_magic_suspend_1(void) -{ - mb(); - barrier(); - BUG_ON(in_atomic()); - spin_lock_irq(&suspend_pagedir_lock); -} - -asmlinkage void do_magic_suspend_2(void) -{ - int is_problem; - read_swapfiles(); - device_power_down(4); - is_problem = suspend_prepare_image(); - device_power_up(); - spin_unlock_irq(&suspend_pagedir_lock); - if (!is_problem) { - kernel_fpu_end(); /* save_processor_state() does kernel_fpu_begin, and we need to revert it in order to pass in_atomic() checks */ - BUG_ON(in_atomic()); - suspend_save_image(); - suspend_power_down(); /* FIXME: if suspend_power_down is commented out, console is lost after few suspends ?! */ - } - - printk(KERN_EMERG "%sSuspend failed, trying to recover...\n", name_suspend); - MDELAY(1000); /* So user can wait and report us messages if armageddon comes :-) */ - - barrier(); - mb(); - spin_lock_irq(&suspend_pagedir_lock); /* Done to disable interrupts */ - mdelay(1000); - - free_pages((unsigned long) pagedir_nosave, pagedir_order); - spin_unlock_irq(&suspend_pagedir_lock); - - device_resume(); - PRINTK( "Fixing swap signatures... " ); - mark_swapfiles(((swp_entry_t) {0}), MARK_SWAP_RESUME); - PRINTK( "ok\n" ); -} - /* * This is main interface to the outside world. It needs to be * called from process context. */ -int software_suspend(void) +static int software_suspend(void) { int res; if (!software_suspend_enabled) @@ -1023,8 +308,6 @@ return 0; } -extern dev_t __init name_to_dev_t(const char *line); - static int __init __read_suspend_image(struct block_device *bdev, union diskpage *cur, int noresume) { swp_entry_t next; @@ -1117,15 +400,31 @@ unsigned long scratch_page = 0; int error; char b[BDEVNAME_SIZE]; +#ifdef MODULE + char *p; + + swsusp_resume_device = + new_decode_dev(simple_strtoul(specialfile, &p, 16)); + if (*p) + swsusp_resume_device = 0; +#else + extern dev_t __init name_to_dev_t(const char *line); - resume_device = name_to_dev_t(specialfile); + swsusp_resume_device = name_to_dev_t(specialfile); +#endif + if (!swsusp_resume_device) { + printk(KERN_ERR "%s%s: Invalid device\n", name_resume, + specialfile); + error = -EINVAL; + goto out; + } scratch_page = get_zeroed_page(GFP_ATOMIC); cur = (void *) scratch_page; if (cur) { struct block_device *bdev; printk("Resuming from device %s\n", - __bdevname(resume_device, b)); - bdev = open_by_devnum(resume_device, FMODE_READ); + __bdevname(swsusp_resume_device, b)); + bdev = open_by_devnum(swsusp_resume_device, FMODE_READ); if (IS_ERR(bdev)) { error = PTR_ERR(bdev); } else { @@ -1155,6 +454,7 @@ default: printk( "%sError %d resuming\n", name_resume, error ); } +out: MDELAY(1000); return error; } @@ -1178,11 +478,17 @@ } /* We enable the possibility of machine suspend */ software_suspend_enabled = 1; - if (!resume_status) + + down(&software_suspend_sem); + software_suspend_module = THIS_MODULE; + software_suspend_hook = software_suspend; + up(&software_suspend_sem); + + if (!noresume && !resume_file[0]) return 0; printk( "%s", name_resume ); - if (resume_status == NORESUME) { + if (noresume) { if(resume_file[0]) read_suspend_image(resume_file, 1); printk( "disabled\n" ); @@ -1193,11 +499,6 @@ if (pm_prepare_console()) printk("swsusp: Can't allocate a console... proceeding\n"); - if (!resume_file[0] && resume_status == RESUME_SPECIFIED) { - printk( "suspension device unspecified\n" ); - return -EINVAL; - } - printk( "resuming from %s\n", resume_file); if (read_suspend_image(resume_file, 0)) goto read_failure; @@ -1210,27 +511,19 @@ return 0; } -late_initcall(software_resume); - -static int __init resume_setup(char *str) +static void __exit software_resume_exit(void) { - if (resume_status == NORESUME) - return 1; - - strncpy( resume_file, str, 255 ); - resume_status = RESUME_SPECIFIED; - - return 1; + down(&software_suspend_sem); + software_suspend_module = 0; + up(&software_suspend_sem); } -static int __init noresume_setup(char *str) -{ - resume_status = NORESUME; - return 1; -} +late_initcall(software_resume); +module_exit(software_resume_exit); + +module_param(noresume, bool, 0); +module_param_string(resume, resume_file, sizeof(resume_file), 0); -__setup("noresume", noresume_setup); -__setup("resume=", resume_setup); +MODULE_LICENSE("GPL"); -EXPORT_SYMBOL(software_suspend); EXPORT_SYMBOL(software_suspend_enabled); --- 1.37/mm/page_io.c 2004-04-12 19:55:21 +02:00 +++ edited/mm/page_io.c 2004-06-16 15:30:02 +02:00 @@ -127,7 +127,7 @@ return ret; } -#if defined(CONFIG_SOFTWARE_SUSPEND) || defined(CONFIG_PM_DISK) +#if defined(CONFIG_SOFTWARE_SUSPEND) || defined(CONFIG_SOFTWARE_SUSPEND_MODULE) || defined(CONFIG_PM_DISK) /* * A scruffy utility function to read or write an arbitrary swap page * and wait on the I/O. The caller must have a ref on the page. --- 1.219/mm/vmscan.c 2004-06-14 04:42:00 +02:00 +++ edited/mm/vmscan.c 2004-06-16 15:30:03 +02:00 @@ -1179,6 +1179,8 @@ current->reclaim_state = NULL; return ret; } + +EXPORT_SYMBOL(shrink_all_memory); #endif #ifdef CONFIG_HOTPLUG_CPU --- /dev/null 2004-05-30 14:45:31.000000000 +0200 +++ b/kernel/power/swsusp-core.c 2004-06-16 15:32:39.000000000 +0200 @@ -0,0 +1,738 @@ +/* + * linux/kernel/power/swsusp-core.c + * + * This file provides symbols required by swusup-arch. + * + * Copyright (C) 1998-2001 Gabor Kuti <[EMAIL PROTECTED]> + * Copyright (C) 1998,2001-2004 Pavel Machek <[EMAIL PROTECTED]> + * Copyright (C) 2004 Herbert Xu <[EMAIL PROTECTED]> + * + * This file is licensed under the GPLv2. + */ + +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/suspend.h> +#include <linux/smp_lock.h> +#include <linux/file.h> +#include <linux/utsname.h> +#include <linux/version.h> +#include <linux/delay.h> +#include <linux/reboot.h> +#include <linux/bitops.h> +#include <linux/vt_kern.h> +#include <linux/kbd_kern.h> +#include <linux/keyboard.h> +#include <linux/spinlock.h> +#include <linux/genhd.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/swap.h> +#include <linux/pm.h> +#include <linux/device.h> +#include <linux/buffer_head.h> +#include <linux/swapops.h> +#include <linux/bootmem.h> +#include <linux/console.h> +#include <linux/highmem.h> +#include <linux/init.h> +#include <linux/cpumask.h> +#include <linux/fs.h> + +#include <asm/uaccess.h> +#include <asm/mmu_context.h> +#include <asm/pgtable.h> +#include <asm/io.h> + +#include "power.h" +#include "swsusp.h" + +/* References to section boundaries */ +extern char __nosave_begin, __nosave_end; + +extern int is_head_of_free_region(struct page *); + +/* Locks */ +spinlock_t suspend_pagedir_lock __nosavedata = SPIN_LOCK_UNLOCKED; + +/* Variables to be preserved over suspend */ +static int pagedir_order_check; +static int nr_copy_pages_check; + +dev_t swsusp_resume_device; +EXPORT_SYMBOL(swsusp_resume_device); + +/* Local variables that should not be affected by save */ +unsigned int nr_copy_pages __nosavedata = 0; +EXPORT_SYMBOL(nr_copy_pages); + +/* Suspend pagedir is allocated before final copy, therefore it + must be freed after resume + + Warning: this is evil. There are actually two pagedirs at time of + resume. One is "pagedir_save", which is empty frame allocated at + time of suspend, that must be freed. Second is "pagedir_nosave", + allocated at time of resume, that travels through memory not to + collide with anything. + + Warning: this is even more evil than it seems. Pagedirs this file + talks about are completely different from page directories used by + MMU hardware. + */ +suspend_pagedir_t *pagedir_nosave __nosavedata = NULL; +EXPORT_SYMBOL(pagedir_nosave); +suspend_pagedir_t *pagedir_save; +EXPORT_SYMBOL(pagedir_save); +int pagedir_order __nosavedata = 0; +EXPORT_SYMBOL(pagedir_order); + +/* + * XXX: We try to keep some more pages free so that I/O operations succeed + * without paging. Might this be more? + */ +#define PAGES_FOR_IO 512 + +const char name_suspend[] = "Suspend Machine: "; +EXPORT_SYMBOL(name_suspend); +const char name_resume[] = "Resume Machine: "; +EXPORT_SYMBOL(name_resume); + +/* + * Saving part... + */ + +static __inline__ int fill_suspend_header(struct suspend_header *sh) +{ + memset((char *)sh, 0, sizeof(*sh)); + + sh->version_code = LINUX_VERSION_CODE; + sh->num_physpages = num_physpages; + strncpy(sh->machine, system_utsname.machine, 8); + strncpy(sh->version, system_utsname.version, 20); + /* FIXME: Is this bogus? --RR */ + sh->num_cpus = num_online_cpus(); + sh->page_size = PAGE_SIZE; + sh->suspend_pagedir = pagedir_nosave; + BUG_ON (pagedir_save != pagedir_nosave); + sh->num_pbes = nr_copy_pages; + /* TODO: needed? mounted fs' last mounted date comparison + * [so they haven't been mounted since last suspend. + * Maybe it isn't.] [we'd need to do this for _all_ fs-es] + */ + return 0; +} + +/* We memorize in swapfile_used what swap devices are used for suspension */ +#define SWAPFILE_UNUSED 0 +#define SWAPFILE_SUSPEND 1 /* This is the suspending device */ +#define SWAPFILE_IGNORED 2 /* Those are other swap devices ignored for suspension */ + +static unsigned short swapfile_used[MAX_SWAPFILES]; +static unsigned short root_swap; +#define MARK_SWAP_SUSPEND 0 +#define MARK_SWAP_RESUME 2 + +static void mark_swapfiles(swp_entry_t prev, int mode) +{ + swp_entry_t entry; + union diskpage *cur; + struct page *page; + + if (root_swap == 0xFFFF) /* ignored */ + return; + + page = alloc_page(GFP_ATOMIC); + if (!page) + panic("Out of memory in mark_swapfiles"); + cur = page_address(page); + /* XXX: this is dirty hack to get first page of swap file */ + entry = swp_entry(root_swap, 0); + rw_swap_page_sync(READ, entry, page); + + if (mode == MARK_SWAP_RESUME) { + if (!memcmp("S1",cur->swh.magic.magic,2)) + memcpy(cur->swh.magic.magic,"SWAP-SPACE",10); + else if (!memcmp("S2",cur->swh.magic.magic,2)) + memcpy(cur->swh.magic.magic,"SWAPSPACE2",10); + else printk("%sUnable to find suspended-data signature (%.10s - misspelled?\n", + name_resume, cur->swh.magic.magic); + } else { + if ((!memcmp("SWAP-SPACE",cur->swh.magic.magic,10))) + memcpy(cur->swh.magic.magic,"S1SUSP....",10); + else if ((!memcmp("SWAPSPACE2",cur->swh.magic.magic,10))) + memcpy(cur->swh.magic.magic,"S2SUSP....",10); + else panic("\nSwapspace is not swapspace (%.10s)\n", cur->swh.magic.magic); + cur->link.next = prev; /* prev is the first/last swap page of the resume area */ + /* link.next lies *no more* in last 4/8 bytes of magic */ + } + rw_swap_page_sync(WRITE, entry, page); + __free_page(page); +} + +/* + * Check whether the swap device is the specified resume + * device, irrespective of whether they are specified by + * identical names. + * + * (Thus, device inode aliasing is allowed. You can say /dev/hda4 + * instead of /dev/ide/host0/bus0/target0/lun0/part4 [if using devfs] + * and they'll be considered the same device. This is *necessary* for + * devfs, since the resume code can only recognize the form /dev/hda4, + * but the suspend code would see the long name.) + */ +static int is_resume_device(const struct swap_info_struct *swap_info) +{ + struct inode *inode = swap_info->swap_file->f_dentry->d_inode; + + return S_ISBLK(inode->i_mode) && resume_device == inode->i_rdev; +} + +static void read_swapfiles(void) /* This is called before saving image */ +{ + int i; + + root_swap = 0xFFFF; + + swap_list_lock(); + for(i=0; i<MAX_SWAPFILES; i++) { + if (swap_info[i].flags == 0) { + swapfile_used[i]=SWAPFILE_UNUSED; + } else { + if (!swsusp_resume_device) { + printk(KERN_WARNING "resume= option should be used to set suspend device" ); + if(root_swap == 0xFFFF) { + swapfile_used[i] = SWAPFILE_SUSPEND; + root_swap = i; + } else + swapfile_used[i] = SWAPFILE_IGNORED; + } else { + /* we ignore all swap devices that are not the resume_file */ + if (is_resume_device(&swap_info[i])) { + swapfile_used[i] = SWAPFILE_SUSPEND; + root_swap = i; + } else { + swapfile_used[i] = SWAPFILE_IGNORED; + } + } + } + } + swap_list_unlock(); +} + +static void lock_swapdevices(void) /* This is called after saving image so modification + will be lost after resume... and that's what we want. */ +{ + int i; + + swap_list_lock(); + for(i = 0; i< MAX_SWAPFILES; i++) + if(swapfile_used[i] == SWAPFILE_IGNORED) { + swap_info[i].flags ^= 0xFF; /* we make the device unusable. A new call to + lock_swapdevices can unlock the devices. */ + } + swap_list_unlock(); +} + +/** + * write_suspend_image - Write entire image to disk. + * + * After writing suspend signature to the disk, suspend may no + * longer fail: we have ready-to-run image in swap, and rollback + * would happen on next reboot -- corrupting data. + * + * Note: The buffer we allocate to use to write the suspend header is + * not freed; its not needed since the system is going down anyway + * (plus it causes an oops and I'm lazy^H^H^H^Htoo busy). + */ +static int write_suspend_image(void) +{ + int i; + swp_entry_t entry, prev = { 0 }; + int nr_pgdir_pages = SUSPEND_PD_PAGES(nr_copy_pages); + union diskpage *cur, *buffer = (union diskpage *)get_zeroed_page(GFP_ATOMIC); + unsigned long address; + struct page *page; + + if (!buffer) + return -ENOMEM; + + printk( "Writing data to swap (%d pages): ", nr_copy_pages ); + for (i=0; i<nr_copy_pages; i++) { + if (!(i%100)) + printk( "." ); + if (!(entry = get_swap_page()).val) + panic("\nNot enough swapspace when writing data" ); + + if (swapfile_used[swp_type(entry)] != SWAPFILE_SUSPEND) + panic("\nPage %d: not enough swapspace on suspend device", i ); + + address = (pagedir_nosave+i)->address; + page = virt_to_page(address); + rw_swap_page_sync(WRITE, entry, page); + (pagedir_nosave+i)->swap_address = entry; + } + printk( "|\n" ); + printk( "Writing pagedir (%d pages): ", nr_pgdir_pages); + for (i=0; i<nr_pgdir_pages; i++) { + cur = (union diskpage *)((char *) pagedir_nosave)+i; + BUG_ON ((char *) cur != (((char *) pagedir_nosave) + i*PAGE_SIZE)); + printk( "." ); + if (!(entry = get_swap_page()).val) { + printk(KERN_CRIT "Not enough swapspace when writing pgdir\n" ); + panic("Don't know how to recover"); + free_page((unsigned long) buffer); + return -ENOSPC; + } + + if(swapfile_used[swp_type(entry)] != SWAPFILE_SUSPEND) + panic("\nNot enough swapspace for pagedir on suspend device" ); + + BUG_ON (sizeof(swp_entry_t) != sizeof(long)); + BUG_ON (PAGE_SIZE % sizeof(struct pbe)); + + cur->link.next = prev; + page = virt_to_page((unsigned long)cur); + rw_swap_page_sync(WRITE, entry, page); + prev = entry; + } + printk("H"); + BUG_ON (sizeof(struct suspend_header) > PAGE_SIZE-sizeof(swp_entry_t)); + BUG_ON (sizeof(union diskpage) != PAGE_SIZE); + BUG_ON (sizeof(struct link) != PAGE_SIZE); + if (!(entry = get_swap_page()).val) + panic( "\nNot enough swapspace when writing header" ); + if (swapfile_used[swp_type(entry)] != SWAPFILE_SUSPEND) + panic("\nNot enough swapspace for header on suspend device" ); + + cur = (void *) buffer; + if (fill_suspend_header(&cur->sh)) + BUG(); /* Not a BUG_ON(): we want fill_suspend_header to be called, always */ + + cur->link.next = prev; + + page = virt_to_page((unsigned long)cur); + rw_swap_page_sync(WRITE, entry, page); + prev = entry; + + printk( "S" ); + mark_swapfiles(prev, MARK_SWAP_SUSPEND); + printk( "|\n" ); + + MDELAY(1000); + return 0; +} + +#ifdef CONFIG_HIGHMEM +struct highmem_page { + char *data; + struct page *page; + struct highmem_page *next; +}; + +struct highmem_page *highmem_copy = NULL; + +static int save_highmem_zone(struct zone *zone) +{ + unsigned long zone_pfn; + for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn) { + struct page *page; + struct highmem_page *save; + void *kaddr; + unsigned long pfn = zone_pfn + zone->zone_start_pfn; + int chunk_size; + + if (!(pfn%1000)) + printk("."); + if (!pfn_valid(pfn)) + continue; + page = pfn_to_page(pfn); + /* + * This condition results from rvmalloc() sans vmalloc_32() + * and architectural memory reservations. This should be + * corrected eventually when the cases giving rise to this + * are better understood. + */ + if (PageReserved(page)) { + printk("highmem reserved page?!\n"); + continue; + } + if ((chunk_size = is_head_of_free_region(page))) { + pfn += chunk_size - 1; + zone_pfn += chunk_size - 1; + continue; + } + save = kmalloc(sizeof(struct highmem_page), GFP_ATOMIC); + if (!save) + return -ENOMEM; + save->next = highmem_copy; + save->page = page; + save->data = (void *) get_zeroed_page(GFP_ATOMIC); + if (!save->data) { + kfree(save); + return -ENOMEM; + } + kaddr = kmap_atomic(page, KM_USER0); + memcpy(save->data, kaddr, PAGE_SIZE); + kunmap_atomic(kaddr, KM_USER0); + highmem_copy = save; + } + return 0; +} + +static int save_highmem(void) +{ + struct zone *zone; + int res = 0; + for_each_zone(zone) { + if (is_highmem(zone)) + res = save_highmem_zone(zone); + if (res) + return res; + } + return 0; +} + +static int restore_highmem(void) +{ + while (highmem_copy) { + struct highmem_page *save = highmem_copy; + void *kaddr; + highmem_copy = save->next; + + kaddr = kmap_atomic(save->page, KM_USER0); + memcpy(kaddr, save->data, PAGE_SIZE); + kunmap_atomic(kaddr, KM_USER0); + free_page((long) save->data); + kfree(save); + } + return 0; +} +#endif + +static int pfn_is_nosave(unsigned long pfn) +{ + unsigned long nosave_begin_pfn = __pa(&__nosave_begin) >> PAGE_SHIFT; + unsigned long nosave_end_pfn = PAGE_ALIGN(__pa(&__nosave_end)) >> PAGE_SHIFT; + return (pfn >= nosave_begin_pfn) && (pfn < nosave_end_pfn); +} + +/* if *pagedir_p != NULL it also copies the counted pages */ +static int count_and_copy_zone(struct zone *zone, struct pbe **pagedir_p) +{ + unsigned long zone_pfn, chunk_size, nr_copy_pages = 0; + struct pbe *pbe = *pagedir_p; + for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn) { + struct page *page; + unsigned long pfn = zone_pfn + zone->zone_start_pfn; + + if (!(pfn%1000)) + printk("."); + if (!pfn_valid(pfn)) + continue; + page = pfn_to_page(pfn); + BUG_ON(PageReserved(page) && PageNosave(page)); + if (PageNosave(page)) + continue; + if (PageReserved(page) && pfn_is_nosave(pfn)) { + PRINTK("[nosave pfn 0x%lx]", pfn); + continue; + } + if ((chunk_size = is_head_of_free_region(page))) { + pfn += chunk_size - 1; + zone_pfn += chunk_size - 1; + continue; + } + nr_copy_pages++; + if (!pbe) + continue; + pbe->orig_address = (long) page_address(page); + copy_page((void *)pbe->address, (void *)pbe->orig_address); + pbe++; + } + *pagedir_p = pbe; + return nr_copy_pages; +} + +static int count_and_copy_data_pages(struct pbe *pagedir_p) +{ + int nr_copy_pages = 0; + struct zone *zone; + for_each_zone(zone) { + if (!is_highmem(zone)) + nr_copy_pages += count_and_copy_zone(zone, &pagedir_p); + } + return nr_copy_pages; +} + +static void free_suspend_pagedir_zone(struct zone *zone, unsigned long pagedir) +{ + unsigned long zone_pfn, pagedir_end, pagedir_pfn, pagedir_end_pfn; + pagedir_end = pagedir + (PAGE_SIZE << pagedir_order); + pagedir_pfn = __pa(pagedir) >> PAGE_SHIFT; + pagedir_end_pfn = __pa(pagedir_end) >> PAGE_SHIFT; + for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn) { + struct page *page; + unsigned long pfn = zone_pfn + zone->zone_start_pfn; + if (!pfn_valid(pfn)) + continue; + page = pfn_to_page(pfn); + if (!TestClearPageNosave(page)) + continue; + else if (pfn >= pagedir_pfn && pfn < pagedir_end_pfn) + continue; + __free_page(page); + } +} + +static void free_suspend_pagedir(unsigned long this_pagedir) +{ + struct zone *zone; + for_each_zone(zone) { + if (!is_highmem(zone)) + free_suspend_pagedir_zone(zone, this_pagedir); + } + free_pages(this_pagedir, pagedir_order); +} + +static suspend_pagedir_t *create_suspend_pagedir(int nr_copy_pages) +{ + int i; + suspend_pagedir_t *pagedir; + struct pbe *p; + struct page *page; + + pagedir_order = get_bitmask_order(SUSPEND_PD_PAGES(nr_copy_pages)); + + p = pagedir = (suspend_pagedir_t *)__get_free_pages(GFP_ATOMIC | __GFP_COLD, pagedir_order); + if (!pagedir) + return NULL; + + page = virt_to_page(pagedir); + for(i=0; i < 1<<pagedir_order; i++) + SetPageNosave(page++); + + while(nr_copy_pages--) { + p->address = get_zeroed_page(GFP_ATOMIC | __GFP_COLD); + if (!p->address) { + free_suspend_pagedir((unsigned long) pagedir); + return NULL; + } + SetPageNosave(virt_to_page(p->address)); + p->orig_address = 0; + p++; + } + return pagedir; +} + +static int suspend_prepare_image(void) +{ + struct sysinfo i; + unsigned int nr_needed_pages = 0; + + pagedir_nosave = NULL; + printk( "/critical section: "); +#ifdef CONFIG_HIGHMEM + printk( "handling highmem" ); + if (save_highmem()) { + printk(KERN_CRIT "%sNot enough free pages for highmem\n", name_suspend); + return -ENOMEM; + } + printk(", "); +#endif + + printk("counting pages to copy" ); + drain_local_pages(); + nr_copy_pages = count_and_copy_data_pages(NULL); + nr_needed_pages = nr_copy_pages + PAGES_FOR_IO; + + printk(" (pages needed: %d+%d=%d free: %d)\n",nr_copy_pages,PAGES_FOR_IO,nr_needed_pages,nr_free_pages()); + if(nr_free_pages() < nr_needed_pages) { + printk(KERN_CRIT "%sCouldn't get enough free pages, on %d pages short\n", + name_suspend, nr_needed_pages-nr_free_pages()); + root_swap = 0xFFFF; + return -ENOMEM; + } + si_swapinfo(&i); /* FIXME: si_swapinfo(&i) returns all swap devices information. + We should only consider resume_file. */ + if (i.freeswap < nr_needed_pages) { + printk(KERN_CRIT "%sThere's not enough swap space available, on %ld pages short\n", + name_suspend, nr_needed_pages-i.freeswap); + return -ENOMEM; + } + + PRINTK( "Alloc pagedir\n" ); + pagedir_save = pagedir_nosave = create_suspend_pagedir(nr_copy_pages); + if (!pagedir_nosave) { + /* Pagedir is big, one-chunk allocation. It is easily possible for this allocation to fail */ + printk(KERN_CRIT "%sCouldn't allocate continuous pagedir\n", name_suspend); + return -ENOMEM; + } + nr_copy_pages_check = nr_copy_pages; + pagedir_order_check = pagedir_order; + + drain_local_pages(); /* During allocating of suspend pagedir, new cold pages may appear. Kill them */ + if (nr_copy_pages != count_and_copy_data_pages(pagedir_nosave)) /* copy */ + BUG(); + + /* + * End of critical section. From now on, we can write to memory, + * but we should not touch disk. This specially means we must _not_ + * touch swap space! Except we must write out our image of course. + */ + + printk( "critical section/: done (%d pages copied)\n", nr_copy_pages ); + return 0; +} + +static void suspend_save_image(void) +{ + device_resume(); + + lock_swapdevices(); + write_suspend_image(); + lock_swapdevices(); /* This will unlock ignored swap devices since writing is finished */ + + /* It is important _NOT_ to umount filesystems at this point. We want + * them synced (in case something goes wrong) but we DO not want to mark + * filesystem clean: it is not. (And it does not matter, if we resume + * correctly, we'll mark system clean, anyway.) + */ +} + +static void suspend_power_down(void) +{ + extern int C_A_D; + C_A_D = 0; + printk(KERN_EMERG "%s%s Trying to power down.\n", name_suspend, TEST_SWSUSP ? "Disable TEST_SWSUSP. NOT ": ""); +#ifdef CONFIG_VT + PRINTK(KERN_EMERG "shift_state: %04x\n", shift_state); + mdelay(1000); + if (TEST_SWSUSP ^ (!!(shift_state & (1 << KG_CTRL)))) + machine_restart(NULL); + else +#endif + { + device_shutdown(); + machine_power_off(); + } + + printk(KERN_EMERG "%sProbably not capable for powerdown. System halted.\n", name_suspend); + machine_halt(); + while (1); + /* NOTREACHED */ +} + +/* + * Magic happens here + */ + +asmlinkage void do_magic_resume_1(void) +{ + barrier(); + mb(); + spin_lock_irq(&suspend_pagedir_lock); /* Done to disable interrupts */ + + device_power_down(4); + PRINTK( "Waiting for DMAs to settle down...\n"); + mdelay(1000); /* We do not want some readahead with DMA to corrupt our memory, right? + Do it with disabled interrupts for best effect. That way, if some + driver scheduled DMA, we have good chance for DMA to finish ;-). */ +} + +EXPORT_SYMBOL(do_magic_resume_1); + +asmlinkage void do_magic_resume_2(void) +{ + BUG_ON (nr_copy_pages_check != nr_copy_pages); + BUG_ON (pagedir_order_check != pagedir_order); + + __flush_tlb_global(); /* Even mappings of "global" things (vmalloc) need to be fixed */ + + PRINTK( "Freeing prev allocated pagedir\n" ); + free_suspend_pagedir((unsigned long) pagedir_save); + +#ifdef CONFIG_HIGHMEM + printk( "Restoring highmem\n" ); + restore_highmem(); +#endif + printk("done, devices\n"); + + device_power_up(); + spin_unlock_irq(&suspend_pagedir_lock); + device_resume(); + + /* Fixme: this is too late; we should do this ASAP to avoid "infinite reboots" problem */ + PRINTK( "Fixing swap signatures... " ); + mark_swapfiles(((swp_entry_t) {0}), MARK_SWAP_RESUME); + PRINTK( "ok\n" ); + +#ifdef SUSPEND_CONSOLE + acquire_console_sem(); + update_screen(fg_console); + release_console_sem(); +#endif +} + +EXPORT_SYMBOL(do_magic_resume_2); + +/* do_magic() is implemented in arch/?/kernel/suspend_asm.S, and basically does: + + if (!resume) { + do_magic_suspend_1(); + save_processor_state(); + SAVE_REGISTERS + do_magic_suspend_2(); + return; + } + GO_TO_SWAPPER_PAGE_TABLES + do_magic_resume_1(); + COPY_PAGES_BACK + RESTORE_REGISTERS + restore_processor_state(); + do_magic_resume_2(); + + */ + +asmlinkage void do_magic_suspend_1(void) +{ + mb(); + barrier(); + BUG_ON(in_atomic()); + spin_lock_irq(&suspend_pagedir_lock); +} + +EXPORT_SYMBOL(do_magic_suspend_1); + +asmlinkage void do_magic_suspend_2(void) +{ + int is_problem; + read_swapfiles(); + device_power_down(4); + is_problem = suspend_prepare_image(); + device_power_up(); + spin_unlock_irq(&suspend_pagedir_lock); + if (!is_problem) { + kernel_fpu_end(); /* save_processor_state() does kernel_fpu_begin, and we need to revert it in order to pass in_atomic() checks */ + BUG_ON(in_atomic()); + suspend_save_image(); + suspend_power_down(); /* FIXME: if suspend_power_down is commented out, console is lost after few suspends ?! */ + } + + printk(KERN_EMERG "%sSuspend failed, trying to recover...\n", name_suspend); + MDELAY(1000); /* So user can wait and report us messages if armageddon comes :-) */ + + barrier(); + mb(); + spin_lock_irq(&suspend_pagedir_lock); /* Done to disable interrupts */ + mdelay(1000); + + free_pages((unsigned long) pagedir_nosave, pagedir_order); + spin_unlock_irq(&suspend_pagedir_lock); + + device_resume(); + PRINTK( "Fixing swap signatures... " ); + mark_swapfiles(((swp_entry_t) {0}), MARK_SWAP_RESUME); + PRINTK( "ok\n" ); +} + +EXPORT_SYMBOL(do_magic_suspend_2); --- /dev/null 2004-06-19 10:21:50.000000000 +0200 +++ b/kernel/power/swsusp.h 2004-06-19 18:37:51.000000000 +0200 @@ -0,0 +1,50 @@ +/* + * linux/kernel/power/swsusp.h + * + * Copyright (c) 2004 Herbert Xu <[EMAIL PROTECTED]> + * + * This file is licensed under the GPLv2. + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/suspend.h> +#include <linux/types.h> + +struct link { + char dummy[PAGE_SIZE - sizeof(swp_entry_t)]; + swp_entry_t next; +}; + +union diskpage { + union swap_header swh; + struct link link; + struct suspend_header sh; +}; + +extern dev_t swsusp_resume_device; +extern const char name_suspend[]; +extern const char name_resume[]; + +extern suspend_pagedir_t *pagedir_save; +extern int pagedir_order; + +/* + * Debug + */ +#define DEBUG_DEFAULT +#undef DEBUG_PROCESS +#undef DEBUG_SLOW +#define TEST_SWSUSP 0 /* Set to 1 to reboot instead of halt machine after suspension */ + +#ifdef DEBUG_DEFAULT +# define PRINTK(f, a...) printk(f, ## a) +#else +# define PRINTK(f, a...) do { } while(0) +#endif + +#ifdef DEBUG_SLOW +#define MDELAY(a) mdelay(a) +#else +#define MDELAY(a) do { } while(0) +#endif -- J'qbpbe, le m'en fquz pe j'qbpbe! Le veux aimeb et mqubib panz je pézqbpbe je djuz tqtaj!