vserver 2.0 rc7
[linux-2.6.git] / kernel / power / swsusp.c
index 9ca3b1c..90b3b68 100644 (file)
 extern const void __nosave_begin, __nosave_end;
 
 /* Variables to be preserved over suspend */
-static int pagedir_order_check;
 static int nr_copy_pages_check;
 
 extern char resume_file[];
-static dev_t resume_device;
+
 /* Local variables that should not be affected by save */
 unsigned int nr_copy_pages __nosavedata = 0;
 
@@ -99,7 +98,6 @@ unsigned int nr_copy_pages __nosavedata = 0;
  */
 suspend_pagedir_t *pagedir_nosave __nosavedata = NULL;
 static suspend_pagedir_t *pagedir_save;
-static int pagedir_order __nosavedata = 0;
 
 #define SWSUSP_SIG     "S1SUSPEND"
 
@@ -170,7 +168,7 @@ static int is_resume_device(const struct swap_info_struct *swap_info)
        struct inode *inode = file->f_dentry->d_inode;
 
        return S_ISBLK(inode->i_mode) &&
-               resume_device == MKDEV(imajor(inode), iminor(inode));
+               swsusp_resume_device == MKDEV(imajor(inode), iminor(inode));
 }
 
 static int swsusp_swap_check(void) /* This is called before saving image */
@@ -225,8 +223,6 @@ static void lock_swapdevices(void)
        swap_list_unlock();
 }
 
-
-
 /**
  *     write_swap_page - Write one page to a fresh swap location.
  *     @addr:  Address we're writing.
@@ -239,7 +235,6 @@ static void lock_swapdevices(void)
  *     This is a partial improvement, since we will at least return other
  *     errors, though we need to eventually fix the damn code.
  */
-
 static int write_page(unsigned long addr, swp_entry_t * loc)
 {
        swp_entry_t entry;
@@ -259,14 +254,12 @@ static int write_page(unsigned long addr, swp_entry_t * loc)
        return error;
 }
 
-
 /**
  *     data_free - Free the swap entries used by the saved image.
  *
  *     Walk the list of used swap entries and free each one. 
  *     This is only used for cleanup when suspend fails.
  */
-
 static void data_free(void)
 {
        swp_entry_t entry;
@@ -282,28 +275,27 @@ static void data_free(void)
        }
 }
 
-
 /**
  *     data_write - Write saved image to swap.
  *
  *     Walk the list of pages in the image and sync each one to swap.
  */
