/* * 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 #include #include #include #include #include #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);