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:
parent
05aa55dddb
commit
f996fc9671
6 changed files with 306 additions and 8 deletions
|
@ -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]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue