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