Commit f996fc96 authored by Bojan Smojver's avatar Bojan Smojver Committed by Rafael J. Wysocki

PM / Hibernate: Compress hibernation image with LZO

Compress hibernation image with LZO in order to save on I/O and
therefore time to hibernate/thaw.

[rjw: Added hibernate=nocompress command line option instead of just
 nocompress which would be confusing, fixed a couple of compiler
 warnings, fixed kerneldoc comments, minor cleanups.]
Signed-off-by: default avatarBojan Smojver <bojan@rexursive.com>
Signed-off-by: default avatarRafael J. Wysocki <rjw@sisk.pl>
parent 05aa55dd
...@@ -2165,6 +2165,11 @@ and is between 256 and 4096 characters. It is defined in the file ...@@ -2165,6 +2165,11 @@ and is between 256 and 4096 characters. It is defined in the file
in <PAGE_SIZE> units (needed only for swap files). in <PAGE_SIZE> units (needed only for swap files).
See Documentation/power/swsusp-and-swap-files.txt See Documentation/power/swsusp-and-swap-files.txt
hibernate= [HIBERNATION]
noresume Don't check if there's a hibernation image
present during boot.
nocompress Don't compress/decompress hibernation images.
retain_initrd [RAM] Keep initrd memory after extraction retain_initrd [RAM] Keep initrd memory after extraction
rhash_entries= [KNL,NET] rhash_entries= [KNL,NET]
......
...@@ -66,7 +66,8 @@ swsusp saves the state of the machine into active swaps and then reboots or ...@@ -66,7 +66,8 @@ swsusp saves the state of the machine into active swaps and then reboots or
powerdowns. You must explicitly specify the swap partition to resume from with powerdowns. You must explicitly specify the swap partition to resume from with
``resume='' kernel option. If signature is found it loads and restores saved ``resume='' kernel option. If signature is found it loads and restores saved
state. If the option ``noresume'' is specified as a boot parameter, it skips state. If the option ``noresume'' is specified as a boot parameter, it skips
the resuming. the resuming. If the option ``hibernate=nocompress'' is specified as a boot
parameter, it saves hibernation image without compression.
In the meantime while the system is suspended you should not add/remove any In the meantime while the system is suspended you should not add/remove any
of the hardware, write to the filesystems, etc. of the hardware, write to the filesystems, etc.
......
...@@ -137,6 +137,8 @@ config SUSPEND_FREEZER ...@@ -137,6 +137,8 @@ config SUSPEND_FREEZER
config HIBERNATION config HIBERNATION
bool "Hibernation (aka 'suspend to disk')" bool "Hibernation (aka 'suspend to disk')"
depends on PM && SWAP && ARCH_HIBERNATION_POSSIBLE depends on PM && SWAP && ARCH_HIBERNATION_POSSIBLE
select LZO_COMPRESS
select LZO_DECOMPRESS
select SUSPEND_NVS if HAS_IOMEM select SUSPEND_NVS if HAS_IOMEM
---help--- ---help---
Enable the suspend to disk (STD) functionality, which is usually Enable the suspend to disk (STD) functionality, which is usually
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include "power.h" #include "power.h"
static int nocompress = 0;
static int noresume = 0; static int noresume = 0;
static char resume_file[256] = CONFIG_PM_STD_PARTITION; static char resume_file[256] = CONFIG_PM_STD_PARTITION;
dev_t swsusp_resume_device; dev_t swsusp_resume_device;
...@@ -638,6 +639,8 @@ int hibernate(void) ...@@ -638,6 +639,8 @@ int hibernate(void)
if (hibernation_mode == HIBERNATION_PLATFORM) if (hibernation_mode == HIBERNATION_PLATFORM)
flags |= SF_PLATFORM_MODE; flags |= SF_PLATFORM_MODE;
if (nocompress)
flags |= SF_NOCOMPRESS_MODE;
pr_debug("PM: writing image.\n"); pr_debug("PM: writing image.\n");
error = swsusp_write(flags); error = swsusp_write(flags);
swsusp_free(); swsusp_free();
...@@ -1004,6 +1007,15 @@ static int __init resume_offset_setup(char *str) ...@@ -1004,6 +1007,15 @@ static int __init resume_offset_setup(char *str)
return 1; return 1;
} }
static int __init hibernate_setup(char *str)
{
if (!strncmp(str, "noresume", 8))
noresume = 1;
else if (!strncmp(str, "nocompress", 10))
nocompress = 1;
return 1;
}
static int __init noresume_setup(char *str) static int __init noresume_setup(char *str)
{ {
noresume = 1; noresume = 1;
...@@ -1013,3 +1025,4 @@ static int __init noresume_setup(char *str) ...@@ -1013,3 +1025,4 @@ static int __init noresume_setup(char *str)
__setup("noresume", noresume_setup); __setup("noresume", noresume_setup);
__setup("resume_offset=", resume_offset_setup); __setup("resume_offset=", resume_offset_setup);
__setup("resume=", resume_setup); __setup("resume=", resume_setup);
__setup("hibernate=", hibernate_setup);
...@@ -134,6 +134,7 @@ extern int swsusp_swap_in_use(void); ...@@ -134,6 +134,7 @@ extern int swsusp_swap_in_use(void);
* the image header. * the image header.
*/ */
#define SF_PLATFORM_MODE 1 #define SF_PLATFORM_MODE 1
#define SF_NOCOMPRESS_MODE 2
/* kernel/power/hibernate.c */ /* kernel/power/hibernate.c */
extern int swsusp_check(void); extern int swsusp_check(void);
......
...@@ -24,6 +24,8 @@ ...@@ -24,6 +24,8 @@
#include <linux/swapops.h> #include <linux/swapops.h>
#include <linux/pm.h> #include <linux/pm.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/lzo.h>
#include <linux/vmalloc.h>
#include "power.h" #include "power.h"
...@@ -357,6 +359,18 @@ static int swap_writer_finish(struct swap_map_handle *handle, ...@@ -357,6 +359,18 @@ static int swap_writer_finish(struct swap_map_handle *handle,
return error; return error;
} }
/* We need to remember how much compressed data we need to read. */
#define LZO_HEADER sizeof(size_t)
/* Number of pages/bytes we'll compress at one time. */
#define LZO_UNC_PAGES 32
#define LZO_UNC_SIZE (LZO_UNC_PAGES * PAGE_SIZE)
/* Number of pages/bytes we need for compressed data (worst case). */
#define LZO_CMP_PAGES DIV_ROUND_UP(lzo1x_worst_compress(LZO_UNC_SIZE) + \
LZO_HEADER, PAGE_SIZE)
#define LZO_CMP_SIZE (LZO_CMP_PAGES * PAGE_SIZE)
/** /**
* save_image - save the suspend image data * save_image - save the suspend image data
*/ */
...@@ -404,6 +418,137 @@ static int save_image(struct swap_map_handle *handle, ...@@ -404,6 +418,137 @@ static int save_image(struct swap_map_handle *handle,
return ret; return ret;
} }
/**
* save_image_lzo - Save the suspend image data compressed with LZO.
* @handle: Swap mam handle to use for saving the image.
* @snapshot: Image to read data from.
* @nr_to_write: Number of pages to save.
*/
static int save_image_lzo(struct swap_map_handle *handle,
struct snapshot_handle *snapshot,
unsigned int nr_to_write)
{
unsigned int m;
int ret = 0;
int nr_pages;
int err2;
struct bio *bio;
struct timeval start;
struct timeval stop;
size_t off, unc_len, cmp_len;
unsigned char *unc, *cmp, *wrk, *page;
page = (void *)__get_free_page(__GFP_WAIT | __GFP_HIGH);
if (!page) {
printk(KERN_ERR "PM: Failed to allocate LZO page\n");
return -ENOMEM;
}
wrk = vmalloc(LZO1X_1_MEM_COMPRESS);
if (!wrk) {
printk(KERN_ERR "PM: Failed to allocate LZO workspace\n");
free_page((unsigned long)page);
return -ENOMEM;
}
unc = vmalloc(LZO_UNC_SIZE);
if (!unc) {
printk(KERN_ERR "PM: Failed to allocate LZO uncompressed\n");
vfree(wrk);
free_page((unsigned long)page);
return -ENOMEM;
}
cmp = vmalloc(LZO_CMP_SIZE);
if (!cmp) {
printk(KERN_ERR "PM: Failed to allocate LZO compressed\n");
vfree(unc);
vfree(wrk);
free_page((unsigned long)page);
return -ENOMEM;
}
printk(KERN_INFO
"PM: Compressing and saving image data (%u pages) ... ",
nr_to_write);
m = nr_to_write / 100;
if (!m)
m = 1;
nr_pages = 0;
bio = NULL;
do_gettimeofday(&start);
for (;;) {
for (off = 0; off < LZO_UNC_SIZE; off += PAGE_SIZE) {
ret = snapshot_read_next(snapshot);
if (ret < 0)
goto out_finish;
if (!ret)
break;
memcpy(unc + off, data_of(*snapshot), PAGE_SIZE);
if (!(nr_pages % m))
printk(KERN_CONT "\b\b\b\b%3d%%", nr_pages / m);
nr_pages++;
}
if (!off)
break;
unc_len = off;
ret = lzo1x_1_compress(unc, unc_len,
cmp + LZO_HEADER, &cmp_len, wrk);
if (ret < 0) {
printk(KERN_ERR "PM: LZO compression failed\n");
break;
}
if (unlikely(!cmp_len ||
cmp_len > lzo1x_worst_compress(unc_len))) {
printk(KERN_ERR "PM: Invalid LZO compressed length\n");
ret = -1;
break;
}
*(size_t *)cmp = cmp_len;
/*
* Given we are writing one page at a time to disk, we copy
* that much from the buffer, although the last bit will likely
* be smaller than full page. This is OK - we saved the length
* of the compressed data, so any garbage at the end will be
* discarded when we read it.
*/
for (off = 0; off < LZO_HEADER + cmp_len; off += PAGE_SIZE) {
memcpy(page, cmp + off, PAGE_SIZE);
ret = swap_write_page(handle, page, &bio);
if (ret)
goto out_finish;
}
}
out_finish:
err2 = hib_wait_on_bio_chain(&bio);
do_gettimeofday(&stop);
if (!ret)
ret = err2;
if (!ret)
printk(KERN_CONT "\b\b\b\bdone\n");
else
printk(KERN_CONT "\n");
swsusp_show_speed(&start, &stop, nr_to_write, "Wrote");
vfree(cmp);
vfree(unc);
vfree(wrk);
free_page((unsigned long)page);
return ret;
}
/** /**
* enough_swap - Make sure we have enough swap to save the image. * enough_swap - Make sure we have enough swap to save the image.
* *
...@@ -411,12 +556,16 @@ static int save_image(struct swap_map_handle *handle, ...@@ -411,12 +556,16 @@ static int save_image(struct swap_map_handle *handle,
* space avaiable from the resume partition. * space avaiable from the resume partition.
*/ */
static int enough_swap(unsigned int nr_pages) static int enough_swap(unsigned int nr_pages, unsigned int flags)
{ {
unsigned int free_swap = count_swap_pages(root_swap, 1); unsigned int free_swap = count_swap_pages(root_swap, 1);
unsigned int required;
pr_debug("PM: Free swap pages: %u\n", free_swap); pr_debug("PM: Free swap pages: %u\n", free_swap);
return free_swap > nr_pages + PAGES_FOR_IO;
required = PAGES_FOR_IO + ((flags & SF_NOCOMPRESS_MODE) ?
nr_pages : (nr_pages * LZO_CMP_PAGES) / LZO_UNC_PAGES + 1);
return free_swap > required;
} }
/** /**
...@@ -443,7 +592,7 @@ int swsusp_write(unsigned int flags) ...@@ -443,7 +592,7 @@ int swsusp_write(unsigned int flags)
printk(KERN_ERR "PM: Cannot get swap writer\n"); printk(KERN_ERR "PM: Cannot get swap writer\n");
return error; return error;
} }
if (!enough_swap(pages)) { if (!enough_swap(pages, flags)) {
printk(KERN_ERR "PM: Not enough free swap\n"); printk(KERN_ERR "PM: Not enough free swap\n");
error = -ENOSPC; error = -ENOSPC;
goto out_finish; goto out_finish;
...@@ -458,8 +607,11 @@ int swsusp_write(unsigned int flags) ...@@ -458,8 +607,11 @@ int swsusp_write(unsigned int flags)
} }
header = (struct swsusp_info *)data_of(snapshot); header = (struct swsusp_info *)data_of(snapshot);
error = swap_write_page(&handle, header, NULL); error = swap_write_page(&handle, header, NULL);
if (!error) if (!error) {
error = save_image(&handle, &snapshot, pages - 1); error = (flags & SF_NOCOMPRESS_MODE) ?
save_image(&handle, &snapshot, pages - 1) :
save_image_lzo(&handle, &snapshot, pages - 1);
}
out_finish: out_finish:
error = swap_writer_finish(&handle, flags, error); error = swap_writer_finish(&handle, flags, error);
return error; return error;
...@@ -589,6 +741,127 @@ static int load_image(struct swap_map_handle *handle, ...@@ -589,6 +741,127 @@ static int load_image(struct swap_map_handle *handle,
return error; return error;
} }
/**
* load_image_lzo - Load compressed image data and decompress them with LZO.
* @handle: Swap map handle to use for loading data.
* @snapshot: Image to copy uncompressed data into.
* @nr_to_read: Number of pages to load.
*/
static int load_image_lzo(struct swap_map_handle *handle,
struct snapshot_handle *snapshot,
unsigned int nr_to_read)
{
unsigned int m;
int error = 0;
struct timeval start;
struct timeval stop;
unsigned nr_pages;
size_t off, unc_len, cmp_len;
unsigned char *unc, *cmp, *page;
page = (void *)__get_free_page(__GFP_WAIT | __GFP_HIGH);
if (!page) {
printk(KERN_ERR "PM: Failed to allocate LZO page\n");
return -ENOMEM;
}
unc = vmalloc(LZO_UNC_SIZE);
if (!unc) {
printk(KERN_ERR "PM: Failed to allocate LZO uncompressed\n");
free_page((unsigned long)page);
return -ENOMEM;
}
cmp = vmalloc(LZO_CMP_SIZE);
if (!cmp) {
printk(KERN_ERR "PM: Failed to allocate LZO compressed\n");
vfree(unc);
free_page((unsigned long)page);
return -ENOMEM;
}
printk(KERN_INFO
"PM: Loading and decompressing image data (%u pages) ... ",
nr_to_read);
m = nr_to_read / 100;
if (!m)
m = 1;
nr_pages = 0;
do_gettimeofday(&start);
error = snapshot_write_next(snapshot);
if (error <= 0)
goto out_finish;
for (;;) {
error = swap_read_page(handle, page, NULL); /* sync */
if (error)
break;
cmp_len = *(size_t *)page;
if (unlikely(!cmp_len ||
cmp_len > lzo1x_worst_compress(LZO_UNC_SIZE))) {
printk(KERN_ERR "PM: Invalid LZO compressed length\n");
error = -1;
break;
}
memcpy(cmp, page, PAGE_SIZE);
for (off = PAGE_SIZE; off < LZO_HEADER + cmp_len; off += PAGE_SIZE) {
error = swap_read_page(handle, page, NULL); /* sync */
if (error)
goto out_finish;
memcpy(cmp + off, page, PAGE_SIZE);
}
unc_len = LZO_UNC_SIZE;
error = lzo1x_decompress_safe(cmp + LZO_HEADER, cmp_len,
unc, &unc_len);
if (error < 0) {
printk(KERN_ERR "PM: LZO decompression failed\n");
break;
}
if (unlikely(!unc_len ||
unc_len > LZO_UNC_SIZE ||
unc_len & (PAGE_SIZE - 1))) {
printk(KERN_ERR "PM: Invalid LZO uncompressed length\n");
error = -1;
break;
}
for (off = 0; off < unc_len; off += PAGE_SIZE) {
memcpy(data_of(*snapshot), unc + off, PAGE_SIZE);
if (!(nr_pages % m))
printk("\b\b\b\b%3d%%", nr_pages / m);
nr_pages++;
error = snapshot_write_next(snapshot);
if (error <= 0)
goto out_finish;
}
}
out_finish:
do_gettimeofday(&stop);
if (!error) {
printk("\b\b\b\bdone\n");
snapshot_write_finalize(snapshot);
if (!snapshot_image_loaded(snapshot))
error = -ENODATA;
} else
printk("\n");
swsusp_show_speed(&start, &stop, nr_to_read, "Read");
vfree(cmp);
vfree(unc);
free_page((unsigned long)page);
return error;
}
/** /**
* swsusp_read - read the hibernation image. * swsusp_read - read the hibernation image.
* @flags_p: flags passed by the "frozen" kernel in the image header should * @flags_p: flags passed by the "frozen" kernel in the image header should
...@@ -612,8 +885,11 @@ int swsusp_read(unsigned int *flags_p) ...@@ -612,8 +885,11 @@ int swsusp_read(unsigned int *flags_p)
goto end; goto end;
if (!error) if (!error)
error = swap_read_page(&handle, header, NULL); error = swap_read_page(&handle, header, NULL);
if (!error) if (!error) {
error = load_image(&handle, &snapshot, header->pages - 1); error = (*flags_p & SF_NOCOMPRESS_MODE) ?
load_image(&handle, &snapshot, header->pages - 1) :
load_image_lzo(&handle, &snapshot, header->pages - 1);
}
swap_reader_finish(&handle); swap_reader_finish(&handle);
end: end:
if (!error) if (!error)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment