Commit f996fc9671d088bd5f52a70f18c64bfe3d0e418f
Committed by
Rafael J. Wysocki
1 parent
05aa55dddb
Exists in
master
and in
4 other branches
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>
Showing 6 changed files with 306 additions and 8 deletions Side-by-side Diff
Documentation/kernel-parameters.txt
... | ... | @@ -2165,6 +2165,11 @@ |
2165 | 2165 | in <PAGE_SIZE> units (needed only for swap files). |
2166 | 2166 | See Documentation/power/swsusp-and-swap-files.txt |
2167 | 2167 | |
2168 | + hibernate= [HIBERNATION] | |
2169 | + noresume Don't check if there's a hibernation image | |
2170 | + present during boot. | |
2171 | + nocompress Don't compress/decompress hibernation images. | |
2172 | + | |
2168 | 2173 | retain_initrd [RAM] Keep initrd memory after extraction |
2169 | 2174 | |
2170 | 2175 | rhash_entries= [KNL,NET] |
Documentation/power/swsusp.txt
... | ... | @@ -66,7 +66,8 @@ |
66 | 66 | powerdowns. You must explicitly specify the swap partition to resume from with |
67 | 67 | ``resume='' kernel option. If signature is found it loads and restores saved |
68 | 68 | state. If the option ``noresume'' is specified as a boot parameter, it skips |
69 | -the resuming. | |
69 | +the resuming. If the option ``hibernate=nocompress'' is specified as a boot | |
70 | +parameter, it saves hibernation image without compression. | |
70 | 71 | |
71 | 72 | In the meantime while the system is suspended you should not add/remove any |
72 | 73 | of the hardware, write to the filesystems, etc. |
kernel/power/Kconfig
... | ... | @@ -137,6 +137,8 @@ |
137 | 137 | config HIBERNATION |
138 | 138 | bool "Hibernation (aka 'suspend to disk')" |
139 | 139 | depends on PM && SWAP && ARCH_HIBERNATION_POSSIBLE |
140 | + select LZO_COMPRESS | |
141 | + select LZO_DECOMPRESS | |
140 | 142 | select SUSPEND_NVS if HAS_IOMEM |
141 | 143 | ---help--- |
142 | 144 | Enable the suspend to disk (STD) functionality, which is usually |
kernel/power/hibernate.c
... | ... | @@ -29,6 +29,7 @@ |
29 | 29 | #include "power.h" |
30 | 30 | |
31 | 31 | |
32 | +static int nocompress = 0; | |
32 | 33 | static int noresume = 0; |
33 | 34 | static char resume_file[256] = CONFIG_PM_STD_PARTITION; |
34 | 35 | dev_t swsusp_resume_device; |
... | ... | @@ -638,6 +639,8 @@ |
638 | 639 | |
639 | 640 | if (hibernation_mode == HIBERNATION_PLATFORM) |
640 | 641 | flags |= SF_PLATFORM_MODE; |
642 | + if (nocompress) | |
643 | + flags |= SF_NOCOMPRESS_MODE; | |
641 | 644 | pr_debug("PM: writing image.\n"); |
642 | 645 | error = swsusp_write(flags); |
643 | 646 | swsusp_free(); |
... | ... | @@ -1004,6 +1007,15 @@ |
1004 | 1007 | return 1; |
1005 | 1008 | } |
1006 | 1009 | |
1010 | +static int __init hibernate_setup(char *str) | |
1011 | +{ | |
1012 | + if (!strncmp(str, "noresume", 8)) | |
1013 | + noresume = 1; | |
1014 | + else if (!strncmp(str, "nocompress", 10)) | |
1015 | + nocompress = 1; | |
1016 | + return 1; | |
1017 | +} | |
1018 | + | |
1007 | 1019 | static int __init noresume_setup(char *str) |
1008 | 1020 | { |
1009 | 1021 | noresume = 1; |
... | ... | @@ -1013,4 +1025,5 @@ |
1013 | 1025 | __setup("noresume", noresume_setup); |
1014 | 1026 | __setup("resume_offset=", resume_offset_setup); |
1015 | 1027 | __setup("resume=", resume_setup); |
1028 | +__setup("hibernate=", hibernate_setup); |
kernel/power/power.h
kernel/power/swap.c
... | ... | @@ -24,6 +24,8 @@ |
24 | 24 | #include <linux/swapops.h> |
25 | 25 | #include <linux/pm.h> |
26 | 26 | #include <linux/slab.h> |
27 | +#include <linux/lzo.h> | |
28 | +#include <linux/vmalloc.h> | |
27 | 29 | |
28 | 30 | #include "power.h" |
29 | 31 | |
... | ... | @@ -357,6 +359,18 @@ |
357 | 359 | return error; |
358 | 360 | } |
359 | 361 | |
362 | +/* We need to remember how much compressed data we need to read. */ | |
363 | +#define LZO_HEADER sizeof(size_t) | |
364 | + | |
365 | +/* Number of pages/bytes we'll compress at one time. */ | |
366 | +#define LZO_UNC_PAGES 32 | |
367 | +#define LZO_UNC_SIZE (LZO_UNC_PAGES * PAGE_SIZE) | |
368 | + | |
369 | +/* Number of pages/bytes we need for compressed data (worst case). */ | |
370 | +#define LZO_CMP_PAGES DIV_ROUND_UP(lzo1x_worst_compress(LZO_UNC_SIZE) + \ | |
371 | + LZO_HEADER, PAGE_SIZE) | |
372 | +#define LZO_CMP_SIZE (LZO_CMP_PAGES * PAGE_SIZE) | |
373 | + | |
360 | 374 | /** |
361 | 375 | * save_image - save the suspend image data |
362 | 376 | */ |
363 | 377 | |
364 | 378 | |
365 | 379 | |
366 | 380 | |
... | ... | @@ -404,19 +418,154 @@ |
404 | 418 | return ret; |
405 | 419 | } |
406 | 420 | |
421 | + | |
407 | 422 | /** |
423 | + * save_image_lzo - Save the suspend image data compressed with LZO. | |
424 | + * @handle: Swap mam handle to use for saving the image. | |
425 | + * @snapshot: Image to read data from. | |
426 | + * @nr_to_write: Number of pages to save. | |
427 | + */ | |
428 | +static int save_image_lzo(struct swap_map_handle *handle, | |
429 | + struct snapshot_handle *snapshot, | |
430 | + unsigned int nr_to_write) | |
431 | +{ | |
432 | + unsigned int m; | |
433 | + int ret = 0; | |
434 | + int nr_pages; | |
435 | + int err2; | |
436 | + struct bio *bio; | |
437 | + struct timeval start; | |
438 | + struct timeval stop; | |
439 | + size_t off, unc_len, cmp_len; | |
440 | + unsigned char *unc, *cmp, *wrk, *page; | |
441 | + | |
442 | + page = (void *)__get_free_page(__GFP_WAIT | __GFP_HIGH); | |
443 | + if (!page) { | |
444 | + printk(KERN_ERR "PM: Failed to allocate LZO page\n"); | |
445 | + return -ENOMEM; | |
446 | + } | |
447 | + | |
448 | + wrk = vmalloc(LZO1X_1_MEM_COMPRESS); | |
449 | + if (!wrk) { | |
450 | + printk(KERN_ERR "PM: Failed to allocate LZO workspace\n"); | |
451 | + free_page((unsigned long)page); | |
452 | + return -ENOMEM; | |
453 | + } | |
454 | + | |
455 | + unc = vmalloc(LZO_UNC_SIZE); | |
456 | + if (!unc) { | |
457 | + printk(KERN_ERR "PM: Failed to allocate LZO uncompressed\n"); | |
458 | + vfree(wrk); | |
459 | + free_page((unsigned long)page); | |
460 | + return -ENOMEM; | |
461 | + } | |
462 | + | |
463 | + cmp = vmalloc(LZO_CMP_SIZE); | |
464 | + if (!cmp) { | |
465 | + printk(KERN_ERR "PM: Failed to allocate LZO compressed\n"); | |
466 | + vfree(unc); | |
467 | + vfree(wrk); | |
468 | + free_page((unsigned long)page); | |
469 | + return -ENOMEM; | |
470 | + } | |
471 | + | |
472 | + printk(KERN_INFO | |
473 | + "PM: Compressing and saving image data (%u pages) ... ", | |
474 | + nr_to_write); | |
475 | + m = nr_to_write / 100; | |
476 | + if (!m) | |
477 | + m = 1; | |
478 | + nr_pages = 0; | |
479 | + bio = NULL; | |
480 | + do_gettimeofday(&start); | |
481 | + for (;;) { | |
482 | + for (off = 0; off < LZO_UNC_SIZE; off += PAGE_SIZE) { | |
483 | + ret = snapshot_read_next(snapshot); | |
484 | + if (ret < 0) | |
485 | + goto out_finish; | |
486 | + | |
487 | + if (!ret) | |
488 | + break; | |
489 | + | |
490 | + memcpy(unc + off, data_of(*snapshot), PAGE_SIZE); | |
491 | + | |
492 | + if (!(nr_pages % m)) | |
493 | + printk(KERN_CONT "\b\b\b\b%3d%%", nr_pages / m); | |
494 | + nr_pages++; | |
495 | + } | |
496 | + | |
497 | + if (!off) | |
498 | + break; | |
499 | + | |
500 | + unc_len = off; | |
501 | + ret = lzo1x_1_compress(unc, unc_len, | |
502 | + cmp + LZO_HEADER, &cmp_len, wrk); | |
503 | + if (ret < 0) { | |
504 | + printk(KERN_ERR "PM: LZO compression failed\n"); | |
505 | + break; | |
506 | + } | |
507 | + | |
508 | + if (unlikely(!cmp_len || | |
509 | + cmp_len > lzo1x_worst_compress(unc_len))) { | |
510 | + printk(KERN_ERR "PM: Invalid LZO compressed length\n"); | |
511 | + ret = -1; | |
512 | + break; | |
513 | + } | |
514 | + | |
515 | + *(size_t *)cmp = cmp_len; | |
516 | + | |
517 | + /* | |
518 | + * Given we are writing one page at a time to disk, we copy | |
519 | + * that much from the buffer, although the last bit will likely | |
520 | + * be smaller than full page. This is OK - we saved the length | |
521 | + * of the compressed data, so any garbage at the end will be | |
522 | + * discarded when we read it. | |
523 | + */ | |
524 | + for (off = 0; off < LZO_HEADER + cmp_len; off += PAGE_SIZE) { | |
525 | + memcpy(page, cmp + off, PAGE_SIZE); | |
526 | + | |
527 | + ret = swap_write_page(handle, page, &bio); | |
528 | + if (ret) | |
529 | + goto out_finish; | |
530 | + } | |
531 | + } | |
532 | + | |
533 | +out_finish: | |
534 | + err2 = hib_wait_on_bio_chain(&bio); | |
535 | + do_gettimeofday(&stop); | |
536 | + if (!ret) | |
537 | + ret = err2; | |
538 | + if (!ret) | |
539 | + printk(KERN_CONT "\b\b\b\bdone\n"); | |
540 | + else | |
541 | + printk(KERN_CONT "\n"); | |
542 | + swsusp_show_speed(&start, &stop, nr_to_write, "Wrote"); | |
543 | + | |
544 | + vfree(cmp); | |
545 | + vfree(unc); | |
546 | + vfree(wrk); | |
547 | + free_page((unsigned long)page); | |
548 | + | |
549 | + return ret; | |
550 | +} | |
551 | + | |
552 | +/** | |
408 | 553 | * enough_swap - Make sure we have enough swap to save the image. |
409 | 554 | * |
410 | 555 | * Returns TRUE or FALSE after checking the total amount of swap |
411 | 556 | * space avaiable from the resume partition. |
412 | 557 | */ |
413 | 558 | |
414 | -static int enough_swap(unsigned int nr_pages) | |
559 | +static int enough_swap(unsigned int nr_pages, unsigned int flags) | |
415 | 560 | { |
416 | 561 | unsigned int free_swap = count_swap_pages(root_swap, 1); |
562 | + unsigned int required; | |
417 | 563 | |
418 | 564 | pr_debug("PM: Free swap pages: %u\n", free_swap); |
419 | - return free_swap > nr_pages + PAGES_FOR_IO; | |
565 | + | |
566 | + required = PAGES_FOR_IO + ((flags & SF_NOCOMPRESS_MODE) ? | |
567 | + nr_pages : (nr_pages * LZO_CMP_PAGES) / LZO_UNC_PAGES + 1); | |
568 | + return free_swap > required; | |
420 | 569 | } |
421 | 570 | |
422 | 571 | /** |
... | ... | @@ -443,7 +592,7 @@ |
443 | 592 | printk(KERN_ERR "PM: Cannot get swap writer\n"); |
444 | 593 | return error; |
445 | 594 | } |
446 | - if (!enough_swap(pages)) { | |
595 | + if (!enough_swap(pages, flags)) { | |
447 | 596 | printk(KERN_ERR "PM: Not enough free swap\n"); |
448 | 597 | error = -ENOSPC; |
449 | 598 | goto out_finish; |
... | ... | @@ -458,8 +607,11 @@ |
458 | 607 | } |
459 | 608 | header = (struct swsusp_info *)data_of(snapshot); |
460 | 609 | error = swap_write_page(&handle, header, NULL); |
461 | - if (!error) | |
462 | - error = save_image(&handle, &snapshot, pages - 1); | |
610 | + if (!error) { | |
611 | + error = (flags & SF_NOCOMPRESS_MODE) ? | |
612 | + save_image(&handle, &snapshot, pages - 1) : | |
613 | + save_image_lzo(&handle, &snapshot, pages - 1); | |
614 | + } | |
463 | 615 | out_finish: |
464 | 616 | error = swap_writer_finish(&handle, flags, error); |
465 | 617 | return error; |
... | ... | @@ -590,6 +742,127 @@ |
590 | 742 | } |
591 | 743 | |
592 | 744 | /** |
745 | + * load_image_lzo - Load compressed image data and decompress them with LZO. | |
746 | + * @handle: Swap map handle to use for loading data. | |
747 | + * @snapshot: Image to copy uncompressed data into. | |
748 | + * @nr_to_read: Number of pages to load. | |
749 | + */ | |
750 | +static int load_image_lzo(struct swap_map_handle *handle, | |
751 | + struct snapshot_handle *snapshot, | |
752 | + unsigned int nr_to_read) | |
753 | +{ | |
754 | + unsigned int m; | |
755 | + int error = 0; | |
756 | + struct timeval start; | |
757 | + struct timeval stop; | |
758 | + unsigned nr_pages; | |
759 | + size_t off, unc_len, cmp_len; | |
760 | + unsigned char *unc, *cmp, *page; | |
761 | + | |
762 | + page = (void *)__get_free_page(__GFP_WAIT | __GFP_HIGH); | |
763 | + if (!page) { | |
764 | + printk(KERN_ERR "PM: Failed to allocate LZO page\n"); | |
765 | + return -ENOMEM; | |
766 | + } | |
767 | + | |
768 | + unc = vmalloc(LZO_UNC_SIZE); | |
769 | + if (!unc) { | |
770 | + printk(KERN_ERR "PM: Failed to allocate LZO uncompressed\n"); | |
771 | + free_page((unsigned long)page); | |
772 | + return -ENOMEM; | |
773 | + } | |
774 | + | |
775 | + cmp = vmalloc(LZO_CMP_SIZE); | |
776 | + if (!cmp) { | |
777 | + printk(KERN_ERR "PM: Failed to allocate LZO compressed\n"); | |
778 | + vfree(unc); | |
779 | + free_page((unsigned long)page); | |
780 | + return -ENOMEM; | |
781 | + } | |
782 | + | |
783 | + printk(KERN_INFO | |
784 | + "PM: Loading and decompressing image data (%u pages) ... ", | |
785 | + nr_to_read); | |
786 | + m = nr_to_read / 100; | |
787 | + if (!m) | |
788 | + m = 1; | |
789 | + nr_pages = 0; | |
790 | + do_gettimeofday(&start); | |
791 | + | |
792 | + error = snapshot_write_next(snapshot); | |
793 | + if (error <= 0) | |
794 | + goto out_finish; | |
795 | + | |
796 | + for (;;) { | |
797 | + error = swap_read_page(handle, page, NULL); /* sync */ | |
798 | + if (error) | |
799 | + break; | |
800 | + | |
801 | + cmp_len = *(size_t *)page; | |
802 | + if (unlikely(!cmp_len || | |
803 | + cmp_len > lzo1x_worst_compress(LZO_UNC_SIZE))) { | |
804 | + printk(KERN_ERR "PM: Invalid LZO compressed length\n"); | |
805 | + error = -1; | |
806 | + break; | |
807 | + } | |
808 | + | |
809 | + memcpy(cmp, page, PAGE_SIZE); | |
810 | + for (off = PAGE_SIZE; off < LZO_HEADER + cmp_len; off += PAGE_SIZE) { | |
811 | + error = swap_read_page(handle, page, NULL); /* sync */ | |
812 | + if (error) | |
813 | + goto out_finish; | |
814 | + | |
815 | + memcpy(cmp + off, page, PAGE_SIZE); | |
816 | + } | |
817 | + | |
818 | + unc_len = LZO_UNC_SIZE; | |
819 | + error = lzo1x_decompress_safe(cmp + LZO_HEADER, cmp_len, | |
820 | + unc, &unc_len); | |
821 | + if (error < 0) { | |
822 | + printk(KERN_ERR "PM: LZO decompression failed\n"); | |
823 | + break; | |
824 | + } | |
825 | + | |
826 | + if (unlikely(!unc_len || | |
827 | + unc_len > LZO_UNC_SIZE || | |
828 | + unc_len & (PAGE_SIZE - 1))) { | |
829 | + printk(KERN_ERR "PM: Invalid LZO uncompressed length\n"); | |
830 | + error = -1; | |
831 | + break; | |
832 | + } | |
833 | + | |
834 | + for (off = 0; off < unc_len; off += PAGE_SIZE) { | |
835 | + memcpy(data_of(*snapshot), unc + off, PAGE_SIZE); | |
836 | + | |
837 | + if (!(nr_pages % m)) | |
838 | + printk("\b\b\b\b%3d%%", nr_pages / m); | |
839 | + nr_pages++; | |
840 | + | |
841 | + error = snapshot_write_next(snapshot); | |
842 | + if (error <= 0) | |
843 | + goto out_finish; | |
844 | + } | |
845 | + } | |
846 | + | |
847 | +out_finish: | |
848 | + do_gettimeofday(&stop); | |
849 | + if (!error) { | |
850 | + printk("\b\b\b\bdone\n"); | |
851 | + snapshot_write_finalize(snapshot); | |
852 | + if (!snapshot_image_loaded(snapshot)) | |
853 | + error = -ENODATA; | |
854 | + } else | |
855 | + printk("\n"); | |
856 | + swsusp_show_speed(&start, &stop, nr_to_read, "Read"); | |
857 | + | |
858 | + vfree(cmp); | |
859 | + vfree(unc); | |
860 | + free_page((unsigned long)page); | |
861 | + | |
862 | + return error; | |
863 | +} | |
864 | + | |
865 | +/** | |
593 | 866 | * swsusp_read - read the hibernation image. |
594 | 867 | * @flags_p: flags passed by the "frozen" kernel in the image header should |
595 | 868 | * be written into this memeory location |
... | ... | @@ -612,8 +885,11 @@ |
612 | 885 | goto end; |
613 | 886 | if (!error) |
614 | 887 | error = swap_read_page(&handle, header, NULL); |
615 | - if (!error) | |
616 | - error = load_image(&handle, &snapshot, header->pages - 1); | |
888 | + if (!error) { | |
889 | + error = (*flags_p & SF_NOCOMPRESS_MODE) ? | |
890 | + load_image(&handle, &snapshot, header->pages - 1) : | |
891 | + load_image_lzo(&handle, &snapshot, header->pages - 1); | |
892 | + } | |
617 | 893 | swap_reader_finish(&handle); |
618 | 894 | end: |
619 | 895 | if (!error) |