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