root/drivers/acpi/nvs.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. suspend_nvs_register
  2. acpi_nvs_register
  3. acpi_nvs_for_each_region
  4. suspend_nvs_register
  5. suspend_nvs_free
  6. suspend_nvs_alloc
  7. suspend_nvs_save
  8. suspend_nvs_restore

   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 #include <linux/io.h>
   9 #include <linux/kernel.h>
  10 #include <linux/list.h>
  11 #include <linux/mm.h>
  12 #include <linux/slab.h>
  13 #include <linux/acpi.h>
  14 
  15 #include "internal.h"
  16 
  17 /* ACPI NVS regions, APEI may use it */
  18 
  19 struct nvs_region {
  20         __u64 phys_start;
  21         __u64 size;
  22         struct list_head node;
  23 };
  24 
  25 static LIST_HEAD(nvs_region_list);
  26 
  27 #ifdef CONFIG_ACPI_SLEEP
  28 static int suspend_nvs_register(unsigned long start, unsigned long size);
  29 #else
  30 static inline int suspend_nvs_register(unsigned long a, unsigned long b)
  31 {
  32         return 0;
  33 }
  34 #endif
  35 
  36 int 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 
  50 int 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 
  73 struct 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 
  82 static 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  */
  93 static 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  */
 128 void 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_iomem(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  */
 152 int 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  */
 169 int 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  */
 201 void 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

/* [<][>][^][v][top][bottom][index][help] */