GNU Linux-libre 4.9.301-gnu1
[releases.git] / drivers / acpi / nvs.c
1 /*
2  * nvs.c - Routines for saving and restoring ACPI NVS memory region
3  *
4  * Copyright (C) 2008-2011 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/acpi.h>
15
16 #include "internal.h"
17
18 /* ACPI NVS regions, APEI may use it */
19
20 struct nvs_region {
21         __u64 phys_start;
22         __u64 size;
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
37 int acpi_nvs_register(__u64 start, __u64 size)
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
51 int acpi_nvs_for_each_region(int (*func)(__u64 start, __u64 size, void *data),
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
85 /**
86  *      suspend_nvs_register - register platform NVS memory region to save
87  *      @start - physical address of the region
88  *      @size - size of the region
89  *
90  *      The NVS region need not be page-aligned (both ends) and we arrange
91  *      things so that the data from page-aligned addresses in this region will
92  *      be copied into separate RAM pages.
93  */
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
126 /**
127  *      suspend_nvs_free - free data pages allocated for saving NVS regions
128  */
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_iomem(entry->kaddr,
143                                                             entry->size);
144                                 }
145                                 entry->kaddr = NULL;
146                         }
147                 }
148 }
149
150 /**
151  *      suspend_nvs_alloc - allocate memory necessary for saving NVS regions
152  */
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
167 /**
168  *      suspend_nvs_save - save NVS memory regions
169  */
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
196 /**
197  *      suspend_nvs_restore - restore NVS memory regions
198  *
199  *      This function is going to be called with interrupts disabled, so it
200  *      cannot iounmap the virtual addresses used to access the NVS region.
201  */
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