blob: 54b6ab8040a6ed05039ce59d576969371e7fa3f8 [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>
Rafael J. Wysockica9b6002011-01-07 01:45:58 +010015#include <acpi/acpiosxf.h>
Cornelia Huckfce2b1112009-06-10 01:28:19 +020016
17/*
18 * Platforms, like ACPI, may want us to save some memory used by them during
Matthew Garrettdd4c4f12010-05-28 16:32:14 -040019 * suspend and to restore the contents of this memory during the subsequent
Cornelia Huckfce2b1112009-06-10 01:28:19 +020020 * resume. The code below implements a mechanism allowing us to do that.
21 */
22
23struct nvs_page {
24 unsigned long phys_start;
25 unsigned int size;
26 void *kaddr;
27 void *data;
28 struct list_head node;
29};
30
31static LIST_HEAD(nvs_list);
32
33/**
Matthew Garrettdd4c4f12010-05-28 16:32:14 -040034 * suspend_nvs_register - register platform NVS memory region to save
Cornelia Huckfce2b1112009-06-10 01:28:19 +020035 * @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 */
Matthew Garrettdd4c4f12010-05-28 16:32:14 -040042int suspend_nvs_register(unsigned long start, unsigned long size)
Cornelia Huckfce2b1112009-06-10 01:28:19 +020043{
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/**
Matthew Garrettdd4c4f12010-05-28 16:32:14 -040072 * suspend_nvs_free - free data pages allocated for saving NVS regions
Cornelia Huckfce2b1112009-06-10 01:28:19 +020073 */
Matthew Garrettdd4c4f12010-05-28 16:32:14 -040074void suspend_nvs_free(void)
Cornelia Huckfce2b1112009-06-10 01:28:19 +020075{
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) {
Rafael J. Wysockica9b6002011-01-07 01:45:58 +010083 acpi_os_unmap_memory(entry->kaddr, entry->size);
Cornelia Huckfce2b1112009-06-10 01:28:19 +020084 entry->kaddr = NULL;
85 }
86 }
87}
88
89/**
Matthew Garrettdd4c4f12010-05-28 16:32:14 -040090 * suspend_nvs_alloc - allocate memory necessary for saving NVS regions
Cornelia Huckfce2b1112009-06-10 01:28:19 +020091 */
Matthew Garrettdd4c4f12010-05-28 16:32:14 -040092int suspend_nvs_alloc(void)
Cornelia Huckfce2b1112009-06-10 01:28:19 +020093{
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) {
Matthew Garrettdd4c4f12010-05-28 16:32:14 -040099 suspend_nvs_free();
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200100 return -ENOMEM;
101 }
102 }
103 return 0;
104}
105
106/**
Matthew Garrettdd4c4f12010-05-28 16:32:14 -0400107 * suspend_nvs_save - save NVS memory regions
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200108 */
Jiri Slaby26fcaf62011-01-07 01:42:31 +0100109int suspend_nvs_save(void)
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200110{
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) {
Rafael J. Wysockica9b6002011-01-07 01:45:58 +0100117 entry->kaddr = acpi_os_map_memory(entry->phys_start,
118 entry->size);
Jiri Slaby26fcaf62011-01-07 01:42:31 +0100119 if (!entry->kaddr) {
120 suspend_nvs_free();
121 return -ENOMEM;
122 }
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200123 memcpy(entry->data, entry->kaddr, entry->size);
124 }
Jiri Slaby26fcaf62011-01-07 01:42:31 +0100125
126 return 0;
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200127}
128
129/**
Matthew Garrettdd4c4f12010-05-28 16:32:14 -0400130 * suspend_nvs_restore - restore NVS memory regions
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200131 *
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 */
Matthew Garrettdd4c4f12010-05-28 16:32:14 -0400135void suspend_nvs_restore(void)
Cornelia Huckfce2b1112009-06-10 01:28:19 +0200136{
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}