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