xref: /openbmc/linux/drivers/acpi/nvs.c (revision c819e2cf)
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
213