[PATCH] swsusp: introduce the swap map structure

This patch introduces the swap map structure that can be used by swsusp for
keeping tracks of data pages written to the swap.   The structure itself is
described in a comment within the patch.

The overall idea is to reduce the amount of metadata written to the swap and
to write and read the image pages sequentially, in a file-alike way.  This
makes the swap-handling part of swsusp fairly independent of its
snapshot-handling part and will hopefully allow us to completely separate
these two parts in the future.

This patch is needed to remove the suspend image size limit imposed by the
limited size of the swsusp_info structure, which is essential for x86-64
systems with more than 512 MB of RAM.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Acked-by: Pavel Machek <pavel@suse.cz>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
This commit is contained in:
Rafael J. Wysocki 2006-01-06 00:13:05 -08:00 коммит произвёл Linus Torvalds
Родитель f2d97f0296
Коммит 7088a5c001
5 изменённых файлов: 426 добавлений и 189 удалений

Просмотреть файл

@ -14,11 +14,7 @@
typedef struct pbe { typedef struct pbe {
unsigned long address; /* address of the copy */ unsigned long address; /* address of the copy */
unsigned long orig_address; /* original address of page */ unsigned long orig_address; /* original address of page */
swp_entry_t swap_address; struct pbe *next;
struct pbe *next; /* also used as scratch space at
* end of page (see link, diskpage)
*/
} suspend_pagedir_t; } suspend_pagedir_t;
#define for_each_pbe(pbe, pblist) \ #define for_each_pbe(pbe, pblist) \

Просмотреть файл