-
 static int data_write(void)
 {
-       int error = 0;
-       int i;
+       int error = 0, i = 0;
        unsigned int mod = nr_copy_pages / 100;
+       struct pbe *p;
 
        if (!mod)
                mod = 1;
 
        printk( "Writing data to swap (%d pages)...     ", nr_copy_pages );
-       for (i = 0; i < nr_copy_pages && !error; i++) {
+       for_each_pbe(p, pagedir_nosave) {
                if (!(i%mod))
                        printk( "\b\b\b\b%3d%%", i / mod );
-               error = write_page((pagedir_nosave+i)->address,
-                                         &((pagedir_nosave+i)->swap_address));
+               if ((error = write_page(p->address, &(p->swap_address))))
+                       return error;
+               i++;
        }
        printk("\b\b\b\bdone\n");
        return error;
@@ -326,15 +318,14 @@ static void dump_info(void)
 
 static void init_header(void)
 {
-       memset(&swsusp_info,0,sizeof(swsusp_info));
+       memset(&swsusp_info, 0, sizeof(swsusp_info));
        swsusp_info.version_code = LINUX_VERSION_CODE;
        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.image_pages = nr_copy_pages;
-       dump_info();
 }
 
 static int close_swap(void)
@@ -342,7 +333,8 @@ static int close_swap(void)
        swp_entry_t entry;
        int error;
 
-       error = write_page((unsigned long)&swsusp_info,&entry);
+       dump_info();
+       error = write_page((unsigned long)&swsusp_info, &entry);
        if (!error) { 
                printk( "S" );
                error = mark_swapfiles(entry);
@@ -373,15 +365,18 @@ static void free_pagedir_entries(void)
 
 static int write_pagedir(void)
 {
-       unsigned long addr = (unsigned long)pagedir_nosave;
        int error = 0;
-       int n = SUSPEND_PD_PAGES(nr_copy_pages);
-       int i;
+       unsigned n = 0;
+       struct pbe * pbe;
+
+       printk( "Writing pagedir...");
+       for_each_pb_page(pbe, pagedir_nosave) {
+               if ((error = write_page((unsigned long)pbe, &swsusp_info.pagedir[n++])))
+                       return error;
+       }
 
        swsusp_info.pagedir_pages = n;
-       printk( "Writing pagedir (%d pages)\n", n);
-       for (i = 0; i < n && !error; i++, addr += PAGE_SIZE)
-               error = write_page(addr, &swsusp_info.pagedir[i]);
+       printk("done (%u pages)\n", n);
        return error;
 }
 
@@ -567,8 +562,8 @@ static void copy_data_pages(void)
        struct zone *zone;
        unsigned long zone_pfn;
        struct pbe * pbe = pagedir_nosave;
-       int to_copy = nr_copy_pages;
        
+       pr_debug("copy_data_pages(): pages to copy: %d\n", nr_copy_pages);
        for_each_zone(zone) {
                if (is_highmem(zone))
                        continue;
@@ -577,78 +572,126 @@ static void copy_data_pages(void)
                        if (saveable(zone, &zone_pfn)) {
                                struct page * page;
                                page = pfn_to_page(zone_pfn + zone->zone_start_pfn);
+                               BUG_ON(!pbe);
                                pbe->orig_address = (long) page_address(page);
                                /* copy_page is not usable for copying task structs. */
                                memcpy((void *)pbe->address, (void *)pbe->orig_address, PAGE_SIZE);
-                               pbe++;
-                               to_copy--;
+                               pbe = pbe->next;
                        }
                }
        }
-       BUG_ON(to_copy);
+       BUG_ON(pbe);
 }
 
 
 /**
- *     calc_order - Determine the order of allocation needed for pagedir_save.
- *
- *     This looks tricky, but is just subtle. Please fix it some time.
- *     Since there are %nr_copy_pages worth of pages in the snapshot, we need
- *     to allocate enough contiguous space to hold 
- *             (%nr_copy_pages * sizeof(struct pbe)), 
- *     which has the saved/orig locations of the page.. 
- *
- *     SUSPEND_PD_PAGES() tells us how many pages we need to hold those 
- *     structures, then we call get_bitmask_order(), which will tell us the
- *     last bit set in the number, starting with 1. (If we need 30 pages, that
- *     is 0x0000001e in hex. The last bit is the 5th, which is the order we 
- *     would use to allocate 32 contiguous pages).
- *
- *     Since we also need to save those pages, we add the number of pages that
- *     we need to nr_copy_pages, and in case of an overflow, do the 
- *     calculation again to update the number of pages needed. 
- *
- *     With this model, we will tend to waste a lot of memory if we just cross
- *     an order boundary. Plus, the higher the order of allocation that we try
- *     to do, the more likely we are to fail in a low-memory situtation 
- *     (though we're unlikely to get this far in such a case, since swsusp 
- *     requires half of memory to be free anyway).
+ *     calc_nr - Determine the number of pages needed for a pbe list.
  */
 
-
-static void calc_order(void)
+static int calc_nr(int nr_copy)
 {
-       int diff = 0;
-       int order = 0;
+       int extra = 0;
+       int mod = !!(nr_copy % PBES_PER_PAGE);
+       int diff = (nr_copy / PBES_PER_PAGE) + mod;
 
        do {
-               diff = get_bitmask_order(SUSPEND_PD_PAGES(nr_copy_pages)) - order;
-               if (diff) {
-                       order += diff;
-                       nr_copy_pages += 1 << diff;
-               }
-       } while(diff);
-       pagedir_order = order;
+               extra += diff;
+               nr_copy += diff;
+               mod = !!(nr_copy % PBES_PER_PAGE);
+               diff = (nr_copy / PBES_PER_PAGE) + mod - extra;
+       } while (diff > 0);
+
+       return nr_copy;
 }
 
