diff options
Diffstat (limited to 'kernel/power/tuxonice_swap.c')
-rw-r--r-- | kernel/power/tuxonice_swap.c | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/kernel/power/tuxonice_swap.c b/kernel/power/tuxonice_swap.c new file mode 100644 index 000000000..9f555c932 --- /dev/null +++ b/kernel/power/tuxonice_swap.c @@ -0,0 +1,474 @@ +/* + * kernel/power/tuxonice_swap.c + * + * Copyright (C) 2004-2015 Nigel Cunningham (nigel at nigelcunningham com au) + * + * 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 <linux/fs_uuid.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_bio.h" +#include "tuxonice_alloc.h" +#include "tuxonice_builtin.h" + +static struct toi_module_ops toi_swapops; + +/* For swapfile automatically swapon/off'd. */ +static char swapfilename[255] = ""; +static int toi_swapon_status; + +/* Swap Pages */ +static unsigned long swap_allocated; + +static struct sysinfo swapinfo; + +static int is_ram_backed(struct swap_info_struct *si) +{ + if (!strncmp(si->bdev->bd_disk->disk_name, "ram", 3) || + !strncmp(si->bdev->bd_disk->disk_name, "zram", 4)) + return 1; + + return 0; +} + +/** + * 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(KERN_ERR "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; +} + +static int add_blocks_to_extent_chain(struct toi_bdev_info *chain, + unsigned long start, unsigned long end) +{ + if (test_action_state(TOI_TEST_BIO)) + toi_message(TOI_IO, TOI_VERBOSE, 0, "Adding extent %lu-%lu to " + "chain %p.", start << chain->bmap_shift, + end << chain->bmap_shift, chain); + + return toi_add_to_extent_chain(&chain->blocks, start, end); +} + + +static int get_main_pool_phys_params(struct toi_bdev_info *chain) +{ + struct hibernate_extent *extentpointer = NULL; + unsigned long address, extent_min = 0, extent_max = 0; + int empty = 1; + + toi_message(TOI_IO, TOI_VERBOSE, 0, "get main pool phys params for " + "chain %d.", chain->allocator_index); + + if (!chain->allocations.first) + return 0; + + if (chain->blocks.first) + toi_put_extent_chain(&chain->blocks); + + toi_extent_for_each(&chain->allocations, extentpointer, address) { + swp_entry_t swap_address = (swp_entry_t) { address }; + struct block_device *bdev; + sector_t new_sector = map_swap_entry(swap_address, &bdev); + + if (empty) { + empty = 0; + extent_min = extent_max = new_sector; + continue; + } + + if (new_sector == extent_max + 1) { + extent_max++; + continue; + } + + if (add_blocks_to_extent_chain(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; + } + + if (!empty && + add_blocks_to_extent_chain(chain, extent_min, extent_max)) { + printk(KERN_ERR "Out of memory while making block chains.\n"); + return -ENOMEM; + } + + return 0; +} + +/* + * 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(void) +{ + unsigned int i; + + si_swapinfo(&swapinfo); + swapinfo.freeswap = 0; + swapinfo.totalswap = 0; + + for (i = 0; i < MAX_SWAPFILES; i++) { + struct swap_info_struct *si = get_swap_info_struct(i); + if (si && (si->flags & SWP_WRITEOK) && !is_ram_backed(si)) { + swapinfo.totalswap += si->inuse_pages; + swapinfo.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 unsigned long toi_swap_storage_available(void) +{ + toi_message(TOI_IO, TOI_VERBOSE, 0, "In toi_swap_storage_available."); + si_swapinfo_no_compcache(); + return swapinfo.freeswap + swap_allocated; +} + +static int toi_swap_initialise(int starting_cycle) +{ + if (!starting_cycle) + return 0; + + enable_swapfile(); + return 0; +} + +static void toi_swap_cleanup(int ending_cycle) +{ + if (!ending_cycle) + return; + + disable_swapfile(); +} + +static void toi_swap_free_storage(struct toi_bdev_info *chain) +{ + /* Free swap entries */ + struct hibernate_extent *extentpointer; + unsigned long extentvalue; + + toi_message(TOI_IO, TOI_VERBOSE, 0, "Freeing storage for chain %p.", + chain); + + swap_allocated -= chain->allocations.size; + toi_extent_for_each(&chain->allocations, extentpointer, extentvalue) + swap_free((swp_entry_t) { extentvalue }); + + toi_put_extent_chain(&chain->allocations); +} + +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 }); + swap_allocated -= (max - min + 1); +} + +/* + * Allocation of a single swap type. Swap priorities are handled at the higher + * level. + */ +static int toi_swap_allocate_storage(struct toi_bdev_info *chain, + unsigned long request) +{ + unsigned long gotten = 0; + + toi_message(TOI_IO, TOI_VERBOSE, 0, " Swap allocate storage: Asked to" + " allocate %lu pages from device %d.", request, + chain->allocator_index); + + while (gotten < request) { + swp_entry_t start, end; + if (0) { + /* Broken at the moment for SSDs */ + get_swap_range_of_type(chain->allocator_index, &start, &end, + request - gotten + 1); + } else { + start = end = get_swap_page_of_type(chain->allocator_index); + } + if (start.val) { + int added = end.val - start.val + 1; + if (toi_add_to_extent_chain(&chain->allocations, + start.val, end.val)) { + printk(KERN_INFO "Failed to allocate extent for " + "%lu-%lu.\n", start.val, end.val); + free_swap_range(start.val, end.val); + break; + } + gotten += added; + swap_allocated += added; + } else + break; + } + + toi_message(TOI_IO, TOI_VERBOSE, 0, " Allocated %lu pages.", gotten); + return gotten; +} + +static int toi_swap_register_storage(void) +{ + int i, result = 0; + + toi_message(TOI_IO, TOI_VERBOSE, 0, "toi_swap_register_storage."); + for (i = 0; i < MAX_SWAPFILES; i++) { + struct swap_info_struct *si = get_swap_info_struct(i); + struct toi_bdev_info *devinfo; + unsigned char *p; + unsigned char buf[256]; + struct fs_info *fs_info; + + if (!si || !(si->flags & SWP_WRITEOK) || is_ram_backed(si)) + continue; + + devinfo = toi_kzalloc(39, sizeof(struct toi_bdev_info), + GFP_ATOMIC); + if (!devinfo) { + printk("Failed to allocate devinfo struct for swap " + "device %d.\n", i); + return -ENOMEM; + } + + devinfo->bdev = si->bdev; + devinfo->allocator = &toi_swapops; + devinfo->allocator_index = i; + + fs_info = fs_info_from_block_dev(si->bdev); + if (fs_info && !IS_ERR(fs_info)) { + memcpy(devinfo->uuid, &fs_info->uuid, 16); + free_fs_info(fs_info); + } else + result = (int) PTR_ERR(fs_info); + + if (!fs_info) + printk("fs_info from block dev returned %d.\n", result); + devinfo->dev_t = si->bdev->bd_dev; + devinfo->prio = si->prio; + devinfo->bmap_shift = 3; + devinfo->blocks_per_page = 1; + + p = d_path(&si->swap_file->f_path, buf, sizeof(buf)); + sprintf(devinfo->name, "swap on %s", p); + + toi_message(TOI_IO, TOI_VERBOSE, 0, "Registering swap storage:" + " Device %d (%lx), prio %d.", i, + (unsigned long) devinfo->dev_t, devinfo->prio); + toi_bio_ops.register_storage(devinfo); + } + + return 0; +} + +static unsigned long toi_swap_free_unused_storage(struct toi_bdev_info *chain, unsigned long used) +{ + struct hibernate_extent *extentpointer = NULL; + unsigned long extentvalue; + unsigned long i = 0, first_freed = 0; + + toi_extent_for_each(&chain->allocations, extentpointer, extentvalue) { + i++; + if (i > used) { + swap_free((swp_entry_t) { extentvalue }); + if (!first_freed) + first_freed = extentvalue; + } + } + + return first_freed; +} + +/* + * 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; + + len = scnprintf(buffer, size, "- Swap Allocator enabled.\n"); + if (swapfilename[0]) + len += scnprintf(buffer+len, size-len, + " Attempting to automatically swapon: %s.\n", + swapfilename); + + si_swapinfo_no_compcache(); + + len += scnprintf(buffer+len, size-len, + " Swap available for image: %lu pages.\n", + swapinfo.freeswap + swap_allocated); + + return len; +} + +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 || !(si->flags & SWP_WRITEOK)) + 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, PAGE_SIZE, "%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[BDEVNAME_SIZE]; + 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_bio_allocator_ops toi_bio_swapops = { + .register_storage = toi_swap_register_storage, + .storage_available = toi_swap_storage_available, + .allocate_storage = toi_swap_allocate_storage, + .bmap = get_main_pool_phys_params, + .free_storage = toi_swap_free_storage, + .free_unused_storage = toi_swap_free_unused_storage, +}; + +static struct toi_module_ops toi_swapops = { + .type = BIO_ALLOCATOR_MODULE, + .name = "swap storage", + .directory = "swap", + .module = THIS_MODULE, + .memory_needed = toi_swap_memory_needed, + .print_debug_info = toi_swap_print_debug_stats, + .initialise = toi_swap_initialise, + .cleanup = toi_swap_cleanup, + .bio_allocator_ops = &toi_bio_swapops, + + .sysfs_data = sysfs_params, + .num_sysfs_entries = sizeof(sysfs_params) / + sizeof(struct toi_sysfs_data), +}; + +/* ---- Registration ---- */ +static __init int toi_swap_load(void) +{ + return toi_register_module(&toi_swapops); +} + +late_initcall(toi_swap_load); |