xref: /linux/drivers/acpi/nvs.c (revision 394d83c17fac2b7bcf05cb99d1e945135767bb6b)
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 #include <acpi/acpiosxf.h>
16 
17 /*
18  * Platforms, like ACPI, may want us to save some memory used by them during
19  * suspend and to restore the contents of this memory during the subsequent
20  * resume.  The code below implements a mechanism allowing us to do that.
21  */
22 
23 struct nvs_page {
24 	unsigned long phys_start;
25 	unsigned int size;
26 	void *kaddr;
27 	void *data;
28 	struct list_head node;
29 };
30 
31 static LIST_HEAD(nvs_list);
32 
33 /**
34  *	suspend_nvs_register - register platform NVS memory region to save
35  *	@start - physical address of the region
36  *	@size - size of the region
37  *
38  *	The NVS region need not be page-aligned (both ends) and we arrange
39  *	things so that the data from page-aligned addresses in this region will
40  *	be copied into separate RAM pages.
41  */
42 int suspend_nvs_register(unsigned long start, unsigned long size)
43 {
44 	struct nvs_page *entry, *next;
45 
46 	while (size > 0) {
47 		unsigned int nr_bytes;
48 
49 		entry = kzalloc(sizeof(struct nvs_page), GFP_KERNEL);
50 		if (!entry)
51 			goto Error;
52 
53 		list_add_tail(&entry->node, &nvs_list);
54 		entry->phys_start = start;
55 		nr_bytes = PAGE_SIZE - (start & ~PAGE_MASK);
56 		entry->size = (size < nr_bytes) ? size : nr_bytes;
57 
58 		start += entry->size;
59 		size -= entry->size;
60 	}
61 	return 0;
62 
63  Error:
64 	list_for_each_entry_safe(entry, next, &nvs_list, node) {
65 		list_del(&entry->node);
66 		kfree(entry);
67 	}
68 	return -ENOMEM;
69 }
70 
71 /**
72  *	suspend_nvs_free - free data pages allocated for saving NVS regions
73  */
74 void suspend_nvs_free(void)
75 {
76 	struct nvs_page *entry;
77 
78 	list_for_each_entry(entry, &nvs_list, node)
79 		if (entry->data) {
80 			free_page((unsigned long)entry->data);
81 			entry->data = NULL;
82 			if (entry->kaddr) {
83 				acpi_os_unmap_memory(entry->kaddr, entry->size);
84 				entry->kaddr = NULL;
85 			}
86 		}
87 }
88 
89 /**
90  *	suspend_nvs_alloc - allocate memory necessary for saving NVS regions
91  */
92 int suspend_nvs_alloc(void)
93 {
94 	struct nvs_page *entry;
95 
96 	list_for_each_entry(entry, &nvs_list, node) {
97 		entry->data = (void *)__get_free_page(GFP_KERNEL);
98 		if (!entry->data) {
99 			suspend_nvs_free();
100 			return -ENOMEM;
101 		}
102 	}
103 	return 0;
104 }
105 
106 /**
107  *	suspend_nvs_save - save NVS memory regions
108  */
109 int suspend_nvs_save(void)
110 {
111 	struct nvs_page *entry;
112 
113 	printk(KERN_INFO "PM: Saving platform NVS memory\n");
114 
115 	list_for_each_entry(entry, &nvs_list, node)
116 		if (entry->data) {
117 			entry->kaddr = acpi_os_map_memory(entry->phys_start,
118 							  entry->size);
119 			if (!entry->kaddr) {
120 				suspend_nvs_free();
121 				return -ENOMEM;
122 			}
123 			memcpy(entry->data, entry->kaddr, entry->size);
124 		}
125 
126 	return 0;
127 }
128 
129 /**
130  *	suspend_nvs_restore - restore NVS memory regions
131  *
132  *	This function is going to be called with interrupts disabled, so it
133  *	cannot iounmap the virtual addresses used to access the NVS region.
134  */
135 void suspend_nvs_restore(void)
136 {
137 	struct nvs_page *entry;
138 
139 	printk(KERN_INFO "PM: Restoring platform NVS memory\n");
140 
141 	list_for_each_entry(entry, &nvs_list, node)
142 		if (entry->data)
143 			memcpy(entry->kaddr, entry->data, entry->size);
144 }
145