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