nfsd: track last inode only in use_wgather case
[safe/jmp/linux-2.6] / kernel / power / swsusp.c
index f9238fa..78c3504 100644 (file)
@@ -49,6 +49,9 @@
 #include <linux/bootmem.h>
 #include <linux/syscalls.h>
 #include <linux/highmem.h>
+#include <linux/time.h>
+#include <linux/rbtree.h>
+#include <linux/io.h>
 
 #include "power.h"
 
@@ -62,109 +65,136 @@ unsigned long image_size = 500 * 1024 * 1024;
 
 int in_suspend __nosavedata = 0;
 
-#ifdef CONFIG_HIGHMEM
-unsigned int count_highmem_pages(void);
-int save_highmem(void);
-int restore_highmem(void);
-#else
-static int save_highmem(void) { return 0; }
-static int restore_highmem(void) { return 0; }
-static unsigned int count_highmem_pages(void) { return 0; }
-#endif
-
 /**
  *     The following functions are used for tracing the allocated
  *     swap pages, so that they can be freed in case of an error.
- *
- *     The functions operate on a linked bitmap structure defined
- *     in power.h
  */
 
-void free_bitmap(struct bitmap_page *bitmap)
-{
-       struct bitmap_page *bp;
+struct swsusp_extent {
+       struct rb_node node;
+       unsigned long start;
+       unsigned long end;
+};
 
-       while (bitmap) {
-               bp = bitmap->next;
-               free_page((unsigned long)bitmap);
-               bitmap = bp;
-       }
-}
+static struct rb_root swsusp_extents = RB_ROOT;
 
