/* * Copyright (C) 2004-2015 Nigel Cunningham (nigel at nigelcunningham com au) * * This file is released under the GPLv2. */ #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; /* * 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) { printk(KERN_INFO "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; } struct toi_boot_kernel_data toi_bkd __nosavedata __attribute__((aligned(PAGE_SIZE))) = { 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), }; 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; } /** * 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); } int toi_wait = CONFIG_TOI_DEFAULT_WAIT; struct toi_core_fns *toi_core_fns; unsigned long toi_result; struct pagedir pagedir1 = {1}; struct toi_cbw **toi_first_cbw; int toi_next_cbw; 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 int toi_translate_err_default = TOI_CONTINUE_REQ; 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) { printk(KERN_INFO "TuxOnIce: Ignoring late initcall, as requested.\n"); return; } if (toi_core_fns) toi_core_fns->try_resume(); else printk(KERN_INFO "TuxOnIce core not loaded yet.\n"); } int toi_lowlevel_builtin(void) { int error = 0; save_processor_state(); error = swsusp_arch_suspend(); if (error) printk(KERN_ERR "Error %d hibernating\n", error); /* Restore control flow appears here */ if (!toi_in_hibernate) { copyback_high(); set_toi_state(TOI_NOW_RESUMING); } restore_processor_state(); return error; } unsigned long toi_compress_bytes_in; unsigned long toi_compress_bytes_out; int toi_in_suspend(void) { return in_suspend; } unsigned long toi_state = ((1 << TOI_BOOT_TIME) | (1 << TOI_IGNORE_LOGLEVEL) | (1 << TOI_IO_STOPPED)); /* The number of hibernates we have started (some may have been cancelled) */ unsigned int nr_hibernates; int toi_running; __nosavedata int toi_in_hibernate; __nosavedata struct pbe *restore_highmem_pblist; int toi_trace_allocs; void toi_read_lock_tasklist(void) { read_lock(&tasklist_lock); } void toi_read_unlock_tasklist(void) { read_unlock(&tasklist_lock); } #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; } #endif /* toi_generate_free_page_map * * Description: This routine generates a bitmap of free pages from the * lists used by the memory manager. We then use the bitmap * to quickly calculate which pages to save and in which * pagesets. */ void toi_generate_free_page_map(void) { int order, cpu, t; unsigned long flags, i; struct zone *zone; struct list_head *curr; unsigned long pfn; struct page *page; for_each_populated_zone(zone) { if (!zone->spanned_pages) continue; spin_lock_irqsave(&zone->lock, flags); for (i = 0; i < zone->spanned_pages; i++) { pfn = zone->zone_start_pfn + i; if (!pfn_valid(pfn)) continue; page = pfn_to_page(pfn); ClearPageNosaveFree(page); } for_each_migratetype_order(order, t) { list_for_each(curr, &zone->free_area[order].free_list[t]) { unsigned long j; pfn = page_to_pfn(list_entry(curr, struct page, lru)); for (j = 0; j < (1UL << order); j++) SetPageNosaveFree(pfn_to_page(pfn + j)); } } for_each_online_cpu(cpu) { struct per_cpu_pageset *pset = per_cpu_ptr(zone->pageset, cpu); struct per_cpu_pages *pcp = &pset->pcp; struct page *page; int t; for (t = 0; t < MIGRATE_PCPTYPES; t++) list_for_each_entry(page, &pcp->lists[t], lru) SetPageNosaveFree(page); } spin_unlock_irqrestore(&zone->lock, flags); } } /* toi_size_of_free_region * * Description: Return the number of pages that are free, beginning with and * including this one. */ int toi_size_of_free_region(struct zone *zone, unsigned long start_pfn) { unsigned long this_pfn = start_pfn, end_pfn = zone_end_pfn(zone); while (pfn_valid(this_pfn) && this_pfn < end_pfn && PageNosaveFree(pfn_to_page(this_pfn))) this_pfn++; return this_pfn - start_pfn; } static int __init toi_wait_setup(char *str) { int value; if (sscanf(str, "=%d", &value)) { if (value < -1 || value > 255) printk(KERN_INFO "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)) 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)) 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) && 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) && 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) && value) toi_bkd.toi_action |= (1 << TOI_NO_READAHEAD); return 1; } __setup("toi_no_readahead", toi_disable_readahead_setup);