* 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>:
+ * Steve Doddi <dirk@loth.demon.co.uk>:
* Support the possibility of hardware state restoring.
*
* Raph <grey.havens@earthling.net>:
* 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/console.h>
#include <linux/highmem.h>
#include <linux/bio.h>
+#include <linux/mount.h>
#include <asm/uaccess.h>
#include <asm/mmu_context.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
+
/* References to section boundaries */
extern const void __nosave_begin, __nosave_end;
static unsigned int nr_copy_pages __nosavedata = 0;
/* Suspend pagedir is allocated before final copy, therefore it
- must be freed after resume
+ must be freed after resume
Warning: this is evil. There are actually two pagedirs at time of
resume. One is "pagedir_save", which is empty frame allocated at
- time of suspend, that must be freed. Second is "pagedir_nosave",
+ time of suspend, that must be freed. Second is "pagedir_nosave",
allocated at time of resume, that travels through memory not to
collide with anything.
#define SWSUSP_SIG "S1SUSPEND"
static struct swsusp_header {
- char reserved[PAGE_SIZE - 20 - sizeof(swp_entry_t)];
+ 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];
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;
+ dst.page = virt_to_page((void *)&swsusp_header);
+ dst.offset = 0;
+ dst.length = PAGE_SIZE;
+
+ error = crypto_cipher_encrypt((struct crypto_tfm *)mem, &dst, &src,
+ PAGE_SIZE);
+
+ if (!error)
+ error = write_page((unsigned long)&swsusp_header,
+ &(p->swap_address));
+ return error;
+}
+
+static __inline__ int crypto_read(struct pbe *p, void *mem)
+{
+ int error = 0;
+ struct scatterlist src, dst;
+
+ error = bio_read_page(swp_offset(p->swap_address), (void *)p->address);
+ if (!error) {
+ src.offset = 0;
+ src.length = PAGE_SIZE;
+ dst.offset = 0;
+ dst.length = PAGE_SIZE;
+ src.page = dst.page = virt_to_page((void *)p->address);
+
+ error = crypto_cipher_decrypt((struct crypto_tfm *)mem, &dst,
+ &src, PAGE_SIZE);
+ }
+ return error;
+}
+#else
+static __inline__ int crypto_init(int mode, void *mem)
+{
+ return 0;
+}
+
+static __inline__ void crypto_exit(void *mem)
+{
+}
+
+static __inline__ int crypto_write(struct pbe *p, void *mem)
+{
+ return write_page(p->address, &(p->swap_address));
+}
+
+static __inline__ int crypto_read(struct pbe *p, void *mem)
+{
+ return bio_read_page(swp_offset(p->swap_address), (void *)p->address);
+}
+#endif
+
static int mark_swapfiles(swp_entry_t prev)
{
int error;
- rw_swap_page_sync(READ,
+ rw_swap_page_sync(READ,
swp_entry(root_swap, 0),
virt_to_page((unsigned long)&swsusp_header));
if (!memcmp("SWAP-SPACE",swsusp_header.sig, 10) ||
!memcmp("SWAPSPACE2",swsusp_header.sig, 10)) {
memcpy(swsusp_header.orig_sig,swsusp_header.sig, 10);
memcpy(swsusp_header.sig,SWSUSP_SIG, 10);
+ memcpy(swsusp_header.key_iv, key_iv, MAXKEY+MAXIV);
swsusp_header.swsusp_info = prev;
- error = rw_swap_page_sync(WRITE,
+ error = rw_swap_page_sync(WRITE,
swp_entry(root_swap, 0),
virt_to_page((unsigned long)
&swsusp_header));
static int swsusp_swap_check(void) /* This is called before saving image */
{
int i, len;
-
+
len=strlen(resume_file);
root_swap = 0xFFFF;
-
- swap_list_lock();
- for(i=0; i<MAX_SWAPFILES; i++) {
- if (swap_info[i].flags == 0) {
+
+ spin_lock(&swap_lock);
+ for (i=0; i<MAX_SWAPFILES; i++) {
+ if (!(swap_info[i].flags & SWP_WRITEOK)) {
swapfile_used[i]=SWAPFILE_UNUSED;
} else {
- if(!len) {
+ if (!len) {
printk(KERN_WARNING "resume= option should be used to set suspend device" );
- if(root_swap == 0xFFFF) {
+ if (root_swap == 0xFFFF) {
swapfile_used[i] = SWAPFILE_SUSPEND;
root_swap = i;
} else
- swapfile_used[i] = SWAPFILE_IGNORED;
+ swapfile_used[i] = SWAPFILE_IGNORED;
} else {
/* we ignore all swap devices that are not the resume_file */
if (is_resume_device(&swap_info[i])) {
}
}
}
- swap_list_unlock();
+ spin_unlock(&swap_lock);
return (root_swap != 0xffff) ? 0 : -ENODEV;
}
* This is called after saving image so modification
* will be lost after resume... and that's what we want.
* we make the device unusable. A new call to
- * lock_swapdevices can unlock the devices.
+ * lock_swapdevices can unlock the devices.
*/
static void lock_swapdevices(void)
{
int i;
- swap_list_lock();
- for(i = 0; i< MAX_SWAPFILES; i++)
- if(swapfile_used[i] == SWAPFILE_IGNORED) {
- swap_info[i].flags ^= 0xFF;
+ spin_lock(&swap_lock);
+ for (i = 0; i< MAX_SWAPFILES; i++)
+ if (swapfile_used[i] == SWAPFILE_IGNORED) {
+ swap_info[i].flags ^= SWP_WRITEOK;
}
- swap_list_unlock();
+ spin_unlock(&swap_lock);
}
/**
* @loc: Place to store the entry we used.
*
* Allocate a new swap entry and 'sync' it. Note we discard -EIO
- * errors. That is an artifact left over from swsusp. It did not
+ * errors. That is an artifact left over from swsusp. It did not
* check the return of rw_swap_page_sync() at all, since most pages
* written back to swap would return -EIO.
* This is a partial improvement, since we will at least return other
int error = 0;
entry = get_swap_page();
- if (swp_offset(entry) &&
+ if (swp_offset(entry) &&
swapfile_used[swp_type(entry)] == SWAPFILE_SUSPEND) {
error = rw_swap_page_sync(WRITE, entry,
virt_to_page(addr));
/**
* data_free - Free the swap entries used by the saved image.
*
- * Walk the list of used swap entries and free each one.
+ * Walk the list of used swap entries and free each one.
* This is only used for cleanup when suspend fails.
*/
static void data_free(void)
int error = 0, i = 0;
unsigned int mod = nr_copy_pages / 100;
struct pbe *p;
+ void *tfm;
+
+ if ((error = crypto_init(1, &tfm)))
+ return error;
if (!mod)
mod = 1;
printk( "Writing data to swap (%d pages)... ", nr_copy_pages );
- for_each_pbe(p, pagedir_nosave) {
+ for_each_pbe (p, pagedir_nosave) {
if (!(i%mod))
printk( "\b\b\b\b%3d%%", i / mod );
- if ((error = write_page(p->address, &(p->swap_address))))
+ if ((error = crypto_write(p, tfm))) {
+ crypto_exit(tfm);
return error;
+ }
i++;
}
printk("\b\b\b\bdone\n");
+ crypto_exit(tfm);
return error;
}
dump_info();
error = write_page((unsigned long)&swsusp_info, &entry);
- if (!error) {
+ if (!error) {
printk( "S" );
error = mark_swapfiles(entry);
printk( "|\n" );
struct pbe * pbe;
printk( "Writing pagedir...");
- for_each_pb_page(pbe, pagedir_nosave) {
+ for_each_pb_page (pbe, pagedir_nosave) {
if ((error = write_page((unsigned long)pbe, &swsusp_info.pagedir[n++])))
return error;
}
* write_suspend_image - Write entire image and metadata.
*
*/
-
static int write_suspend_image(void)
{
int error;
if ((error = close_swap()))
goto FreePagedir;
Done:
+ memset(key_iv, 0, MAXKEY+MAXIV);
return error;
FreePagedir:
free_pagedir_entries();
int res = 0;
pr_debug("swsusp: Saving Highmem\n");
- for_each_zone(zone) {
+ for_each_zone (zone) {
if (is_highmem(zone))
res = save_highmem_zone(zone);
if (res)
nr_copy_pages = 0;
- for_each_zone(zone) {
+ for_each_zone (zone) {
if (is_highmem(zone))
continue;
mark_free_pages(zone);
struct zone *zone;
unsigned long zone_pfn;
struct pbe * pbe = pagedir_nosave;
-
+
pr_debug("copy_data_pages(): pages to copy: %d\n", nr_copy_pages);
- for_each_zone(zone) {
+ for_each_zone (zone) {
if (is_highmem(zone))
continue;
mark_free_pages(zone);
static int calc_nr(int nr_copy)
{
- int extra = 0;
- int mod = !!(nr_copy % PBES_PER_PAGE);
- int diff = (nr_copy / PBES_PER_PAGE) + mod;
-
- do {
- extra += diff;
- nr_copy += diff;
- mod = !!(nr_copy % PBES_PER_PAGE);
- diff = (nr_copy / PBES_PER_PAGE) + mod - extra;
- } while (diff > 0);
-
- return nr_copy;
+ return nr_copy + (nr_copy+PBES_PER_PAGE-2)/(PBES_PER_PAGE-1);
}
/**
{
struct pbe * p;
- for_each_pbe(p, pagedir_save) {
+ for_each_pbe (p, pagedir_save) {
if (p->address) {
ClearPageNosave(virt_to_page(p->address));
free_page(p->address);
{
struct pbe * p;
- for_each_pbe(p, pagedir_save) {
+ for_each_pbe (p, pagedir_save) {
p->address = get_zeroed_page(GFP_ATOMIC | __GFP_COLD);
if (!p->address)
return -ENOMEM;
/**
* enough_free_mem - Make sure we enough free memory to snapshot.
*
- * Returns TRUE or FALSE after checking the number of available
+ * Returns TRUE or FALSE after checking the number of available
* free pages.
*/
/**
* enough_swap - Make sure we have enough swap to save the image.
*
- * Returns TRUE or FALSE after checking the total amount of swap
+ * Returns TRUE or FALSE after checking the total amount of swap
* space avaiable.
*
* FIXME: si_swapinfo(&i) returns all swap devices information.
- * We should only consider resume_device.
+ * We should only consider resume_device.
*/
static int enough_swap(void)
{
int error;
+ pagedir_nosave = NULL;
+ nr_copy_pages = calc_nr(nr_copy_pages);
+
pr_debug("suspend: (pages needed: %d + %d free: %d)\n",
nr_copy_pages, PAGES_FOR_IO, nr_free_pages());
- pagedir_nosave = NULL;
if (!enough_free_mem())
return -ENOMEM;
if (!enough_swap())
return -ENOSPC;
- nr_copy_pages = calc_nr(nr_copy_pages);
-
if (!(pagedir_save = alloc_pagedir(nr_copy_pages))) {
printk(KERN_ERR "suspend: Allocating pagedir failed.\n");
return -ENOMEM;
error = swsusp_alloc();
if (error)
return error;
-
- /* During allocating of suspend pagedir, new cold pages may appear.
+
+ /* During allocating of suspend pagedir, new cold pages may appear.
* Kill them.
*/
drain_local_pages();
asmlinkage int swsusp_save(void)
{
- int error = 0;
-
- if ((error = swsusp_swap_check())) {
- printk(KERN_ERR "swsusp: FATAL: cannot find swap device, try "
- "swapon -a!\n");
- return error;
- }
return suspend_prepare_image();
}
if ((error = device_power_down(PMSG_FREEZE))) {
printk(KERN_ERR "Some devices failed to power down, aborting suspend\n");
local_irq_enable();
- swsusp_free();
return error;
}
+
+ if ((error = swsusp_swap_check())) {
+ printk(KERN_ERR "swsusp: cannot find swap device, try swapon -a.\n");
+ device_power_up();
+ local_irq_enable();
+ return error;
+ }
+
save_processor_state();
if ((error = swsusp_arch_suspend()))
- swsusp_free();
+ printk(KERN_ERR "Error %d suspending\n", error);
/* Restore control flow magically appears here */
restore_processor_state();
BUG_ON (nr_copy_pages_check != nr_copy_pages);
BUG_ON(!error);
restore_processor_state();
restore_highmem();
+ touch_softlockup_watchdog();
device_power_up();
local_irq_enable();
return error;
}
-/* More restore stuff */
-
-/*
- * Returns true if given address/order collides with any orig_address
- */
-static int does_collide_order(unsigned long addr, int order)
-{
- int i;
-
- for (i=0; i < (1<<order); i++)
- if (!PageNosaveFree(virt_to_page(addr + i * PAGE_SIZE)))
- return 1;
- return 0;
-}
-
/**
* On resume, for storing the PBE list and the image,
* we can only use memory pages that do not conflict with the pages
unsigned long m;
m = get_zeroed_page(gfp_mask);
- while (does_collide_order(m, 0)) {
+ while (!PageNosaveFree(virt_to_page(m))) {
eat_page((void *)m);
m = get_zeroed_page(gfp_mask);
if (!m)
/* Set page flags */
- for_each_zone(zone) {
+ for_each_zone (zone) {
for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn)
SetPageNosaveFree(pfn_to_page(zone_pfn +
zone->zone_start_pfn));
/* Relocate colliding pages */
for_each_pb_page (pbpage, pblist) {
- if (does_collide_order((unsigned long)pbpage, 0)) {
+ if (!PageNosaveFree(virt_to_page((unsigned long)pbpage))) {
m = (void *)get_usable_page(GFP_ATOMIC | __GFP_COLD);
if (!m) {
error = -ENOMEM;
static const char * sanity_check(void)
{
dump_info();
- if(swsusp_info.version_code != LINUX_VERSION_CODE)
+ if (swsusp_info.version_code != LINUX_VERSION_CODE)
return "kernel version";
- if(swsusp_info.num_physpages != num_physpages)
+ if (swsusp_info.num_physpages != num_physpages)
return "memory size";
if (strcmp(swsusp_info.uts.sysname,system_utsname.sysname))
return "system type";
return "version";
if (strcmp(swsusp_info.uts.machine,system_utsname.machine))
return "machine";
- if(swsusp_info.cpus != num_online_cpus())
+#if 0
+ /* We can't use number of online CPUs when we use hotplug to remove them ;-))) */
+ if (swsusp_info.cpus != num_possible_cpus())
return "number of cpus";
+#endif
return NULL;
}
return error;
if (!memcmp(SWSUSP_SIG, swsusp_header.sig, 10)) {
memcpy(swsusp_header.sig, swsusp_header.orig_sig, 10);
+ memcpy(key_iv, swsusp_header.key_iv, MAXKEY+MAXIV);
+ memset(swsusp_header.key_iv, 0, MAXKEY+MAXIV);
/*
* Reset swap signature now.
*/
error = bio_write_page(0, &swsusp_header);
} else {
- printk(KERN_ERR "swsusp: Suspend partition has wrong signature?\n");
return -EINVAL;
}
if (!error)
int error = 0;
int i = 0;
int mod = swsusp_info.image_pages / 100;
+ void *tfm;
+
+ if ((error = crypto_init(0, &tfm)))
+ return error;
if (!mod)
mod = 1;
if (!(i % mod))
printk("\b\b\b\b%3d%%", i / mod);
- error = bio_read_page(swp_offset(p->swap_address),
- (void *)p->address);
- if (error)
+ if ((error = crypto_read(p, tfm))) {
+ crypto_exit(tfm);
return error;
+ }
i++;
}
printk("\b\b\b\bdone\n");
+ crypto_exit(tfm);
return error;
}
-extern dev_t name_to_dev_t(const char *line);
-
/**
* read_pagedir - Read page backup list pages from swap
*/
{
int error;
- if (!swsusp_resume_device) {
- if (!strlen(resume_file))
- return -ENOENT;
- swsusp_resume_device = name_to_dev_t(resume_file);
- pr_debug("swsusp: Resume From Partition %s\n", resume_file);
- } else {
- pr_debug("swsusp: Resume From Partition %d:%d\n",
- MAJOR(swsusp_resume_device), MINOR(swsusp_resume_device));
- }
-
resume_bdev = open_by_devnum(swsusp_resume_device, FMODE_READ);
if (!IS_ERR(resume_bdev)) {
set_blocksize(resume_bdev, PAGE_SIZE);
error = read_suspend_image();
blkdev_put(resume_bdev);
+ memset(key_iv, 0, MAXKEY+MAXIV);
if (!error)
pr_debug("swsusp: Reading resume file was successful\n");