From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S261513AbUKXReW (ORCPT ); Wed, 24 Nov 2004 12:34:22 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S262739AbUKXRYg (ORCPT ); Wed, 24 Nov 2004 12:24:36 -0500 Received: from pop5-1.us4.outblaze.com ([205.158.62.125]:15765 "HELO pop5-1.us4.outblaze.com") by vger.kernel.org with SMTP id S262767AbUKXRGZ (ORCPT ); Wed, 24 Nov 2004 12:06:25 -0500 Subject: Suspend 2 merge: 48/51: Swapwriter From: Nigel Cunningham Reply-To: ncunningham@linuxmail.org To: Linux Kernel Mailing List In-Reply-To: <1101292194.5805.180.camel@desktop.cunninghams> References: <1101292194.5805.180.camel@desktop.cunninghams> Content-Type: text/plain Message-Id: <1101300479.5805.389.camel@desktop.cunninghams> Mime-Version: 1.0 X-Mailer: Ximian Evolution 1.4.6-1mdk Date: Thu, 25 Nov 2004 00:02:26 +1100 Content-Transfer-Encoding: 7bit Sender: linux-kernel-owner@vger.kernel.org X-Mailing-List: linux-kernel@vger.kernel.org This is the swapwriter. It is forms the glue between the highlevel I/O routines in io.c and the blockwriter routines in block_io.c. It is responsible for allocating storage, translating the requests for pages within pagesets into devices and blocks and the like. It is abstracted from the block writer because the plan is that we'll eventually have a generic file writer (ie not using swapspace, but a simple file, possibly on NFS). The swapwriter can automatically swapon and swapoff swapspace for you. Handy if you have a swapdevice you only want to be used for suspending. It understands the swsusp signature so we can play nicely with swsusp (note that the Makefile order is important here: swsusp will balk at our signature, so we need to look first). diff -ruN 854-swapwriter-old/kernel/power/suspend_swap.c 854-swapwriter-new/kernel/power/suspend_swap.c --- 854-swapwriter-old/kernel/power/suspend_swap.c 1970-01-01 10:00:00.000000000 +1000 +++ 854-swapwriter-new/kernel/power/suspend_swap.c 2004-11-17 16:38:59.000000000 +1100 @@ -0,0 +1,1967 @@ +/* + * Swapwriter.c + * + * Copyright 2004 Nigel Cunningham + * + * Distributed under GPLv2. + * + * This file encapsulates functions for usage of swap space as a + * backing store. + */ + +#include +#include +#include +#include + +#include "suspend.h" +#include "block_io.h" +#include "proc.h" +#include "plugins.h" + +#define SIGNATURE_VER 6 + +/* --- Struct of pages stored on disk */ + +static struct suspend_plugin_ops swapwriterops; + +struct swaplink { + char dummy[PAGE_SIZE - sizeof(swp_entry_t)]; + swp_entry_t next; +}; + +union diskpage { + union swap_header swh; /* swh.magic is the only member used */ + struct swaplink link; + struct suspend_header sh; +}; + +union p_diskpage { + union diskpage *pointer; + char *ptr; + unsigned long address; +}; + +#define SIGNATURE_LENGTH 10 + +// - Manage swap signature. +static int prepare_signature(struct submit_params * first_header_page, + char * current_header); +static int parse_signature(char * signature, int restore); + +// Higher Level +static int readahead_index = 0, readahead_submit_index = 0; +static int readahead_allocs = 0, readahead_frees = 0; + +static char * swapwriter_buffer = NULL; +static int swapwriter_buffer_posn = 0; +static int swapwriter_page_index = 0; +static unsigned long * header_link = NULL; +#define BYTES_PER_HEADER_PAGE (PAGE_SIZE - sizeof(swp_entry_t)) + +/* + * --------------------------------------------------------------- + * + * Internal Data Structures + * + * --------------------------------------------------------------- + */ + +/* header_data contains data that is needed to reload pagedir1, and + * is therefore saved in the suspend header. + * + * Pagedir2 swap comes before pagedir1 swap (save order), and the first swap + * entry for pagedir1 to use is set when pagedir2 is written (when we know how + * much swap it used). Since this first entry is almost certainly not at the + * start of a range, the firstoffset variable below tells us where to start in + * the range. All of this means we don't have to worry about getting different + * compression ratios for the kernel and cache (when compressing the image). + * We can simply allocate one pool of swap (size determined using expected + * compression ratio) and use it without worrying whether one pageset + * compresses better and the other worse (this is what happens). As long as the + * user gets the expected compression right, it will work. + */ + +struct { + /* Range chains for swap & blocks */ + struct rangechain swapranges; + struct rangechain block_chain[MAX_SWAPFILES]; + + /* Location of start of pagedir 1 */ + struct range * pd1start_block_range; + unsigned long pd1start_block_offset; + int pd1start_chain; + + /* Devices used for swap */ + dev_t swapdevs[MAX_SWAPFILES]; + char blocksizes[MAX_SWAPFILES]; + + /* Asynchronous I/O limit */ + int max_async_ios; +} header_data; + + +dev_t header_device = 0; +struct block_device * header_block_device = NULL; +struct range * this_range_page = NULL, * next_range_page = NULL; +int headerblocksize = PAGE_SIZE; +int headerblock; + +/* For swapfile automatically swapon/off'd. */ +static char swapfilename[256] = ""; +extern asmlinkage long sys_swapon(const char * specialfile, int swap_flags); + +int suspend_swapon_status = 0; + +/* Must be silent - might be called from cat /proc/suspend/debug_info + * Returns 0 if was off, -EBUSY if was on, error value otherwise. + */ +static int enable_swapfile(void) +{ + int activateswapresult = -EINVAL; + + if (suspend_swapon_status) + return 0; + + if (swapfilename[0]) { + /* Attempt to swap on with maximum priority */ + activateswapresult = sys_swapon(swapfilename, 0xFFFF); + if ((activateswapresult) && (activateswapresult != -EBUSY)) + printk(name_suspend + "The swapfile/partition specified by " + "/proc/suspend/swapfile (%s) could not" + " be turned on (error %d). Attempting " + "to continue.\n", + swapfilename, activateswapresult); + if (!activateswapresult) + suspend_swapon_status = 1; + } + return activateswapresult; +} + +extern asmlinkage long sys_swapoff(const char * specialfile); +/* Returns 0 if was on, -EINVAL if was off, error value otherwise */ +static int disable_swapfile(void) +{ + int result = -EINVAL; + + if (!suspend_swapon_status) + return 0; + + if (swapfilename[0]) { + result = sys_swapoff(swapfilename); + if (result == -EINVAL) + return 0; /* Wasn't on */ + if (!result) + suspend_swapon_status = 0; + } + + return result; +} + +static int manage_swapfile(int enable) +{ + static int result; + mm_segment_t oldfs; + + oldfs = get_fs(); set_fs(KERNEL_DS); + if (enable) + result = enable_swapfile(); + else + result = disable_swapfile(); + set_fs(oldfs); + + return result; +} + +/* + * --------------------------------------------------------------- + * + * Current state. + * + * --------------------------------------------------------------- + */ + +/* Which pagedir are we saving/reloading? Needed so we can know whether to + * remember the last swap entry used at the end of writing pageset2, and + * get that location when saving or reloading pageset1.*/ +static int current_stream = 0; + +/* Pointer to current swap entry being loaded/saved. */ +static struct range * currentblockrange = NULL; +static unsigned long currentblockoffset = 0; +static int currentblockchain = 0; +static int currentblocksperpage = 0; + +/* Header Page Information */ +static int header_pages_allocated = 0; +static struct submit_params * first_header_submit_info = NULL, + * last_header_submit_info = NULL, * current_header_submit_info = NULL; + +/* + * --------------------------------------------------------------- + * + * User Specified Parameters + * + * --------------------------------------------------------------- + */ + +static int resume_firstblock = 0; +static int resume_firstblocksize = PAGE_SIZE; +static dev_t resume_device = 0; +static struct block_device * resume_block_device = NULL; + +/* + * --------------------------------------------------------------- + * + * Disk I/O routines + * + * --------------------------------------------------------------- + */ +extern char swapfilename[]; + +extern int expected_compression; + +struct sysinfo swapinfo; + +#define MARK_SWAP_SUSPEND 0 +#define MARK_SWAP_RESUME 1 + +static int swapwriter_invalidate_image(void); + +static int get_phys_params(swp_entry_t entry) +{ + int swapfilenum = swp_type(entry); + unsigned long offset = swp_offset(entry); + struct swap_info_struct * sis = get_swap_info_struct(swapfilenum); + sector_t sector = map_swap_page(sis, offset); + + add_to_range_chain(&header_data.block_chain[swapfilenum], sector); + return 1; +} + +static int get_header_params(struct submit_params * headerpage) +{ + swp_entry_t entry = headerpage->swap_address; + int swapfilenum = swp_type(entry); + unsigned long offset = swp_offset(entry); + struct swap_info_struct * sis = get_swap_info_struct(swapfilenum); + sector_t sector = map_swap_page(sis, offset); + + headerpage->dev = sis->bdev, + headerpage->blocks[0] = sector; + headerpage->blocks_used = 1; + headerpage->readahead_index = -1; + return 0; +} + +static inline int get_blocks_per_page(int chain) +{ + return 1; +#if 0 + int result = PAGE_SIZE / + suspend_bio_ops.get_block_size(swap_info[chain].bdev); + printk("Block size for chain %d is %d,\n", + chain, result); + return result; +#endif +} + +static int try_to_parse_resume_device(char * commandline) +{ + resume_device = name_to_dev_t(commandline); + if (!resume_device) { + if (test_suspend_state(SUSPEND_BOOT_TIME)) + suspend_early_boot_message(1, "Failed to translate the device name into a device id.\n"); + else + printk(name_suspend "Failed to translate \"%s\" into a device id.\n", commandline); + return 1; + } + + resume_block_device = open_by_devnum(resume_device, FMODE_READ); + + if (IS_ERR(resume_block_device)) { + printk("Open by devnum returned %p given %x.\n", resume_block_device, resume_device); + if (test_suspend_state(SUSPEND_BOOT_TIME)) + suspend_early_boot_message(1, "Failed to get access to the device on which Software Suspend's header should be found."); + else + printk("Failed to get access to the device on which Software Suspend's header should be found.\n"); + return 1; + } + + return 0; +} + +static int try_to_parse_header_device(void) +{ + header_block_device = open_by_devnum(header_device, FMODE_READ); + + if (IS_ERR(header_block_device)) { + if (suspend_early_boot_message(1, + "Failed to get access to the " + "resume header device.\nYou could be " + "booting with a 2.6 kernel when you " + "suspended a 2.4 kernel.")) + swapwriter_invalidate_image(); + + return -EINVAL; + } + + if (set_blocksize(header_block_device, PAGE_SIZE) < 0) { + if (suspend_early_boot_message(1, "Failed to set the blocksize" + "for a swap device.")) + do { } while(0); + swapwriter_invalidate_image(); + return -EINVAL; + } + + return 0; +} + +static void open_other_swap_device(int i, dev_t thisdevice) +{ + swap_info[i].bdev = open_by_devnum(thisdevice, FMODE_READ); + set_blocksize(swap_info[i].bdev, PAGE_SIZE); +} + +static inline char * get_path_for_swapfile(int which, char * path_page) +{ + return d_path( swap_info[which].swap_file->f_dentry, + swap_info[which].swap_file->f_vfsmnt, + path_page, + PAGE_SIZE); +} + +static void swapwriter_noresume_reset(void) +{ + int i; + + /* + * If we have read part of the image, we might have filled header_data with + * data that should be zeroed out. + */ + + memset((char *) &header_data, 0, sizeof(header_data)); + for (i = 0; i < MAX_SWAPFILES; i++) { + swap_info[i].bdev = NULL; + } + +} + +static void swapwriter_dpm_set_devices(void) +{ + int i; + + /* Set our device(s) as remaining on. */ + for (i = 0; i < MAX_SWAPFILES; i++) { + if (!swap_info[i].bdev) + continue; + + device_switch_trees((swap_info[i].bdev)->bd_disk->driverfs_dev, + suspend_device_tree); + } +} + +/* + * + */ + +int parse_signature(char * header, int restore) +{ + int type = -1; + + if (!memcmp("SWAP-SPACE",header,10)) + return 0; + else if (!memcmp("SWAPSPACE2",header,10)) + return 1; + + else if (!memcmp("pmdisk", header,6)) + type = 2; + + else if (!memcmp("S1SUSP",header,6)) + type = 4; + else if (!memcmp("S2SUSP",header,6)) + type = 5; + + else if (!memcmp("1R",header,2)) + type = 6; + else if (!memcmp("2R",header,2)) + type = 7; + + else if (!memcmp("std",header,3)) + type = 8; + else if (!memcmp("STD",header,3)) + type = 9; + + else if (!memcmp("sd",header,2)) + type = 10; + else if (!memcmp("SD",header,2)) + type = 11; + + else if (!memcmp("z",header,1)) + type = 12; + else if (!memcmp("Z",header,1)) + type = 13; + + /* + * Put bdev of suspend header in last byte of swap header + * (unsigned short) + */ + if (type > 11) { + dev_t * header_ptr = (dev_t *) &header[1]; + unsigned char * headerblocksize_ptr = + (unsigned char *) &header[5]; + unsigned long * headerblock_ptr = (unsigned long *) &header[6]; + header_device = *header_ptr; + /* + * We are now using the highest bit of the char to indicate + * whether we have attempted to resume from this image before. + */ + clear_suspend_state(SUSPEND_RESUMED_BEFORE); + if (((int) *headerblocksize_ptr) & 0x80) + set_suspend_state(SUSPEND_RESUMED_BEFORE); + headerblocksize = 512 * (((int) *headerblocksize_ptr) & 0xf); + headerblock = *headerblock_ptr; + } + + if ((restore) && (type > 5)) { + /* We only reset our own signatures */ + if (type & 1) + memcpy(header,"SWAPSPACE2",10); + else + memcpy(header,"SWAP-SPACE",10); + } + + return type; +} + +/* + * prepare_signature + */ + +static int prepare_signature(struct submit_params * header_page_info, + char * current_header) +{ + int current_type = parse_signature(current_header, 0); + dev_t * header_ptr = (dev_t *) (¤t_header[1]); + unsigned char * headerblocksize_ptr = + (unsigned char *) (¤t_header[5]); + unsigned long * headerblock_ptr = + (unsigned long *) (¤t_header[6]); + + if ((current_type > 1) && (current_type < 6)) + return 1; + + if (current_type & 1) + current_header[0] = 'Z'; + else + current_header[0] = 'z'; + *header_ptr = header_page_info->dev->bd_dev; + *headerblocksize_ptr = + (unsigned char) (PAGE_SIZE / 512 / + header_page_info->blocks_used); + /* prev is the first/last swap page of the resume area */ + *headerblock_ptr = (unsigned long) header_page_info->blocks[0]; + suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 1, + "Saving header block size of %ld (%ld 512 " + "byte blocks per page).\n", + PAGE_SIZE / header_page_info->blocks_used, + PAGE_SIZE / 512 / header_page_info->blocks_used); + return 0; +} + +extern int signature_check(char * header, int fix); + +static int free_swap_pages_for_header(void) +{ + if (!first_header_submit_info) + return 1; + + PRINTFREEMEM("at start of free_swap_pages_for_header"); + + while (first_header_submit_info) { + struct submit_params * next = first_header_submit_info->next; + if (first_header_submit_info->swap_address.val) + swap_free(first_header_submit_info->swap_address); + kfree(first_header_submit_info); + first_header_submit_info = next; + } + + suspend_message(SUSPEND_WRITER, SUSPEND_LOW, 1, + " Freed %d swap pages in free_swap_pages_for_header.\n", + header_pages_allocated); + first_header_submit_info = last_header_submit_info = NULL; + header_pages_allocated = 0; + PRINTFREEMEM("at end of free_swap_pages_for_header"); + suspend_store_free_mem(SUSPEND_FREE_HEADER_STORAGE, 1); + return 0; +} + +static void get_main_pool_phys_params(void) +{ + struct range * rangepointer = NULL; + unsigned long address; + int i; + + for (i = 0; i < MAX_SWAPFILES; i++) + if (header_data.block_chain[i].first) + put_range_chain(&header_data.block_chain[i]); + + range_for_each(&header_data.swapranges, rangepointer, address) + get_phys_params(range_val_to_swap_entry(address)); +} + +extern void put_range(struct range * range); + +static unsigned long swapwriter_storage_allocated(void) +{ + return (header_data.swapranges.size + header_pages_allocated); +} + +static long swapwriter_storage_available(void) +{ + si_swapinfo(&swapinfo); + return (swapinfo.freeswap + (long) swapwriter_storage_allocated()); +} + +static int swapwriter_initialise(void) +{ + manage_swapfile(1); + return 0; +} + +static void swapwriter_cleanup(void) +{ + manage_swapfile(0); +} + +static int swapwriter_release_storage(void) +{ + int i = 0, swapcount = 0; + +#ifdef CONFIG_SOFTWARE_SUSPEND_KEEP_IMAGE + if ((TEST_ACTION_STATE(SUSPEND_KEEP_IMAGE)) && test_suspend_state(SUSPEND_NOW_RESUMING)) + return 0; +#endif + + free_swap_pages_for_header(); + + if (header_data.swapranges.first) { + /* Free swap entries */ + struct range * rangepointer; + unsigned long rangevalue; + swp_entry_t entry; + range_for_each(&header_data.swapranges, rangepointer, + rangevalue) { + entry = range_val_to_swap_entry(rangevalue); + swap_free(entry); + + swapcount++; + check_shift_keys(0, NULL); + } + put_range_chain(&header_data.swapranges); + + for (i = 0; i < MAX_SWAPFILES; i++) + if (header_data.block_chain[i].first) + put_range_chain(&header_data.block_chain[i]); + } + + suspend_message(SUSPEND_WRITER, SUSPEND_MEDIUM, 0, + "Freed %d swap pages in free_swap.\n", swapcount); + + return 0; +} + +static long swapwriter_allocate_header_space(unsigned long space_really_requested) +{ + /* space_requested was going to be in bytes... not yet */ + int i, ret = 0; + unsigned long space_requested; + + /* + * Up to here in the process, we haven't taken account of the fact + * that we need an extra four bytes per 4092 bytes written for link + * to the next page on which the header will be written. We factor + * that in here. + */ + space_requested = ((4096 * space_really_requested + 4091) / 4092); + space_requested = (space_requested * 4 + 4091) / 4092; + space_requested += space_really_requested; + + PRINTFREEMEM("at start of allocate_header_space"); + + for (i=(header_pages_allocated+1); i<=space_requested; i++) { + struct submit_params * new_submit_param; + + /* Get a submit structure */ + new_submit_param = kmalloc(sizeof(struct submit_params), GFP_ATOMIC); + + if (!new_submit_param) { + header_pages_allocated = i - 1; + printk("Failed to kmalloc a struct submit param.\n"); + ret = -ENOMEM; + goto out; + } + + memset(new_submit_param, 0, sizeof(struct submit_params)); + + if (last_header_submit_info) { + last_header_submit_info->next = new_submit_param; + last_header_submit_info = new_submit_param; + } else + last_header_submit_info = first_header_submit_info = + new_submit_param; + + /* Get swap entry */ + new_submit_param->swap_address = get_swap_page(); + + if ((!new_submit_param->swap_address.val) && + (header_data.swapranges.first)) { + /* + * Steal one from pageset swap chain. If, as a result, + * it is too small, more swap will be allocated or + * memory eaten. + */ + + new_submit_param->swap_address = + range_val_to_swap_entry( + header_data.swapranges.first->minimum); + if (header_data.swapranges.first->minimum < + header_data.swapranges.first->maximum) + header_data.swapranges.first->minimum++; + else { + struct range * oldfirst = + header_data.swapranges.first; + header_data.swapranges.first = oldfirst->next; + header_data.swapranges.frees++; + header_data.swapranges.prevtoprev = + header_data.swapranges.prevtolastaccessed = + header_data.swapranges.lastaccessed = NULL; + if (header_data.swapranges.last == oldfirst) + header_data.swapranges.last = NULL; + put_range(oldfirst); + } + + header_data.swapranges.size--; + + /* + * Recalculate block chains for main pool. + * We don't assume blocks are at start of a chain and + * don't know how many blocks per swap entry. + */ + get_main_pool_phys_params(); + } + if (!new_submit_param->swap_address.val) { + free_swap_pages_for_header(); + printk("Unable to allocate swap page for header.\n"); + ret = -ENOMEM; + goto out; + } + if (get_header_params(new_submit_param)) { + printk("Failed to get header parameters.\n"); + ret = -EFAULT; + goto out; + } + suspend_message(SUSPEND_HEADER, SUSPEND_MEDIUM, 0, + " Got header page %d/%d. Dev is %x. Block is %lu. " + "Blocksperpage is %d.\n", + i, space_requested, + new_submit_param->dev, + new_submit_param->blocks[0], + new_submit_param->blocks_used); + } + header_pages_allocated = space_requested; + suspend_message(SUSPEND_HEADER, SUSPEND_LOW, 1, + " Have %d swap pages in swapwriter::" + "allocate_header_space.\n", + header_pages_allocated); +out: + PRINTFREEMEM("at end of swapwriter::allocate_header_space"); + suspend_store_free_mem(SUSPEND_FREE_HEADER_STORAGE, 0); + return ret; +} + +static int swapwriter_allocate_storage(unsigned long space_requested) +{ + int i, swapcount = 0, result = 0; + int lastsize = header_data.swapranges.size; + int numwanted = (int) (space_requested); + int pages_to_get = numwanted - header_data.swapranges.size; + + if (numwanted < 1) + return 0; + + suspend_message(SUSPEND_WRITER, SUSPEND_MEDIUM, 0, + "Started with swapranges.size == %d. " + "Seeking to allocate %d more.\n", + header_data.swapranges.size, + pages_to_get); + + for(i=0; i < pages_to_get; i++) { + swp_entry_t entry; + suspend_message(SUSPEND_WRITER, SUSPEND_VERBOSE, 1, ""); + entry = get_swap_page(); + if (!entry.val) { + suspend_message(SUSPEND_WRITER, SUSPEND_MEDIUM, 0, + "Allocated %d/%d swap pages for main pool " + "in allocate_swap.\n", + swapcount, numwanted); + printk("Unable to allocate enough swap." + " Got %d pages of %d wanted.\n", + i, pages_to_get); + result = -ENOSPC; + goto out; + } + swapcount++; + { + int result = + add_to_range_chain(&header_data.swapranges, + swap_entry_to_range_val(entry)); + if (result) + printk("add_to_range_chain returned %d.\n", + result); + } + if (header_data.swapranges.size != (lastsize + 1)) + printk("swapranges.size == %d.\n", + header_data.swapranges.size); + lastsize = header_data.swapranges.size; + check_shift_keys(0, NULL); + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) + break; + } + suspend_message(SUSPEND_WRITER, SUSPEND_MEDIUM, 0, + " Allocated %d/%d swap pages in allocate_swap.\n", + swapcount, numwanted); + + suspend_message(SUSPEND_WRITER, SUSPEND_MEDIUM, 0, + "Finished with swapranges.size == %d.\n", + header_data.swapranges.size); + +out: + get_main_pool_phys_params(); + + /* Any memory we allocate will be for range pages */ + suspend_store_free_mem(SUSPEND_FREE_RANGE_PAGES, 0); + return result; +} + +static int swapwriter_write_header_chunk(char * buffer, int buffer_size); +static int header_bytes_written; + +static int swapwriter_write_header_init(void) +{ + int i; + + header_bytes_written = 0; + + for (i = 0; i < MAX_SWAPFILES; i++) + if (swap_info[i].swap_file) { + header_data.swapdevs[i] = swap_info[i].bdev->bd_dev; + header_data.blocksizes[i] = + block_size(swap_info[i].bdev); + } + + header_data.max_async_ios = max_async_ios; + + swapwriter_buffer = (char *) get_zeroed_page(GFP_ATOMIC); + header_link = + (unsigned long *) (swapwriter_buffer + BYTES_PER_HEADER_PAGE); + swapwriter_page_index = 1; + + current_header_submit_info = first_header_submit_info; + + /* Info needed to bootstrap goes at the start of the header. + * First we save the 'header_data' struct, 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. + */ + swapwriter_write_header_chunk((char *) &header_data, + sizeof(header_data)); + + return 0; +} + +static int swapwriter_write_header_chunk(char * buffer, int buffer_size) +{ + int bytes_left = buffer_size; + + /* + * We buffer the writes until a page is full and to use the last + * sizeof(swp_entry_t) bytes for links between pages. This is + * totally transparent to the caller. + * + * Note also that buffer_size can be > PAGE_SIZE. + */ + + header_bytes_written += buffer_size; + + suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0, + "\nStart of write_header_chunk loop with %d bytes to store.\n", + buffer_size); + + while (bytes_left) { + char * source_start = buffer + buffer_size - bytes_left; + char * dest_start = swapwriter_buffer + swapwriter_buffer_posn; + int dest_capacity = BYTES_PER_HEADER_PAGE - swapwriter_buffer_posn; + swp_entry_t next_header_page; + if (bytes_left <= dest_capacity) { + suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0, + "Storing %d bytes from %p-%p in page %d, %p-%p.\n", + bytes_left, + source_start, source_start + bytes_left - 1, + swapwriter_page_index, + dest_start, dest_start + bytes_left - 1); + memcpy(dest_start, source_start, bytes_left); + swapwriter_buffer_posn += bytes_left; + return 0; + } + + /* A page is full */ + suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0, + "Storing %d bytes from %p-%p in page %d, %p-%p.\n", + dest_capacity, + source_start, source_start + dest_capacity - 1, + swapwriter_page_index, + dest_start, dest_start + dest_capacity - 1); + memcpy(dest_start, source_start, dest_capacity); + bytes_left -= dest_capacity; + + BUG_ON(!current_header_submit_info); + + if (!current_header_submit_info->next) { + suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0, + "This submit_info is the last one. Link zeroed.\n"); + *header_link = 0; + } else { + next_header_page = + swp_entry(swp_type( + current_header_submit_info->next->swap_address), + current_header_submit_info->next->blocks[0]); + + *header_link = next_header_page.val; + + suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0, + "Header link is at %p. " + "Contents set to swap device #%ld, block %ld.\n", + header_link, + (long) swp_type(next_header_page), + swp_offset(next_header_page)); + } + + suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0, + "Writing header page %d/%d. " + "Dev is %x. Block is %lu. Blocksperpage is %d.\n", + swapwriter_page_index, header_pages_allocated, + current_header_submit_info->dev->bd_dev, + current_header_submit_info->blocks[0], + current_header_submit_info->blocks_used); + + current_header_submit_info->page = + virt_to_page(swapwriter_buffer); + check_shift_keys(0, NULL); + suspend_bio_ops.submit_io(WRITE, current_header_submit_info, 0); + + swapwriter_buffer_posn = 0; + swapwriter_page_index++; + current_header_submit_info = current_header_submit_info->next; + } + + return 0; +} + +static int swapwriter_write_header_cleanup(void) +{ + /* Write any unsaved data */ + if (swapwriter_buffer_posn) { + *header_link = 0; + + suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0, + "Writing header page %d/%d. " + "Dev is %x. Block is %lu. Blocksperpage is %d.\n", + swapwriter_page_index, header_pages_allocated, + current_header_submit_info->dev->bd_dev, + current_header_submit_info->blocks[0], + current_header_submit_info->blocks_used); + + current_header_submit_info->page = + virt_to_page(swapwriter_buffer); + suspend_bio_ops.submit_io(WRITE, + current_header_submit_info, 0); + } + + /* Adjust swap header */ + suspend_bio_ops.bdev_page_io(READ, resume_block_device, resume_firstblock, + virt_to_page(swapwriter_buffer)); + + prepare_signature(first_header_submit_info, + ((union swap_header *) swapwriter_buffer)->magic.magic); + + suspend_bio_ops.bdev_page_io(WRITE, resume_block_device, resume_firstblock, + virt_to_page(swapwriter_buffer)); + + free_pages((unsigned long) swapwriter_buffer, 0); + swapwriter_buffer = NULL; + header_link = NULL; + + suspend_bio_ops.finish_all_io(); + + return 0; +} + +/* ------------------------- HEADER READING ------------------------- */ + +/* + * read_header_init() + * + * Description: + * 1. Attempt to read the device specified with resume2=. + * 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 swapwriter 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 swapwriter_read_header_init(void) +{ + int i; + + swapwriter_page_index = 1; + + swapwriter_buffer = (char *) get_zeroed_page(GFP_ATOMIC); + + if (!header_device) { + printk("read_header_init called when we haven't " + "verified there is an image!\n"); + return -EINVAL; + } + + /* + * If the header is not on the resume_device, get the resume device first. + */ + if (header_device != resume_device) { + int result = try_to_parse_header_device(); + + if (result) + return result; + } else + header_block_device = resume_block_device; + + /* Read swapwriter configuration */ + suspend_bio_ops.bdev_page_io(READ, header_block_device, headerblock, + virt_to_page((unsigned long) swapwriter_buffer)); + //FIXME Remember location of next page to be read. + + suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0, + "Retrieving %d bytes from %x:%x to page %d, %p-%p.\n", + header_block_device->bd_dev, headerblock, + sizeof(header_data), + swapwriter_page_index, + swapwriter_buffer, swapwriter_buffer + sizeof(header_data) - 1); + memcpy(&header_data, swapwriter_buffer, sizeof(header_data)); + + /* Restore device info */ + for (i = 0; i < MAX_SWAPFILES; i++) { + dev_t thisdevice = header_data.swapdevs[i]; + + suspend_message(SUSPEND_WRITER, SUSPEND_VERBOSE, 1, + "Swap device %d is %x.", i, thisdevice); + + if (!thisdevice) + continue; + + if (thisdevice == resume_device) { + suspend_message(SUSPEND_WRITER, SUSPEND_VERBOSE, 0, + "Resume root device %x", thisdevice); + swap_info[i].bdev = resume_block_device; + /* Mark as used so the device doesn't get suspended. */ + swap_info[i].swap_file = (struct file *) 0xffffff; + continue; + } + + if (thisdevice == header_device) { + suspend_message(SUSPEND_WRITER, SUSPEND_VERBOSE, 0, + "Resume header device %x", thisdevice); + swap_info[i].bdev = header_block_device; + /* Mark as used so the device doesn't get suspended. */ + swap_info[i].swap_file = (struct file *) 0xffffff; + continue; + } + + open_other_swap_device(i, thisdevice); + swap_info[i].swap_file = (struct file *) 0xffffff; + } + + max_async_ios = header_data.max_async_ios; + + swapwriter_buffer_posn = sizeof(header_data); + + return 0; +} + +static int swapwriter_read_header_chunk(char * buffer, int buffer_size) +{ + int bytes_left = buffer_size, ret = 0; + + check_shift_keys(0, ""); + + /* Read a chunk of the header */ + while ((bytes_left) && (!ret)) { + swp_entry_t next = + ((union p_diskpage) swapwriter_buffer).pointer->link.next; + struct block_device * dev = swap_info[swp_type(next)].bdev; + int pos = swp_offset(next); + char * dest_start = buffer + buffer_size - bytes_left; + char * source_start = + swapwriter_buffer + swapwriter_buffer_posn; + int source_capacity = + BYTES_PER_HEADER_PAGE - swapwriter_buffer_posn; + + if (bytes_left <= source_capacity) { + suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0, + "Retrieving %d bytes from page %d, " + "%p-%p into %p-%p.\n", + bytes_left, + swapwriter_page_index, + source_start, source_start + bytes_left - 1, + dest_start, dest_start + bytes_left - 1); + memcpy(dest_start, source_start, bytes_left); + swapwriter_buffer_posn += bytes_left; + return buffer_size; + } + + /* Next to read the next page */ + suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0, + "Retrieving %d bytes from page %d, %p-%p to %p-%p.\n", + source_capacity, + swapwriter_page_index, + source_start, source_start + source_capacity - 1, + dest_start, dest_start + source_capacity - 1); + memcpy(dest_start, source_start, source_capacity); + bytes_left -= source_capacity; + + suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0, + "Header link is at %p. Contents set to %lx = " + "swap device #%x, block %d.\n", + &((union p_diskpage) swapwriter_buffer).pointer->link.next, + ((union p_diskpage) swapwriter_buffer).pointer->link.next.val, + dev->bd_dev, pos); + + swapwriter_page_index++; + + suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0, + "Reading header page %d. Dev is %x. Block is %lu.\n", + swapwriter_page_index, dev->bd_dev, pos); + + suspend_bio_ops.bdev_page_io(READ, dev, pos, virt_to_page(swapwriter_buffer)); + + swapwriter_buffer_posn = 0; + } + + return buffer_size - bytes_left; +} + +static int swapwriter_read_header_cleanup(void) +{ + free_pages((unsigned long) swapwriter_buffer, 0); + return 0; +} + +static int swapwriter_prepare_save_ranges(void) +{ + int i; + + relativise_chain(&header_data.swapranges); + + for (i = 0; i < MAX_SWAPFILES; i++) + relativise_chain(&header_data.block_chain[i]); + + header_data.pd1start_block_range = + RANGE_RELATIVE(header_data.pd1start_block_range); + + suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0, + "Pagedir1 firstblockrange is %p.\n", + header_data.pd1start_block_range); + + return 0; +} + +static int swapwriter_post_load_ranges(void) +{ + int i; + + if (get_rangepages_list()) + return -ENOMEM; + + absolutise_chain(&header_data.swapranges); + + for (i = 0; i < MAX_SWAPFILES; i++) + absolutise_chain(&header_data.block_chain[i]); + + header_data.pd1start_block_range = + RANGE_ABSOLUTE(header_data.pd1start_block_range); + + return 0; +} + +static int swapwriter_write_init(int stream_number) +{ + if (stream_number == 1) { + currentblockrange = header_data.pd1start_block_range; + currentblockoffset = header_data.pd1start_block_offset; + currentblockchain = header_data.pd1start_chain; + } else + for (currentblockchain = 0; currentblockchain < MAX_SWAPFILES; + currentblockchain++) + if (header_data.block_chain[currentblockchain].first) { + currentblockrange = + header_data. + block_chain[currentblockchain].first; + currentblockoffset = currentblockrange->minimum; + break; + } + + BUG_ON(!currentblockrange); + + currentblocksperpage = PAGE_SIZE / + suspend_bio_ops.get_block_size(swap_info[currentblockchain].bdev); + + suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0, + "Stream %d beginning from position: chain %d. " + "range %p, block %ld.\n", + stream_number, + currentblockchain, currentblockrange, currentblockoffset); + + swapwriter_page_index = 1; + current_stream = stream_number; + + suspend_bio_ops.reset_io_stats(); + + return 0; +} + +static int swapwriter_write_chunk(struct page * buffer_page) +{ + int i; + struct submit_params submit_params; + + if (TEST_ACTION_STATE(SUSPEND_TEST_FILTER_SPEED)) + return 0; + + if (currentblockchain == MAX_SWAPFILES) { + printk("Error! We have run out of blocks for writing data.\n"); + for (i = 0; i < MAX_SWAPFILES; i++) { + if (!swap_info[i].swap_file) + printk("Swap slot %d is unused.\n", i); + else + printk("Swap slot %d is device %x.\n", + i, swap_info[i].bdev->bd_dev); + if (header_data.block_chain[i].size) + printk("Chain size for device %d is %d.\n", i, + header_data.block_chain[i].size); + } + return -ENOSPC; + } + + if (!currentblockrange) { + do { + currentblockchain++; + } while ((currentblockchain < MAX_SWAPFILES) && + (!header_data.block_chain[currentblockchain].first)); + + /* We can validly not have a new blockrange. We + * might be compressing data and the user was + * too optimistic in setting the compression + * ratio or we're just copying the pageset. */ + + if (currentblockchain == MAX_SWAPFILES) { + printk("Argh. Ran out of block chains.\n"); + return -ENOSPC; + } + + currentblockrange = + header_data.block_chain[currentblockchain].first; + currentblockoffset = currentblockrange->minimum; + currentblocksperpage = PAGE_SIZE / + suspend_bio_ops.get_block_size(swap_info[currentblockchain].bdev); + } + + submit_params.readahead_index = -1; + submit_params.page = buffer_page; + submit_params.dev = swap_info[currentblockchain].bdev; + submit_params.blocks_used = currentblocksperpage; + + /* Get the blocks */ + for (i = 0; i < currentblocksperpage; i++) { + submit_params.blocks[i] = currentblockoffset; + GET_RANGE_NEXT(currentblockrange, currentblockoffset); + } + + suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0, + "Writing page %d. Dev is %x. Block is %lu. " + "Blocksperpage is %d.\n", + swapwriter_page_index, + submit_params.dev, + submit_params.blocks[0], + currentblocksperpage); + + suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1, + "page:%d. bdev:%x. blocks (%d):", + swapwriter_page_index, + submit_params.dev->bd_dev, + submit_params.blocks_used); + + for (i = 0; i < currentblocksperpage; i++) + suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 0, + "0x%lx%s", + submit_params.blocks[i], + ((i+1) < currentblocksperpage) ? "," : "\n"); + + check_shift_keys(0, NULL); + + suspend_bio_ops.submit_io(WRITE, &submit_params, 0); + + swapwriter_page_index++; + + return 0; +} + +static int swapwriter_write_cleanup(void) +{ + if (current_stream == 2) { + header_data.pd1start_block_range = currentblockrange; + header_data.pd1start_block_offset = currentblockoffset; + header_data.pd1start_chain = currentblockchain; + } + + suspend_bio_ops.finish_all_io(); + + suspend_bio_ops.check_io_stats(); + + return 0; +} + +static int swapwriter_read_init(int stream_number) +{ + int i; + + if (stream_number == 1) { + currentblockrange = header_data.pd1start_block_range; + currentblockoffset = header_data.pd1start_block_offset; + currentblockchain = header_data.pd1start_chain; + } else { + currentblockrange = NULL; + currentblockoffset = 0; + currentblockchain = 0; + for (currentblockchain = 0; currentblockchain < MAX_SWAPFILES; + currentblockchain++) + if (header_data.block_chain[currentblockchain].first) { + currentblockrange = + header_data.block_chain[currentblockchain].first; + currentblockoffset = currentblockrange->minimum; + break; + } + + if (!currentblockrange){ + printk("Error! Can't find any block chain data.\n"); + for (i = 0; i < MAX_SWAPFILES; i++) { + if (!swap_info[i].swap_file) + printk("Swap slot %d is unused.\n", i); + else + printk("Swap slot %d is device %x.\n", + i, swap_info[i].bdev->bd_dev); + if (header_data.block_chain[i].size) + printk("Chain size for device %d" + " is %d.\n", i, + header_data.block_chain[i].size); + printk("First entry in chain at %p.\n", + header_data.block_chain[i].first); + } + BUG_ON(1); + } + } + suspend_message(SUSPEND_WRITER, SUSPEND_MEDIUM, 0, + "Stream %d beginning from position: chain %d. " + "range %p, block %ld.\n", + stream_number, + currentblockchain, currentblockrange, currentblockoffset); + + currentblocksperpage = get_blocks_per_page(currentblockchain); + + swapwriter_page_index = 1; + + suspend_bio_ops.reset_io_stats(); + + readahead_index = readahead_submit_index = -1; + readahead_allocs = readahead_frees = 0; + + return 0; +} + +static int swapwriter_begin_read_chunk(struct page * page, + int readahead_index, int sync) +{ + int i; + struct submit_params submit_params; + + if (currentblockchain == MAX_SWAPFILES) { + /* Readahead might ask us to read too many blocks */ + printk("Currentblockchain == MAX_SWAPFILES. Begin_read_chunk returning -ENODATA.\n"); + return -ENODATA; + } + + if (!currentblockrange) { + do { + currentblockchain++; + } while ((!header_data.block_chain[currentblockchain].first) && + (currentblockchain < MAX_SWAPFILES)); + + /* We can validly not have a new blockrange. We + * might have allocated exactly the right amount + * of swap for the image and be reading the last + * block now. + */ + + if (currentblockchain == MAX_SWAPFILES) { + prepare_status(1, 0, + "Currentblockchain == MAX_SWAPFILES and " + "more data to be read. " + "Begin_read_chunk returning -ENOSPC."); + return -ENOSPC; + } + + currentblockrange = + header_data.block_chain[currentblockchain].first; + currentblockoffset = currentblockrange->minimum; + currentblocksperpage = get_blocks_per_page(currentblockchain); + } + + submit_params.readahead_index = readahead_index; + submit_params.page = page; + submit_params.dev = swap_info[currentblockchain].bdev; + submit_params.blocks_used = currentblocksperpage; + + /* Get the blocks. There is no chance that they span chains. */ + for (i = 0; i < currentblocksperpage; i++) { + submit_params.blocks[i] = currentblockoffset; + GET_RANGE_NEXT(currentblockrange, currentblockoffset); + } + + suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0, + "Reading page %d. Dev is %x. Block is %lu. " + "Blocksperpage is %d. Page is %p(%lx). Readahead index is %d.", + swapwriter_page_index, + submit_params.dev, + submit_params.blocks[0], + currentblocksperpage, + page, page_address(page), + readahead_index); + + suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1, + "page:%d. bdev:%x. blocks (%d):", + swapwriter_page_index, + submit_params.dev->bd_dev, + submit_params.blocks_used); + + for (i = 0; i < currentblocksperpage; i++) + suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 0, + "0x%lx%s", + submit_params.blocks[i], + ((i+1) < currentblocksperpage) ? "," : "\n"); + + check_shift_keys(0, NULL); + + if ((i = suspend_bio_ops.submit_io(READ, &submit_params, sync))) + return -EPERM; + + swapwriter_page_index++; + + check_shift_keys(0, NULL); + + return 0; +} + +/* Note that we ignore the sync parameter. We are implementing + * read ahead, and will always wait until our readhead buffer has + * been read before returning. + */ + +static int swapwriter_read_chunk(struct page * buffer_page, int sync) +{ + static int last_result; + unsigned long * virt; + + suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0, + "At entrance to swapwriter_read_chunk.\n"); + + if (sync == SUSPEND_ASYNC) + return swapwriter_begin_read_chunk(buffer_page, -1, sync); + + /* Start new readahead while we wait for our page */ + if (readahead_index == -1) { + last_result = 0; + readahead_index = readahead_submit_index = 0; + } + + /* Start a new readahead? */ + if (last_result) { + /* We failed to submit a read, and have cleaned up + * all the readahead previously submitted */ + if (readahead_submit_index == readahead_index) + return -EPERM; + goto wait; + } + + do { + if ((test_suspend_state(SUSPEND_USE_MEMORY_POOL)) && (suspend_memory_pool_level(1) < 50)) + break; + + if (suspend_bio_ops.prepare_readahead(readahead_submit_index)) + break; + + readahead_allocs++; + + suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0, + "\nBeginning new readahead %d.\n", + readahead_submit_index); + + last_result = swapwriter_begin_read_chunk( + suspend_bio_ops.readahead_pages[readahead_submit_index], + readahead_submit_index, SUSPEND_ASYNC); + if (last_result) { + printk("Begin read chunk for page %d returned %d.\n", + readahead_submit_index, last_result); + suspend_bio_ops.cleanup_readahead(readahead_submit_index); + break; + } + + readahead_submit_index++; + + if (readahead_submit_index == max_async_ios) + readahead_submit_index = 0; + + } while((!last_result) && (readahead_submit_index != readahead_index) && + (!suspend_bio_ops.readahead_ready(readahead_index))); + +wait: + suspend_bio_ops.wait_on_readahead(readahead_index); + + virt = kmap_atomic(buffer_page, KM_USER1); + suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0, + "Returned result of readahead %d," + " Copying data from %p to %p.\n", readahead_index, + page_address(suspend_bio_ops.readahead_pages[readahead_index]), + virt); + + memcpy(virt, page_address(suspend_bio_ops.readahead_pages[readahead_index]), + PAGE_SIZE); + kunmap_atomic(virt, KM_USER1); + + suspend_bio_ops.cleanup_readahead(readahead_index); + + readahead_frees++; + + readahead_index++; + if (readahead_index == max_async_ios) + readahead_index = 0; + + return 0; +} + +static int swapwriter_read_cleanup(void) +{ + suspend_bio_ops.finish_all_io(); + while (readahead_index != readahead_submit_index) { + suspend_bio_ops.cleanup_readahead(readahead_index); + readahead_frees++; + readahead_index++; + if (readahead_index == max_async_ios) + readahead_index = 0; + } + suspend_bio_ops.check_io_stats(); + BUG_ON(readahead_allocs != readahead_frees); + return 0; +} + +extern unsigned int nr_suspends; + +/* swapwriter_invalidate_image + * + */ +static int swapwriter_invalidate_image(void) +{ + union p_diskpage cur; + int result = 0; + char newsig[11]; + + cur.address = get_zeroed_page(GFP_ATOMIC); + if (!cur.address) { + printk("Unable to allocate a page for restoring the swap signature.\n"); + return -ENOMEM; + } + + suspend_store_free_mem(SUSPEND_FREE_INVALIDATE_IMAGE, 0); + + /* + * If nr_suspends == 0, we must be booting, so no swap pages + * will be recorded as used yet. + */ + + if (nr_suspends > 0) + swapwriter_release_storage(); + + /* + * We don't do a sanity check here: we want to restore the swap + * whatever version of kernel made the suspend image. + * + * We need to write swap, but swap may not be enabled so + * we write the device directly + */ + + suspend_bio_ops.bdev_page_io(READ, resume_block_device, + resume_firstblock, virt_to_page(cur.pointer)); + + result = parse_signature(cur.pointer->swh.magic.magic, 1); + + if (result < 4) + goto out; + + strncpy(newsig, cur.pointer->swh.magic.magic, 10); + newsig[10] = 0; + suspend_message(SUSPEND_ANY_SECTION, SUSPEND_VERBOSE, 0, + "Swap signature will be set to %s.\n", newsig); + + suspend_bio_ops.bdev_page_io(WRITE, resume_block_device, resume_firstblock, + virt_to_page(cur.pointer)); + + if (!nr_suspends) + printk(KERN_WARNING name_suspend "Image invalidated.\n"); +out: + suspend_bio_ops.finish_all_io(); + free_pages(cur.address, 0); + suspend_store_free_mem(SUSPEND_FREE_INVALIDATE_IMAGE, 1); + 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 suspend & resume). + * + */ +static unsigned long swapwriter_memory_needed(void) +{ + return 1; +} + +/* Print debug info + * + * Description: + */ + +static int swapwriter_print_debug_stats(char * buffer, int size) +{ + int len = 0; + struct sysinfo sysinfo; + + if (active_writer != &swapwriterops) { + len = suspend_snprintf(buffer, size, "- Swapwriter inactive.\n"); + return len; + } + + len = suspend_snprintf(buffer, size, "- Swapwriter active.\n"); + if (swapfilename[0]) + len+= suspend_snprintf(buffer+len, size-len, + " Attempting to automatically swapon: %s.\n", swapfilename); + + si_swapinfo(&sysinfo); + + len+= suspend_snprintf(buffer+len, size-len, " Swap available for image: %ld pages.\n", + sysinfo.freeswap + swapwriter_storage_allocated()); + + return len; +} + +/* + * Storage needed + * + * Returns amount of space in the swap header required + * for the swapwriter'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 unsigned long swapwriter_storage_needed(void) +{ + return sizeof(header_data); +} + +/* + * Image_exists + * + */ + +static int swapwriter_image_exists(void) +{ + int signature_found; + union p_diskpage diskpage; + + if (!resume_device) { + printk("Not even trying to read header because resume_device is not set.\n"); + return 0; + } + + //PRINTFREEMEM("at start of swapwriter_image_exists."); + + diskpage.address = get_zeroed_page(GFP_ATOMIC); + + /* FIXME: Make sure bdev_page_io handles wrong parameters */ + suspend_bio_ops.bdev_page_io(READ, resume_block_device, resume_firstblock, virt_to_page(diskpage.ptr)); + suspend_bio_ops.finish_all_io(); + signature_found = parse_signature(diskpage.pointer->swh.magic.magic, 0); + + if (signature_found < 2) { + printk(KERN_ERR name_suspend "This is normal swap space.\n" ); + return 0; /* non fatal error */ + } else if (signature_found == -1) { + printk(KERN_ERR name_suspend "Unable to find a signature. Could you have moved a swap file?\n"); + return 0; + } else if (signature_found < 6) { + if ((!(test_suspend_state(SUSPEND_NORESUME_SPECIFIED))) + && suspend_early_boot_message(1, "Detected the signature of an alternate implementation.\n")) + set_suspend_state(SUSPEND_NORESUME_SPECIFIED); + return 0; + } else if ((signature_found >> 1) != SIGNATURE_VER) { + if ((!(test_suspend_state(SUSPEND_NORESUME_SPECIFIED))) && + suspend_early_boot_message(1, "Found a different style suspend image signature.")) + set_suspend_state(SUSPEND_NORESUME_SPECIFIED); + } + + return 1; +} + +/* + * Mark resume attempted. + * + * Record that we tried to resume from this image. + */ + +static void swapwriter_mark_resume_attempted(void) +{ + union p_diskpage diskpage; + int signature_found; + + if (!resume_device) { + printk("Not even trying to record attempt at resuming" + " because resume_device is not set.\n"); + return; + } + + diskpage.address = get_zeroed_page(GFP_ATOMIC); + + /* FIXME: Make sure bdev_page_io handles wrong parameters */ + suspend_bio_ops.bdev_page_io(READ, resume_block_device, resume_firstblock, virt_to_page(diskpage.ptr)); + signature_found = parse_signature(diskpage.pointer->swh.magic.magic, 0); + + switch (signature_found) { + case 12: + case 13: + diskpage.pointer->swh.magic.magic[5] |= 0x80; + break; + } + + suspend_bio_ops.bdev_page_io(WRITE, resume_block_device, resume_firstblock, + virt_to_page(diskpage.ptr)); + suspend_bio_ops.finish_all_io(); + free_pages(diskpage.address, 0); + return; +} + +/* + * Parse Image Location + * + * Attempt to parse a resume2= parameter. + * Swap Writer accepts: + * resume2=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). + * BLOCKSIZE is the logical blocksize >= 512 & <= PAGE_SIZE, + * mod 512 == 0 of the device. + * Data is validated by attempting to read a swap header from the + * location given. Failure will result in swapwriter refusing to + * save an image, and a reboot with correct parameters will be + * necessary. + */ + +static int swapwriter_parse_image_location(char * commandline, int only_writer) +{ + char *thischar, *devstart = NULL, *colon = NULL, *at_symbol = NULL; + union p_diskpage diskpage; + int signature_found; + + CLEAR_RESULT_STATE(SUSPEND_ABORTED); + + if (strncmp(commandline, "swap:", 5)) { + if (!only_writer) { + printk(name_suspend "Swapwriter: Image location doesn't begin with 'swap:'\n"); + return 1; + } + } else + commandline += 5; + + devstart = thischar = commandline; + while ((*thischar != ':') && ((thischar - commandline) < 250) && (*thischar)) + thischar++; + + if (*thischar == ':') { + colon = thischar; + *colon = 0; + thischar++; + } + + while ((*thischar != '@') && ((thischar - commandline) < 250) && (*thischar)) + thischar++; + + if (*thischar == '@') { + at_symbol = thischar; + *at_symbol = 0; + } + + if (colon) + resume_firstblock = (int) simple_strtoul(colon + 1, NULL, 0); + else + resume_firstblock = 0; + printk("Looking for first block of swap header at block %x.\n", resume_firstblock); + + if (at_symbol) { + resume_firstblocksize = (int) simple_strtoul(at_symbol + 1, NULL, 0); + if (resume_firstblocksize & 0x1FF) + printk("Blocksizes are usually a multiple of 512. Don't expect this to work!\n"); + } else + resume_firstblocksize = 4096; + printk("Setting logical block size of resume device to %d.\n", resume_firstblocksize); + + if (try_to_parse_resume_device(devstart)) + goto invalid; + + if (colon) + *colon = ':'; + if (at_symbol) + *at_symbol = '@'; + + if ((suspend_bio_ops.get_block_size(resume_block_device) + != resume_firstblocksize) && + (suspend_bio_ops.set_block_size(resume_block_device, resume_firstblocksize) + == -EINVAL)) + goto invalid; + + diskpage.address = get_zeroed_page(GFP_ATOMIC); + if (suspend_bio_ops.bdev_page_io(READ, resume_block_device, resume_firstblock, virt_to_page(diskpage.ptr))) { + printk(KERN_ERR name_suspend "Failed to submit I/O.\n"); + return -EINVAL; + } + suspend_bio_ops.finish_all_io(); + signature_found = parse_signature(diskpage.pointer->swh.magic.magic, 0); + free_page((unsigned long) diskpage.address); + + if (signature_found != -1) { + printk(KERN_ERR name_suspend "Swap space signature found.\n"); + return 0; + } + + printk(KERN_ERR name_suspend "Sorry. No swap signature found at specified location.\n"); + return -EINVAL; + +invalid: + if (colon) + *colon = ':'; + if (at_symbol) + *at_symbol = '@'; + printk(KERN_ERR name_suspend "Sorry. Location looks invalid.\n"); + return -EINVAL; +} + +int header_locations_read_proc(char * page, char ** start, off_t off, int count, + int *eof, void *data) +{ + int i, printedpartitionsmessage = 0, len = 0, haveswap = 0, device_block_size; + struct inode *swapf = 0; + int zone; + char * path_page = (char *) __get_free_page(GFP_KERNEL); + char * path; + int path_len; + + *eof = 1; + if (!page) + return 0; + + for (i = 0; i < MAX_SWAPFILES; i++) { + if (!swap_info[i].swap_file) + continue; + + if (S_ISBLK(swap_info[i].swap_file->f_dentry->d_inode->i_mode)) { + haveswap = 1; + if (!printedpartitionsmessage) { + len += sprintf(page + len, + "For swap partitions, simply use the format: resume2=swap:/dev/hda1.\n"); + printedpartitionsmessage = 1; + } + } else { + path_len = 0; + + path = get_path_for_swapfile(i, path_page); + path_len = sprintf(path_page, "%-31s ", path); + + haveswap = 1; + swapf = swap_info[i].swap_file->f_dentry->d_inode; + device_block_size = block_size(swap_info[i].bdev); + if (!(zone = bmap(swapf,0))) { + len+= sprintf(page + len, + "Swapfile %-31s has been corrupted. Reuse mkswap on it and try again.\n", + path_page); + } else { + len+= sprintf(page + len, "For swapfile `%s`, use resume2=swap:/dev/:0x%x@%d.\n", + path_page, + zone, device_block_size); + } + + } + } + + if (!haveswap) + len = sprintf(page, "You need to turn on swap partitions before examining this file.\n"); + + free_pages((unsigned long) path_page, 0); + return len; +} + +extern int attempt_to_parse_resume_device(void); + +static struct suspend_proc_data swapwriter_proc_data[] = { + { + .filename = "swapfilename", + .permissions = PROC_RW, + .type = SUSPEND_PROC_DATA_STRING, + .data = { + .string = { + .variable = swapfilename, + .max_length = 255, + } + } + }, + + { + .filename = "headerlocations", + .permissions = PROC_READONLY, + .type = SUSPEND_PROC_DATA_CUSTOM, + .data = { + .special = { + .read_proc = header_locations_read_proc, + } + } + }, + + { .filename = "disable_swapwriter", + .permissions = PROC_RW, + .type = SUSPEND_PROC_DATA_INTEGER, + .data = { + .integer = { + .variable = &swapwriterops.disabled, + .minimum = 0, + .maximum = 1, + } + }, + .write_proc = attempt_to_parse_resume_device, + } +}; + +static struct suspend_plugin_ops swapwriterops = { + .type = WRITER_PLUGIN, + .name = "Swap Writer", + .memory_needed = swapwriter_memory_needed, + .print_debug_info = swapwriter_print_debug_stats, + .storage_needed = swapwriter_storage_needed, + .initialise = swapwriter_initialise, + .cleanup = swapwriter_cleanup, + .dpm_set_devices = swapwriter_dpm_set_devices, + .ops = { + .writer = { + .write_init = swapwriter_write_init, + .write_chunk = swapwriter_write_chunk, + .write_cleanup = swapwriter_write_cleanup, + .read_init = swapwriter_read_init, + .read_chunk = swapwriter_read_chunk, + .read_cleanup = swapwriter_read_cleanup, + .noresume_reset = swapwriter_noresume_reset, + .storage_available = swapwriter_storage_available, + .storage_allocated = swapwriter_storage_allocated, + .release_storage = swapwriter_release_storage, + .allocate_header_space = swapwriter_allocate_header_space, + .allocate_storage = swapwriter_allocate_storage, + .image_exists = swapwriter_image_exists, + .mark_resume_attempted = swapwriter_mark_resume_attempted, + .write_header_init = swapwriter_write_header_init, + .write_header_chunk = swapwriter_write_header_chunk, + .write_header_cleanup = swapwriter_write_header_cleanup, + .read_header_init = swapwriter_read_header_init, + .read_header_chunk = swapwriter_read_header_chunk, + .read_header_cleanup = swapwriter_read_header_cleanup, + .prepare_save_ranges = swapwriter_prepare_save_ranges, + .post_load_ranges = swapwriter_post_load_ranges, + .invalidate_image = swapwriter_invalidate_image, + .parse_image_location = swapwriter_parse_image_location, + } + } +}; + +/* ---- Registration ---- */ +static __init int swapwriter_load(void) +{ + int result; + int i, numfiles = sizeof(swapwriter_proc_data) / sizeof(struct suspend_proc_data); + + if (!(result = suspend_register_plugin(&swapwriterops))) { + printk("Software Suspend Swap Writer registered.\n"); + + for (i=0; i< numfiles; i++) + suspend_register_procfile(&swapwriter_proc_data[i]); + } + return result; +} + +#ifdef MODULE +static __exit void swapwriter_unload(void) +{ + int i, numfiles = sizeof(swapwriter_proc_data) / sizeof(struct suspend_proc_data); + + printk("Software Suspend Swap Writer unloading.\n"); + + for (i=0; i< numfiles; i++) + suspend_unregister_procfile(&swapwriter_proc_data[i]); + suspend_unregister_plugin(&swapwriterops); +} + +module_init(swapwriter_load); +module_exit(swapwriter_unload); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Nigel Cunningham"); +MODULE_DESCRIPTION("Suspend2 swap writer"); +#else +late_initcall(swapwriter_load); +#endif