]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - kernel/power/swsusp.c
Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/linville/wirel...
[linux-2.6-omap-h63xx.git] / kernel / power / swsusp.c
index 5da304c8f1f66473233d68d713a66e2a17c59d54..a92c9145155975b306d52dab08fb095e3af8370e 100644 (file)
@@ -64,14 +64,6 @@ unsigned long image_size = 500 * 1024 * 1024;
 
 int in_suspend __nosavedata = 0;
 
-#ifdef CONFIG_HIGHMEM
-unsigned int count_highmem_pages(void);
-int restore_highmem(void);
-#else
-static inline int restore_highmem(void) { return 0; }
-static inline 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.
@@ -196,7 +188,8 @@ void swsusp_show_speed(struct timeval *start, struct timeval *stop,
                centisecs = 1;  /* avoid div-by-zero */
        k = nr_pages * (PAGE_SIZE / 1024);
        kps = (k * 100) / centisecs;
-       printk("%s %d kbytes in %d.%02d seconds (%d.%02d MB/s)\n", msg, k,
+       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);
 }
@@ -227,7 +220,7 @@ int swsusp_shrink_memory(void)
        char *p = "-\\|/";
        struct timeval start, stop;
 
-       printk("Shrinking memory...  ");
+       printk(KERN_INFO "PM: Shrinking memory...  ");
        do_gettimeofday(&start);
        do {
                long size, highmem_size;
@@ -270,70 +263,124 @@ int swsusp_shrink_memory(void)
        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;
 
-       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();
-       /* NOTE:  device_power_up() is just a resume() for devices
-        * that suspended with irqs off ... no overall powerup.
-        */
-       device_power_up();
- Enable_irqs:
-       local_irq_enable();
-       return error;
+ 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;
+                       }
+               }
 }
 
-int swsusp_resume(void)
+/**
+ *     hibernate_nvs_alloc - allocate memory necessary for saving NVS regions
+ */
+int hibernate_nvs_alloc(void)
 {
-       int error;
-
-       local_irq_disable();
-       /* NOTE:  device_power_down() is just a suspend() with irqs off;
-        * it has no special "power things down" semantics
-        */
-       if (device_power_down(PMSG_PRETHAW))
-               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 = restore_highmem();
-       if (!error) {
-               error = swsusp_arch_resume();
-               /* The code below is only ever reached in case of a failure.
-                * Otherwise execution continues at place where
-                * swsusp_arch_suspend() was called
-                */
-               BUG_ON(!error);
-               /* This call to restore_highmem() undos the previous one */
-               restore_highmem();
+       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;
+               }
        }
-       /* 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();
-       touch_softlockup_watchdog();
-       device_power_up();
-       local_irq_enable();
-       return error;
+       return 0;
+}
+
+/**
+ *     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);
+               }
+}
+
+/**
+ *     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)
+{
+       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);
 }