@ -25,9 +25,9 @@
extern suspend_disk_method_t pm_disk_mode; extern suspend_disk_method_t pm_disk_mode;
extern int swsusp_suspend(void); extern int swsusp_suspend(void);
extern int swsusp_write(void); extern int swsusp_write(struct pbe *pblist, unsigned int nr_pages);
extern int swsusp_check(void); extern int swsusp_check(void);
extern int swsusp_read(void); extern int swsusp_read(struct pbe **pblist_ptr);
extern void swsusp_close(void); extern void swsusp_close(void);
extern int swsusp_resume(void); extern int swsusp_resume(void);
@ -176,7 +176,7 @@ int pm_suspend_disk(void)
if (in_suspend) { if (in_suspend) {
device_resume(); device_resume();
pr_debug("PM: writing image.\n"); pr_debug("PM: writing image.\n");
error = swsusp_write(); error = swsusp_write(pagedir_nosave, nr_copy_pages);
if (!error) if (!error)
power_down(pm_disk_mode); power_down(pm_disk_mode);
else { else {
@ -247,7 +247,7 @@ static int software_resume(void)
pr_debug("PM: Reading swsusp image.\n"); pr_debug("PM: Reading swsusp image.\n");
if ((error = swsusp_read())) { if ((error = swsusp_read(&pagedir_nosave))) {
swsusp_free(); swsusp_free();
goto Thaw; goto Thaw;
} }

Просмотреть файл

@ -9,19 +9,14 @@
#define SUSPEND_CONSOLE (MAX_NR_CONSOLES-1) #define SUSPEND_CONSOLE (MAX_NR_CONSOLES-1)
#endif #endif
#define MAX_PBES ((PAGE_SIZE - sizeof(struct new_utsname) \
- 4 - 3*sizeof(unsigned long) - sizeof(int) \
- sizeof(void *)) / sizeof(swp_entry_t))
struct swsusp_info { struct swsusp_info {
struct new_utsname uts; struct new_utsname uts;
u32 version_code; u32 version_code;
unsigned long num_physpages; unsigned long num_physpages;
int cpus; int cpus;
unsigned long image_pages; unsigned long image_pages;
unsigned long pagedir_pages; unsigned long pages;
suspend_pagedir_t * suspend_pagedir; swp_entry_t start;
swp_entry_t pagedir[MAX_PBES];
} __attribute__((aligned(PAGE_SIZE))); } __attribute__((aligned(PAGE_SIZE)));
@ -67,6 +62,8 @@ extern asmlinkage int swsusp_arch_resume(void);
extern void free_pagedir(struct pbe *pblist); extern void free_pagedir(struct pbe *pblist);
extern struct pbe *alloc_pagedir(unsigned nr_pages, gfp_t gfp_mask, int safe_needed); extern struct pbe *alloc_pagedir(unsigned nr_pages, gfp_t gfp_mask, int safe_needed);
extern void create_pbe_list(struct pbe *pblist, unsigned nr_pages);
extern void swsusp_free(void); extern void swsusp_free(void);
extern int alloc_data_pages(struct pbe *pblist, gfp_t gfp_mask, int safe_needed); extern int alloc_data_pages(struct pbe *pblist, gfp_t gfp_mask, int safe_needed);
extern unsigned int snapshot_nr_pages(void);
extern struct pbe *snapshot_pblist(void);
extern void snapshot_pblist_set(struct pbe *pblist);

Просмотреть файл

@ -33,6 +33,9 @@
#include "power.h" #include "power.h"
struct pbe *pagedir_nosave;
unsigned int nr_copy_pages;
#ifdef CONFIG_HIGHMEM #ifdef CONFIG_HIGHMEM
struct highmem_page { struct highmem_page {
char *data; char *data;
@ -244,7 +247,7 @@ static inline void fill_pb_page(struct pbe *pbpage)
* of memory pages allocated with alloc_pagedir() * of memory pages allocated with alloc_pagedir()
*/ */
void create_pbe_list(struct pbe *pblist, unsigned int nr_pages) static inline void create_pbe_list(struct pbe *pblist, unsigned int nr_pages)
{ {
struct pbe *pbpage, *p; struct pbe *pbpage, *p;
unsigned int num = PBES_PER_PAGE; unsigned int num = PBES_PER_PAGE;
@ -261,7 +264,6 @@ void create_pbe_list(struct pbe *pblist, unsigned int nr_pages)
p->next = p + 1; p->next = p + 1;
p->next = NULL; p->next = NULL;
} }
pr_debug("create_pbe_list(): initialized %d PBEs\n", num);
} }
/** /**
@ -332,7 +334,8 @@ struct pbe *alloc_pagedir(unsigned int nr_pages, gfp_t gfp_mask, int safe_needed
if (!pbe) { /* get_zeroed_page() failed */ if (!pbe) { /* get_zeroed_page() failed */
free_pagedir(pblist); free_pagedir(pblist);
pblist = NULL; pblist = NULL;
} } else
create_pbe_list(pblist, nr_pages);
return pblist; return pblist;
} }
@ -395,7 +398,6 @@ static struct pbe *swsusp_alloc(unsigned int nr_pages)
printk(KERN_ERR "suspend: Allocating pagedir failed.\n"); printk(KERN_ERR "suspend: Allocating pagedir failed.\n");
return NULL; return NULL;
} }
create_pbe_list(pblist, nr_pages);
if (alloc_data_pages(pblist, GFP_ATOMIC | __GFP_COLD, 0)) { if (alloc_data_pages(pblist, GFP_ATOMIC | __GFP_COLD, 0)) {
printk(KERN_ERR "suspend: Allocating image pages failed.\n"); printk(KERN_ERR "suspend: Allocating image pages failed.\n");
@ -421,10 +423,6 @@ asmlinkage int swsusp_save(void)
(nr_pages + PBES_PER_PAGE - 1) / PBES_PER_PAGE, (nr_pages + PBES_PER_PAGE - 1) / PBES_PER_PAGE,
PAGES_FOR_IO, nr_free_pages()); PAGES_FOR_IO, nr_free_pages());
/* This is needed because of the fixed size of swsusp_info */
if (MAX_PBES < (nr_pages + PBES_PER_PAGE - 1) / PBES_PER_PAGE)
return -ENOSPC;
if (!enough_free_mem(nr_pages)) { if (!enough_free_mem(nr_pages)) {
printk(KERN_ERR "swsusp: Not enough free memory\n"); printk(KERN_ERR "swsusp: Not enough free memory\n");
return -ENOMEM; return -ENOMEM;

Просмотреть файл

@ -30,6 +30,9 @@
* Alex Badea <vampire@go.ro>: * Alex Badea <vampire@go.ro>:
* Fixed runaway init * Fixed runaway init
* *
* Rafael J. Wysocki <rjw@sisk.pl>
* Added the swap map data structure and reworked the handling of swap
*
* More state savers are welcome. Especially for the scsi layer... * More state savers are welcome. Especially for the scsi layer...
* *
* For TODOs,FIXMEs also look in Documentation/power/swsusp.txt * For TODOs,FIXMEs also look in Documentation/power/swsusp.txt
@ -76,18 +79,6 @@ static int restore_highmem(void) { return 0; }
extern char resume_file[]; extern char resume_file[];
/* Local variables that should not be affected by save */
unsigned int nr_copy_pages __nosavedata = 0;
/* Suspend pagedir is allocated before final copy, therefore it
must be freed after resume
Warning: this is even more evil than it seems. Pagedirs this file
talks about are completely different from page directories used by
MMU hardware.
*/
suspend_pagedir_t *pagedir_nosave __nosavedata = NULL;
#define SWSUSP_SIG "S1SUSPEND" #define SWSUSP_SIG "S1SUSPEND"
static struct swsusp_header { static struct swsusp_header {
@ -238,48 +229,205 @@ static int write_page(unsigned long addr, swp_entry_t *loc)
} }
/** /**
* data_free - Free the swap entries used by the saved image. * Swap map-handling functions
* *
* Walk the list of used swap entries and free each one. * The swap map is a data structure used for keeping track of each page
* This is only used for cleanup when suspend fails. * written to the swap. It consists of many swap_map_page structures
* that contain each an array of MAP_PAGE_SIZE swap entries.
* These structures are linked together with the help of either the
* .next (in memory) or the .next_swap (in swap) member.
*
* The swap map is created during suspend. At that time we need to keep
* it in memory, because we have to free all of the allocated swap
* entries if an error occurs. The memory needed is preallocated
* so that we know in advance if there's enough of it.
*
* The first swap_map_page structure is filled with the swap entries that
* correspond to the first MAP_PAGE_SIZE data pages written to swap and
* so on. After the all of the data pages have been written, the order
* of the swap_map_page structures in the map is reversed so that they
* can be read from swap in the original order. This causes the data
* pages to be loaded in exactly the same order in which they have been
* saved.
*
* During resume we only need to use one swap_map_page structure
* at a time, which means that we only need to use two memory pages for
* reading the image - one for reading the swap_map_page structures
* and the second for reading the data pages from swap.
*/ */
static void data_free(void)
{
swp_entry_t entry;
struct pbe *p;
for_each_pbe (p, pagedir_nosave) { #define MAP_PAGE_SIZE ((PAGE_SIZE - sizeof(swp_entry_t) - sizeof(void *)) \
entry = p->swap_address; / sizeof(swp_entry_t))
if (entry.val)
swap_free(entry); struct swap_map_page {
else swp_entry_t entries[MAP_PAGE_SIZE];
break; swp_entry_t next_swap;
struct swap_map_page *next;
};
static inline void free_swap_map(struct swap_map_page *swap_map)
{
struct swap_map_page *swp;
while (swap_map) {
swp = swap_map->next;
free_page((unsigned long)swap_map);
swap_map = swp;
}
}
static struct swap_map_page *alloc_swap_map(unsigned int nr_pages)
{
struct swap_map_page *swap_map, *swp;
unsigned n = 0;
if (!nr_pages)
return NULL;
pr_debug("alloc_swap_map(): nr_pages = %d\n", nr_pages);
swap_map = (struct swap_map_page *)get_zeroed_page(GFP_ATOMIC);
swp = swap_map;
for (n = MAP_PAGE_SIZE; n < nr_pages; n += MAP_PAGE_SIZE) {
swp->next = (struct swap_map_page *)get_zeroed_page(GFP_ATOMIC);
swp = swp->next;
if (!swp) {
free_swap_map(swap_map);
return NULL;
}
}
return swap_map;
}
/**
* reverse_swap_map - reverse the order of pages in the swap map
* @swap_map
*/
static inline struct swap_map_page *reverse_swap_map(struct swap_map_page *swap_map)
{
struct swap_map_page *prev, *next;
prev = NULL;
while (swap_map) {
next = swap_map->next;
swap_map->next = prev;
prev = swap_map;
swap_map = next;
}
return prev;
}
/**
* free_swap_map_entries - free the swap entries allocated to store
* the swap map @swap_map (this is only called in case of an error)
*/
static inline void free_swap_map_entries(struct swap_map_page *swap_map)
{
while (swap_map) {
if (swap_map->next_swap.val)
swap_free(swap_map->next_swap);
swap_map = swap_map->next;
} }
} }
/** /**
* data_write - Write saved image to swap. * save_swap_map - save the swap map used for tracing the data pages
* * stored in the swap
* Walk the list of pages in the image and sync each one to swap.
*/ */
static int data_write(void)
static int save_swap_map(struct swap_map_page *swap_map, swp_entry_t *start)
{ {
int error = 0, i = 0; swp_entry_t entry = (swp_entry_t){0};
unsigned int mod = nr_copy_pages / 100; int error;
struct pbe *p;
if (!mod) while (swap_map) {
mod = 1; swap_map->next_swap = entry;
if ((error = write_page((unsigned long)swap_map, &entry)))
printk( "Writing data to swap (%d pages)... ", nr_copy_pages );
for_each_pbe (p, pagedir_nosave) {
if (!(i%mod))
printk( "\b\b\b\b%3d%%", i / mod );
if ((error = write_page(p->address, &p->swap_address)))
return error; return error;
i++; swap_map = swap_map->next;
} }
printk("\b\b\b\bdone\n"); *start = entry;
return 0;
}
/**
* free_image_entries - free the swap entries allocated to store
* the image data pages (this is only called in case of an error)
*/
static inline void free_image_entries(struct swap_map_page *swp)
{
unsigned k;
while (swp) {
for (k = 0; k < MAP_PAGE_SIZE; k++)
if (swp->entries[k].val)
swap_free(swp->entries[k]);
swp = swp->next;
}
}
/**
* The swap_map_handle structure is used for handling the swap map in
* a file-alike way
*/
struct swap_map_handle {
struct swap_map_page *cur;
unsigned int k;
};
static inline void init_swap_map_handle(struct swap_map_handle *handle,
struct swap_map_page *map)
{
handle->cur = map;
handle->k = 0;
}
static inline int swap_map_write_page(struct swap_map_handle *handle,
unsigned long addr)
{
int error;
error = write_page(addr, handle->cur->entries + handle->k);
if (error)
return error;
if (++handle->k >= MAP_PAGE_SIZE) {
handle->cur = handle->cur->next;
handle->k = 0;
}
return 0;
}
/**
* save_image_data - save the data pages pointed to by the PBEs
* from the list @pblist using the swap map handle @handle
* (assume there are @nr_pages data pages to save)
*/
static int save_image_data(struct pbe *pblist,
struct swap_map_handle *handle,
unsigned int nr_pages)
{
unsigned int m;
struct pbe *p;
int error = 0;
printk("Saving image data pages (%u pages) ... ", nr_pages);
m = nr_pages / 100;
if (!m)
m = 1;
nr_pages = 0;
for_each_pbe (p, pblist) {
error = swap_map_write_page(handle, p->address);
if (error)
break;
if (!(nr_pages % m))
printk("\b\b\b\b%3d%%", nr_pages / m);
nr_pages++;
}
if (!error)
printk("\b\b\b\bdone\n");
return error; return error;
} }
@ -295,19 +443,20 @@ static void dump_info(void)
pr_debug(" swsusp: UTS Domain: %s\n",swsusp_info.uts.domainname); pr_debug(" swsusp: UTS Domain: %s\n",swsusp_info.uts.domainname);
pr_debug(" swsusp: CPUs: %d\n",swsusp_info.cpus); pr_debug(" swsusp: CPUs: %d\n",swsusp_info.cpus);
pr_debug(" swsusp: Image: %ld Pages\n",swsusp_info.image_pages); pr_debug(" swsusp: Image: %ld Pages\n",swsusp_info.image_pages);
pr_debug(" swsusp: Pagedir: %ld Pages\n",swsusp_info.pagedir_pages); pr_debug(" swsusp: Total: %ld Pages\n", swsusp_info.pages);
} }
static void init_header(void) static void init_header(unsigned int nr_pages)
{ {
memset(&swsusp_info, 0, sizeof(swsusp_info)); memset(&swsusp_info, 0, sizeof(swsusp_info));
swsusp_info.version_code = LINUX_VERSION_CODE; swsusp_info.version_code = LINUX_VERSION_CODE;
swsusp_info.num_physpages = num_physpages; swsusp_info.num_physpages = num_physpages;
memcpy(&swsusp_info.uts, &system_utsname, sizeof(system_utsname)); memcpy(&swsusp_info.uts, &system_utsname, sizeof(system_utsname));
swsusp_info.suspend_pagedir = pagedir_nosave;
swsusp_info.cpus = num_online_cpus(); swsusp_info.cpus = num_online_cpus();
swsusp_info.image_pages = nr_copy_pages; swsusp_info.image_pages = nr_pages;
swsusp_info.pages = nr_pages +
((nr_pages * sizeof(long) + PAGE_SIZE - 1) >> PAGE_SHIFT);
} }
static int close_swap(void) static int close_swap(void)
@ -326,39 +475,53 @@ static int close_swap(void)
} }
/** /**
* free_pagedir_entries - Free pages used by the page directory. * pack_orig_addresses - the .orig_address fields of the PBEs from the
* * list starting at @pbe are stored in the array @buf[] (1 page)
* This is used during suspend for error recovery.
*/ */
static void free_pagedir_entries(void) static inline struct pbe *pack_orig_addresses(unsigned long *buf,
struct pbe *pbe)
{ {
int i; int j;
for (i = 0; i < swsusp_info.pagedir_pages; i++) for (j = 0; j < PAGE_SIZE / sizeof(long) && pbe; j++) {
swap_free(swsusp_info.pagedir[i]); buf[j] = pbe->orig_address;
pbe = pbe->next;
}
if (!pbe)
for (; j < PAGE_SIZE / sizeof(long); j++)
buf[j] = 0;
return pbe;
} }
/** /**
* write_pagedir - Write the array of pages holding the page directory. * save_image_metadata - save the .orig_address fields of the PBEs
* @last: Last swap entry we write (needed for header). * from the list @pblist using the swap map handle @handle
*/ */
static int write_pagedir(void) static int save_image_metadata(struct pbe *pblist,
struct swap_map_handle *handle)
{ {
int error = 0; unsigned long *buf;
unsigned int n = 0; unsigned int n = 0;
struct pbe *pbe; struct pbe *p;
int error = 0;
printk( "Writing pagedir..."); printk("Saving image metadata ... ");
for_each_pb_page (pbe, pagedir_nosave) { buf = (unsigned long *)get_zeroed_page(GFP_ATOMIC);
if ((error = write_page((unsigned long)pbe, &swsusp_info.pagedir[n++]))) if (!buf)
return error; return -ENOMEM;
p = pblist;
while (p) {
p = pack_orig_addresses(buf, p);
error = swap_map_write_page(handle, (unsigned long)buf);
if (error)
break;
n++;
} }
free_page((unsigned long)buf);
swsusp_info.pagedir_pages = n; if (!error)
printk("done (%u pages)\n", n); printk("done (%u pages saved)\n", n);
return error; return error;
} }
@ -384,33 +547,48 @@ static int enough_swap(unsigned int nr_pages)
/** /**
* write_suspend_image - Write entire image and metadata. * write_suspend_image - Write entire image and metadata.
*
*/ */
static int write_suspend_image(void) static int write_suspend_image(struct pbe *pblist, unsigned int nr_pages)
{ {
struct swap_map_page *swap_map;
struct swap_map_handle handle;
int error; int error;
if (!enough_swap(nr_copy_pages)) { if (!enough_swap(nr_pages)) {
printk(KERN_ERR "swsusp: Not enough free swap\n"); printk(KERN_ERR "swsusp: Not enough free swap\n");
return -ENOSPC; return -ENOSPC;
} }
init_header(); init_header(nr_pages);
if ((error = data_write())) swap_map = alloc_swap_map(swsusp_info.pages);
goto FreeData; if (!swap_map)
return -ENOMEM;
init_swap_map_handle(&handle, swap_map);
if ((error = write_pagedir())) error = save_image_metadata(pblist, &handle);
goto FreePagedir; if (!error)
error = save_image_data(pblist, &handle, nr_pages);
if (error)
goto Free_image_entries;
if ((error = close_swap())) swap_map = reverse_swap_map(swap_map);
goto FreePagedir; error = save_swap_map(swap_map, &swsusp_info.start);
Done: if (error)
goto Free_map_entries;
error = close_swap();
if (error)
goto Free_map_entries;
Free_swap_map:
free_swap_map(swap_map);
return error; return error;
FreePagedir:
free_pagedir_entries(); Free_map_entries:
FreeData: free_swap_map_entries(swap_map);
data_free(); Free_image_entries:
goto Done; free_image_entries(swap_map);
goto Free_swap_map;
} }
/* It is important _NOT_ to umount filesystems at this point. We want /* It is important _NOT_ to umount filesystems at this point. We want
@ -418,7 +596,7 @@ static int write_suspend_image(void)
* filesystem clean: it is not. (And it does not matter, if we resume * filesystem clean: it is not. (And it does not matter, if we resume
* correctly, we'll mark system clean, anyway.) * correctly, we'll mark system clean, anyway.)
*/ */
int swsusp_write(void) int swsusp_write(struct pbe *pblist, unsigned int nr_pages)
{ {
int error; int error;
@ -427,14 +605,12 @@ int swsusp_write(void)
return error; return error;
} }
lock_swapdevices(); lock_swapdevices();
error = write_suspend_image(); error = write_suspend_image(pblist, nr_pages);
/* This will unlock ignored swap devices since writing is finished */ /* This will unlock ignored swap devices since writing is finished */
lock_swapdevices(); lock_swapdevices();
return error; return error;
} }
int swsusp_suspend(void) int swsusp_suspend(void)
{ {
int error; int error;
@ -531,7 +707,6 @@ static void copy_page_backup_list(struct pbe *dst, struct pbe *src)
/* We assume both lists contain the same number of elements */ /* We assume both lists contain the same number of elements */
while (src) { while (src) {
dst->orig_address = src->orig_address; dst->orig_address = src->orig_address;
dst->swap_address = src->swap_address;
dst = dst->next; dst = dst->next;
src = src->next; src = src->next;
} }
@ -611,6 +786,61 @@ static int bio_write_page(pgoff_t page_off, void *page)
return submit(WRITE, page_off, page); return submit(WRITE, page_off, page);
} }
/**
* The following functions allow us to read data using a swap map
* in a file-alike way
*/
static inline void release_swap_map_reader(struct swap_map_handle *handle)
{
if (handle->cur)
free_page((unsigned long)handle->cur);
handle->cur = NULL;
}
static inline int get_swap_map_reader(struct swap_map_handle *handle,
swp_entry_t start)
{
int error;
if (!swp_offset(start))
return -EINVAL;
handle->cur = (struct swap_map_page *)get_zeroed_page(GFP_ATOMIC);
if (!handle->cur)
return -ENOMEM;
error = bio_read_page(swp_offset(start), handle->cur);
if (error) {
release_swap_map_reader(handle);
return error;
}
handle->k = 0;
return 0;
}
static inline int swap_map_read_page(struct swap_map_handle *handle, void *buf)
{
unsigned long offset;
int error;
if (!handle->cur)
return -EINVAL;
offset = swp_offset(handle->cur->entries[handle->k]);
if (!offset)
return -EINVAL;
error = bio_read_page(offset, buf);
if (error)
return error;
if (++handle->k >= MAP_PAGE_SIZE) {
handle->k = 0;
offset = swp_offset(handle->cur->next_swap);
if (!offset)
release_swap_map_reader(handle);
else
error = bio_read_page(offset, handle->cur);
}
return error;
}
/* /*
* Sanity check if this image makes sense with this kernel/swap context * Sanity check if this image makes sense with this kernel/swap context
* I really don't think that it's foolproof but more than nothing.. * I really don't think that it's foolproof but more than nothing..
@ -639,7 +869,6 @@ static const char *sanity_check(void)
return NULL; return NULL;
} }
static int check_header(void) static int check_header(void)
{ {
const char *reason = NULL; const char *reason = NULL;
@ -653,7 +882,6 @@ static int check_header(void)
printk(KERN_ERR "swsusp: Resume mismatch: %s\n",reason); printk(KERN_ERR "swsusp: Resume mismatch: %s\n",reason);
return -EPERM; return -EPERM;
} }
nr_copy_pages = swsusp_info.image_pages;
return error; return error;
} }
@ -680,74 +908,87 @@ static int check_sig(void)
} }
/** /**
* data_read - Read image pages from swap. * load_image_data - load the image data using the swap map handle
* * @handle and store them using the page backup list @pblist
* You do not need to check for overlaps, check_pagedir() * (assume there are @nr_pages pages to load)
* already did that.
*/ */
static int data_read(struct pbe *pblist) static int load_image_data(struct pbe *pblist,
struct swap_map_handle *handle,
unsigned int nr_pages)
{ {
int error;
unsigned int m;
struct pbe *p; struct pbe *p;
int error = 0;
int i = 0;
int mod = swsusp_info.image_pages / 100;
if (!mod) if (!pblist)
mod = 1; return -EINVAL;
printk("Loading image data pages (%u pages) ... ", nr_pages);
printk("swsusp: Reading image data (%lu pages): ", m = nr_pages / 100;
swsusp_info.image_pages); if (!m)
m = 1;
for_each_pbe (p, pblist) { nr_pages = 0;
if (!(i % mod)) p = pblist;
printk("\b\b\b\b%3d%%", i / mod); while (p) {
error = swap_map_read_page(handle, (void *)p->address);
if ((error = bio_read_page(swp_offset(p->swap_address), if (error)
(void *)p->address))) break;
return error; p = p->next;
if (!(nr_pages % m))
i++; printk("\b\b\b\b%3d%%", nr_pages / m);
nr_pages++;
} }
printk("\b\b\b\bdone\n"); if (!error)
printk("\b\b\b\bdone\n");
return error; return error;
} }
/** /**
* read_pagedir - Read page backup list pages from swap * unpack_orig_addresses - copy the elements of @buf[] (1 page) to
* the PBEs in the list starting at @pbe
*/ */
static int read_pagedir(struct pbe *pblist) static inline struct pbe *unpack_orig_addresses(unsigned long *buf,
struct pbe *pbe)
{ {
struct pbe *pbpage, *p; int j;
unsigned int i = 0;
int error;
if (!pblist) for (j = 0; j < PAGE_SIZE / sizeof(long) && pbe; j++) {
return -EFAULT; pbe->orig_address = buf[j];
pbe = pbe->next;
printk("swsusp: Reading pagedir (%lu pages)\n",
swsusp_info.pagedir_pages);
for_each_pb_page (pbpage, pblist) {
unsigned long offset = swp_offset(swsusp_info.pagedir[i++]);
error = -EFAULT;
if (offset) {
p = (pbpage + PB_PAGE_SKIP)->next;
error = bio_read_page(offset, (void *)pbpage);
(pbpage + PB_PAGE_SKIP)->next = p;
}
if (error)
break;
} }
return pbe;
if (!error)
BUG_ON(i != swsusp_info.pagedir_pages);
return error;
} }
/**
* load_image_metadata - load the image metadata using the swap map
* handle @handle and put them into the PBEs in the list @pblist
*/
static int load_image_metadata(struct pbe *pblist, struct swap_map_handle *handle)
{
struct pbe *p;
unsigned long *buf;
unsigned int n = 0;
int error = 0;
printk("Loading image metadata ... ");
buf = (unsigned long *)get_zeroed_page(GFP_ATOMIC);
if (!buf)
return -ENOMEM;
p = pblist;
while (p) {
error = swap_map_read_page(handle, buf);
if (error)
break;
p = unpack_orig_addresses(buf, p);
n++;
}
free_page((unsigned long)buf);
if (!error)
printk("done (%u pages loaded)\n", n);
return error;
}
static int check_suspend_image(void) static int check_suspend_image(void)
{ {
@ -762,34 +1003,39 @@ static int check_suspend_image(void)
return 0; return 0;
} }
static int read_suspend_image(void) static int read_suspend_image(struct pbe **pblist_ptr)
{ {
int error = 0; int error = 0;
struct pbe *p; struct pbe *p, *pblist;
struct swap_map_handle handle;
unsigned int nr_pages = swsusp_info.image_pages;
if (!(p = alloc_pagedir(nr_copy_pages, GFP_ATOMIC, 0))) p = alloc_pagedir(nr_pages, GFP_ATOMIC, 0);
if (!p)
return -ENOMEM; return -ENOMEM;
error = get_swap_map_reader(&handle, swsusp_info.start);
if ((error = read_pagedir(p))) if (error)
/* The PBE list at p will be released by swsusp_free() */
return error; return error;
create_pbe_list(p, nr_copy_pages); error = load_image_metadata(p, &handle);
mark_unsafe_pages(p); if (!error) {
pagedir_nosave = alloc_pagedir(nr_copy_pages, GFP_ATOMIC, 1); mark_unsafe_pages(p);
if (pagedir_nosave) { pblist = alloc_pagedir(nr_pages, GFP_ATOMIC, 1);
create_pbe_list(pagedir_nosave, nr_copy_pages); if (pblist)
copy_page_backup_list(pagedir_nosave, p); copy_page_backup_list(pblist, p);
free_pagedir(p);
if (!pblist)
error = -ENOMEM;
/* Allocate memory for the image and read the data from swap */
if (!error)
error = alloc_data_pages(pblist, GFP_ATOMIC, 1);
if (!error)
error = load_image_data(pblist, &handle, nr_pages);
if (!error)
*pblist_ptr = pblist;
} }
free_pagedir(p); release_swap_map_reader(&handle);
if (!pagedir_nosave)
return -ENOMEM;
/* Allocate memory for the image and read the data from swap */
error = alloc_data_pages(pagedir_nosave, GFP_ATOMIC, 1);
if (!error)
error = data_read(pagedir_nosave);
return error; return error;
} }
@ -821,7 +1067,7 @@ int swsusp_check(void)
* swsusp_read - Read saved image from swap. * swsusp_read - Read saved image from swap.
*/ */
int swsusp_read(void) int swsusp_read(struct pbe **pblist_ptr)
{ {
int error; int error;
@ -830,7 +1076,7 @@ int swsusp_read(void)
return PTR_ERR(resume_bdev); return PTR_ERR(resume_bdev);
} }
error = read_suspend_image(); error = read_suspend_image(pblist_ptr);
blkdev_put(resume_bdev); blkdev_put(resume_bdev);
if (!error) if (!error)