vserver 1.9.5.x5
[linux-2.6.git] / kernel / power / swsusp.c
index 1c49df1..9ca3b1c 100644 (file)
 #include <asm/uaccess.h>
 #include <asm/mmu_context.h>
 #include <asm/pgtable.h>
+#include <asm/tlbflush.h>
 #include <asm/io.h>
 
 #include "power.h"
 
 /* References to section boundaries */
-extern char __nosave_begin, __nosave_end;
-
-extern int is_head_of_free_region(struct page *);
+extern const void __nosave_begin, __nosave_end;
 
 /* Variables to be preserved over suspend */
-int pagedir_order_check;
-int nr_copy_pages_check;
+static int pagedir_order_check;
+static int nr_copy_pages_check;
 
 extern char resume_file[];
 static dev_t resume_device;
@@ -104,14 +103,14 @@ static int pagedir_order __nosavedata = 0;
 
 #define SWSUSP_SIG     "S1SUSPEND"
 
-struct swsusp_header {
+static struct swsusp_header {
        char reserved[PAGE_SIZE - 20 - sizeof(swp_entry_t)];
        swp_entry_t swsusp_info;
        char    orig_sig[10];
        char    sig[10];
 } __attribute__((packed, aligned(PAGE_SIZE))) swsusp_header;
 
-struct swsusp_info swsusp_info;
+static struct swsusp_info swsusp_info;
 
 /*
  * XXX: We try to keep some more pages free so that I/O operations succeed
@@ -174,7 +173,7 @@ static int is_resume_device(const struct swap_info_struct *swap_info)
                resume_device == MKDEV(imajor(inode), iminor(inode));
 }
 
-int swsusp_swap_check(void) /* This is called before saving image */
+static int swsusp_swap_check(void) /* This is called before saving image */
 {
        int i, len;
        
@@ -294,15 +293,19 @@ static int data_write(void)
 {
        int error = 0;
        int i;
+       unsigned int mod = nr_copy_pages / 100;
+
+       if (!mod)
+               mod = 1;
 
-       printk( "Writing data to swap (%d pages): ", nr_copy_pages );
+       printk( "Writing data to swap (%d pages)...     ", nr_copy_pages );
        for (i = 0; i < nr_copy_pages && !error; i++) {
-               if (!(i%100))
-                       printk( "." );
+               if (!(i%mod))
+                       printk( "\b\b\b\b%3d%%", i / mod );
                error = write_page((pagedir_nosave+i)->address,
                                          &((pagedir_nosave+i)->swap_address));
        }
-       printk(" %d Pages done.\n",i);
+       printk("\b\b\b\bdone\n");
        return error;
 }
 
@@ -417,17 +420,17 @@ struct highmem_page {
        struct highmem_page *next;
 };
 
-struct highmem_page *highmem_copy = NULL;
+static struct highmem_page *highmem_copy;
 
 static int save_highmem_zone(struct zone *zone)
 {
        unsigned long zone_pfn;
+       mark_free_pages(zone);
        for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn) {
                struct page *page;
                struct highmem_page *save;
                void *kaddr;
                unsigned long pfn = zone_pfn + zone->zone_start_pfn;
-               int chunk_size;
 
                if (!(pfn%1000))
                        printk(".");
@@ -444,11 +447,9 @@ static int save_highmem_zone(struct zone *zone)
                        printk("highmem reserved page?!\n");
                        continue;
                }
-               if ((chunk_size = is_head_of_free_region(page))) {
-                       pfn += chunk_size - 1;
-                       zone_pfn += chunk_size - 1;
+               BUG_ON(PageNosave(page));
+               if (PageNosaveFree(page))
                        continue;
-               }
                save = kmalloc(sizeof(struct highmem_page), GFP_ATOMIC);
                if (!save)
                        return -ENOMEM;
@@ -520,21 +521,16 @@ static int pfn_is_nosave(unsigned long pfn)
  *     We save a page if it's Reserved, and not in the range of pages
  *     statically defined as 'unsaveable', or if it isn't reserved, and
  *     isn't part of a free chunk of pages.
- *     If it is part of a free chunk, we update @pfn to point to the last 
- *     page of the chunk.
  */
 
 static int saveable(struct zone * zone, unsigned long * zone_pfn)
 {
        unsigned long pfn = *zone_pfn + zone->zone_start_pfn;
-       unsigned long chunk_size;
        struct page * page;
 
        if (!pfn_valid(pfn))
                return 0;
 
-       if (!(pfn%1000))
-               printk(".");
        page = pfn_to_page(pfn);
        BUG_ON(PageReserved(page) && PageNosave(page));
        if (PageNosave(page))
@@ -543,10 +539,8 @@ static int saveable(struct zone * zone, unsigned long * zone_pfn)
                pr_debug("[nosave pfn 0x%lx]", pfn);
                return 0;
        }
-       if ((chunk_size = is_head_of_free_region(page))) {
-               *zone_pfn += chunk_size - 1;
+       if (PageNosaveFree(page))
                return 0;
-       }
 
        return 1;
 }
@@ -559,10 +553,11 @@ static void count_data_pages(void)
        nr_copy_pages = 0;
 
        for_each_zone(zone) {
-               if (!is_highmem(zone)) {
-                       for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn)
-                               nr_copy_pages += saveable(zone, &zone_pfn);
-               }
+               if (is_highmem(zone))
+                       continue;
+               mark_free_pages(zone);
+               for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn)
+                       nr_copy_pages += saveable(zone, &zone_pfn);
        }
 }
 
