1fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck/*
2d146df18c13d16e321efa8ef9b57c95c3bec1722Rafael J. Wysocki * nvs.c - Routines for saving and restoring ACPI NVS memory region
3fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck *
4d146df18c13d16e321efa8ef9b57c95c3bec1722Rafael J. Wysocki * Copyright (C) 2008-2011 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
5fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck *
6fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck * This file is released under the GPLv2.
7fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck */
8fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
9fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck#include <linux/io.h>
10fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck#include <linux/kernel.h>
11fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck#include <linux/list.h>
12fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck#include <linux/mm.h>
135a0e3ad6af8660be21ca98a971cd00f331318c05Tejun Heo#include <linux/slab.h>
14d146df18c13d16e321efa8ef9b57c95c3bec1722Rafael J. Wysocki#include <linux/acpi.h>
152d6d9fd3a54a28c6f67f26eb6c74803307a1b11eRafael J. Wysocki#include <linux/acpi_io.h>
16ca9b600be38c73b7d25acfb8b7e4e9a9e941d881Rafael J. Wysocki#include <acpi/acpiosxf.h>
17fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
18b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying/* ACPI NVS regions, APEI may use it */
19b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying
20b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Yingstruct nvs_region {
21b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying	__u64 phys_start;
22b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying	__u64 size;
23b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying	struct list_head node;
24b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying};
25b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying
26b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Yingstatic LIST_HEAD(nvs_region_list);
27b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying
28b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying#ifdef CONFIG_ACPI_SLEEP
29b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Yingstatic int suspend_nvs_register(unsigned long start, unsigned long size);
30b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying#else
31b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Yingstatic inline int suspend_nvs_register(unsigned long a, unsigned long b)
32b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying{
33b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying	return 0;
34b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying}
35b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying#endif
36b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying
37b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Yingint acpi_nvs_register(__u64 start, __u64 size)
38b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying{
39b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying	struct nvs_region *region;
40b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying
41b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying	region = kmalloc(sizeof(*region), GFP_KERNEL);
42b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying	if (!region)
43b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying		return -ENOMEM;
44b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying	region->phys_start = start;
45b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying	region->size = size;
46b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying	list_add_tail(&region->node, &nvs_region_list);
47b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying
48b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying	return suspend_nvs_register(start, size);
49b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying}
50b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying
51b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Yingint acpi_nvs_for_each_region(int (*func)(__u64 start, __u64 size, void *data),
52b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying			     void *data)
53b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying{
54b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying	int rc;
55b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying	struct nvs_region *region;
56b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying
57b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying	list_for_each_entry(region, &nvs_region_list, node) {
58b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying		rc = func(region->phys_start, region->size, data);
59b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying		if (rc)
60b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying			return rc;
61b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying	}
62b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying
63b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying	return 0;
64b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying}
65b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying
66b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying
67b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying#ifdef CONFIG_ACPI_SLEEP
68fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck/*
69fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck * Platforms, like ACPI, may want us to save some memory used by them during
70dd4c4f17d722ffeb2515bf781400675a30fcead7Matthew Garrett * suspend and to restore the contents of this memory during the subsequent
71fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck * resume.  The code below implements a mechanism allowing us to do that.
72fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck */
73fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
74fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huckstruct nvs_page {
75fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	unsigned long phys_start;
76fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	unsigned int size;
77fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	void *kaddr;
78fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	void *data;
79bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki	bool unmap;
80fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	struct list_head node;
81fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck};
82fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
83fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huckstatic LIST_HEAD(nvs_list);
84fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
85fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck/**
86dd4c4f17d722ffeb2515bf781400675a30fcead7Matthew Garrett *	suspend_nvs_register - register platform NVS memory region to save
87fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck *	@start - physical address of the region
88fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck *	@size - size of the region
89fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck *
90fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck *	The NVS region need not be page-aligned (both ends) and we arrange
91fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck *	things so that the data from page-aligned addresses in this region will
92fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck *	be copied into separate RAM pages.
93fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck */
94b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Yingstatic int suspend_nvs_register(unsigned long start, unsigned long size)
95fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck{
96fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	struct nvs_page *entry, *next;
97fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
98c6436f5a395d346e9f4892d7aeed4c3f99261f0fBjorn Helgaas	pr_info("PM: Registering ACPI NVS region [mem %#010lx-%#010lx] (%ld bytes)\n",
99c6436f5a395d346e9f4892d7aeed4c3f99261f0fBjorn Helgaas		start, start + size - 1, size);
100bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki
101fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	while (size > 0) {
102fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		unsigned int nr_bytes;
103fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
104fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		entry = kzalloc(sizeof(struct nvs_page), GFP_KERNEL);
105fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		if (!entry)
106fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck			goto Error;
107fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
108fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		list_add_tail(&entry->node, &nvs_list);
109fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		entry->phys_start = start;
110fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		nr_bytes = PAGE_SIZE - (start & ~PAGE_MASK);
111fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		entry->size = (size < nr_bytes) ? size : nr_bytes;
112fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
113fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		start += entry->size;
114fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		size -= entry->size;
115fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	}
116fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	return 0;
117fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
118fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck Error:
119fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	list_for_each_entry_safe(entry, next, &nvs_list, node) {
120fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		list_del(&entry->node);
121fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		kfree(entry);
122fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	}
123fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	return -ENOMEM;
124fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck}
125fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
126fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck/**
127dd4c4f17d722ffeb2515bf781400675a30fcead7Matthew Garrett *	suspend_nvs_free - free data pages allocated for saving NVS regions
128fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck */
129dd4c4f17d722ffeb2515bf781400675a30fcead7Matthew Garrettvoid suspend_nvs_free(void)
130fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck{
131fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	struct nvs_page *entry;
132fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
133fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	list_for_each_entry(entry, &nvs_list, node)
134fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		if (entry->data) {
135fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck			free_page((unsigned long)entry->data);
136fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck			entry->data = NULL;
137fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck			if (entry->kaddr) {
138bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki				if (entry->unmap) {
139bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki					iounmap(entry->kaddr);
140bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki					entry->unmap = false;
141bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki				} else {
142bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki					acpi_os_unmap_memory(entry->kaddr,
143bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki							     entry->size);
144bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki				}
145fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck				entry->kaddr = NULL;
146fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck			}
147fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		}
148fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck}
149fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
150fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck/**
151dd4c4f17d722ffeb2515bf781400675a30fcead7Matthew Garrett *	suspend_nvs_alloc - allocate memory necessary for saving NVS regions
152fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck */
153dd4c4f17d722ffeb2515bf781400675a30fcead7Matthew Garrettint suspend_nvs_alloc(void)
154fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck{
155fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	struct nvs_page *entry;
156fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
157fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	list_for_each_entry(entry, &nvs_list, node) {
158fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		entry->data = (void *)__get_free_page(GFP_KERNEL);
159fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		if (!entry->data) {
160dd4c4f17d722ffeb2515bf781400675a30fcead7Matthew Garrett			suspend_nvs_free();
161fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck			return -ENOMEM;
162fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		}
163fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	}
164fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	return 0;
165fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck}
166fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
167fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck/**
168dd4c4f17d722ffeb2515bf781400675a30fcead7Matthew Garrett *	suspend_nvs_save - save NVS memory regions
169fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck */
17026fcaf60fe3861409eb4c455c5c0d0f00f599b08Jiri Slabyint suspend_nvs_save(void)
171fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck{
172fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	struct nvs_page *entry;
173fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
174fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	printk(KERN_INFO "PM: Saving platform NVS memory\n");
175fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
176fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	list_for_each_entry(entry, &nvs_list, node)
177fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		if (entry->data) {
178bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki			unsigned long phys = entry->phys_start;
179bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki			unsigned int size = entry->size;
180bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki
181bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki			entry->kaddr = acpi_os_get_iomem(phys, size);
182bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki			if (!entry->kaddr) {
183bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki				entry->kaddr = acpi_os_ioremap(phys, size);
184bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki				entry->unmap = !!entry->kaddr;
185bb45e394e21eb2abc710ad43d98ebac1069bf355Rafael J. Wysocki			}
18626fcaf60fe3861409eb4c455c5c0d0f00f599b08Jiri Slaby			if (!entry->kaddr) {
18726fcaf60fe3861409eb4c455c5c0d0f00f599b08Jiri Slaby				suspend_nvs_free();
18826fcaf60fe3861409eb4c455c5c0d0f00f599b08Jiri Slaby				return -ENOMEM;
18926fcaf60fe3861409eb4c455c5c0d0f00f599b08Jiri Slaby			}
190fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck			memcpy(entry->data, entry->kaddr, entry->size);
191fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		}
19226fcaf60fe3861409eb4c455c5c0d0f00f599b08Jiri Slaby
19326fcaf60fe3861409eb4c455c5c0d0f00f599b08Jiri Slaby	return 0;
194fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck}
195fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
196fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck/**
197dd4c4f17d722ffeb2515bf781400675a30fcead7Matthew Garrett *	suspend_nvs_restore - restore NVS memory regions
198fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck *
199fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck *	This function is going to be called with interrupts disabled, so it
200fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck *	cannot iounmap the virtual addresses used to access the NVS region.
201fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck */
202dd4c4f17d722ffeb2515bf781400675a30fcead7Matthew Garrettvoid suspend_nvs_restore(void)
203fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck{
204fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	struct nvs_page *entry;
205fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
206fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	printk(KERN_INFO "PM: Restoring platform NVS memory\n");
207fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck
208fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck	list_for_each_entry(entry, &nvs_list, node)
209fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck		if (entry->data)
210fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck			memcpy(entry->kaddr, entry->data, entry->size);
211fce2b111fae9151a53dabb36513b398d03337a19Cornelia Huck}
212b54ac6d2a25084667da781c7ca2cebef52a2bcddHuang Ying#endif
213