-struct bitmap_page *alloc_bitmap(unsigned int nr_bits)
+static int swsusp_extents_insert(unsigned long swap_offset)
 {
-       struct bitmap_page *bitmap, *bp;
-       unsigned int n;
-
-       if (!nr_bits)
-               return NULL;
-
-       bitmap = (struct bitmap_page *)get_zeroed_page(GFP_KERNEL);
-       bp = bitmap;
-       for (n = BITMAP_PAGE_BITS; n < nr_bits; n += BITMAP_PAGE_BITS) {
-               bp->next = (struct bitmap_page *)get_zeroed_page(GFP_KERNEL);
-               bp = bp->next;
-               if (!bp) {
-                       free_bitmap(bitmap);
-                       return NULL;
+       struct rb_node **new = &(swsusp_extents.rb_node);
+       struct rb_node *parent = NULL;
+       struct swsusp_extent *ext;
+
+       /* Figure out where to put the new node */
+       while (*new) {
+               ext = container_of(*new, struct swsusp_extent, node);
+               parent = *new;
+               if (swap_offset < ext->start) {
+                       /* Try to merge */
+                       if (swap_offset == ext->start - 1) {
+                               ext->start--;
+                               return 0;
+                       }
+                       new = &((*new)->rb_left);
+               } else if (swap_offset > ext->end) {
+                       /* Try to merge */
+                       if (swap_offset == ext->end + 1) {
+                               ext->end++;
+                               return 0;
+                       }
+                       new = &((*new)->rb_right);
+               } else {
+                       /* It already is in the tree */
+                       return -EINVAL;
                }
        }
-       return bitmap;
-}
-
-static int bitmap_set(struct bitmap_page *bitmap, unsigned long bit)
-{
-       unsigned int n;
+       /* Add the new node and rebalance the tree. */
+       ext = kzalloc(sizeof(struct swsusp_extent), GFP_KERNEL);
+       if (!ext)
+               return -ENOMEM;
 
-       n = BITMAP_PAGE_BITS;
-       while (bitmap && n <= bit) {
-               n += BITMAP_PAGE_BITS;
-               bitmap = bitmap->next;
-       }
-       if (!bitmap)
-               return -EINVAL;
-       n -= BITMAP_PAGE_BITS;
-       bit -= n;
-       n = 0;
-       while (bit >= BITS_PER_CHUNK) {
-               bit -= BITS_PER_CHUNK;
-               n++;
-       }
-       bitmap->chunks[n] |= (1UL << bit);
+       ext->start = swap_offset;
+       ext->end = swap_offset;
+       rb_link_node(&ext->node, parent, new);
+       rb_insert_color(&ext->node, &swsusp_extents);
        return 0;
 }
 
-unsigned long alloc_swap_page(int swap, struct bitmap_page *bitmap)
+/**
+ *     alloc_swapdev_block - allocate a swap page and register that it has
+ *     been allocated, so that it can be freed in case of an error.
+ */
+
+sector_t alloc_swapdev_block(int swap)
 {
        unsigned long offset;
 
        offset = swp_offset(get_swap_page_of_type(swap));
        if (offset) {
-               if (bitmap_set(bitmap, offset)) {
+               if (swsusp_extents_insert(offset))
                        swap_free(swp_entry(swap, offset));
-                       offset = 0;
-               }
+               else
+                       return swapdev_block(swap, offset);
        }
-       return offset;
+       return 0;
 }
 
-void free_all_swap_pages(int swap, struct bitmap_page *bitmap)
+/**
+ *     free_all_swap_pages - free swap pages allocated for saving image data.
+ *     It also frees the extents used to register which swap entres had been
+ *     allocated.
+ */
+
+void free_all_swap_pages(int swap)
 {
-       unsigned int bit, n;
-       unsigned long test;
-
-       bit = 0;
-       while (bitmap) {
-               for (n = 0; n < BITMAP_PAGE_CHUNKS; n++)
-                       for (test = 1UL; test; test <<= 1) {
-                               if (bitmap->chunks[n] & test)
-                                       swap_free(swp_entry(swap, bit));
-                               bit++;
-                       }
-               bitmap = bitmap->next;
+       struct rb_node *node;
+
+       while ((node = swsusp_extents.rb_node)) {
+               struct swsusp_extent *ext;
+               unsigned long offset;
+
+               ext = container_of(node, struct swsusp_extent, node);
+               rb_erase(node, &swsusp_extents);
+               for (offset = ext->start; offset <= ext->end; offset++)
+                       swap_free(swp_entry(swap, offset));
+
+               kfree(ext);
        }
 }
 
+int swsusp_swap_in_use(void)
+{
+       return (swsusp_extents.rb_node != NULL);
+}
+
+/**
+ *     swsusp_show_speed - print the time elapsed between two events represented by
+ *     @start and @stop
+ *
+ *     @nr_pages -     number of pages processed between @start and @stop
+ *     @msg -          introductory message to print
+ */
+
+void swsusp_show_speed(struct timeval *start, struct timeval *stop,
+                       unsigned nr_pages, char *msg)
+{
+       s64 elapsed_centisecs64;
+       int centisecs;
+       int k;
+       int kps;
+
+       elapsed_centisecs64 = timeval_to_ns(stop) - timeval_to_ns(start);
+       do_div(elapsed_centisecs64, NSEC_PER_SEC / 100);
+       centisecs = elapsed_centisecs64;
+       if (centisecs == 0)
+               centisecs = 1;  /* avoid div-by-zero */
+       k = nr_pages * (PAGE_SIZE / 1024);
+       kps = (k * 100) / centisecs;
+       printk(KERN_INFO "PM: %s %d kbytes in %d.%02d seconds (%d.%02d MB/s)\n",
+                       msg, k,
+                       centisecs / 100, centisecs % 100,
+                       kps / 1000, (kps % 1000) / 10);
+}
+
 /**
  *     swsusp_shrink_memory -  Try to free as much memory as needed
  *
@@ -184,22 +214,37 @@ static inline unsigned long __shrink_memory(long tmp)
 
 int swsusp_shrink_memory(void)
 {
-       long size, tmp;
+       long tmp;
        struct zone *zone;
        unsigned long pages = 0;
        unsigned int i = 0;
        char *p = "-\\|/";
+       struct timeval start, stop;
 
-       printk("Shrinking memory...  ");
+       printk(KERN_INFO "PM: Shrinking memory...  ");
+       do_gettimeofday(&start);
        do {
-               size = 2 * count_highmem_pages();
-               size += size / 50 + count_data_pages();
-               size += (size + PBES_PER_PAGE - 1) / PBES_PER_PAGE +
-                       PAGES_FOR_IO;
+               long size, highmem_size;
+
+               highmem_size = count_highmem_pages();
+               size = count_data_pages() + PAGES_FOR_IO + SPARE_PAGES;
                tmp = size;
-               for_each_zone (zone)
-                       if (!is_highmem(zone))
-                               tmp -= zone->free_pages;
+               size += highmem_size;
+               for_each_populated_zone(zone) {
+                       tmp += snapshot_additional_pages(zone);
+                       if (is_highmem(zone)) {
+                               highmem_size -=
+                                       zone_page_state(zone, NR_FREE_PAGES);
+                       } else {
+                               tmp -= zone_page_state(zone, NR_FREE_PAGES);
+                               tmp += zone->lowmem_reserve[ZONE_NORMAL];
+                       }
+               }
+
+               if (highmem_size < 0)
+                       highmem_size = 0;
+
+               tmp += highmem_size;
                if (tmp > 0) {
                        tmp = __shrink_memory(tmp);
                        if (!tmp)
@@ -211,69 +256,131 @@ int swsusp_shrink_memory(void)
                }
                printk("\b%c", p[i++%4]);
        } while (tmp > 0);
+       do_gettimeofday(&stop);
        printk("\bdone (%lu pages freed)\n", pages);
+       swsusp_show_speed(&start, &stop, pages, "Freed");
 
        return 0;
 }
 
-int swsusp_suspend(void)
+/*
+ * Platforms, like ACPI, may want us to save some memory used by them during
+ * hibernation and to restore the contents of this memory during the subsequent
+ * resume.  The code below implements a mechanism allowing us to do that.
+ */
+
+struct nvs_page {
+       unsigned long phys_start;
+       unsigned int size;
+       void *kaddr;
+       void *data;
+       struct list_head node;
+};
+
+static LIST_HEAD(nvs_list);
+
+/**
+ *     hibernate_nvs_register - register platform NVS memory region to save
+ *     @start - physical address of the region
+ *     @size - size of the region
+ *
+ *     The NVS region need not be page-aligned (both ends) and we arrange
+ *     things so that the data from page-aligned addresses in this region will
+ *     be copied into separate RAM pages.
+ */
+int hibernate_nvs_register(unsigned long start, unsigned long size)
 {
-       int error;
-
-       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))) {
-               printk(KERN_ERR "Some devices failed to power down, aborting suspend\n");
-               goto Enable_irqs;
+       struct nvs_page *entry, *next;
+
+       while (size > 0) {
+               unsigned int nr_bytes;
+
+               entry = kzalloc(sizeof(struct nvs_page), GFP_KERNEL);
+               if (!entry)
+                       goto Error;
+
+               list_add_tail(&entry->node, &nvs_list);
+               entry->phys_start = start;
+               nr_bytes = PAGE_SIZE - (start & ~PAGE_MASK);
+               entry->size = (size < nr_bytes) ? size : nr_bytes;
+
+               start += entry->size;
+               size -= entry->size;
        }
+       return 0;
+
+ Error:
+       list_for_each_entry_safe(entry, next, &nvs_list, node) {
+               list_del(&entry->node);
+               kfree(entry);
+       }
+       return -ENOMEM;
+}
+
+/**
+ *     hibernate_nvs_free - free data pages allocated for saving NVS regions
+ */
+void hibernate_nvs_free(void)
+{
+       struct nvs_page *entry;
+
+       list_for_each_entry(entry, &nvs_list, node)
+               if (entry->data) {
+                       free_page((unsigned long)entry->data);
+                       entry->data = NULL;
+                       if (entry->kaddr) {
+                               iounmap(entry->kaddr);
+                               entry->kaddr = NULL;
+                       }
+               }
+}
 
-       if ((error = save_highmem())) {
-               printk(KERN_ERR "swsusp: Not enough free pages for highmem\n");
-               goto Restore_highmem;
+/**
+ *     hibernate_nvs_alloc - allocate memory necessary for saving NVS regions
+ */
+int hibernate_nvs_alloc(void)
+{
+       struct nvs_page *entry;
+
+       list_for_each_entry(entry, &nvs_list, node) {
+               entry->data = (void *)__get_free_page(GFP_KERNEL);
+               if (!entry->data) {
+                       hibernate_nvs_free();
+                       return -ENOMEM;
+               }
        }
+       return 0;
+}
 
-       save_processor_state();
-       if ((error = swsusp_arch_suspend()))
-               printk(KERN_ERR "Error %d suspending\n", error);
-       /* Restore control flow magically appears here */
-       restore_processor_state();
-Restore_highmem:
-       restore_highmem();
-       device_power_up();
-Enable_irqs:
-       local_irq_enable();
-       return error;
+/**
+ *     hibernate_nvs_save - save NVS memory regions
+ */
+void hibernate_nvs_save(void)
+{
+       struct nvs_page *entry;
+
+       printk(KERN_INFO "PM: Saving platform NVS memory\n");
+
+       list_for_each_entry(entry, &nvs_list, node)
+               if (entry->data) {
+                       entry->kaddr = ioremap(entry->phys_start, entry->size);
+                       memcpy(entry->data, entry->kaddr, entry->size);
+               }
 }
 
-int swsusp_resume(void)
+/**
+ *     hibernate_nvs_restore - restore NVS memory regions
+ *
+ *     This function is going to be called with interrupts disabled, so it
+ *     cannot iounmap the virtual addresses used to access the NVS region.
+ */
+void hibernate_nvs_restore(void)
 {
-       int error;
-       local_irq_disable();
-       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();
-       /* Code below is only ever reached in case of failure. Otherwise
-        * execution continues at place where swsusp_arch_suspend was called
-         */
-       BUG_ON(!error);
-       /* The only reason why swsusp_arch_resume() can fail is memory being
-        * very tight, so we have to free it as soon as we can to avoid
-        * subsequent failures
-        */
-       swsusp_free();
-       restore_processor_state();
-       restore_highmem();
-       touch_softlockup_watchdog();
-       device_power_up();
-       local_irq_enable();
-       return error;
+       struct nvs_page *entry;
+
+       printk(KERN_INFO "PM: Restoring platform NVS memory\n");
+
+       list_for_each_entry(entry, &nvs_list, node)
+               if (entry->data)
+                       memcpy(entry->kaddr, entry->data, entry->size);
 }