/*
* linux/kernel/power/swsusp.c
*
* This file provides code to write suspend image to swap and read it back.
*
* Copyright (C) 1998-2001 Gabor Kuti <seasons@fornax.hu>
* Copyright (C) 1998,2001-2005 Pavel Machek <pavel@suse.cz>
*
* This file is released under the GPLv2.
*
* I'd like to thank the following people for their work:
*
* Pavel Machek <pavel@ucw.cz>:
* Modifications, defectiveness pointing, being with me at the very beginning,
* suspend to swap space, stop all tasks. Port to 2.4.18-ac and 2.5.17.
*
* Steve Doddi <dirk@loth.demon.co.uk>:
* Support the possibility of hardware state restoring.
*
* Raph <grey.havens@earthling.net>:
* Support for preserving states of network devices and virtual console
* (including X and svgatextmode)
*
* Kurt Garloff <garloff@suse.de>:
* Straightened the critical function in order to prevent compilers from
* playing tricks with local variables.
*
* Andreas Mohr <a.mohr@mailto.de>
*
* Alex Badea <vampire@go.ro>:
* Fixed runaway init
*
* Andreas Steinmetz <ast@domdv.de>:
* Added encrypted suspend option
*
* More state savers are welcome. Especially for the scsi layer...
*
* For TODOs,FIXMEs also look in Documentation/power/swsusp.txt
*/
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/suspend.h>
#include <linux/smp_lock.h>
#include <linux/file.h>
#include <linux/utsname.h>
#include <linux/version.h>
#include <linux/delay.h>
#include <linux/bitops.h>
#include <linux/spinlock.h>
#include <linux/genhd.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/swap.h>
#include <linux/pm.h>
#include <linux/device.h>
#include <linux/buffer_head.h>
#include <linux/swapops.h>
#include <linux/bootmem.h>
#include <linux/syscalls.h>
#include <linux/highmem.h>
#include <linux/bio.h>
#include <asm/uaccess.h>
#include <asm/mmu_context.h>
#include <asm/pgtable.h>
#include <asm/tlbflush.h>
#include <asm/io.h>
#include <linux/random.h>
#include <linux/crypto.h>
#include <asm/scatterlist.h>
#include "power.h"
#define CIPHER "aes"
#define MAXKEY 32
#define MAXIV 32
extern char resume_file[];
/* Local variables that should not be affected by save */
unsigned int nr_copy_pages __nosavedata = 0;
/* Suspend pagedir is allocated before final copy, therefore it
must be freed after resume
Warning: this is even more evil than it seems. Pagedirs this file
talks about are completely different from page directories used by
MMU hardware.
*/
suspend_pagedir_t *pagedir_nosave __nosavedata = NULL;
#define SWSUSP_SIG "S1SUSPEND"
static struct swsusp_header {
char reserved[PAGE_SIZE - 20 - MAXKEY - MAXIV - sizeof(swp_entry_t)];
u8 key_iv[MAXKEY+MAXIV];
swp_entry_t swsusp_info;
char orig_sig[10];
char sig[10];
} __attribute__((packed, aligned(PAGE_SIZE))) swsusp_header;
static struct swsusp_info swsusp_info;
/*
* Saving part...
*/
/* We memorize in swapfile_used what swap devices are used for suspension */
#define SWAPFILE_UNUSED 0
#define SWAPFILE_SUSPEND 1 /* This is the suspending device */
#define SWAPFILE_IGNORED 2 /* Those are other swap devices ignored for suspension */
static unsigned short swapfile_used[MAX_SWAPFILES];
static unsigned short root_swap;
static int write_page(unsigned long addr, swp_entry_t *loc);
static int bio_read_page(pgoff_t page_off, void *page);
static u8 key_iv[MAXKEY+MAXIV];
#ifdef CONFIG_SWSUSP_ENCRYPT
static int crypto_init(int mode, void **mem)
{
int error = 0;
int len;
char *modemsg;
struct crypto_tfm *tfm;
modemsg = mode ? "suspend not possible" : "resume not possible";
tfm = crypto_alloc_tfm(CIPHER, CRYPTO_TFM_MODE_CBC);
if(!tfm) {
printk(KERN_ERR "swsusp: no tfm, %s\n", modemsg);
error = -EINVAL;
goto out;
}
if(MAXKEY < crypto_tfm_alg_min_keysize(tfm)) {
printk(KERN_ERR "swsusp: key buffer too small, %s\n", modemsg);
error = -ENOKEY;
goto fail;
}
if (mode)
get_random_bytes(key_iv, MAXKEY+MAXIV);
len = crypto_tfm_alg_max_keysize(tfm);
if (len > MAXKEY)
len = MAXKEY;
if (crypto_cipher_setkey(tfm, key_iv, len)) {
printk(KERN_ERR "swsusp: key setup failure, %s\n", modemsg);
error = -EKEYREJECTED;
goto fail;
}
len = crypto_tfm_alg_ivsize(tfm);
if (MAXIV < len) {
printk(KERN_ERR "swsusp: iv buffer too small, %s\n", modemsg);
error = -EOVERFLOW;
goto fail;
}
crypto_cipher_set_iv(tfm, key_iv+MAXKEY, len);
*mem=(void *)tfm;
goto out;
fail: crypto_free_tfm(tfm);
out: return error;
}
static __inline__ void crypto_exit(void *mem)
{
crypto_free_tfm((struct crypto_tfm *)mem);
}
static __inline__ int crypto_write(struct pbe *p, void *mem)
{
int error = 0;
struct scatterlist src, dst;
src.page = virt_to_page(p->address);
src.offset = 0;
src.length = PAGE_SIZE;