@@ -572,52 +567,25 @@ static void copy_data_pages(void)
        struct zone *zone;
        unsigned long zone_pfn;
        struct pbe * pbe = pagedir_nosave;
+       int to_copy = nr_copy_pages;
        
        for_each_zone(zone) {
-               if (!is_highmem(zone))
-                       for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn) {
-                               if (saveable(zone, &zone_pfn)) {
-                                       struct page * page;
-                                       page = pfn_to_page(zone_pfn + zone->zone_start_pfn);
-                                       pbe->orig_address = (long) page_address(page);
-                                       /* copy_page is no usable for copying task structs. */
-                                       memcpy((void *)pbe->address, (void *)pbe->orig_address, PAGE_SIZE);
-                                       pbe++;
-                               }
-                       }
-       }
-}
-
-
-static void free_suspend_pagedir_zone(struct zone *zone, unsigned long pagedir)
-{
-       unsigned long zone_pfn, pagedir_end, pagedir_pfn, pagedir_end_pfn;
-       pagedir_end = pagedir + (PAGE_SIZE << pagedir_order);
-       pagedir_pfn = __pa(pagedir) >> PAGE_SHIFT;
-       pagedir_end_pfn = __pa(pagedir_end) >> PAGE_SHIFT;
-       for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn) {
-               struct page *page;
-               unsigned long pfn = zone_pfn + zone->zone_start_pfn;
-               if (!pfn_valid(pfn))
-                       continue;
-               page = pfn_to_page(pfn);
-               if (!TestClearPageNosave(page))
-                       continue;
-               else if (pfn >= pagedir_pfn && pfn < pagedir_end_pfn)
+               if (is_highmem(zone))
                        continue;
-               __free_page(page);
-       }
-}
-
-void swsusp_free(void)
-{
-       unsigned long p = (unsigned long)pagedir_save;
-       struct zone *zone;
-       for_each_zone(zone) {
-               if (!is_highmem(zone))
-                       free_suspend_pagedir_zone(zone, p);
+               mark_free_pages(zone);
+               for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn) {
+                       if (saveable(zone, &zone_pfn)) {
+                               struct page * page;
+                               page = pfn_to_page(zone_pfn + zone->zone_start_pfn);
+                               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--;
+                       }
+               }
        }
-       free_pages(p, pagedir_order);
+       BUG_ON(to_copy);
 }
 
 
@@ -683,6 +651,24 @@ static int alloc_pagedir(void)
        return 0;
 }
 
