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: Bojan Smojver <bojan@rexursive.com>
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
This commit is contained in:
Bojan Smojver 2010-09-09 23:06:23 +02:00 committed by Rafael J. Wysocki
parent 05aa55dddb
commit f996fc9671
6 changed files with 306 additions and 8 deletions

View 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).
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
rhash_entries= [KNL,NET]

View file

@ -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
``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
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
of the hardware, write to the filesystems, etc.

View file

@ -137,6 +137,8 @@ config SUSPEND_FREEZER
config HIBERNATION
bool "Hibernation (aka 'suspend to disk')"
depends on PM && SWAP && ARCH_HIBERNATION_POSSIBLE
select LZO_COMPRESS
select LZO_DECOMPRESS
select SUSPEND_NVS if HAS_IOMEM
---help---
Enable the suspend to disk (STD) functionality, which is usually

View file

@ -29,6 +29,7 @@
#include "power.h"
static int nocompress = 0;
static int noresume = 0;
static char resume_file[256] = CONFIG_PM_STD_PARTITION;
dev_t swsusp_resume_device;
@ -638,6 +639,8 @@ int hibernate(void)
if (hibernation_mode == HIBERNATION_PLATFORM)
flags |= SF_PLATFORM_MODE;
if (nocompress)
flags |= SF_NOCOMPRESS_MODE;
pr_debug("PM: writing image.\n");
error = swsusp_write(flags);
swsusp_free();
@ -1004,6 +1007,15 @@ static int __init resume_offset_setup(char *str)
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)
{
noresume = 1;
@ -1013,3 +1025,4 @@ static int __init noresume_setup(char *str)
__setup("noresume", noresume_setup);
__setup("resume_offset=", resume_offset_setup);
__setup("resume=", resume_setup);
__setup("hibernate=", hibernate_setup);

View file

@ -134,6 +134,7 @@ extern int swsusp_swap_in_use(void);
* the image header.
*/
#define SF_PLATFORM_MODE 1
#define SF_NOCOMPRESS_MODE 2
/* kernel/power/hibernate.c */
extern int swsusp_check(void);

View file

@ -24,6 +24,8 @@
#include <linux/swapops.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/lzo.h>
#include <linux/vmalloc.h>
#include "power.h"
@ -357,6 +359,18 @@ static int swap_writer_finish(struct swap_map_handle *handle,
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
*/
@ -404,6 +418,137 @@ static int save_image(struct swap_map_handle *handle,
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.
*
@ -411,12 +556,16 @@ static int save_image(struct swap_map_handle *handle,
* 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 required;
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)
printk(KERN_ERR "PM: Cannot get swap writer\n");
return error;
}
if (!enough_swap(pages)) {
if (!enough_swap(pages, flags)) {
printk(KERN_ERR "PM: Not enough free swap\n");
error = -ENOSPC;
goto out_finish;
@ -458,8 +607,11 @@ int swsusp_write(unsigned int flags)
}
header = (struct swsusp_info *)data_of(snapshot);
error = swap_write_page(&handle, header, NULL);
if (!error)
error = save_image(&handle, &snapshot, pages - 1);
if (!error) {
error = (flags & SF_NOCOMPRESS_MODE) ?
save_image(&handle, &snapshot, pages - 1) :
save_image_lzo(&handle, &snapshot, pages - 1);
}
out_finish:
error = swap_writer_finish(&handle, flags, error);
return error;
@ -589,6 +741,127 @@ static int load_image(struct swap_map_handle *handle,
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.
* @flags_p: flags passed by the "frozen" kernel in the image header should
@ -612,8 +885,11 @@ int swsusp_read(unsigned int *flags_p)
goto end;
if (!error)
error = swap_read_page(&handle, header, NULL);
if (!error)
error = load_image(&handle, &snapshot, header->pages - 1);
if (!error) {
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);
end:
if (!error)