/* * Copyright (C) 2004-2014 Nigel Cunningham (nigel at tuxonice net) * * This file is released under the GPLv2. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tuxonice_io.h" #include "tuxonice.h" #include "tuxonice_extent.h" #include "tuxonice_netlink.h" #include "tuxonice_prepare_image.h" #include "tuxonice_ui.h" #include "tuxonice_sysfs.h" #include "tuxonice_pagedir.h" #include "tuxonice_modules.h" #include "tuxonice_builtin.h" #include "tuxonice_power_off.h" #include "tuxonice_alloc.h" unsigned long toi_bootflags_mask; EXPORT_SYMBOL_GPL(toi_bootflags_mask); /* * Highmem related functions (x86 only). */ #ifdef CONFIG_HIGHMEM /** * copyback_high: Restore highmem pages. * * Highmem data and pbe lists are/can be stored in highmem. * The format is slightly different to the lowmem pbe lists * used for the assembly code: the last pbe in each page is * a struct page * instead of struct pbe *, pointing to the * next page where pbes are stored (or NULL if happens to be * the end of the list). Since we don't want to generate * unnecessary deltas against swsusp code, we use a cast * instead of a union. **/ static void copyback_high(void) { struct page *pbe_page = (struct page *)restore_highmem_pblist; struct pbe *this_pbe, *first_pbe; unsigned long *origpage, *copypage; int pbe_index = 1; if (!pbe_page) return; this_pbe = (struct pbe *)kmap_atomic(pbe_page); first_pbe = this_pbe; while (this_pbe) { int loop = (PAGE_SIZE / sizeof(unsigned long)) - 1; origpage = kmap_atomic(pfn_to_page((unsigned long)this_pbe->orig_address)); copypage = kmap_atomic((struct page *)this_pbe->address); while (loop >= 0) { *(origpage + loop) = *(copypage + loop); loop--; } kunmap_atomic(origpage); kunmap_atomic(copypage); if (!this_pbe->next) break; if (pbe_index < PBES_PER_PAGE) { this_pbe++; pbe_index++; } else { pbe_page = (struct page *)this_pbe->next; kunmap_atomic(first_pbe); if (!pbe_page) return; this_pbe = (struct pbe *)kmap_atomic(pbe_page); first_pbe = this_pbe; pbe_index = 1; } } kunmap_atomic(first_pbe); } #else /* CONFIG_HIGHMEM */ static void copyback_high(void) { } #endif char toi_wait_for_keypress_dev_console(int timeout) { int fd, this_timeout = 255, orig_kthread = 0; char key = '\0'; struct termios t, t_backup; /* We should be guaranteed /dev/console exists after populate_rootfs() * in init/main.c. */ fd = sys_open("/dev/console", O_RDONLY, 0); if (fd < 0) { pr_warn("Couldn't open /dev/console.\n"); return key; } if (sys_ioctl(fd, TCGETS, (long)&t) < 0) goto out_close; memcpy(&t_backup, &t, sizeof(t)); t.c_lflag &= ~(ISIG | ICANON | ECHO); t.c_cc[VMIN] = 0; new_timeout: if (timeout > 0) { this_timeout = timeout < 26 ? timeout : 25; timeout -= this_timeout; this_timeout *= 10; } t.c_cc[VTIME] = this_timeout; if (sys_ioctl(fd, TCSETS, (long)&t) < 0) goto out_restore; if (current->flags & PF_KTHREAD) { orig_kthread = (current->flags & PF_KTHREAD); current->flags &= ~PF_KTHREAD; } while (1) { if (sys_read(fd, &key, 1) <= 0) { if (timeout) goto new_timeout; key = '\0'; break; } key = tolower(key); if (test_toi_state(TOI_SANITY_CHECK_PROMPT)) { if (key == 'c') { set_toi_state(TOI_CONTINUE_REQ); break; } else if (key == ' ') break; } else break; } if (orig_kthread) current->flags |= PF_KTHREAD; out_restore: sys_ioctl(fd, TCSETS, (long)&t_backup); out_close: sys_close(fd); return key; } EXPORT_SYMBOL_GPL(toi_wait_for_keypress_dev_console); struct toi_boot_kernel_data __nosavedata __aligned(PAGE_SIZE) toi_bkd = { MY_BOOT_KERNEL_DATA_VERSION, 0, #ifdef CONFIG_TOI_REPLACE_SWSUSP (1 << TOI_REPLACE_SWSUSP) | #endif (1 << TOI_NO_FLUSHER_THREAD) | (1 << TOI_PAGESET2_FULL) }; EXPORT_SYMBOL_GPL(toi_bkd); struct block_device *toi_open_by_devnum(dev_t dev) { struct block_device *bdev = bdget(dev); int err = -ENOMEM; if (bdev) err = blkdev_get(bdev, FMODE_READ | FMODE_NDELAY, NULL); return err ? ERR_PTR(err) : bdev; } EXPORT_SYMBOL_GPL(toi_open_by_devnum); /** * toi_close_bdev: Close a swap bdev. * * int: The swap entry number to close. */ void toi_close_bdev(struct block_device *bdev) { blkdev_put(bdev, FMODE_READ | FMODE_NDELAY); } EXPORT_SYMBOL_GPL(toi_close_bdev); int toi_wait = CONFIG_TOI_DEFAULT_WAIT; EXPORT_SYMBOL_GPL(toi_wait); struct toi_core_fns *toi_core_fns; EXPORT_SYMBOL_GPL(toi_core_fns); unsigned long toi_result; EXPORT_SYMBOL_GPL(toi_result); struct pagedir pagedir1 = { 1 }; EXPORT_SYMBOL_GPL(pagedir1); unsigned long toi_get_nonconflicting_page(void) { return toi_core_fns->get_nonconflicting_page(); } int toi_post_context_save(void) { return toi_core_fns->post_context_save(); } int try_tuxonice_hibernate(void) { if (!toi_core_fns) return -ENODEV; return toi_core_fns->try_hibernate(); } static int num_resume_calls; #ifdef CONFIG_TOI_IGNORE_LATE_INITCALL static int ignore_late_initcall = 1; #else static int ignore_late_initcall; #endif #ifdef CONFIG_TOI_ENHANCE int toi_ignore_late_initcall(void) { return ignore_late_initcall; } EXPORT_SYMBOL_GPL(toi_ignore_late_initcall); #endif int toi_translate_err_default = TOI_CONTINUE_REQ; EXPORT_SYMBOL_GPL(toi_translate_err_default); void try_tuxonice_resume(void) { if (!hibernation_available()) return; /* Don't let it wrap around eventually */ if (num_resume_calls < 2) num_resume_calls++; if (num_resume_calls == 1 && ignore_late_initcall) { pr_warn("TuxOnIce: Ignoring late initcall, as requested.\n"); return; } if (toi_core_fns) toi_core_fns->try_resume(); else pr_warn("TuxOnIce core not loaded yet.\n"); } int toi_lowlevel_builtin(void) { int error = 0; save_processor_state(); error = swsusp_arch_suspend(); if (error) pr_err("Error %d hibernating\n", error); #ifdef CONFIG_TOI_ENHANCE if (test_result_state(TOI_ARCH_PREPARE_FAILED)) hib_err("CAUTION: error(%d/0x%08x)\n", error, (unsigned int)toi_result); #endif /* Restore control flow appears here */ if (!toi_in_hibernate) { copyback_high(); set_toi_state(TOI_NOW_RESUMING); } restore_processor_state(); return error; } EXPORT_SYMBOL_GPL(toi_lowlevel_builtin); unsigned long toi_compress_bytes_in; EXPORT_SYMBOL_GPL(toi_compress_bytes_in); unsigned long toi_compress_bytes_out; EXPORT_SYMBOL_GPL(toi_compress_bytes_out); int toi_in_suspend(void) { return in_suspend; } EXPORT_SYMBOL_GPL(toi_in_suspend); unsigned long toi_state = ((1 << TOI_BOOT_TIME) | (1 << TOI_IGNORE_LOGLEVEL) | (1 << TOI_IO_STOPPED)); EXPORT_SYMBOL_GPL(toi_state); /* The number of hibernates we have started (some may have been cancelled) */ unsigned int nr_hibernates; EXPORT_SYMBOL_GPL(nr_hibernates); int toi_running; EXPORT_SYMBOL_GPL(toi_running); __nosavedata int toi_in_hibernate; EXPORT_SYMBOL_GPL(toi_in_hibernate); __nosavedata struct pbe *restore_highmem_pblist; EXPORT_SYMBOL_GPL(restore_highmem_pblist); int toi_trace_allocs; EXPORT_SYMBOL_GPL(toi_trace_allocs); void toi_read_lock_tasklist(void) { read_lock(&tasklist_lock); } EXPORT_SYMBOL_GPL(toi_read_lock_tasklist); void toi_read_unlock_tasklist(void) { read_unlock(&tasklist_lock); } EXPORT_SYMBOL_GPL(toi_read_unlock_tasklist); #ifdef CONFIG_TOI_ZRAM_SUPPORT int (*toi_flag_zram_disks)(void); int toi_do_flag_zram_disks(void) { return toi_flag_zram_disks ? (*toi_flag_zram_disks) () : 0; } EXPORT_SYMBOL_GPL(toi_do_flag_zram_disks); #endif static int __init toi_wait_setup(char *str) { int value; if (sscanf(str, "=%d", &value) == 1) { if (value < -1 || value > 255) pr_warn("TuxOnIce_wait outside range -1 to " "255.\n"); else toi_wait = value; } return 1; } __setup("toi_wait", toi_wait_setup); static int __init toi_translate_retry_setup(char *str) { toi_translate_err_default = 0; return 1; } __setup("toi_translate_retry", toi_translate_retry_setup); static int __init toi_debug_setup(char *str) { toi_bkd.toi_action |= (1 << TOI_LOGALL); toi_bootflags_mask |= (1 << TOI_LOGALL); toi_bkd.toi_debug_state = 255; toi_bkd.toi_default_console_level = 7; return 1; } __setup("toi_debug_setup", toi_debug_setup); static int __init toi_pause_setup(char *str) { toi_bkd.toi_action |= (1 << TOI_PAUSE); toi_bootflags_mask |= (1 << TOI_PAUSE); return 1; } __setup("toi_pause", toi_pause_setup); #ifdef CONFIG_PM_DEBUG static int __init toi_trace_allocs_setup(char *str) { int value; if (sscanf(str, "=%d", &value) == 1) toi_trace_allocs = value; return 1; } __setup("toi_trace_allocs", toi_trace_allocs_setup); #endif static int __init toi_ignore_late_initcall_setup(char *str) { int value; if (sscanf(str, "=%d", &value) == 1) ignore_late_initcall = value; return 1; } __setup("toi_initramfs_resume_only", toi_ignore_late_initcall_setup); static int __init toi_force_no_multithreaded_setup(char *str) { int value; toi_bkd.toi_action &= ~(1 << TOI_NO_MULTITHREADED_IO); toi_bootflags_mask |= (1 << TOI_NO_MULTITHREADED_IO); if (sscanf(str, "=%d", &value) == 1 && value) toi_bkd.toi_action |= (1 << TOI_NO_MULTITHREADED_IO); return 1; } __setup("toi_no_multithreaded", toi_force_no_multithreaded_setup); #ifdef CONFIG_KGDB static int __init toi_post_resume_breakpoint_setup(char *str) { int value; toi_bkd.toi_action &= ~(1 << TOI_POST_RESUME_BREAKPOINT); toi_bootflags_mask |= (1 << TOI_POST_RESUME_BREAKPOINT); if (sscanf(str, "=%d", &value) == 1 && value) toi_bkd.toi_action |= (1 << TOI_POST_RESUME_BREAKPOINT); return 1; } __setup("toi_post_resume_break", toi_post_resume_breakpoint_setup); #endif static int __init toi_disable_readahead_setup(char *str) { int value; toi_bkd.toi_action &= ~(1 << TOI_NO_READAHEAD); toi_bootflags_mask |= (1 << TOI_NO_READAHEAD); if (sscanf(str, "=%d", &value) == 1 && value) toi_bkd.toi_action |= (1 << TOI_NO_READAHEAD); return 1; } __setup("toi_no_readahead", toi_disable_readahead_setup);