+/**
+ *     free_pagedir - free pages allocated with alloc_pagedir()
+ */
+
+static inline void free_pagedir(struct pbe *pblist)
+{
+       struct pbe *pbe;
+
+       while (pblist) {
+               pbe = (pblist + PB_PAGE_SKIP)->next;
+               free_page((unsigned long)pblist);
+               pblist = pbe;
+       }
+}
+
+/**
+ *     fill_pb_page - Create a list of PBEs on a given memory page
+ */
+
+static inline void fill_pb_page(struct pbe *pbpage)
+{
+       struct pbe *p;
+
+       p = pbpage;
+       pbpage += PB_PAGE_SKIP;
+       do
+               p->next = p + 1;
+       while (++p < pbpage);
+}
+
+/**
+ *     create_pbe_list - Create a list of PBEs on top of a given chain
+ *     of memory pages allocated with alloc_pagedir()
+ */
+
+static void create_pbe_list(struct pbe *pblist, unsigned nr_pages)
+{
+       struct pbe *pbpage, *p;
+       unsigned num = PBES_PER_PAGE;
+
+       for_each_pb_page (pbpage, pblist) {
+               if (num >= nr_pages)
+                       break;
+
+               fill_pb_page(pbpage);
+               num += PBES_PER_PAGE;
+       }
+       if (pbpage) {
+               for (num -= PBES_PER_PAGE - 1, p = pbpage; num < nr_pages; p++, num++)
+                       p->next = p + 1;
+               p->next = NULL;
+       }
+       pr_debug("create_pbe_list(): initialized %d PBEs\n", num);
+}
 
 /**
  *     alloc_pagedir - Allocate the page directory.
  *
- *     First, determine exactly how many contiguous pages we need and
+ *     First, determine exactly how many pages we need and
  *     allocate them.
+ *
+ *     We arrange the pages in a chain: each page is an array of PBES_PER_PAGE
+ *     struct pbe elements (pbes) and the last element in the page points
+ *     to the next page.
+ *
+ *     On each page we set up a list of struct_pbe elements.
  */
 
