blob: f11656b234df80087fb2997452889116ae7c603e [file] [log] [blame]
/*
* Copyright (C) 2004-2010 Nigel Cunningham (nigel at tuxonice net)
*
* This file is released under the GPLv2.
*/
#include <linux/resume-trace.h>
#include <linux/kernel.h>
#include <linux/swap.h>
#include <linux/syscalls.h>
#include <linux/bio.h>
#include <linux/root_dev.h>
#include <linux/freezer.h>
#include <linux/reboot.h>
#include <linux/writeback.h>
#include <linux/tty.h>
#include <linux/crypto.h>
#include <linux/cpu.h>
#include <linux/ctype.h>
#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;
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;
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;
}
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 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_NO_MULTITHREADED_IO) | */
(1 << TOI_LATE_CPU_HOTPLUG) | (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
int toi_translate_err_default = TOI_CONTINUE_REQ;
EXPORT_SYMBOL_GPL(toi_translate_err_default);
void try_tuxonice_resume(void)
{
/* 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);
#ifdef CONFIG_MTK_HIBERNATION
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);
EXPORT_SYMBOL_GPL(toi_flag_zram_disks);
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)) {
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);