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