2005-06-13 machida@sm.sony.co.jp This patch enables preserving hibernation system image over boot cycle. M kernel/power/Kconfig M kernel/power/swsusp.c M kernel/power/disk.c ----------------- Index: linux-2.6.11.12/kernel/power/Kconfig =================================================================== --- linux-2.6.11.12.orig/kernel/power/Kconfig +++ linux-2.6.11.12/kernel/power/Kconfig @@ -79,3 +79,17 @@ config SWSUSP_MTDBLOCK_FLUSH ---help--- When suspend to disk (software suspend), flush the MTD block. +config PRESERVE_SWSUSP_IMAGE + bool "Preserve swsuspend image" + depends on SOFTWARE_SUSPEND + default n + ---help--- + Useally boot with swsup destories the swsusp image. + This function enables to preserve swsup image over boot cycle. + Default behavior is not chaged even this configuration turned on. + + To preseve swsusp image, specify following option to command line; + + prsv-img + + Index: linux-2.6.11.12/kernel/power/disk.c =================================================================== --- linux-2.6.11.12.orig/kernel/power/disk.c +++ linux-2.6.11.12/kernel/power/disk.c @@ -28,6 +28,25 @@ extern int swsusp_write(void); extern int swsusp_read(void); extern int swsusp_resume(void); extern int swsusp_free(void); +extern void dump_pagedir_nosave(void); +#ifdef CONFIG_PRESERVE_SWSUSP_IMAGE +extern int preserve_swsusp_image; +extern dev_t resume_device_nosave __nosavedata; +extern int swsusp_swap_rdonly(dev_t); +extern int swsusp_swap_off(dev_t); +#else +#define preserve_swsusp_image 0 +#define resume_device_nosave 0 +static inline int swsusp_swap_rdonly(dev_t dev) +{ + return 0; +} +static inline int swsusp_swap_off(dev_t dev) +{ + return 0; +} +#endif + static int noresume = 0; @@ -107,6 +126,26 @@ static void free_some_memory(void) printk("\bdone (%li pages freed)\n", pages); } +#ifdef CONFIG_PRESERVE_SWSUSP_IMAGE +void finish_in_resume(void) +{ + device_resume(); + platform_finish(); + enable_nonboot_cpus(); + thaw_processes(); + if (preserve_swsusp_image) { + swsusp_swap_off(resume_device_nosave); + } + pm_restore_console(); +} +#else +void finish_in_resume(void) +{ + finish(); +} +#endif + + static inline void platform_finish(void) { @@ -195,8 +234,15 @@ int pm_suspend_disk(void) error = swsusp_write(); if (!error) power_down(pm_disk_mode); - } else + } else { pr_debug("PM: Image restored successfully.\n"); + if (preserve_swsusp_image) { + swsusp_swap_rdonly(resume_device_nosave); + } + swsusp_free(); + finish_in_resume(); + return 0; + } swsusp_free(); Done: finish(); Index: linux-2.6.11.12/kernel/power/swsusp.c =================================================================== --- linux-2.6.11.12.orig/kernel/power/swsusp.c +++ linux-2.6.11.12/kernel/power/swsusp.c @@ -112,12 +112,35 @@ static struct swsusp_header { static struct swsusp_info swsusp_info; +#ifdef CONFIG_PRESERVE_SWSUSP_IMAGE +dev_t resume_device_nosave __nosavedata; +struct swsusp_header swsusp_header_nosave __nosavedata ; +#endif + /* * 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 +#ifdef CONFIG_PRESERVE_SWSUSP_IMAGE +int preserve_swsusp_image=0; +static int __init preserve_swsusp_image_setup(char *str) +{ + if (*str) + return 0; + preserve_swsusp_image = 1; + return 1; +} +#else +static int __init preserve_swsusp_image_setup(char *str) +{ + return 0; +} +#endif + +__setup("prsv-img", preserve_swsusp_image_setup); + /* * Saving part... */ @@ -1114,6 +1137,52 @@ static int __init check_header(void) return error; } +#ifdef CONFIG_PRESERVE_SWSUSP_IMAGE +/** + * mark_swapfiles - Revert swap signature + * + * Assumed that swsusp_header holds correct data + * and rw_swap_page_sync() works + */ +int mark_swsusp(dev_t dev) +{ + int error; + + resume_bdev = open_by_devnum(dev, FMODE_WRITE); + if (!IS_ERR(resume_bdev)) { + set_blocksize(resume_bdev, PAGE_SIZE); + error = bio_write_page(0, &swsusp_header_nosave); + blkdev_put(resume_bdev); + } else + error = PTR_ERR(resume_bdev); + + if (!error) + pr_debug("swsusp: Mark swsusp again\n"); + else + pr_debug("swsusp: Error %d marking swsusp\n", error); + return error; +} +inline static void update_swsusp_header(void) +{ + swsusp_header_nosave=swsusp_header; +} + +inline static void update_swsusp_device(void) +{ + resume_device_nosave = resume_device; +} +#else +inline static void update_swsusp_header(void) +{ + ; +} + +inline static void update_swsusp_device(void) +{ + ; +} +#endif + static int __init check_sig(void) { int error; @@ -1122,6 +1191,7 @@ static int __init check_sig(void) if ((error = bio_read_page(0, &swsusp_header))) return error; if (!memcmp(SWSUSP_SIG, swsusp_header.sig, 10)) { + update_swsusp_header(); memcpy(swsusp_header.sig, swsusp_header.orig_sig, 10); /* @@ -1224,6 +1294,7 @@ int __init swsusp_read(void) resume_device = name_to_dev_t(resume_file); pr_debug("swsusp: Resume From Partition: %s\n", resume_file); + update_swsusp_device(); resume_bdev = open_by_devnum(resume_device, FMODE_READ); if (!IS_ERR(resume_bdev)) { Index: linux-2.6.11.12/mm/swapfile.c =================================================================== --- linux-2.6.11.12.orig/mm/swapfile.c +++ linux-2.6.11.12/mm/swapfile.c @@ -1124,6 +1124,186 @@ int page_queue_congested(struct page *pa } #endif +#ifdef CONFIG_PRESERVE_SWSUSP_IMAGE +extern int mark_swsusp(dev_t); + +#ifdef DEBUG +extern void cons_write(char *); + +#undef pr_debug +#define pr_debug(fmt,arg...) \ + do { \ + char __cw_buf[64]; \ + __cw_buf[63]='\0'; \ + snprintf(__cw_buf, 63, fmt,##arg); \ + cons_write(__cw_buf); \ + } while (0) +#endif /*DEBUG*/ + +/** + * find_swapdev_info - Find swap block device info, currently used + * + */ + +static struct swap_info_struct *find_swapdev_info(dev_t dev, + int *p_prev, int *p_type) +{ + int prev, type; + struct inode *inode; + struct swap_info_struct * si = NULL; + + prev = -1; + for (type = swap_list.head; type >= 0; type = swap_info[type].next) { + si = swap_info + type; + pr_debug("P:0x%8.8x, TYPE:0x%8.8x\n", (int)si, type); + if (si->flags & SWP_USED) { + inode = si->swap_file->f_mapping->host; + if (S_ISBLK(inode->i_mode)) { + pr_debug("INODE: 0x%8.8x:0x%8.8x\n", + dev, + MKDEV(imajor(inode), iminor(inode))); + if (dev == MKDEV(imajor(inode), iminor(inode))) + break; + } + } + prev = type; + } + + if (type<0) { + si = 0; + } + *p_type = type; + *p_prev = prev; + return si; +} + +/** + * swsusp_swap_rdonly - Find Swap device for swsusp and mark ReadOnly + * + */ +int swsusp_swap_rdonly(dev_t resume_dev) +{ + struct swap_info_struct * si = NULL; + int prev; + int type; + int found=0; + + swap_list_lock(); + + si = find_swapdev_info(resume_dev, &prev, &type); + if (si) { + si->flags &= ~SWP_WRITEOK; + found = 1; + } + swap_list_unlock(); + return found; + +} + +/** + * swsusp_swpoff - Turn off swap and set signature for swsusp image + * + */ +int swsusp_swap_off(dev_t resume_dev) +{ + struct swap_info_struct * si = NULL; + unsigned short *swap_map; + int i; + int type, prev; + struct block_device *bdev; + int err = 0; + + swap_list_lock(); + + si = find_swapdev_info(resume_dev, &prev, &type); + + /* swap area for swsusp image is not writable */ + if ((!si) || (si->flags & SWP_WRITEOK)) { + err = -EINVAL; + swap_list_unlock(); + goto out; + } + + if (!security_vm_enough_memory(si->pages)) { + vm_unacct_memory(si->pages); + } else { + err = -ENOMEM; + si->flags |= SWP_WRITEOK; + swap_list_unlock(); + goto out; + } + + pr_debug("swsusp_swapoff:inuse_pages:%ld\n", si->inuse_pages); + pr_debug("swsusp_swapoff:pages:%d\n", si->pages); + if (prev < 0) { + swap_list.head = si->next; + } else { + swap_info[prev].next = si->next; + } + if (type == swap_list.next) { + /* just pick something that's safe... */ + swap_list.next = swap_list.head; + } + nr_swap_pages -= si->pages; + total_swap_pages -= si->pages; + bdev = I_BDEV(si->swap_file->f_mapping->host); + swap_list_unlock(); + + current->flags |= PF_SWAPOFF; + err = try_to_unuse(type); + current->flags &= ~PF_SWAPOFF; + + /* wait for any unplug function to finish */ + down_write(&swap_unplug_sem); + up_write(&swap_unplug_sem); + + if (err) { + pr_debug("swsusp_swapoff:err:%d\n", err); + /* re-insert swap space back into swap_list */ + swap_list_lock(); + for (prev = -1, i = swap_list.head; i >= 0; prev = i, i = swap_info[i].next) + if (si->prio >= swap_info[i].prio) + break; + si->next = i; + if (prev < 0) + swap_list.head = swap_list.next = si - swap_info; + else + swap_info[prev].next = si - swap_info; + nr_swap_pages += si->pages; + total_swap_pages += si->pages; + si->flags |= SWP_WRITEOK; + swap_list_unlock(); + goto out; + } + + down(&swapon_sem); + swap_list_lock(); + drain_mmlist(); + swap_device_lock(si); + si->swap_file = NULL; + si->max = 0; + swap_map = si->swap_map; + si->swap_map = NULL; + si->flags = 0; + destroy_swap_extents(si); + swap_device_unlock(si); + swap_list_unlock(); + up(&swapon_sem); + vfree(swap_map); + + /* set SWSUSP signature, again */ + mark_swsusp(resume_dev); + + /* release device */ + set_blocksize(bdev, si->old_block_size); + bd_release(bdev); + err = 0; + +out: + return err; +} +#endif /*CONFIG_PRESERVE_SWSUSP_IMAGE*/ + asmlinkage long sys_swapoff(const char __user * specialfile) { struct swap_info_struct * p = NULL;