+/**
+ *     free_image_pages - Free pages allocated for snapshot
+ */
+
+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++) {
+               if (p->address) {
+                       ClearPageNosave(virt_to_page(p->address));
+                       free_page(p->address);
+                       p->address = 0;
+               }
+       }
+}
 
 /**
  *     alloc_image_pages - Allocate pages for the snapshot.
@@ -696,18 +682,19 @@ static int alloc_image_pages(void)
 
        for (i = 0, p = pagedir_save; i < nr_copy_pages; i++, p++) {
                p->address = get_zeroed_page(GFP_ATOMIC | __GFP_COLD);
-               if(!p->address)
-                       goto Error;
+               if (!p->address)
+                       return -ENOMEM;
                SetPageNosave(virt_to_page(p->address));
        }
        return 0;
- Error:
-       do { 
-               if (p->address)
-                       free_page(p->address);
-               p->address = 0;
-       } while (p-- > pagedir_save);
-       return -ENOMEM;
+}
+
+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);
 }
 
 
@@ -766,11 +753,11 @@ static int swsusp_alloc(void)
                return -ENOSPC;
 
        if ((error = alloc_pagedir())) {
-               pr_debug("suspend: Allocating pagedir failed.\n");
+               printk(KERN_ERR "suspend: Allocating pagedir failed.\n");
                return error;
        }
        if ((error = alloc_image_pages())) {
-               pr_debug("suspend: Allocating image pages failed.\n");
+               printk(KERN_ERR "suspend: Allocating image pages failed.\n");
                swsusp_free();
                return error;
        }
@@ -780,21 +767,20 @@ static int swsusp_alloc(void)
        return 0;
 }
 
-int suspend_prepare_image(void)
+static int suspend_prepare_image(void)
 {
-       unsigned int nr_needed_pages = 0;
        int error;
 
        pr_debug("swsusp: critical section: \n");
        if (save_highmem()) {
                printk(KERN_CRIT "Suspend machine: Not enough free pages for highmem\n");
+               restore_highmem();
                return -ENOMEM;
        }
 
        drain_local_pages();
        count_data_pages();
        printk("swsusp: Need to copy %u pages\n",nr_copy_pages);
-       nr_needed_pages = nr_copy_pages + PAGES_FOR_IO;
 
        error = swsusp_alloc();
        if (error)
@@ -843,8 +829,11 @@ asmlinkage int swsusp_save(void)
 {
        int error = 0;
 
-       if ((error = swsusp_swap_check()))
+       if ((error = swsusp_swap_check())) {
+               printk(KERN_ERR "swsusp: FATAL: cannot find swap device, try "
+                               "swapon -a!\n");
                return error;
+       }
        return suspend_prepare_image();
 }
 
@@ -854,11 +843,22 @@ int swsusp_suspend(void)
        if ((error = arch_prepare_suspend()))
                return error;
        local_irq_disable();
+       /* At this point, device_suspend() has been called, but *not*
+        * device_power_down(). We *must* device_power_down() now.
+        * Otherwise, drivers for some devices (e.g. interrupt controllers)
+        * become desynchronized with the actual state of the hardware
+        * at resume time, and evil weirdness ensues.
+        */
+       if ((error = device_power_down(PMSG_FREEZE))) {
+               local_irq_enable();
+               return error;
+       }
        save_processor_state();
        error = swsusp_arch_suspend();
        /* Restore control flow magically appears here */
        restore_processor_state();
        restore_highmem();
+       device_power_up();
        local_irq_enable();
        return error;
 }
@@ -878,6 +878,7 @@ int swsusp_resume(void)
 {
        int error;
        local_irq_disable();
+       device_power_down(PMSG_FREEZE);
        /* We'll ignore saved state, but this gets preempt count (etc) right */
        save_processor_state();
        error = swsusp_arch_resume();
@@ -887,30 +888,23 @@ int swsusp_resume(void)
        BUG_ON(!error);
        restore_processor_state();
        restore_highmem();
+       device_power_up();
        local_irq_enable();
        return error;
 }
 
-
-
 /* More restore stuff */
 
-#define does_collide(addr) does_collide_order(pagedir_nosave, addr, 0)
-
 /*
  * Returns true if given address/order collides with any orig_address 
  */
-static int __init does_collide_order(suspend_pagedir_t *pagedir, unsigned long addr,
-               int order)
+static int __init does_collide_order(unsigned long addr, int order)
 {
        int i;
-       unsigned long addre = addr + (PAGE_SIZE<<order);
        
-       for (i=0; i < nr_copy_pages; i++)
-               if ((pagedir+i)->orig_address >= addr &&
-                       (pagedir+i)->orig_address < addre)
+       for (i=0; i < (1<<order); i++)
+               if (!PageNosaveFree(virt_to_page(addr + i * PAGE_SIZE)))
                        return 1;
-
        return 0;
 }
 
@@ -929,7 +923,7 @@ static int __init check_pagedir(void)
                        addr = get_zeroed_page(GFP_ATOMIC);
                        if(!addr)
                                return -ENOMEM;
-               } while (does_collide(addr));
+               } while (does_collide_order(addr, 0));
 
                (pagedir_nosave+i)->address = addr;
        }
@@ -946,16 +940,34 @@ static int __init swsusp_pagedir_relocate(void)
        void **eaten_memory = NULL;
        void **c = eaten_memory, *m, *f;
        int ret = 0;
+       struct zone *zone;
+       int i;
+       struct pbe *p;
+       unsigned long zone_pfn;
 
        printk("Relocating pagedir ");
 
-       if (!does_collide_order(old_pagedir, (unsigned long)old_pagedir, pagedir_order)) {
+       /* Set page flags */
+
+       for_each_zone(zone) {
+               for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn)
+                       SetPageNosaveFree(pfn_to_page(zone_pfn +
+                                       zone->zone_start_pfn));
+       }
+
+       /* Clear orig address */
+
+       for(i = 0, p = pagedir_nosave; i < nr_copy_pages; i++, p++) {
+               ClearPageNosaveFree(virt_to_page(p->orig_address));
+       }
+
+       if (!does_collide_order((unsigned long)old_pagedir, pagedir_order)) {
                printk("not necessary\n");
                return check_pagedir();
        }
 
        while ((m = (void *) __get_free_pages(GFP_ATOMIC, pagedir_order)) != NULL) {
-               if (!does_collide_order(old_pagedir, (unsigned long)m, pagedir_order))
+               if (!does_collide_order((unsigned long)m, pagedir_order))
                        break;
                eaten_memory = m;
                printk( "." ); 
@@ -978,6 +990,8 @@ static int __init swsusp_pagedir_relocate(void)
                c = *c;
                free_pages((unsigned long)f, pagedir_order);
        }
