Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
nvs.c
Go to the documentation of this file.
1 /*
2  * nvs.c - Routines for saving and restoring ACPI NVS memory region
3  *
4  * Copyright (C) 2008-2011 Rafael J. Wysocki <[email protected]>, 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/acpi.h>
15 #include <linux/acpi_io.h>
16 #include <acpi/acpiosxf.h>
17 
18 /* ACPI NVS regions, APEI may use it */
19 
20 struct nvs_region {
23  struct list_head node;
24 };
25 
26 static LIST_HEAD(nvs_region_list);
27 
28 #ifdef CONFIG_ACPI_SLEEP
29 static int suspend_nvs_register(unsigned long start, unsigned long size);
30 #else
31 static inline int suspend_nvs_register(unsigned long a, unsigned long b)
32 {
33  return 0;
34 }
35 #endif
36 
38 {
39  struct nvs_region *region;
40 
41  region = kmalloc(sizeof(*region), GFP_KERNEL);
42  if (!region)
43  return -ENOMEM;
44  region->phys_start = start;
45  region->size = size;
46  list_add_tail(&region->node, &nvs_region_list);
47 
48  return suspend_nvs_register(start, size);
49 }
50 
52  void *data)
53 {
54  int rc;
55  struct nvs_region *region;
56 
57  list_for_each_entry(region, &nvs_region_list, node) {
58  rc = func(region->phys_start, region->size, data);
59  if (rc)
60  return rc;
61  }
62 
63  return 0;
64 }
65 
66 
67 #ifdef CONFIG_ACPI_SLEEP
68 /*
69  * Platforms, like ACPI, may want us to save some memory used by them during
70  * suspend and to restore the contents of this memory during the subsequent
71  * resume. The code below implements a mechanism allowing us to do that.
72  */
73 
74 struct nvs_page {
75  unsigned long phys_start;
76  unsigned int size;
77  void *kaddr;
78  void *data;
79  bool unmap;
80  struct list_head node;
81 };
82 
83 static LIST_HEAD(nvs_list);
84 
94 static int suspend_nvs_register(unsigned long start, unsigned long size)
95 {
96  struct nvs_page *entry, *next;
97 
98  pr_info("PM: Registering ACPI NVS region [mem %#010lx-%#010lx] (%ld bytes)\n",
99  start, start + size - 1, size);
100 
101  while (size > 0) {
102  unsigned int nr_bytes;
103 
104  entry = kzalloc(sizeof(struct nvs_page), GFP_KERNEL);
105  if (!entry)
106  goto Error;
107 
108  list_add_tail(&entry->node, &nvs_list);
109  entry->phys_start = start;
110  nr_bytes = PAGE_SIZE - (start & ~PAGE_MASK);
111  entry->size = (size < nr_bytes) ? size : nr_bytes;
112 
113  start += entry->size;
114  size -= entry->size;
115  }
116  return 0;
117 
118  Error:
119  list_for_each_entry_safe(entry, next, &nvs_list, node) {
120  list_del(&entry->node);
121  kfree(entry);
122  }
123  return -ENOMEM;
124 }
125 
129 void suspend_nvs_free(void)
130 {
131  struct nvs_page *entry;
132 
133  list_for_each_entry(entry, &nvs_list, node)
134  if (entry->data) {
135  free_page((unsigned long)entry->data);
136  entry->data = NULL;
137  if (entry->kaddr) {
138  if (entry->unmap) {
139  iounmap(entry->kaddr);
140  entry->unmap = false;
141  } else {
142  acpi_os_unmap_memory(entry->kaddr,
143  entry->size);
144  }
145  entry->kaddr = NULL;
146  }
147  }
148 }
149 
153 int suspend_nvs_alloc(void)
154 {
155  struct nvs_page *entry;
156 
157  list_for_each_entry(entry, &nvs_list, node) {
158  entry->data = (void *)__get_free_page(GFP_KERNEL);
159  if (!entry->data) {
160  suspend_nvs_free();
161  return -ENOMEM;
162  }
163  }
164  return 0;
165 }
166 
170 int suspend_nvs_save(void)
171 {
172  struct nvs_page *entry;
173 
174  printk(KERN_INFO "PM: Saving platform NVS memory\n");
175 
176  list_for_each_entry(entry, &nvs_list, node)
177  if (entry->data) {
178  unsigned long phys = entry->phys_start;
179  unsigned int size = entry->size;
180 
181  entry->kaddr = acpi_os_get_iomem(phys, size);
182  if (!entry->kaddr) {
183  entry->kaddr = acpi_os_ioremap(phys, size);
184  entry->unmap = !!entry->kaddr;
185  }
186  if (!entry->kaddr) {
187  suspend_nvs_free();
188  return -ENOMEM;
189  }
190  memcpy(entry->data, entry->kaddr, entry->size);
191  }
192 
193  return 0;
194 }
195 
202 void suspend_nvs_restore(void)
203 {
204  struct nvs_page *entry;
205 
206  printk(KERN_INFO "PM: Restoring platform NVS memory\n");
207 
208  list_for_each_entry(entry, &nvs_list, node)
209  if (entry->data)
210  memcpy(entry->kaddr, entry->data, entry->size);
211 }
212 #endif