PM / Hibernate: Restore old swap signature to avoid user space breakage
[linux-2.6.git] / kernel / power / nvs.c
1 /*
2  * linux/kernel/power/hibernate_nvs.c - Routines for handling NVS memory
3  *
4  * Copyright (C) 2008,2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
5  *
6  * This file is released under the GPLv2.
7  */
8
9 #include <linux/io.h>
10 #include <linux/kernel.h>
11 #include <linux/list.h>
12 #include <linux/mm.h>
13 #include <linux/slab.h>
14 #include <linux/suspend.h>
15
16 /*
17  * Platforms, like ACPI, may want us to save some memory used by them during
18  * suspend and to restore the contents of this memory during the subsequent
19  * resume.  The code below implements a mechanism allowing us to do that.
20  */
21
22 struct nvs_page {
23         unsigned long phys_start;
24         unsigned int size;
25         void *kaddr;
26         void *data;
27         struct list_head node;
28 };
29
30 static LIST_HEAD(nvs_list);
31
32 /**
33  *      suspend_nvs_register - register platform NVS memory region to save
34  *      @start - physical address of the region
35  *      @size - size of the region
36  *
37  *      The NVS region need not be page-aligned (both ends) and we arrange
38  *      things so that the data from page-aligned addresses in this region will
39  *      be copied into separate RAM pages.
40  */
41 int suspend_nvs_register(unsigned long start, unsigned long size)
42 {
43         struct nvs_page *entry, *next;
44
45         while (size > 0) {
46                 unsigned int nr_bytes;
47
48                 entry = kzalloc(sizeof(struct nvs_page), GFP_KERNEL);
49                 if (!entry)
50                         goto Error;
51
52                 list_add_tail(&entry->node, &nvs_list);
53                 entry->phys_start = start;
54                 nr_bytes = PAGE_SIZE - (start & ~PAGE_MASK);
55                 entry->size = (size < nr_bytes) ? size : nr_bytes;
56
57                 start += entry->size;
58                 size -= entry->size;
59         }
60         return 0;
61
62  Error:
63         list_for_each_entry_safe(entry, next, &nvs_list, node) {
64                 list_del(&entry->node);
65                 kfree(entry);
66         }
67         return -ENOMEM;
68 }
69
70 /**
71  *      suspend_nvs_free - free data pages allocated for saving NVS regions
72  */
73 void suspend_nvs_free(void)
74 {
75         struct nvs_page *entry;
76
77         list_for_each_entry(entry, &nvs_list, node)
78                 if (entry->data) {
79                         free_page((unsigned long)entry->data);
80                         entry->data = NULL;
81                         if (entry->kaddr) {
82                                 iounmap(entry->kaddr);
83                                 entry->kaddr = NULL;
84                         }
85                 }
86 }
87
88 /**
89  *      suspend_nvs_alloc - allocate memory necessary for saving NVS regions
90  */
91 int suspend_nvs_alloc(void)
92 {
93         struct nvs_page *entry;
94
95         list_for_each_entry(entry, &nvs_list, node) {
96                 entry->data = (void *)__get_free_page(GFP_KERNEL);
97                 if (!entry->data) {
98                         suspend_nvs_free();
99                         return -ENOMEM;
100                 }
101         }
102         return 0;
103 }
104
105 /**
106  *      suspend_nvs_save - save NVS memory regions
107  */
108 void suspend_nvs_save(void)
109 {
110         struct nvs_page *entry;
111
112         printk(KERN_INFO "PM: Saving platform NVS memory\n");
113
114         list_for_each_entry(entry, &nvs_list, node)
115                 if (entry->data) {
116                         entry->kaddr = ioremap(entry->phys_start, entry->size);
117                         memcpy(entry->data, entry->kaddr, entry->size);
118                 }
119 }
120
121 /**
122  *      suspend_nvs_restore - restore NVS memory regions
123  *
124  *      This function is going to be called with interrupts disabled, so it
125  *      cannot iounmap the virtual addresses used to access the NVS region.
126  */
127 void suspend_nvs_restore(void)
128 {
129         struct nvs_page *entry;
130
131         printk(KERN_INFO "PM: Restoring platform NVS memory\n");
132
133         list_for_each_entry(entry, &nvs_list, node)
134                 if (entry->data)
135                         memcpy(entry->kaddr, entry->data, entry->size);
136 }