blob: ef28613d51927043a0d567b353a6ef85a0ac32d8 [file] [log] [blame]
Cornelia Huckfce2b1112009-06-10 01:28:19 +02001/*
Rafael J. Wysockid146df12011-01-07 01:44:28 +01002 * nvs.c - Routines for saving and restoring ACPI NVS memory region
Cornelia Huckfce2b1112009-06-10 01:28:19 +02003 *
Rafael J. Wysockid146df12011-01-07 01:44:28 +01004 * Copyright (C) 2008-2011 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
Cornelia Huckfce2b1112009-06-10 01:28:19 +02005 *
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>
Tejun Heo5a0e3ad2010-03-24 17:04:11 +090013#include <linux/slab.h>
Rafael J. Wysockid146df12011-01-07 01:44:28 +010014#include <linux/acpi.h>
Cornelia Huckfce2b1112009-06-10 01:28:19 +020015
Huang Yingb54ac6d2011-12-08 11:25:49 +080016/* ACPI NVS regions, APEI may use it */
17
18struct nvs_region {
19 __u64 phys_start;
20 __u64 size;
21 struct list_head node;
22};
23
24static LIST_HEAD(nvs_region_list);
25
26#ifdef CONFIG_ACPI_SLEEP
27static int suspend_nvs_register(unsigned long start, unsigned long size);
28#else
29static inline int suspend_nvs_register(unsigned long a, unsigned long b)
30{
31 return 0;
32}
33#endif
34
35int acpi_nvs_register(__u64 start, __u64 size)
36{
37 struct nvs_region *region;
38
39 region = kmalloc(sizeof(*region), GFP_KERNEL);
40 if (!region)
41 return -ENOMEM;
42 region->phys_start = start;
43 region->size = size;
44 list_add_tail(&region->node, &nvs_region_list);
45
46 return suspend_nvs_register(start, size);
47}
48
49int acpi_nvs_for_each_region(int (*func)(__u64 start, __u64 size, void *data),
50 void *data)
51{
52 int rc;
53 struct nvs_region *region;
54
55 list_for_each_entry(region, &nvs_region_list, node) {
56 rc = func(region->phys_start, region->size, data);
57 if (rc)
58 return rc;
59 }
60
61 return 0;
62}
63
64
65#ifdef CONFIG_ACPI_SLEEP
Cornelia Huckfce2b1112009-06-10 01:28:19 +020066/*
67 * Platforms, like ACPI, may want us to save some memory used by them during
Matthew Garrettdd4c4f12010-05-28 16:32:14 -040068 * suspend and to restore the contents of this memory during the subsequent
Cornelia Huckfce2b1112009-06-10 01:28:19 +020069 * resume. The code below implements a mechanism allowing us to do that.
70 */
71
72struct nvs_page {
73 unsigned long phys_start;
74 unsigned int size;
75 void *kaddr;
76 void *data;
Rafael J. Wysockibb45e392011-02-08 23:38:38 +010077 bool unmap;
Cornelia Huckfce2b1112009-06-10 01:28:19 +020078 struct list_head node;
79};
80
81static LIST_HEAD(nvs_list);
82
83/**
Matthew Garrettdd4c4f12010-05-28 16:32:14 -040084 * suspend_nvs_register - register platform NVS memory region to save
Cornelia Huckfce2b1112009-06-10 01:28:19 +020085 * @start - physical address of the region
86 * @size - size of the region
87 *
88 * The NVS region need not be page-aligned (both ends) and we arrange
89 * things so that the data from page-aligned addresses in this region will
90 * be copied into separate RAM pages.
91 */
Huang Yingb54ac6d2011-12-08 11:25:49 +080092static int suspend_nvs_register(unsigned long start, unsigned long size)
Cornelia Huckfce2b1112009-06-10 01:28:19 +020093{
94 struct nvs_page *entry, *next;
95
Bjorn Helgaasc6436f5a2012-02-13 17:04:43 -070096 pr_info("PM: Registering ACPI NVS region [mem %#010lx-%#010lx] (%ld bytes)\n",
97 start, start + size - 1, size);
Rafael J. Wysockibb45e392011-02-08 23:38:38 +010098
Cornelia Huckfce2b1112009-06-10 01:28:19 +020099 while (size > 0) {
100 unsigned int nr_bytes;
101
102 entry = kzalloc(sizeof(struct nvs_page), GFP_KERNEL);
103 if (!entry)
104 goto Error;
105
106 list_add_tail(&entry->node, &nvs_list);
107 entry->phys_start = start;
108 nr_bytes = PAGE_SIZE - (start & ~PAGE_MASK);
109 entry->size = (size < nr_bytes) ? size : nr_bytes;
110
111 start += entry->size;
112 size -= entry->size;
113 }
114 return 0;
115
116 Error:
117 list_for_each_entry_safe(entry, next, &nvs_list, node) {
118 list_del(&entry->node);
119 kfree(entry);
120 }
121 return -ENOMEM;
122}
123
124/**
Matthew Garrettdd4c4f12010-05-28 16:32:14 -0400125 * suspend_nvs_free - free data pages allocated for saving NVS regions
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200126 */
Matthew Garrettdd4c4f12010-05-28 16:32:14 -0400127void suspend_nvs_free(void)
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200128{
129 struct nvs_page *entry;
130
131 list_for_each_entry(entry, &nvs_list, node)
132 if (entry->data) {
133 free_page((unsigned long)entry->data);
134 entry->data = NULL;
135 if (entry->kaddr) {
Rafael J. Wysockibb45e392011-02-08 23:38:38 +0100136 if (entry->unmap) {
137 iounmap(entry->kaddr);
138 entry->unmap = false;
139 } else {
140 acpi_os_unmap_memory(entry->kaddr,
141 entry->size);
142 }
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200143 entry->kaddr = NULL;
144 }
145 }
146}
147
148/**
Matthew Garrettdd4c4f12010-05-28 16:32:14 -0400149 * suspend_nvs_alloc - allocate memory necessary for saving NVS regions
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200150 */
Matthew Garrettdd4c4f12010-05-28 16:32:14 -0400151int suspend_nvs_alloc(void)
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200152{
153 struct nvs_page *entry;
154
155 list_for_each_entry(entry, &nvs_list, node) {
156 entry->data = (void *)__get_free_page(GFP_KERNEL);
157 if (!entry->data) {
Matthew Garrettdd4c4f12010-05-28 16:32:14 -0400158 suspend_nvs_free();
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200159 return -ENOMEM;
160 }
161 }
162 return 0;
163}
164
165/**
Matthew Garrettdd4c4f12010-05-28 16:32:14 -0400166 * suspend_nvs_save - save NVS memory regions
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200167 */
Jiri Slaby26fcaf62011-01-07 01:42:31 +0100168int suspend_nvs_save(void)
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200169{
170 struct nvs_page *entry;
171
172 printk(KERN_INFO "PM: Saving platform NVS memory\n");
173
174 list_for_each_entry(entry, &nvs_list, node)
175 if (entry->data) {
Rafael J. Wysockibb45e392011-02-08 23:38:38 +0100176 unsigned long phys = entry->phys_start;
177 unsigned int size = entry->size;
178
179 entry->kaddr = acpi_os_get_iomem(phys, size);
180 if (!entry->kaddr) {
181 entry->kaddr = acpi_os_ioremap(phys, size);
182 entry->unmap = !!entry->kaddr;
183 }
Jiri Slaby26fcaf62011-01-07 01:42:31 +0100184 if (!entry->kaddr) {
185 suspend_nvs_free();
186 return -ENOMEM;
187 }
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200188 memcpy(entry->data, entry->kaddr, entry->size);
189 }
Jiri Slaby26fcaf62011-01-07 01:42:31 +0100190
191 return 0;
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200192}
193
194/**
Matthew Garrettdd4c4f12010-05-28 16:32:14 -0400195 * suspend_nvs_restore - restore NVS memory regions
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200196 *
197 * This function is going to be called with interrupts disabled, so it
198 * cannot iounmap the virtual addresses used to access the NVS region.
199 */
Matthew Garrettdd4c4f12010-05-28 16:32:14 -0400200void suspend_nvs_restore(void)
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200201{
202 struct nvs_page *entry;
203
204 printk(KERN_INFO "PM: Restoring platform NVS memory\n");
205
206 list_for_each_entry(entry, &nvs_list, node)
207 if (entry->data)
208 memcpy(entry->kaddr, entry->data, entry->size);
209}
Huang Yingb54ac6d2011-12-08 11:25:49 +0800210#endif