-static int alloc_pagedir(void)
+static struct pbe * alloc_pagedir(unsigned nr_pages)
 {
-       calc_order();
-       pagedir_save = (suspend_pagedir_t *)__get_free_pages(GFP_ATOMIC | __GFP_COLD,
-                                                            pagedir_order);
-       if (!pagedir_save)
-               return -ENOMEM;
-       memset(pagedir_save, 0, (1 << pagedir_order) * PAGE_SIZE);
-       pagedir_nosave = pagedir_save;
-       return 0;
+       unsigned num;
+       struct pbe *pblist, *pbe;
+
+       if (!nr_pages)
+               return NULL;
+
+       pr_debug("alloc_pagedir(): nr_pages = %d\n", nr_pages);
+       pblist = (struct pbe *)get_zeroed_page(GFP_ATOMIC | __GFP_COLD);
+       for (pbe = pblist, num = PBES_PER_PAGE; pbe && num < nr_pages;
+                       pbe = pbe->next, num += PBES_PER_PAGE) {
+               pbe += PB_PAGE_SKIP;
+               pbe->next = (struct pbe *)get_zeroed_page(GFP_ATOMIC | __GFP_COLD);
+       }
+       if (!pbe) { /* get_zeroed_page() failed */
+               free_pagedir(pblist);
+               pblist = NULL;
+        }
+       return pblist;
 }
 
 /**
@@ -658,10 +701,8 @@ static int alloc_pagedir(void)
 static void free_image_pages(void)
 {
        struct pbe * p;
-       int i;
 
-       p = pagedir_save;
-       for (i = 0, p = pagedir_save; i < nr_copy_pages; i++, p++) {
+       for_each_pbe(p, pagedir_save) {
                if (p->address) {
                        ClearPageNosave(virt_to_page(p->address));
                        free_page(p->address);
@@ -672,15 +713,13 @@ static void free_image_pages(void)
 
 /**
  *     alloc_image_pages - Allocate pages for the snapshot.
- *
  */
 
 static int alloc_image_pages(void)
 {
        struct pbe * p;
-       int i;
 
-       for (i = 0, p = pagedir_save; i < nr_copy_pages; i++, p++) {
+       for_each_pbe(p, pagedir_save) {
                p->address = get_zeroed_page(GFP_ATOMIC | __GFP_COLD);
                if (!p->address)
                        return -ENOMEM;
@@ -694,7 +733,7 @@ void swsusp_free(void)
        BUG_ON(PageNosave(virt_to_page(pagedir_save)));
        BUG_ON(PageNosaveFree(virt_to_page(pagedir_save)));
        free_image_pages();
-       free_pages((unsigned long) pagedir_save, pagedir_order);
+       free_pagedir(pagedir_save);
 }
 
 
@@ -752,10 +791,14 @@ static int swsusp_alloc(void)
        if (!enough_swap())
                return -ENOSPC;
 
-       if ((error = alloc_pagedir())) {
+       nr_copy_pages = calc_nr(nr_copy_pages);
+
+       if (!(pagedir_save = alloc_pagedir(nr_copy_pages))) {
                printk(KERN_ERR "suspend: Allocating pagedir failed.\n");
-               return error;
+               return -ENOMEM;
        }
+       create_pbe_list(pagedir_save, nr_copy_pages);
+       pagedir_nosave = pagedir_save;
        if ((error = alloc_image_pages())) {
                printk(KERN_ERR "suspend: Allocating image pages failed.\n");
                swsusp_free();
@@ -763,7 +806,6 @@ static int swsusp_alloc(void)
        }
 
        nr_copy_pages_check = nr_copy_pages;
-       pagedir_order_check = pagedir_order;
        return 0;
 }
 
@@ -780,7 +822,7 @@ static int suspend_prepare_image(void)
 
        drain_local_pages();
        count_data_pages();
-       printk("swsusp: Need to copy %u pages\n",nr_copy_pages);
+       printk("swsusp: Need to copy %u pages\n", nr_copy_pages);
 
        error = swsusp_alloc();
        if (error)
@@ -850,35 +892,29 @@ int swsusp_suspend(void)
         * at resume time, and evil weirdness ensues.
         */
        if ((error = device_power_down(PMSG_FREEZE))) {
+               printk(KERN_ERR "Some devices failed to power down, aborting suspend\n");
                local_irq_enable();
+               swsusp_free();
                return error;
        }
        save_processor_state();
-       error = swsusp_arch_suspend();
+       if ((error = swsusp_arch_suspend()))
+               swsusp_free();
        /* Restore control flow magically appears here */
        restore_processor_state();
+       BUG_ON (nr_copy_pages_check != nr_copy_pages);
        restore_highmem();
        device_power_up();
        local_irq_enable();
        return error;
 }
 
-
-asmlinkage int swsusp_restore(void)
-{
-       BUG_ON (nr_copy_pages_check != nr_copy_pages);
-       BUG_ON (pagedir_order_check != pagedir_order);
-       
-       /* Even mappings of "global" things (vmalloc) need to be fixed */
-       __flush_tlb_global();
-       return 0;
-}
-
 int swsusp_resume(void)
 {
        int error;
        local_irq_disable();
-       device_power_down(PMSG_FREEZE);
+       if (device_power_down(PMSG_FREEZE))
+               printk(KERN_ERR "Some devices failed to power down, very bad\n");
        /* We'll ignore saved state, but this gets preempt count (etc) right */
        save_processor_state();
        error = swsusp_arch_resume();
@@ -898,7 +934,7 @@ int swsusp_resume(void)
 /*
  * Returns true if given address/order collides with any orig_address 
  */
-static int __init does_collide_order(unsigned long addr, int order)
+static int does_collide_order(unsigned long addr, int order)
 {
        int i;
        
@@ -908,44 +944,104 @@ static int __init does_collide_order(unsigned long addr, int order)
        return 0;
 }
 
-/*
- * We check here that pagedir & pages it points to won't collide with pages
- * where we're going to restore from the loaded pages later
+/**
+ *     On resume, for storing the PBE list and the image,
+ *     we can only use memory pages that do not conflict with the pages
+ *     which had been used before suspend.
+ *
+ *     We don't know which pages are usable until we allocate them.
+ *
+ *     Allocated but unusable (ie eaten) memory pages are linked together
+ *     to create a list, so that we can free them easily
+ *
+ *     We could have used a type other than (void *)
+ *     for this purpose, but ...
  */
-static int __init check_pagedir(void)
+static void **eaten_memory = NULL;
+
+static inline void eat_page(void *page)
 {
-       int i;
+       void **c;
 
-       for(i=0; i < nr_copy_pages; i++) {
-               unsigned long addr;
+       c = eaten_memory;
+       eaten_memory = page;
+       *eaten_memory = c;
+}
 
-               do {
-                       addr = get_zeroed_page(GFP_ATOMIC);
-                       if(!addr)
-                               return -ENOMEM;
-               } while (does_collide_order(addr, 0));
+static unsigned long get_usable_page(unsigned gfp_mask)
+{
+       unsigned long m;
 
-               (pagedir_nosave+i)->address = addr;
+       m = get_zeroed_page(gfp_mask);
+       while (does_collide_order(m, 0)) {
+               eat_page((void *)m);
+               m = get_zeroed_page(gfp_mask);
+               if (!m)
+                       break;
        }
-       return 0;
+       return m;
 }
 
-static int __init swsusp_pagedir_relocate(void)
+static void free_eaten_memory(void)
 {
-       /*
-        * We have to avoid recursion (not to overflow kernel stack),
-        * and that's why code looks pretty cryptic 
+       unsigned long m;
+       void **c;
+       int i = 0;
+
+       c = eaten_memory;
+       while (c) {
+               m = (unsigned long)c;
+               c = *c;
+               free_page(m);
+               i++;
+       }
+       eaten_memory = NULL;
+       pr_debug("swsusp: %d unused pages freed\n", i);
+}
+
+/**
+ *     check_pagedir - We ensure here that pages that the PBEs point to
+ *     won't collide with pages where we're going to restore from the loaded
+ *     pages later
+ */
+
+static int check_pagedir(struct pbe *pblist)
+{
+       struct pbe *p;
+
+       /* This is necessary, so that we can free allocated pages
+        * in case of failure
         */
-       suspend_pagedir_t *old_pagedir = pagedir_nosave;
-       void **eaten_memory = NULL;
-       void **c = eaten_memory, *m, *f;
-       int ret = 0;
+       for_each_pbe (p, pblist)
+               p->address = 0UL;
+
+       for_each_pbe (p, pblist) {
+               p->address = get_usable_page(GFP_ATOMIC);
+               if (!p->address)
+                       return -ENOMEM;
+       }
+       return 0;
+}
+
+/**
+ *     swsusp_pagedir_relocate - It is possible, that some memory pages
+ *     occupied by the list of PBEs collide with pages where we're going to
+ *     restore from the loaded pages later.  We relocate them here.
+ */
+
+static struct pbe * swsusp_pagedir_relocate(struct pbe *pblist)
+{
        struct zone *zone;
-       int i;
-       struct pbe *p;
        unsigned long zone_pfn;
+       struct pbe *pbpage, *tail, *p;
+       void *m;
+       int rel = 0, error = 0;
 
-       printk("Relocating pagedir ");
+       if (!pblist) /* a sanity check */
+               return NULL;
+
+       pr_debug("swsusp: Relocating pagedir (%lu pages to check)\n",
+                       swsusp_info.pagedir_pages);
 
        /* Set page flags */
 
@@ -955,48 +1051,55 @@ static int __init swsusp_pagedir_relocate(void)
                                        zone->zone_start_pfn));
        }
 
-       /* Clear orig address */
+       /* Clear orig addresses */
 
-       for(i = 0, p = pagedir_nosave; i < nr_copy_pages; i++, p++) {
+       for_each_pbe (p, pblist)
                ClearPageNosaveFree(virt_to_page(p->orig_address));
-       }
 
-       if (!does_collide_order((unsigned long)old_pagedir, pagedir_order)) {
-               printk("not necessary\n");
-               return check_pagedir();
-       }
+       tail = pblist + PB_PAGE_SKIP;
 
-       while ((m = (void *) __get_free_pages(GFP_ATOMIC, pagedir_order)) != NULL) {
-               if (!does_collide_order((unsigned long)m, pagedir_order))
-                       break;
-               eaten_memory = m;
-               printk( "." ); 
-               *eaten_memory = c;
-               c = eaten_memory;
-       }
+       /* Relocate colliding pages */
 
-       if (!m) {
-               printk("out of memory\n");
-               ret = -ENOMEM;
-       } else {
-               pagedir_nosave =
-                       memcpy(m, old_pagedir, PAGE_SIZE << pagedir_order);
+       for_each_pb_page (pbpage, pblist) {
+               if (does_collide_order((unsigned long)pbpage, 0)) {
+                       m = (void *)get_usable_page(GFP_ATOMIC | __GFP_COLD);
+                       if (!m) {
+                               error = -ENOMEM;
+                               break;
+                       }
+                       memcpy(m, (void *)pbpage, PAGE_SIZE);
+                       if (pbpage == pblist)
+                               pblist = (struct pbe *)m;
+                       else
+                               tail->next = (struct pbe *)m;
+
+                       eat_page((void *)pbpage);
+                       pbpage = (struct pbe *)m;
+
+                       /* We have to link the PBEs again */
+
+                       for (p = pbpage; p < pbpage + PB_PAGE_SKIP; p++)
+                               if (p->next) /* needed to save the end */
+                                       p->next = p + 1;
+
+                       rel++;
+               }
+               tail = pbpage + PB_PAGE_SKIP;
        }
 
-       c = eaten_memory;
-       while (c) {
-               printk(":");
-               f = c;
-               c = *c;
-               free_pages((unsigned long)f, pagedir_order);
+       if (error) {
+               printk("\nswsusp: Out of memory\n\n");
+               free_pagedir(pblist);
+               free_eaten_memory();
+               pblist = NULL;
        }
-       if (ret)
-               return ret;
-       printk("|\n");
-       return check_pagedir();
+       else
+               printk("swsusp: Relocated %d pages\n", rel);
+
+       return pblist;
 }
 
-/**
+/*
  *     Using bio to read from swap.
  *     This code requires a bit more work than just using buffer heads
  *     but, it is the recommended way for 2.5/2.6.
@@ -1075,7 +1178,7 @@ static int bio_write_page(pgoff_t page_off, void * page)
  * I really don't think that it's foolproof but more than nothing..
  */
 
-static const char * __init sanity_check(void)
+static const char * sanity_check(void)
 {
        dump_info();
        if(swsusp_info.version_code != LINUX_VERSION_CODE)
@@ -1096,7 +1199,7 @@ static const char * __init sanity_check(void)
 }
 
 
-static int __init check_header(void)
+static int check_header(void)
 {
        const char * reason = NULL;
        int error;
@@ -1110,11 +1213,10 @@ static int __init check_header(void)
                return -EPERM;
        }
        nr_copy_pages = swsusp_info.image_pages;
-       pagedir_order = get_bitmask_order(SUSPEND_PD_PAGES(nr_copy_pages));
        return error;
 }
 
-static int __init check_sig(void)
+static int check_sig(void)
 {
        int error;
 
@@ -1129,7 +1231,7 @@ static int __init check_sig(void)
                 */
                error = bio_write_page(0, &swsusp_header);
        } else { 
-               pr_debug(KERN_ERR "swsusp: Suspend partition has wrong signature?\n");
+               printk(KERN_ERR "swsusp: Suspend partition has wrong signature?\n");
                return -EINVAL;
        }
        if (!error)
@@ -1138,104 +1240,194 @@ static int __init check_sig(void)
 }
 
 /**
- *     swsusp_read_data - Read image pages from swap.
+ *     data_read - Read image pages from swap.
  *
  *     You do not need to check for overlaps, check_pagedir()
  *     already did that.
  */
 
-static int __init data_read(void)
+static int data_read(struct pbe *pblist)
 {
        struct pbe * p;
-       int error;
-       int i;
-       int mod = nr_copy_pages / 100;
+       int error = 0;
+       int i = 0;
+       int mod = swsusp_info.image_pages / 100;
 
        if (!mod)
                mod = 1;
 
-       if ((error = swsusp_pagedir_relocate()))
-               return error;
+       printk("swsusp: Reading image data (%lu pages):     ",
+                       swsusp_info.image_pages);
+
+       for_each_pbe (p, pblist) {
+               if (!(i % mod))
+                       printk("\b\b\b\b%3d%%", i / mod);
 
-       printk( "Reading image data (%d pages):     ", nr_copy_pages );
-       for(i = 0, p = pagedir_nosave; i < nr_copy_pages && !error; i++, p++) {
-               if (!(i%mod))
-                       printk( "\b\b\b\b%3d%%", i / mod );
                error = bio_read_page(swp_offset(p->swap_address),
                                  (void *)p->address);
+               if (error)
+                       return error;
+
+               i++;
        }
-       printk(" %d done.\n",i);
+       printk("\b\b\b\bdone\n");
        return error;
-
 }
 
-extern dev_t __init name_to_dev_t(const char *line);
+extern dev_t name_to_dev_t(const char *line);
 
-static int __init read_pagedir(void)
+/**
+ *     read_pagedir - Read page backup list pages from swap
+ */
+
+static int read_pagedir(struct pbe *pblist)
 {
-       unsigned long addr;
-       int i, n = swsusp_info.pagedir_pages;
-       int error = 0;
+       struct pbe *pbpage, *p;
+       unsigned i = 0;
+       int error;
 
-       addr = __get_free_pages(GFP_ATOMIC, pagedir_order);
-       if (!addr)
-               return -ENOMEM;
-       pagedir_nosave = (struct pbe *)addr;
+       if (!pblist)
+               return -EFAULT;
 
-       pr_debug("swsusp: Reading pagedir (%d Pages)\n",n);
+       printk("swsusp: Reading pagedir (%lu pages)\n",
+                       swsusp_info.pagedir_pages);
 
-       for (i = 0; i < n && !error; i++, addr += PAGE_SIZE) {
-               unsigned long offset = swp_offset(swsusp_info.pagedir[i]);
-               if (offset)
-                       error = bio_read_page(offset, (void *)addr);
-               else
-                       error = -EFAULT;
+       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;
        }
+
        if (error)
-               free_pages((unsigned long)pagedir_nosave, pagedir_order);
+               free_page((unsigned long)pblist);
+
+       BUG_ON(i != swsusp_info.pagedir_pages);
+
        return error;
 }
 
-static int __init read_suspend_image(void)
+
+static int check_suspend_image(void)
 {
        int error = 0;
 
        if ((error = check_sig()))
                return error;
+
        if ((error = check_header()))
                return error;
-       if ((error = read_pagedir()))
+
+       return 0;
+}
+
+static int read_suspend_image(void)
+{
+       int error = 0;
+       struct pbe *p;
+
+       if (!(p = alloc_pagedir(nr_copy_pages)))
+               return -ENOMEM;
+
+       if ((error = read_pagedir(p)))
                return error;
-       if ((error = data_read()))
-               free_pages((unsigned long)pagedir_nosave, pagedir_order);
+
+       create_pbe_list(p, nr_copy_pages);
+
+       if (!(pagedir_nosave = swsusp_pagedir_relocate(p)))
+               return -ENOMEM;
+
+       /* Allocate memory for the image and read the data from swap */
+
+       error = check_pagedir(pagedir_nosave);
+       free_eaten_memory();
+       if (!error)
+               error = data_read(pagedir_nosave);
+
+       if (error) { /* We fail cleanly */
+               for_each_pbe (p, pagedir_nosave)
+                       if (p->address) {
+                               free_page(p->address);
+                               p->address = 0UL;
+                       }
+               free_pagedir(pagedir_nosave);
+       }
        return error;
 }
 
 /**
- *     swsusp_read - Read saved image from swap.
+ *      swsusp_check - Check for saved image in swap
  */
 
-int __init swsusp_read(void)
+int swsusp_check(void)
 {
        int error;
 
-       if (!strlen(resume_file))
-               return -ENOENT;
-
-       resume_device = name_to_dev_t(resume_file);
-       pr_debug("swsusp: Resume From Partition: %s\n", resume_file);
+       if (!swsusp_resume_device) {
+               if (!strlen(resume_file))
+                       return -ENOENT;
+               swsusp_resume_device = name_to_dev_t(resume_file);
+               pr_debug("swsusp: Resume From Partition %s\n", resume_file);
+       } else {
+               pr_debug("swsusp: Resume From Partition %d:%d\n",
+                        MAJOR(swsusp_resume_device), MINOR(swsusp_resume_device));
+       }
 
-       resume_bdev = open_by_devnum(resume_device, FMODE_READ);
+       resume_bdev = open_by_devnum(swsusp_resume_device, FMODE_READ);
        if (!IS_ERR(resume_bdev)) {
                set_blocksize(resume_bdev, PAGE_SIZE);
-               error = read_suspend_image();
-               blkdev_put(resume_bdev);
+               error = check_suspend_image();
+               if (error)
+                   blkdev_put(resume_bdev);
        } else
                error = PTR_ERR(resume_bdev);
 
        if (!error)
-               pr_debug("Reading resume file was successful\n");
+               pr_debug("swsusp: resume file found\n");
+       else
+               pr_debug("swsusp: Error %d check for resume file\n", error);
+       return error;
+}
+
+/**
+ *     swsusp_read - Read saved image from swap.
+ */
+
+int swsusp_read(void)
+{
+       int error;
+
+       if (IS_ERR(resume_bdev)) {
+               pr_debug("swsusp: block device not initialised\n");
+               return PTR_ERR(resume_bdev);
+       }
+
+       error = read_suspend_image();
+       blkdev_put(resume_bdev);
+
+       if (!error)
+               pr_debug("swsusp: Reading resume file was successful\n");
        else
                pr_debug("swsusp: Error %d resuming\n", error);
        return error;
 }
+
+/**
+ *     swsusp_close - close swap device.
+ */
+
+void swsusp_close(void)
+{
+       if (IS_ERR(resume_bdev)) {
+               pr_debug("swsusp: block device not initialised\n");
+               return;
+       }
+
+       blkdev_put(resume_bdev);
+}