+       if (ret)
+               return ret;
        printk("|\n");
        return check_pagedir();
 }
@@ -993,24 +1007,14 @@ static int __init swsusp_pagedir_relocate(void)
 
 static atomic_t io_done = ATOMIC_INIT(0);
 
-static void start_io(void)
-{
-       atomic_set(&io_done,1);
-}
-
 static int end_io(struct bio * bio, unsigned int num, int err)
 {
-       atomic_set(&io_done,0);
+       if (!test_bit(BIO_UPTODATE, &bio->bi_flags))
+               panic("I/O error reading memory image");
+       atomic_set(&io_done, 0);
        return 0;
 }
 
-static void wait_io(void)
-{
-       while(atomic_read(&io_done))
-               io_schedule();
-}
-
-
 static struct block_device * resume_bdev;
 
 /**
@@ -1045,20 +1049,23 @@ static int submit(int rw, pgoff_t page_off, void * page)
 
        if (rw == WRITE)
                bio_set_pages_dirty(bio);
-       start_io();
+
+       atomic_set(&io_done, 1);
        submit_bio(rw | (1 << BIO_RW_SYNC), bio);
-       wait_io();
+       while (atomic_read(&io_done))
+               yield();
+
  Done:
        bio_put(bio);
        return error;
 }
 
-int bio_read_page(pgoff_t page_off, void * page)
+static int bio_read_page(pgoff_t page_off, void * page)
 {
        return submit(READ, page_off, page);
 }
 
-int bio_write_page(pgoff_t page_off, void * page)
+static int bio_write_page(pgoff_t page_off, void * page)
 {
        return submit(WRITE, page_off, page);
 }
@@ -1103,6 +1110,7 @@ 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;
 }
 
@@ -1121,7 +1129,7 @@ static int __init check_sig(void)
                 */
                error = bio_write_page(0, &swsusp_header);
        } else { 
-               pr_debug(KERN_ERR "swsusp: Invalid partition type.\n");
+               pr_debug(KERN_ERR "swsusp: Suspend partition has wrong signature?\n");
                return -EINVAL;
        }
        if (!error)
@@ -1141,14 +1149,18 @@ static int __init data_read(void)
        struct pbe * p;
        int error;
        int i;
+       int mod = nr_copy_pages / 100;
+
+       if (!mod)
+               mod = 1;
 
        if ((error = swsusp_pagedir_relocate()))
                return error;
 
-       printk( "Reading image data (%d pages): ", nr_copy_pages );
+       printk( "Reading image data (%d pages):     ", nr_copy_pages );
        for(i = 0, p = pagedir_nosave; i < nr_copy_pages && !error; i++, p++) {
-               if (!(i%100))
-                       printk( "." );
+               if (!(i%mod))
+                       printk( "\b\b\b\b%3d%%", i / mod );
                error = bio_read_page(swp_offset(p->swap_address),
                                  (void *)p->address);
        }
@@ -1165,14 +1177,12 @@ static int __init read_pagedir(void)
        int i, n = swsusp_info.pagedir_pages;
        int error = 0;
 
-       pagedir_order = get_bitmask_order(n);
-
-       addr =__get_free_pages(GFP_ATOMIC, pagedir_order);
+       addr = __get_free_pages(GFP_ATOMIC, pagedir_order);
        if (!addr)
                return -ENOMEM;
        pagedir_nosave = (struct pbe *)addr;
 
-       pr_debug("pmdisk: Reading pagedir (%d Pages)\n",n);
+       pr_debug("swsusp: Reading pagedir (%d Pages)\n",n);
 
        for (i = 0; i < n && !error; i++, addr += PAGE_SIZE) {
                unsigned long offset = swp_offset(swsusp_info.pagedir[i]);
@@ -1202,7 +1212,7 @@ static int __init read_suspend_image(void)
 }
 
 /**
- *     pmdisk_read - Read saved image from swap.
+ *     swsusp_read - Read saved image from swap.
  */
 
 int __init swsusp_read(void)
@@ -1226,6 +1236,6 @@ int __init swsusp_read(void)
        if (!error)
                pr_debug("Reading resume file was successful\n");
        else
-               pr_debug("pmdisk: Error %d resuming\n", error);
+               pr_debug("swsusp: Error %d resuming\n", error);
        return error;
 }