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(®ion->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