Support for writing an image to swap. Unlike swsusp - and, I think, uswsusp - this code supports multiple swap devices, striped swap and compcache (it ignores compcache storage). Any combination of swap partitions and swap files can be used. Signed-off-by: Nigel Cunningham <nigel@xxxxxxxxxxxx> --- kernel/power/Kconfig | 8 + kernel/power/Makefile | 1 + kernel/power/tuxonice_swap.c | 1319 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1328 insertions(+), 0 deletions(-) create mode 100644 kernel/power/tuxonice_swap.c diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index 51d57d4..d8e62b6 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -201,6 +201,14 @@ menuconfig TOI_CORE simple file. This should be possible, but we're still testing it. + config TOI_SWAP + bool "Swap Allocator" + depends on TOI_CORE && SWAP + default y + ---help--- + This option enables support for storing an image in your + swap space. + comment "General Options" depends on TOI_CORE diff --git a/kernel/power/Makefile b/kernel/power/Makefile index 41daada..9f6d06f 100644 --- a/kernel/power/Makefile +++ b/kernel/power/Makefile @@ -17,6 +17,7 @@ endif obj-$(CONFIG_TOI_CORE) += tuxonice_core.o obj-$(CONFIG_TOI_CRYPTO) += tuxonice_compress.o +obj-$(CONFIG_TOI_SWAP) += tuxonice_block_io.o tuxonice_swap.o obj-$(CONFIG_TOI_FILE) += tuxonice_block_io.o tuxonice_file.o obj-$(CONFIG_PM) += main.o diff --git a/kernel/power/tuxonice_swap.c b/kernel/power/tuxonice_swap.c new file mode 100644 index 0000000..420edec --- /dev/null +++ b/kernel/power/tuxonice_swap.c @@ -0,0 +1,1319 @@ +/* + * kernel/power/tuxonice_swap.c + * + * Copyright (C) 2004-2008 Nigel Cunningham (nigel at tuxonice net) + * + * Distributed under GPLv2. + * + * This file encapsulates functions for usage of swap space as a + * backing store. + */ + +#include <linux/suspend.h> +#include <linux/blkdev.h> +#include <linux/swapops.h> +#include <linux/swap.h> +#include <linux/syscalls.h> + +#include "tuxonice.h" +#include "tuxonice_sysfs.h" +#include "tuxonice_modules.h" +#include "tuxonice_io.h" +#include "tuxonice_ui.h" +#include "tuxonice_extent.h" +#include "tuxonice_block_io.h" +#include "tuxonice_alloc.h" +#include "tuxonice_builtin.h" + +static struct toi_module_ops toi_swapops; + +/* --- Struct of pages stored on disk */ + +struct sig_data { + dev_t device; + unsigned long sector; + int resume_attempted; + int orig_sig_type; +}; + +union diskpage { + union swap_header swh; /* swh.magic is the only member used */ + struct sig_data sig_data; +}; + +union p_diskpage { + union diskpage *pointer; + char *ptr; + unsigned long address; +}; + +enum { + IMAGE_SIGNATURE, + NO_IMAGE_SIGNATURE, + TRIED_RESUME, + NO_TRIED_RESUME, +}; + +/* + * Both of these point to versions of the swap header page. original_sig points + * to the data we read from disk at the start of hibernating or checking whether + * to resume. no_image is the page stored in the image header, showing what the + * swap header page looked like at the start of hibernating. + */ +static char *current_signature_page; +static char no_image_signature_contents[sizeof(struct sig_data)]; + +/* Devices used for swap */ +static struct toi_bdev_info devinfo[MAX_SWAPFILES]; + +/* Extent chains for swap & blocks */ +static struct hibernate_extent_chain swapextents; +static struct hibernate_extent_chain block_chain[MAX_SWAPFILES]; + +static dev_t header_dev_t; +static struct block_device *header_block_device; +static unsigned long headerblock; + +/* For swapfile automatically swapon/off'd. */ +static char swapfilename[32] = ""; +static int toi_swapon_status; + +/* Header Page Information */ +static long header_pages_reserved; + +/* Swap Pages */ +static long swap_pages_allocated; + +/* User Specified Parameters. */ + +static unsigned long resume_firstblock; +static dev_t resume_swap_dev_t; +static struct block_device *resume_block_device; + +static struct sysinfo swapinfo; + +/* Block devices open. */ +struct bdev_opened { + dev_t device; + struct block_device *bdev; +}; + +/* + * Entry MAX_SWAPFILES is the resume block device, which may + * be a swap device not enabled when we hibernate. + * Entry MAX_SWAPFILES + 1 is the header block device, which + * is needed before we find out which slot it occupies. + * + * We use a separate struct to devInfo so that we can track + * the bdevs we open, because if we need to abort resuming + * prior to the atomic restore, they need to be closed, but + * closing them after sucessfully resuming would be wrong. + */ +static struct bdev_opened *bdevs_opened[MAX_SWAPFILES + 2]; + +/** + * close_bdev: Close a swap bdev. + * + * int: The swap entry number to close. + */ +static void close_bdev(int i) +{ + struct bdev_opened *this = bdevs_opened[i]; + + if (!this) + return; + + blkdev_put(this->bdev, FMODE_READ | FMODE_NDELAY); + toi_kfree(8, this, sizeof(*this)); + bdevs_opened[i] = NULL; +} + +/** + * close_bdevs: Close all bdevs we opened. + * + * Close all bdevs that we opened and reset the related vars. + */ +static void close_bdevs(void) +{ + int i; + + for (i = 0; i < MAX_SWAPFILES + 2; i++) + close_bdev(i); + + resume_block_device = NULL; + header_block_device = NULL; +} + +/** + * open_bdev: Open a bdev at resume time. + * + * index: The swap index. May be MAX_SWAPFILES for the resume_dev_t + * (the user can have resume= pointing at a swap partition/file that isn't + * swapon'd when they hibernate. MAX_SWAPFILES+1 for the first page of the + * header. It will be from a swap partition that was enabled when we hibernated, + * but we don't know it's real index until we read that first page. + * dev_t: The device major/minor. + * display_errs: Whether to try to do this quietly. + * + * We stored a dev_t in the image header. Open the matching device without + * requiring /dev/<whatever> in most cases and record the details needed + * to close it later and avoid duplicating work. + */ +static struct block_device *open_bdev(int index, dev_t device, int display_errs) +{ + struct bdev_opened *this; + struct block_device *bdev; + + if (bdevs_opened[index]) { + if (bdevs_opened[index]->device == device) + return bdevs_opened[index]->bdev; + + close_bdev(index); + } + + bdev = toi_open_by_devnum(device, FMODE_READ | FMODE_NDELAY); + + if (IS_ERR(bdev) || !bdev) { + if (display_errs) + toi_early_boot_message(1, TOI_CONTINUE_REQ, + "Failed to get access to block device " + "\"%x\" (error %d).\n Maybe you need " + "to run mknod and/or lvmsetup in an " + "initrd/ramfs?", device, bdev); + return ERR_PTR(-EINVAL); + } + + this = toi_kzalloc(8, sizeof(struct bdev_opened), GFP_KERNEL); + if (!this) { + printk(KERN_WARNING "TuxOnIce: Failed to allocate memory for " + "opening a bdev."); + blkdev_put(bdev, FMODE_READ | FMODE_NDELAY); + return ERR_PTR(-ENOMEM); + } + + bdevs_opened[index] = this; + this->device = device; + this->bdev = bdev; + + return bdev; +} + +/** + * enable_swapfile: Swapon the user specified swapfile prior to hibernating. + * + * Activate the given swapfile if it wasn't already enabled. Remember whether + * we really did swapon it for swapoffing later. + */ +static void enable_swapfile(void) +{ + int activateswapresult = -EINVAL; + + if (swapfilename[0]) { + /* Attempt to swap on with maximum priority */ + activateswapresult = sys_swapon(swapfilename, 0xFFFF); + if (activateswapresult && activateswapresult != -EBUSY) + printk("TuxOnIce: The swapfile/partition specified by " + "/sys/power/tuxonice/swap/swapfile " + "(%s) could not be turned on (error %d). " + "Attempting to continue.\n", + swapfilename, activateswapresult); + if (!activateswapresult) + toi_swapon_status = 1; + } +} + +/** + * disable_swapfile: Swapoff any file swaponed at the start of the cycle. + * + * If we did successfully swapon a file at the start of the cycle, swapoff + * it now (finishing up). + */ +static void disable_swapfile(void) +{ + if (!toi_swapon_status) + return; + + sys_swapoff(swapfilename); + toi_swapon_status = 0; +} + +/** + * try_to_parse_resume_device: Try to parse resume= + * + * Any "swap:" has been stripped away and we just have the path to deal with. + * We attempt to do name_to_dev_t, open and stat the file. Having opened the + * file, get the struct block_device * to match. + */ +static int try_to_parse_resume_device(char *commandline, int quiet) +{ + struct kstat stat; + int error = 0; + + wait_for_device_probe(); + resume_swap_dev_t = name_to_dev_t(commandline); + + if (!resume_swap_dev_t) { + struct file *file = filp_open(commandline, + O_RDONLY|O_LARGEFILE, 0); + + if (!IS_ERR(file) && file) { + vfs_getattr(file->f_vfsmnt, file->f_dentry, &stat); + filp_close(file, NULL); + } else + error = vfs_stat(commandline, &stat); + if (!error) + resume_swap_dev_t = stat.rdev; + } + + if (!resume_swap_dev_t) { + if (quiet) + return 1; + + if (test_toi_state(TOI_TRYING_TO_RESUME)) + toi_early_boot_message(1, TOI_CONTINUE_REQ, + "Failed to translate \"%s\" into a device id.\n", + commandline); + else + printk("TuxOnIce: Can't translate \"%s\" into a device " + "id yet.\n", commandline); + return 1; + } + + resume_block_device = open_bdev(MAX_SWAPFILES, resume_swap_dev_t, 0); + if (IS_ERR(resume_block_device)) { + if (!quiet) + toi_early_boot_message(1, TOI_CONTINUE_REQ, + "Failed to get access to \"%s\", where" + " the swap header should be found.", + commandline); + return 1; + } + + return 0; +} + +/* + * If we have read part of the image, we might have filled memory with + * data that should be zeroed out. + */ +static void toi_swap_noresume_reset(void) +{ + toi_bio_ops.rw_cleanup(READ); + memset((char *) &devinfo, 0, sizeof(devinfo)); +} + +static int get_current_signature(void) +{ + if (!current_signature_page) { + current_signature_page = (char *) toi_get_zeroed_page(38, + TOI_ATOMIC_GFP); + if (!current_signature_page) + return -ENOMEM; + } + + return toi_bio_ops.bdev_page_io(READ, resume_block_device, + resume_firstblock, virt_to_page(current_signature_page)); +} + +static int parse_signature(void) +{ + union p_diskpage swap_header_page; + struct sig_data *sig; + int type; + char *swap_header; + const char *sigs[] = { + "SWAP-SPACE", "SWAPSPACE2", "S1SUSP", "S2SUSP", "S1SUSPEND" + }; + + int result = get_current_signature(); + if (result) + return result; + + swap_header_page = (union p_diskpage) current_signature_page; + sig = (struct sig_data *) current_signature_page; + swap_header = swap_header_page.pointer->swh.magic.magic; + + for (type = 0; type < 5; type++) + if (!memcmp(sigs[type], swap_header, strlen(sigs[type]))) + return type; + + if (memcmp(tuxonice_signature, swap_header, sizeof(tuxonice_signature))) + return -1; + + header_dev_t = sig->device; + clear_toi_state(TOI_RESUMED_BEFORE); + if (sig->resume_attempted) + set_toi_state(TOI_RESUMED_BEFORE); + headerblock = sig->sector; + + return 10; +} + +static void forget_signatures(void) +{ + if (current_signature_page) { + toi_free_page(38, (unsigned long) current_signature_page); + current_signature_page = NULL; + } +} + +/* + * write_modified_signature + * + * Write a (potentially) modified signature page without forgetting the + * original contents. + */ +static int write_modified_signature(int modification) +{ + union p_diskpage swap_header_page; + struct swap_info_struct *si; + int result; + char *orig_sig; + + /* In case we haven't already */ + result = get_current_signature(); + + if (result) + return result; + + swap_header_page.address = toi_get_zeroed_page(38, TOI_ATOMIC_GFP); + + if (!swap_header_page.address) + return -ENOMEM; + + memcpy(swap_header_page.ptr, current_signature_page, PAGE_SIZE); + + switch (modification) { + case IMAGE_SIGNATURE: + + memcpy(no_image_signature_contents, swap_header_page.ptr, + sizeof(no_image_signature_contents)); + + /* Get the details of the header first page. */ + toi_extent_state_goto_start(&toi_writer_posn); + toi_bio_ops.forward_one_page(1, 1); + + si = get_swap_info_struct(toi_writer_posn.current_chain); + + /* Prepare the signature */ + swap_header_page.pointer->sig_data.device = si->bdev->bd_dev; + swap_header_page.pointer->sig_data.sector = + toi_writer_posn.current_offset; + swap_header_page.pointer->sig_data.resume_attempted = 0; + swap_header_page.pointer->sig_data.orig_sig_type = + parse_signature(); + + memcpy(swap_header_page.pointer->swh.magic.magic, + tuxonice_signature, sizeof(tuxonice_signature)); + + break; + case NO_IMAGE_SIGNATURE: + if (!swap_header_page.pointer->sig_data.orig_sig_type) + orig_sig = "SWAP-SPACE"; + else + orig_sig = "SWAPSPACE2"; + + memcpy(swap_header_page.pointer->swh.magic.magic, orig_sig, 10); + memcpy(swap_header_page.ptr, no_image_signature_contents, + sizeof(no_image_signature_contents)); + break; + case TRIED_RESUME: + swap_header_page.pointer->sig_data.resume_attempted = 1; + break; + case NO_TRIED_RESUME: + swap_header_page.pointer->sig_data.resume_attempted = 0; + break; + } + + result = toi_bio_ops.bdev_page_io(WRITE, resume_block_device, + resume_firstblock, virt_to_page(swap_header_page.address)); + + memcpy(current_signature_page, swap_header_page.ptr, PAGE_SIZE); + + toi_free_page(38, swap_header_page.address); + + return result; +} + +/* + * apply_header_reservation + */ +static int apply_header_reservation(void) +{ + int i; + + toi_extent_state_goto_start(&toi_writer_posn); + + for (i = 0; i < header_pages_reserved; i++) + if (toi_bio_ops.forward_one_page(1, 0)) + return -ENOSPC; + + /* The end of header pages will be the start of pageset 2; + * we are now sitting on the first pageset2 page. */ + toi_extent_state_save(&toi_writer_posn, &toi_writer_posn_save[2]); + return 0; +} + +static void toi_swap_reserve_header_space(int request) +{ + header_pages_reserved = (long) request; +} + +static void free_block_chains(void) +{ + int i; + + for (i = 0; i < MAX_SWAPFILES; i++) + if (block_chain[i].first) + toi_put_extent_chain(&block_chain[i]); +} + +static int add_blocks_to_extent_chain(int chain, int start, int end) +{ + if (test_action_state(TOI_TEST_BIO)) + printk(KERN_INFO "Adding extent chain %d %d-%d.\n", chain, + start << devinfo[chain].bmap_shift, + end << devinfo[chain].bmap_shift); + + if (toi_add_to_extent_chain(&block_chain[chain], start, end)) { + free_block_chains(); + return -ENOMEM; + } + + return 0; +} + + +static int get_main_pool_phys_params(void) +{ + struct hibernate_extent *extentpointer = NULL; + unsigned long address; + int extent_min = -1, extent_max = -1, last_chain = -1; + + free_block_chains(); + + toi_extent_for_each(&swapextents, extentpointer, address) { + swp_entry_t swap_address = (swp_entry_t) { address }; + pgoff_t offset = swp_offset(swap_address); + unsigned swapfilenum = swp_type(swap_address); + struct swap_info_struct *sis = + get_swap_info_struct(swapfilenum); + sector_t new_sector = map_swap_page(sis, offset); + + if (devinfo[swapfilenum].ignored) + continue; + + if ((new_sector == extent_max + 1) && + (last_chain == swapfilenum)) { + extent_max++; + continue; + } + + if (extent_min > -1 && add_blocks_to_extent_chain(last_chain, + extent_min, extent_max)) { + printk(KERN_ERR "Out of memory while making block " + "chains.\n"); + return -ENOMEM; + } + + extent_min = new_sector; + extent_max = new_sector; + last_chain = swapfilenum; + } + + if (extent_min > -1 && add_blocks_to_extent_chain(last_chain, + extent_min, extent_max)) { + printk(KERN_ERR "Out of memory while making block chains.\n"); + return -ENOMEM; + } + + return apply_header_reservation(); +} + +static long raw_to_real(long raw) +{ + long result; + + result = raw - (raw * (sizeof(unsigned long) + sizeof(int)) + + (PAGE_SIZE + sizeof(unsigned long) + sizeof(int) + 1)) / + (PAGE_SIZE + sizeof(unsigned long) + sizeof(int)); + + return result < 0 ? 0 : result; +} + +static int toi_swap_storage_allocated(void) +{ + return (int) raw_to_real(swap_pages_allocated - header_pages_reserved); +} + +/* + * Like si_swapinfo, except that we don't include ram backed swap (compcache!) + * and don't need to use the spinlocks (userspace is stopped when this + * function is called). + */ +void si_swapinfo_no_compcache(struct sysinfo *val) +{ + unsigned int i; + + si_swapinfo(&swapinfo); + val->freeswap = 0; + val->totalswap = 0; + + for (i = 0; i < MAX_SWAPFILES; i++) { + struct swap_info_struct *si = get_swap_info_struct(i); + if ((si->flags & SWP_USED) && si->swap_map && + (si->flags & SWP_WRITEOK) && + (strncmp(si->bdev->bd_disk->disk_name, "ram", 3))) { + val->totalswap += si->inuse_pages; + val->freeswap += si->pages - si->inuse_pages; + } + } +} +/* + * We can't just remember the value from allocation time, because other + * processes might have allocated swap in the mean time. + */ +static int toi_swap_storage_available(void) +{ + si_swapinfo_no_compcache(&swapinfo); + return (int) raw_to_real((long) swapinfo.freeswap + + swap_pages_allocated - header_pages_reserved); +} + +static int toi_swap_initialise(int starting_cycle) +{ + int result = 0; + + if (!starting_cycle) + return 0; + + enable_swapfile(); + + if (resume_swap_dev_t && !resume_block_device) { + resume_block_device = open_bdev(MAX_SWAPFILES, + resume_swap_dev_t, 1); + if (IS_ERR(resume_block_device)) + result = 1; + } + + return result; +} + +static void toi_swap_cleanup(int ending_cycle) +{ + if (ending_cycle) + disable_swapfile(); + + close_bdevs(); + + forget_signatures(); +} + +static int toi_swap_release_storage(void) +{ + header_pages_reserved = 0; + swap_pages_allocated = 0; + + if (swapextents.first) { + /* Free swap entries */ + struct hibernate_extent *extentpointer; + unsigned long extentvalue; + toi_extent_for_each(&swapextents, extentpointer, + extentvalue) + swap_free((swp_entry_t) { extentvalue }); + + toi_put_extent_chain(&swapextents); + + free_block_chains(); + } + + return 0; +} + +static void free_swap_range(unsigned long min, unsigned long max) +{ + int j; + + for (j = min; j <= max; j++) + swap_free((swp_entry_t) { j }); +} + +/* + * Round robin allocation (where swap storage has the same priority). + * could make this very inefficient, so we track extents allocated on + * a per-swapfile basis. + */ +static int toi_swap_allocate_storage(int request) +{ + int i, result = 0, to_add[MAX_SWAPFILES], pages_to_get, extra_pages, + gotten = 0, result2; + unsigned long extent_min[MAX_SWAPFILES], extent_max[MAX_SWAPFILES]; + + extra_pages = DIV_ROUND_UP(request * (sizeof(unsigned long) + + sizeof(int)), PAGE_SIZE); + pages_to_get = request + extra_pages - swapextents.size + + header_pages_reserved; + + if (pages_to_get < 1) + return apply_header_reservation(); + + for (i = 0; i < MAX_SWAPFILES; i++) { + struct swap_info_struct *si = get_swap_info_struct(i); + to_add[i] = 0; + if (!(si->flags & SWP_USED) || !si->swap_map || + !(si->flags & SWP_WRITEOK)) + continue; + if (!strncmp(si->bdev->bd_disk->disk_name, "ram", 3)) { + devinfo[i].ignored = 1; + continue; + } + devinfo[i].ignored = 0; + devinfo[i].bdev = si->bdev; + devinfo[i].dev_t = si->bdev->bd_dev; + devinfo[i].bmap_shift = 3; + devinfo[i].blocks_per_page = 1; + } + + while (gotten < pages_to_get) { + swp_entry_t entry; + unsigned long new_value; + unsigned swapfilenum; + + entry = get_swap_page(); + if (!entry.val) + break; + + swapfilenum = swp_type(entry); + new_value = entry.val; + + if (!to_add[swapfilenum]) { + to_add[swapfilenum] = 1; + extent_min[swapfilenum] = new_value; + extent_max[swapfilenum] = new_value; + if (!devinfo[swapfilenum].ignored) + gotten++; + continue; + } + + if (new_value == extent_max[swapfilenum] + 1) { + extent_max[swapfilenum]++; + if (!devinfo[swapfilenum].ignored) + gotten++; + continue; + } + + if (toi_add_to_extent_chain(&swapextents, + extent_min[swapfilenum], + extent_max[swapfilenum])) { + printk(KERN_INFO "Failed to allocate extent for " + "%lu-%lu.\n", extent_min[swapfilenum], + extent_max[swapfilenum]); + free_swap_range(extent_min[swapfilenum], + extent_max[swapfilenum]); + swap_free(entry); + if (!devinfo[swapfilenum].ignored) + gotten -= (extent_max[swapfilenum] - + extent_min[swapfilenum] + 1); + /* Don't try to add again below */ + to_add[swapfilenum] = 0; + break; + } else { + extent_min[swapfilenum] = new_value; + extent_max[swapfilenum] = new_value; + if (!devinfo[swapfilenum].ignored) + gotten++; + } + } + + for (i = 0; i < MAX_SWAPFILES; i++) { + int this_result; + + /* Anything to do for this swap entry? */ + if (!to_add[i]) + continue; + + this_result = toi_add_to_extent_chain(&swapextents, + extent_min[i], extent_max[i]); + + /* Added okay? */ + if (!this_result) + continue; + + /* + * Nope. Remember an error occured, free the swap and subtract + * from the amount of swap allocated. + */ + result = this_result; + + free_swap_range(extent_min[i], extent_max[i]); + if (!devinfo[i].ignored) + gotten -= (extent_max[i] - extent_min[i] + 1); + } + + if (gotten < pages_to_get) { + printk("Got fewer pages than required " + "(%d wanted, %d gotten).\n", + pages_to_get, gotten); + result = -ENOSPC; + } + + swap_pages_allocated += (long) gotten; + + result2 = get_main_pool_phys_params(); + + return result ? result : result2; +} + +static int toi_swap_write_header_init(void) +{ + int i, result; + struct swap_info_struct *si; + + toi_bio_ops.rw_init(WRITE, 0); + toi_writer_buffer_posn = 0; + + /* Info needed to bootstrap goes at the start of the header. + * First we save the positions and devinfo, including the number + * of header pages. Then we save the structs containing data needed + * for reading the header pages back. + * Note that even if header pages take more than one page, when we + * read back the info, we will have restored the location of the + * next header page by the time we go to use it. + */ + + result = toi_bio_ops.rw_header_chunk(WRITE, &toi_swapops, + (char *) &no_image_signature_contents, + sizeof(struct sig_data)); + + if (result) + return result; + + /* Forward one page will be done prior to the read */ + for (i = 0; i < MAX_SWAPFILES; i++) { + si = get_swap_info_struct(i); + if (si->swap_file) + devinfo[i].dev_t = si->bdev->bd_dev; + else + devinfo[i].dev_t = (dev_t) 0; + } + + result = toi_bio_ops.rw_header_chunk(WRITE, &toi_swapops, + (char *) &toi_writer_posn_save, + sizeof(toi_writer_posn_save)); + + if (result) + return result; + + result = toi_bio_ops.rw_header_chunk(WRITE, &toi_swapops, + (char *) &devinfo, sizeof(devinfo)); + + if (result) + return result; + + for (i = 0; i < MAX_SWAPFILES; i++) + toi_serialise_extent_chain(&toi_swapops, &block_chain[i]); + + return 0; +} + +static int toi_swap_write_header_cleanup(void) +{ + int result = toi_bio_ops.write_header_chunk_finish(); + + /* Set signature to save we have an image */ + if (!result) + result = write_modified_signature(IMAGE_SIGNATURE); + + return result; +} + +/* ------------------------- HEADER READING ------------------------- */ + +/* + * read_header_init() + * + * Description: + * 1. Attempt to read the device specified with resume=. + * 2. Check the contents of the swap header for our signature. + * 3. Warn, ignore, reset and/or continue as appropriate. + * 4. If continuing, read the toi_swap configuration section + * of the header and set up block device info so we can read + * the rest of the header & image. + * + * Returns: + * May not return if user choose to reboot at a warning. + * -EINVAL if cannot resume at this time. Booting should continue + * normally. + */ + +static int toi_swap_read_header_init(void) +{ + int i, result = 0; + toi_writer_buffer_posn = 0; + + if (!header_dev_t) { + printk(KERN_INFO "read_header_init called when we haven't " + "verified there is an image!\n"); + return -EINVAL; + } + + /* + * If the header is not on the resume_swap_dev_t, get the resume device + * first. + */ + if (header_dev_t != resume_swap_dev_t) { + header_block_device = open_bdev(MAX_SWAPFILES + 1, + header_dev_t, 1); + + if (IS_ERR(header_block_device)) + return PTR_ERR(header_block_device); + } else + header_block_device = resume_block_device; + + toi_bio_ops.read_header_init(); + + /* + * Read toi_swap configuration. + * Headerblock size taken into account already. + */ + result = toi_bio_ops.bdev_page_io(READ, header_block_device, + headerblock << 3, + virt_to_page((unsigned long) toi_writer_buffer)); + if (result) + return result; + + memcpy(&no_image_signature_contents, toi_writer_buffer, + sizeof(no_image_signature_contents)); + + toi_writer_buffer_posn = sizeof(no_image_signature_contents); + + memcpy(&toi_writer_posn_save, toi_writer_buffer + + toi_writer_buffer_posn, sizeof(toi_writer_posn_save)); + + toi_writer_buffer_posn += sizeof(toi_writer_posn_save); + + memcpy(&devinfo, toi_writer_buffer + toi_writer_buffer_posn, + sizeof(devinfo)); + + toi_writer_buffer_posn += sizeof(devinfo); + + /* Restore device info */ + for (i = 0; i < MAX_SWAPFILES; i++) { + dev_t thisdevice = devinfo[i].dev_t; + struct block_device *bdev_result; + + devinfo[i].bdev = NULL; + + if (!thisdevice || devinfo[i].ignored) + continue; + + if (thisdevice == resume_swap_dev_t) { + devinfo[i].bdev = resume_block_device; + continue; + } + + if (thisdevice == header_dev_t) { + devinfo[i].bdev = header_block_device; + continue; + } + + bdev_result = open_bdev(i, thisdevice, 1); + if (IS_ERR(bdev_result)) + return PTR_ERR(bdev_result); + devinfo[i].bdev = bdevs_opened[i]->bdev; + } + + toi_extent_state_goto_start(&toi_writer_posn); + toi_bio_ops.set_extra_page_forward(); + + for (i = 0; i < MAX_SWAPFILES && !result; i++) + result = toi_load_extent_chain(&block_chain[i]); + + return result; +} + +static int toi_swap_read_header_cleanup(void) +{ + toi_bio_ops.rw_cleanup(READ); + return 0; +} + +/* + * workspace_size + * + * Description: + * Returns the number of bytes of RAM needed for this + * code to do its work. (Used when calculating whether + * we have enough memory to be able to hibernate & resume). + * + */ +static int toi_swap_memory_needed(void) +{ + return 1; +} + +/* + * Print debug info + * + * Description: + */ +static int toi_swap_print_debug_stats(char *buffer, int size) +{ + int len = 0; + struct sysinfo sysinfo; + + if (toiActiveAllocator != &toi_swapops) { + len = scnprintf(buffer, size, + "- SwapAllocator inactive.\n"); + return len; + } + + len = scnprintf(buffer, size, "- SwapAllocator active.\n"); + if (swapfilename[0]) + len += scnprintf(buffer+len, size-len, + " Attempting to automatically swapon: %s.\n", + swapfilename); + + si_swapinfo_no_compcache(&sysinfo); + + len += scnprintf(buffer+len, size-len, + " Swap available for image: %d pages.\n", + (int) sysinfo.freeswap + toi_swap_storage_allocated()); + + return len; +} + +/* + * Storage needed + * + * Returns amount of space in the swap header required + * for the toi_swap's data. This ignores the links between + * pages, which we factor in when allocating the space. + * + * We ensure the space is allocated, but actually save the + * data from write_header_init and therefore don't also define a + * save_config_info routine. + */ +static int toi_swap_storage_needed(void) +{ + int i, result; + result = sizeof(struct sig_data) + sizeof(toi_writer_posn_save) + + sizeof(devinfo); + + for (i = 0; i < MAX_SWAPFILES; i++) { + result += 2 * sizeof(int); + result += (2 * sizeof(unsigned long) * + block_chain[i].num_extents); + } + + return result; +} + +/* + * Image_exists + * + * Returns -1 if don't know, otherwise 0 (no) or 1 (yes). + */ +static int toi_swap_image_exists(int quiet) +{ + int signature_found; + + if (!resume_swap_dev_t) { + if (!quiet) + printk(KERN_INFO "Not even trying to read header " + "because resume_swap_dev_t is not set.\n"); + return -1; + } + + if (!resume_block_device) { + resume_block_device = open_bdev(MAX_SWAPFILES, + resume_swap_dev_t, 1); + if (IS_ERR(resume_block_device)) { + if (!quiet) + printk(KERN_INFO "Failed to open resume dev_t" + " (%x).\n", resume_swap_dev_t); + return -1; + } + } + + signature_found = parse_signature(); + + switch (signature_found) { + case -ENOMEM: + return -1; + case -1: + if (!quiet) + printk(KERN_ERR "TuxOnIce: Unable to find a signature." + " Could you have moved a swap file?\n"); + return -1; + case 0: + case 1: + if (!quiet) + printk(KERN_INFO "TuxOnIce: Normal swapspace found.\n"); + return 0; + case 2: + case 3: + case 4: + if (!quiet) + printk(KERN_INFO "TuxOnIce: Detected another " + "implementation's signature.\n"); + return 0; + case 10: + if (!quiet) + printk(KERN_INFO "TuxOnIce: Detected TuxOnIce binary " + "signature.\n"); + return 1; + } + + printk("Unrecognised parse_signature result (%d).\n", signature_found); + return 0; +} + +/* toi_swap_remove_image + * + */ +static int toi_swap_remove_image(void) +{ + /* + * If nr_hibernates == 0, we must be booting, so no swap pages + * will be recorded as used yet. + */ + + if (nr_hibernates) + toi_swap_release_storage(); + + /* + * We don't do a sanity check here: we want to restore the swap + * whatever version of kernel made the hibernate image. + * + * We need to write swap, but swap may not be enabled so + * we write the device directly + * + * If we don't have an current_signature_page, we didn't + * read an image header, so don't change anything. + */ + + return toi_swap_image_exists(1) ? + write_modified_signature(NO_IMAGE_SIGNATURE) : 0; +} + +/* + * Mark resume attempted. + * + * Record that we tried to resume from this image. We have already read the + * signature in. We just need to write the modified version. + */ +static int toi_swap_mark_resume_attempted(int mark) +{ + if (!resume_swap_dev_t) { + printk(KERN_INFO "Not even trying to record attempt at resuming" + " because resume_swap_dev_t is not set.\n"); + return -ENODEV; + } + + return write_modified_signature(mark ? TRIED_RESUME : NO_TRIED_RESUME); +} + +/* + * Parse Image Location + * + * Attempt to parse a resume= parameter. + * Swap Writer accepts: + * resume=swap:DEVNAME[:FIRSTBLOCK][@BLOCKSIZE] + * + * Where: + * DEVNAME is convertable to a dev_t by name_to_dev_t + * FIRSTBLOCK is the location of the first block in the swap file + * (specifying for a swap partition is nonsensical but not prohibited). + * Data is validated by attempting to read a swap header from the + * location given. Failure will result in toi_swap refusing to + * save an image, and a reboot with correct parameters will be + * necessary. + */ +static int toi_swap_parse_sig_location(char *commandline, + int only_allocator, int quiet) +{ + char *thischar, *devstart, *colon = NULL; + int signature_found, result = -EINVAL, temp_result = 0; + + if (strncmp(commandline, "swap:", 5)) { + /* + * Failing swap:, we'll take a simple + * resume=/dev/hda2, but fall through to + * other allocators if /dev/ isn't matched. + */ + if (strncmp(commandline, "/dev/", 5)) + return 1; + } else + commandline += 5; + + devstart = commandline; + thischar = commandline; + while ((*thischar != ':') && (*thischar != '@') && + ((thischar - commandline) < 250) && (*thischar)) + thischar++; + + if (*thischar == ':') { + colon = thischar; + *colon = 0; + thischar++; + } + + while ((thischar - commandline) < 250 && *thischar) + thischar++; + + if (colon) { + unsigned long block; + temp_result = strict_strtoul(colon + 1, 0, &block); + if (!temp_result) + resume_firstblock = (int) block; + } else + resume_firstblock = 0; + + clear_toi_state(TOI_CAN_HIBERNATE); + clear_toi_state(TOI_CAN_RESUME); + + if (!temp_result) + temp_result = try_to_parse_resume_device(devstart, quiet); + + if (colon) + *colon = ':'; + + if (temp_result) + return -EINVAL; + + signature_found = toi_swap_image_exists(quiet); + + if (signature_found != -1) { + result = 0; + + toi_bio_ops.set_devinfo(devinfo); + toi_writer_posn.chains = &block_chain[0]; + toi_writer_posn.num_chains = MAX_SWAPFILES; + set_toi_state(TOI_CAN_HIBERNATE); + set_toi_state(TOI_CAN_RESUME); + } else + if (!quiet) + printk(KERN_ERR "TuxOnIce: SwapAllocator: No swap " + "signature found at %s.\n", devstart); + return result; +} + +static int header_locations_read_sysfs(const char *page, int count) +{ + int i, printedpartitionsmessage = 0, len = 0, haveswap = 0; + struct inode *swapf = NULL; + int zone; + char *path_page = (char *) toi_get_free_page(10, GFP_KERNEL); + char *path, *output = (char *) page; + int path_len; + + if (!page) + return 0; + + for (i = 0; i < MAX_SWAPFILES; i++) { + struct swap_info_struct *si = get_swap_info_struct(i); + + if (!si->swap_file) + continue; + + if (S_ISBLK(si->swap_file->f_mapping->host->i_mode)) { + haveswap = 1; + if (!printedpartitionsmessage) { + len += sprintf(output + len, + "For swap partitions, simply use the " + "format: resume=swap:/dev/hda1.\n"); + printedpartitionsmessage = 1; + } + } else { + path_len = 0; + + path = d_path(&si->swap_file->f_path, path_page, + PAGE_SIZE); + path_len = snprintf(path_page, 31, "%s", path); + + haveswap = 1; + swapf = si->swap_file->f_mapping->host; + zone = bmap(swapf, 0); + if (!zone) { + len += sprintf(output + len, + "Swapfile %s has been corrupted. Reuse" + " mkswap on it and try again.\n", + path_page); + } else { + char name_buffer[255]; + len += sprintf(output + len, + "For swapfile `%s`," + " use resume=swap:/dev/%s:0x%x.\n", + path_page, + bdevname(si->bdev, name_buffer), + zone << (swapf->i_blkbits - 9)); + } + } + } + + if (!haveswap) + len = sprintf(output, "You need to turn on swap partitions " + "before examining this file.\n"); + + toi_free_page(10, (unsigned long) path_page); + return len; +} + +static struct toi_sysfs_data sysfs_params[] = { + SYSFS_STRING("swapfilename", SYSFS_RW, swapfilename, 255, 0, NULL), + SYSFS_CUSTOM("headerlocations", SYSFS_READONLY, + header_locations_read_sysfs, NULL, 0, NULL), + SYSFS_INT("enabled", SYSFS_RW, &toi_swapops.enabled, 0, 1, 0, + attempt_to_parse_resume_device2), +}; + +static struct toi_module_ops toi_swapops = { + .type = WRITER_MODULE, + .name = "swap storage", + .directory = "swap", + .module = THIS_MODULE, + .memory_needed = toi_swap_memory_needed, + .print_debug_info = toi_swap_print_debug_stats, + .storage_needed = toi_swap_storage_needed, + .initialise = toi_swap_initialise, + .cleanup = toi_swap_cleanup, + + .noresume_reset = toi_swap_noresume_reset, + .storage_available = toi_swap_storage_available, + .storage_allocated = toi_swap_storage_allocated, + .reserve_header_space = toi_swap_reserve_header_space, + .allocate_storage = toi_swap_allocate_storage, + .image_exists = toi_swap_image_exists, + .mark_resume_attempted = toi_swap_mark_resume_attempted, + .write_header_init = toi_swap_write_header_init, + .write_header_cleanup = toi_swap_write_header_cleanup, + .read_header_init = toi_swap_read_header_init, + .read_header_cleanup = toi_swap_read_header_cleanup, + .remove_image = toi_swap_remove_image, + .parse_sig_location = toi_swap_parse_sig_location, + + .sysfs_data = sysfs_params, + .num_sysfs_entries = sizeof(sysfs_params) / + sizeof(struct toi_sysfs_data), +}; + +/* ---- Registration ---- */ +static __init int toi_swap_load(void) +{ + toi_swapops.rw_init = toi_bio_ops.rw_init; + toi_swapops.rw_cleanup = toi_bio_ops.rw_cleanup; + toi_swapops.read_page = toi_bio_ops.read_page; + toi_swapops.write_page = toi_bio_ops.write_page; + toi_swapops.rw_header_chunk = toi_bio_ops.rw_header_chunk; + toi_swapops.rw_header_chunk_noreadahead = + toi_bio_ops.rw_header_chunk_noreadahead; + toi_swapops.io_flusher = toi_bio_ops.io_flusher; + toi_swapops.update_throughput_throttle = + toi_bio_ops.update_throughput_throttle; + toi_swapops.finish_all_io = toi_bio_ops.finish_all_io; + + return toi_register_module(&toi_swapops); +} + +late_initcall(toi_swap_load); -- 1.5.6.3 _______________________________________________ linux-pm mailing list linux-pm@xxxxxxxxxxxxxxxxxxxxxxxxxx https://lists.linux-foundation.org/mailman/listinfo/linux-pm