Merge branch 'upstream' of master.kernel.org:/pub/scm/linux/kernel/git/jgarzik/libata-dev
[sfrench/cifs-2.6.git] / kernel / power / swsusp.c
index c285fc5a232094c9f7b5e27950525ae942bc99b1..d967e875ee82f974d9f4e8e365aa59270a6f5c61 100644 (file)
@@ -31,6 +31,9 @@
  * 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
@@ -63,6 +66,7 @@
 #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;
 
@@ -102,7 +114,8 @@ static suspend_pagedir_t *pagedir_save;
 #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];
@@ -128,6 +141,131 @@ static struct swsusp_info swsusp_info;
 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;
@@ -139,6 +277,7 @@ static int mark_swapfiles(swp_entry_t prev)
            !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,
                                          swp_entry(root_swap, 0),
@@ -178,9 +317,9 @@ static int swsusp_swap_check(void) /* This is called before saving image */
        len=strlen(resume_file);
        root_swap = 0xFFFF;
 
-       swap_list_lock();
+       spin_lock(&swap_lock);
        for (i=0; i<MAX_SWAPFILES; i++) {
-               if (swap_info[i].flags == 0) {
+               if (!(swap_info[i].flags & SWP_WRITEOK)) {
                        swapfile_used[i]=SWAPFILE_UNUSED;
                } else {
                        if (!len) {
@@ -201,7 +340,7 @@ static int swsusp_swap_check(void) /* This is called before saving image */
                        }
                }
        }
-       swap_list_unlock();
+       spin_unlock(&swap_lock);
        return (root_swap != 0xffff) ? 0 : -ENODEV;
 }
 
@@ -215,12 +354,12 @@ static void lock_swapdevices(void)
 {
        int i;
 
-       swap_list_lock();
+       spin_lock(&swap_lock);
        for (i = 0; i< MAX_SWAPFILES; i++)
                if (swapfile_used[i] == SWAPFILE_IGNORED) {
-                       swap_info[i].flags ^= 0xFF;
+                       swap_info[i].flags ^= SWP_WRITEOK;
                }
-       swap_list_unlock();
+       spin_unlock(&swap_lock);
 }
 
 /**
@@ -285,6 +424,10 @@ static int data_write(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;
@@ -293,11 +436,14 @@ static int data_write(void)
        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;
 }
 
@@ -384,7 +530,6 @@ static int write_pagedir(void)
  *     write_suspend_image - Write entire image and metadata.
  *
  */
-
 static int write_suspend_image(void)
 {
        int error;
@@ -399,6 +544,7 @@ static int write_suspend_image(void)
        if ((error = close_swap()))
                goto FreePagedir;
  Done:
+       memset(key_iv, 0, MAXKEY+MAXIV);
        return error;
  FreePagedir:
        free_pagedir_entries();
@@ -590,18 +736,7 @@ static void copy_data_pages(void)
 
 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);
 }
 
 /**
@@ -869,13 +1004,6 @@ extern asmlinkage int swsusp_arch_resume(void);
 
 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();
 }
 
@@ -894,12 +1022,19 @@ int swsusp_suspend(void)
        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);
@@ -924,6 +1059,7 @@ int swsusp_resume(void)
        BUG_ON(!error);
        restore_processor_state();
        restore_highmem();
+       touch_softlockup_watchdog();
        device_power_up();
        local_irq_enable();
        return error;
@@ -1166,9 +1302,9 @@ static int bio_write_page(pgoff_t page_off, void * page)
 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";
@@ -1179,7 +1315,8 @@ static const char * sanity_check(void)
        if (strcmp(swsusp_info.uts.machine,system_utsname.machine))
                return "machine";
 #if 0
-       if(swsusp_info.cpus != num_online_cpus())
+       /* 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;
@@ -1212,13 +1349,14 @@ static int check_sig(void)
                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)
@@ -1239,6 +1377,10 @@ static int data_read(struct pbe *pblist)
        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;
@@ -1250,19 +1392,18 @@ static int data_read(struct pbe *pblist)
                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
  */
@@ -1356,16 +1497,6 @@ int swsusp_check(void)
 {
        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);
@@ -1397,6 +1528,7 @@ int swsusp_read(void)
 
        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");