diff options
author | Rafael J. Wysocki <rjw@sisk.pl> | 2006-03-23 05:59:59 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-03-23 10:38:07 -0500 |
commit | f577eb30afdc68233f25d4d82b04102129262365 (patch) | |
tree | 25d3c2fa8dfbf42fd0d4776a36166736fcc1446a /kernel/power/snapshot.c | |
parent | 2b322ce210aec74ae0d02938d3a01e29fe079469 (diff) |
[PATCH] swsusp: low level interface
Introduce the low level interface that can be used for handling the
snapshot of the system memory by the in-kernel swap-writing/reading code of
swsusp and the userland interface code (to be introduced shortly).
Also change the way in which swsusp records the allocated swap pages and,
consequently, simplifies the in-kernel swap-writing/reading code (this is
necessary for the userland interface too). To this end, it introduces two
helper functions in mm/swapfile.c, so that the swsusp code does not refer
directly to the swap internals.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Adrian Bunk <bunk@stusta.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'kernel/power/snapshot.c')
-rw-r--r-- | kernel/power/snapshot.c | 326 |
1 files changed, 321 insertions, 5 deletions
diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c index 8d5a5986d621..cc349437fb72 100644 --- a/kernel/power/snapshot.c +++ b/kernel/power/snapshot.c | |||
@@ -10,6 +10,7 @@ | |||
10 | */ | 10 | */ |
11 | 11 | ||
12 | 12 | ||
13 | #include <linux/version.h> | ||
13 | #include <linux/module.h> | 14 | #include <linux/module.h> |
14 | #include <linux/mm.h> | 15 | #include <linux/mm.h> |
15 | #include <linux/suspend.h> | 16 | #include <linux/suspend.h> |
@@ -34,7 +35,8 @@ | |||
34 | #include "power.h" | 35 | #include "power.h" |
35 | 36 | ||
36 | struct pbe *pagedir_nosave; | 37 | struct pbe *pagedir_nosave; |
37 | unsigned int nr_copy_pages; | 38 | static unsigned int nr_copy_pages; |
39 | static unsigned int nr_meta_pages; | ||
38 | 40 | ||
39 | #ifdef CONFIG_HIGHMEM | 41 | #ifdef CONFIG_HIGHMEM |
40 | unsigned int count_highmem_pages(void) | 42 | unsigned int count_highmem_pages(void) |
@@ -235,7 +237,7 @@ static void copy_data_pages(struct pbe *pblist) | |||
235 | * free_pagedir - free pages allocated with alloc_pagedir() | 237 | * free_pagedir - free pages allocated with alloc_pagedir() |
236 | */ | 238 | */ |
237 | 239 | ||
238 | void free_pagedir(struct pbe *pblist) | 240 | static void free_pagedir(struct pbe *pblist) |
239 | { | 241 | { |
240 | struct pbe *pbe; | 242 | struct pbe *pbe; |
241 | 243 | ||
@@ -301,7 +303,7 @@ struct eaten_page { | |||
301 | 303 | ||
302 | static struct eaten_page *eaten_pages = NULL; | 304 | static struct eaten_page *eaten_pages = NULL; |
303 | 305 | ||
304 | void release_eaten_pages(void) | 306 | static void release_eaten_pages(void) |
305 | { | 307 | { |
306 | struct eaten_page *p, *q; | 308 | struct eaten_page *p, *q; |
307 | 309 | ||
@@ -376,7 +378,6 @@ struct pbe *alloc_pagedir(unsigned int nr_pages, gfp_t gfp_mask, int safe_needed | |||
376 | if (!nr_pages) | 378 | if (!nr_pages) |
377 | return NULL; | 379 | return NULL; |
378 | 380 | ||
379 | pr_debug("alloc_pagedir(): nr_pages = %d\n", nr_pages); | ||
380 | pblist = alloc_image_page(gfp_mask, safe_needed); | 381 | pblist = alloc_image_page(gfp_mask, safe_needed); |
381 | /* FIXME: rewrite this ugly loop */ | 382 | /* FIXME: rewrite this ugly loop */ |
382 | for (pbe = pblist, num = PBES_PER_PAGE; pbe && num < nr_pages; | 383 | for (pbe = pblist, num = PBES_PER_PAGE; pbe && num < nr_pages; |
@@ -414,6 +415,9 @@ void swsusp_free(void) | |||
414 | } | 415 | } |
415 | } | 416 | } |
416 | } | 417 | } |
418 | nr_copy_pages = 0; | ||
419 | nr_meta_pages = 0; | ||
420 | pagedir_nosave = NULL; | ||
417 | } | 421 | } |
418 | 422 | ||
419 | 423 | ||
@@ -437,7 +441,7 @@ static int enough_free_mem(unsigned int nr_pages) | |||
437 | (nr_pages + PBES_PER_PAGE - 1) / PBES_PER_PAGE); | 441 | (nr_pages + PBES_PER_PAGE - 1) / PBES_PER_PAGE); |
438 | } | 442 | } |
439 | 443 | ||
440 | int alloc_data_pages(struct pbe *pblist, gfp_t gfp_mask, int safe_needed) | 444 | static int alloc_data_pages(struct pbe *pblist, gfp_t gfp_mask, int safe_needed) |
441 | { | 445 | { |
442 | struct pbe *p; | 446 | struct pbe *p; |
443 | 447 | ||
@@ -504,7 +508,319 @@ asmlinkage int swsusp_save(void) | |||
504 | */ | 508 | */ |
505 | 509 | ||
506 | nr_copy_pages = nr_pages; | 510 | nr_copy_pages = nr_pages; |
511 | nr_meta_pages = (nr_pages * sizeof(long) + PAGE_SIZE - 1) >> PAGE_SHIFT; | ||
507 | 512 | ||
508 | printk("swsusp: critical section/: done (%d pages copied)\n", nr_pages); | 513 | printk("swsusp: critical section/: done (%d pages copied)\n", nr_pages); |
509 | return 0; | 514 | return 0; |
510 | } | 515 | } |
516 | |||
517 | static void init_header(struct swsusp_info *info) | ||
518 | { | ||
519 | memset(info, 0, sizeof(struct swsusp_info)); | ||
520 | info->version_code = LINUX_VERSION_CODE; | ||
521 | info->num_physpages = num_physpages; | ||
522 | memcpy(&info->uts, &system_utsname, sizeof(system_utsname)); | ||
523 | info->cpus = num_online_cpus(); | ||
524 | info->image_pages = nr_copy_pages; | ||
525 | info->pages = nr_copy_pages + nr_meta_pages + 1; | ||
526 | } | ||
527 | |||
528 | /** | ||
529 | * pack_orig_addresses - the .orig_address fields of the PBEs from the | ||
530 | * list starting at @pbe are stored in the array @buf[] (1 page) | ||
531 | */ | ||
532 | |||
533 | static inline struct pbe *pack_orig_addresses(unsigned long *buf, struct pbe *pbe) | ||
534 | { | ||
535 | int j; | ||
536 | |||
537 | for (j = 0; j < PAGE_SIZE / sizeof(long) && pbe; j++) { | ||
538 | buf[j] = pbe->orig_address; | ||
539 | pbe = pbe->next; | ||
540 | } | ||
541 | if (!pbe) | ||
542 | for (; j < PAGE_SIZE / sizeof(long); j++) | ||
543 | buf[j] = 0; | ||
544 | return pbe; | ||
545 | } | ||
546 | |||
547 | /** | ||
548 | * snapshot_read_next - used for reading the system memory snapshot. | ||
549 | * | ||
550 | * On the first call to it @handle should point to a zeroed | ||
551 | * snapshot_handle structure. The structure gets updated and a pointer | ||
552 | * to it should be passed to this function every next time. | ||
553 | * | ||
554 | * The @count parameter should contain the number of bytes the caller | ||
555 | * wants to read from the snapshot. It must not be zero. | ||
556 | * | ||
557 | * On success the function returns a positive number. Then, the caller | ||
558 | * is allowed to read up to the returned number of bytes from the memory | ||
559 | * location computed by the data_of() macro. The number returned | ||
560 | * may be smaller than @count, but this only happens if the read would | ||
561 | * cross a page boundary otherwise. | ||
562 | * | ||
563 | * The function returns 0 to indicate the end of data stream condition, | ||
564 | * and a negative number is returned on error. In such cases the | ||
565 | * structure pointed to by @handle is not updated and should not be used | ||
566 | * any more. | ||
567 | */ | ||
568 | |||
569 | int snapshot_read_next(struct snapshot_handle *handle, size_t count) | ||
570 | { | ||
571 | static unsigned long *buffer; | ||
572 | |||
573 | if (handle->page > nr_meta_pages + nr_copy_pages) | ||
574 | return 0; | ||
575 | if (!buffer) { | ||
576 | /* This makes the buffer be freed by swsusp_free() */ | ||
577 | buffer = alloc_image_page(GFP_ATOMIC, 0); | ||
578 | if (!buffer) | ||
579 | return -ENOMEM; | ||
580 | } | ||
581 | if (!handle->offset) { | ||
582 | init_header((struct swsusp_info *)buffer); | ||
583 | handle->buffer = buffer; | ||
584 | handle->pbe = pagedir_nosave; | ||
585 | } | ||
586 | if (handle->prev < handle->page) { | ||
587 | if (handle->page <= nr_meta_pages) { | ||
588 | handle->pbe = pack_orig_addresses(buffer, handle->pbe); | ||
589 | if (!handle->pbe) | ||
590 | handle->pbe = pagedir_nosave; | ||
591 | } else { | ||
592 | handle->buffer = (void *)handle->pbe->address; | ||
593 | handle->pbe = handle->pbe->next; | ||
594 | } | ||
595 | handle->prev = handle->page; | ||
596 | } | ||
597 | handle->buf_offset = handle->page_offset; | ||
598 | if (handle->page_offset + count >= PAGE_SIZE) { | ||
599 | count = PAGE_SIZE - handle->page_offset; | ||
600 | handle->page_offset = 0; | ||
601 | handle->page++; | ||
602 | } else { | ||
603 | handle->page_offset += count; | ||
604 | } | ||
605 | handle->offset += count; | ||
606 | return count; | ||
607 | } | ||
608 | |||
609 | /** | ||
610 | * mark_unsafe_pages - mark the pages that cannot be used for storing | ||
611 | * the image during resume, because they conflict with the pages that | ||
612 | * had been used before suspend | ||
613 | */ | ||
614 | |||
615 | static int mark_unsafe_pages(struct pbe *pblist) | ||
616 | { | ||
617 | struct zone *zone; | ||
618 | unsigned long zone_pfn; | ||
619 | struct pbe *p; | ||
620 | |||
621 | if (!pblist) /* a sanity check */ | ||
622 | return -EINVAL; | ||
623 | |||
624 | /* Clear page flags */ | ||
625 | for_each_zone (zone) { | ||
626 | for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn) | ||
627 | if (pfn_valid(zone_pfn + zone->zone_start_pfn)) | ||
628 | ClearPageNosaveFree(pfn_to_page(zone_pfn + | ||
629 | zone->zone_start_pfn)); | ||
630 | } | ||
631 | |||
632 | /* Mark orig addresses */ | ||
633 | for_each_pbe (p, pblist) { | ||
634 | if (virt_addr_valid(p->orig_address)) | ||
635 | SetPageNosaveFree(virt_to_page(p->orig_address)); | ||
636 | else | ||
637 | return -EFAULT; | ||
638 | } | ||
639 | |||
640 | return 0; | ||
641 | } | ||
642 | |||
643 | static void copy_page_backup_list(struct pbe *dst, struct pbe *src) | ||
644 | { | ||
645 | /* We assume both lists contain the same number of elements */ | ||
646 | while (src) { | ||
647 | dst->orig_address = src->orig_address; | ||
648 | dst = dst->next; | ||
649 | src = src->next; | ||
650 | } | ||
651 | } | ||
652 | |||
653 | static int check_header(struct swsusp_info *info) | ||
654 | { | ||
655 | char *reason = NULL; | ||
656 | |||
657 | if (info->version_code != LINUX_VERSION_CODE) | ||
658 | reason = "kernel version"; | ||
659 | if (info->num_physpages != num_physpages) | ||
660 | reason = "memory size"; | ||
661 | if (strcmp(info->uts.sysname,system_utsname.sysname)) | ||
662 | reason = "system type"; | ||
663 | if (strcmp(info->uts.release,system_utsname.release)) | ||
664 | reason = "kernel release"; | ||
665 | if (strcmp(info->uts.version,system_utsname.version)) | ||
666 | reason = "version"; | ||
667 | if (strcmp(info->uts.machine,system_utsname.machine)) | ||
668 | reason = "machine"; | ||
669 | if (reason) { | ||
670 | printk(KERN_ERR "swsusp: Resume mismatch: %s\n", reason); | ||
671 | return -EPERM; | ||
672 | } | ||
673 | return 0; | ||
674 | } | ||
675 | |||
676 | /** | ||
677 | * load header - check the image header and copy data from it | ||
678 | */ | ||
679 | |||
680 | static int load_header(struct snapshot_handle *handle, | ||
681 | struct swsusp_info *info) | ||
682 | { | ||
683 | int error; | ||
684 | struct pbe *pblist; | ||
685 | |||
686 | error = check_header(info); | ||
687 | if (!error) { | ||
688 | pblist = alloc_pagedir(info->image_pages, GFP_ATOMIC, 0); | ||
689 | if (!pblist) | ||
690 | return -ENOMEM; | ||
691 | pagedir_nosave = pblist; | ||
692 | handle->pbe = pblist; | ||
693 | nr_copy_pages = info->image_pages; | ||
694 | nr_meta_pages = info->pages - info->image_pages - 1; | ||
695 | } | ||
696 | return error; | ||
697 | } | ||
698 | |||
699 | /** | ||
700 | * unpack_orig_addresses - copy the elements of @buf[] (1 page) to | ||
701 | * the PBEs in the list starting at @pbe | ||
702 | */ | ||
703 | |||
704 | static inline struct pbe *unpack_orig_addresses(unsigned long *buf, | ||
705 | struct pbe *pbe) | ||
706 | { | ||
707 | int j; | ||
708 | |||
709 | for (j = 0; j < PAGE_SIZE / sizeof(long) && pbe; j++) { | ||
710 | pbe->orig_address = buf[j]; | ||
711 | pbe = pbe->next; | ||
712 | } | ||
713 | return pbe; | ||
714 | } | ||
715 | |||
716 | /** | ||
717 | * create_image - use metadata contained in the PBE list | ||
718 | * pointed to by pagedir_nosave to mark the pages that will | ||
719 | * be overwritten in the process of restoring the system | ||
720 | * memory state from the image and allocate memory for | ||
721 | * the image avoiding these pages | ||
722 | */ | ||
723 | |||
724 | static int create_image(struct snapshot_handle *handle) | ||
725 | { | ||
726 | int error = 0; | ||
727 | struct pbe *p, *pblist; | ||
728 | |||
729 | p = pagedir_nosave; | ||
730 | error = mark_unsafe_pages(p); | ||
731 | if (!error) { | ||
732 | pblist = alloc_pagedir(nr_copy_pages, GFP_ATOMIC, 1); | ||
733 | if (pblist) | ||
734 | copy_page_backup_list(pblist, p); | ||
735 | free_pagedir(p); | ||
736 | if (!pblist) | ||
737 | error = -ENOMEM; | ||
738 | } | ||
739 | if (!error) | ||
740 | error = alloc_data_pages(pblist, GFP_ATOMIC, 1); | ||
741 | if (!error) { | ||
742 | release_eaten_pages(); | ||
743 | pagedir_nosave = pblist; | ||
744 | } else { | ||
745 | pagedir_nosave = NULL; | ||
746 | handle->pbe = NULL; | ||
747 | nr_copy_pages = 0; | ||
748 | nr_meta_pages = 0; | ||
749 | } | ||
750 | return error; | ||
751 | } | ||
752 | |||
753 | /** | ||
754 | * snapshot_write_next - used for writing the system memory snapshot. | ||
755 | * | ||
756 | * On the first call to it @handle should point to a zeroed | ||
757 | * snapshot_handle structure. The structure gets updated and a pointer | ||
758 | * to it should be passed to this function every next time. | ||
759 | * | ||
760 | * The @count parameter should contain the number of bytes the caller | ||
761 | * wants to write to the image. It must not be zero. | ||
762 | * | ||
763 | * On success the function returns a positive number. Then, the caller | ||
764 | * is allowed to write up to the returned number of bytes to the memory | ||
765 | * location computed by the data_of() macro. The number returned | ||
766 | * may be smaller than @count, but this only happens if the write would | ||
767 | * cross a page boundary otherwise. | ||
768 | * | ||
769 | * The function returns 0 to indicate the "end of file" condition, | ||
770 | * and a negative number is returned on error. In such cases the | ||
771 | * structure pointed to by @handle is not updated and should not be used | ||
772 | * any more. | ||
773 | */ | ||
774 | |||
775 | int snapshot_write_next(struct snapshot_handle *handle, size_t count) | ||
776 | { | ||
777 | static unsigned long *buffer; | ||
778 | int error = 0; | ||
779 | |||
780 | if (handle->prev && handle->page > nr_meta_pages + nr_copy_pages) | ||
781 | return 0; | ||
782 | if (!buffer) { | ||
783 | /* This makes the buffer be freed by swsusp_free() */ | ||
784 | buffer = alloc_image_page(GFP_ATOMIC, 0); | ||
785 | if (!buffer) | ||
786 | return -ENOMEM; | ||
787 | } | ||
788 | if (!handle->offset) | ||
789 | handle->buffer = buffer; | ||
790 | if (handle->prev < handle->page) { | ||
791 | if (!handle->prev) { | ||
792 | error = load_header(handle, (struct swsusp_info *)buffer); | ||
793 | if (error) | ||
794 | return error; | ||
795 | } else if (handle->prev <= nr_meta_pages) { | ||
796 | handle->pbe = unpack_orig_addresses(buffer, handle->pbe); | ||
797 | if (!handle->pbe) { | ||
798 | error = create_image(handle); | ||
799 | if (error) | ||
800 | return error; | ||
801 | handle->pbe = pagedir_nosave; | ||
802 | handle->buffer = (void *)handle->pbe->address; | ||
803 | } | ||
804 | } else { | ||
805 | handle->pbe = handle->pbe->next; | ||
806 | handle->buffer = (void *)handle->pbe->address; | ||
807 | } | ||
808 | handle->prev = handle->page; | ||
809 | } | ||
810 | handle->buf_offset = handle->page_offset; | ||
811 | if (handle->page_offset + count >= PAGE_SIZE) { | ||
812 | count = PAGE_SIZE - handle->page_offset; | ||
813 | handle->page_offset = 0; | ||
814 | handle->page++; | ||
815 | } else { | ||
816 | handle->page_offset += count; | ||
817 | } | ||
818 | handle->offset += count; | ||
819 | return count; | ||
820 | } | ||
821 | |||
822 | int snapshot_image_loaded(struct snapshot_handle *handle) | ||
823 | { | ||
824 | return !(!handle->pbe || handle->pbe->next || !nr_copy_pages || | ||
825 | handle->page <= nr_meta_pages + nr_copy_pages); | ||
826 | } | ||