Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * nvs.c - Routines for saving and restoring ACPI NVS memory region
0004  *
0005  * Copyright (C) 2008-2011 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
0006  */
0007 
0008 #define pr_fmt(fmt) "ACPI: PM: " fmt
0009 
0010 #include <linux/io.h>
0011 #include <linux/kernel.h>
0012 #include <linux/list.h>
0013 #include <linux/mm.h>
0014 #include <linux/slab.h>
0015 #include <linux/acpi.h>
0016 
0017 #include "internal.h"
0018 
0019 /* ACPI NVS regions, APEI may use it */
0020 
0021 struct nvs_region {
0022     __u64 phys_start;
0023     __u64 size;
0024     struct list_head node;
0025 };
0026 
0027 static LIST_HEAD(nvs_region_list);
0028 
0029 #ifdef CONFIG_ACPI_SLEEP
0030 static int suspend_nvs_register(unsigned long start, unsigned long size);
0031 #else
0032 static inline int suspend_nvs_register(unsigned long a, unsigned long b)
0033 {
0034     return 0;
0035 }
0036 #endif
0037 
0038 int acpi_nvs_register(__u64 start, __u64 size)
0039 {
0040     struct nvs_region *region;
0041 
0042     region = kmalloc(sizeof(*region), GFP_KERNEL);
0043     if (!region)
0044         return -ENOMEM;
0045     region->phys_start = start;
0046     region->size = size;
0047     list_add_tail(&region->node, &nvs_region_list);
0048 
0049     return suspend_nvs_register(start, size);
0050 }
0051 
0052 int acpi_nvs_for_each_region(int (*func)(__u64 start, __u64 size, void *data),
0053                  void *data)
0054 {
0055     int rc;
0056     struct nvs_region *region;
0057 
0058     list_for_each_entry(region, &nvs_region_list, node) {
0059         rc = func(region->phys_start, region->size, data);
0060         if (rc)
0061             return rc;
0062     }
0063 
0064     return 0;
0065 }
0066 
0067 
0068 #ifdef CONFIG_ACPI_SLEEP
0069 /*
0070  * Platforms, like ACPI, may want us to save some memory used by them during
0071  * suspend and to restore the contents of this memory during the subsequent
0072  * resume.  The code below implements a mechanism allowing us to do that.
0073  */
0074 
0075 struct nvs_page {
0076     unsigned long phys_start;
0077     unsigned int size;
0078     void *kaddr;
0079     void *data;
0080     bool unmap;
0081     struct list_head node;
0082 };
0083 
0084 static LIST_HEAD(nvs_list);
0085 
0086 /**
0087  * suspend_nvs_register - register platform NVS memory region to save
0088  * @start: Physical address of the region.
0089  * @size: Size of the region.
0090  *
0091  * The NVS region need not be page-aligned (both ends) and we arrange
0092  * things so that the data from page-aligned addresses in this region will
0093  * be copied into separate RAM pages.
0094  */
0095 static int suspend_nvs_register(unsigned long start, unsigned long size)
0096 {
0097     struct nvs_page *entry, *next;
0098 
0099     pr_info("Registering ACPI NVS region [mem %#010lx-%#010lx] (%ld bytes)\n",
0100         start, start + size - 1, size);
0101 
0102     while (size > 0) {
0103         unsigned int nr_bytes;
0104 
0105         entry = kzalloc(sizeof(struct nvs_page), GFP_KERNEL);
0106         if (!entry)
0107             goto Error;
0108 
0109         list_add_tail(&entry->node, &nvs_list);
0110         entry->phys_start = start;
0111         nr_bytes = PAGE_SIZE - (start & ~PAGE_MASK);
0112         entry->size = (size < nr_bytes) ? size : nr_bytes;
0113 
0114         start += entry->size;
0115         size -= entry->size;
0116     }
0117     return 0;
0118 
0119  Error:
0120     list_for_each_entry_safe(entry, next, &nvs_list, node) {
0121         list_del(&entry->node);
0122         kfree(entry);
0123     }
0124     return -ENOMEM;
0125 }
0126 
0127 /**
0128  * suspend_nvs_free - free data pages allocated for saving NVS regions
0129  */
0130 void suspend_nvs_free(void)
0131 {
0132     struct nvs_page *entry;
0133 
0134     list_for_each_entry(entry, &nvs_list, node)
0135         if (entry->data) {
0136             free_page((unsigned long)entry->data);
0137             entry->data = NULL;
0138             if (entry->kaddr) {
0139                 if (entry->unmap) {
0140                     iounmap(entry->kaddr);
0141                     entry->unmap = false;
0142                 } else {
0143                     acpi_os_unmap_iomem(entry->kaddr,
0144                                 entry->size);
0145                 }
0146                 entry->kaddr = NULL;
0147             }
0148         }
0149 }
0150 
0151 /**
0152  * suspend_nvs_alloc - allocate memory necessary for saving NVS regions
0153  */
0154 int suspend_nvs_alloc(void)
0155 {
0156     struct nvs_page *entry;
0157 
0158     list_for_each_entry(entry, &nvs_list, node) {
0159         entry->data = (void *)__get_free_page(GFP_KERNEL);
0160         if (!entry->data) {
0161             suspend_nvs_free();
0162             return -ENOMEM;
0163         }
0164     }
0165     return 0;
0166 }
0167 
0168 /**
0169  * suspend_nvs_save - save NVS memory regions
0170  */
0171 int suspend_nvs_save(void)
0172 {
0173     struct nvs_page *entry;
0174 
0175     pr_info("Saving platform NVS memory\n");
0176 
0177     list_for_each_entry(entry, &nvs_list, node)
0178         if (entry->data) {
0179             unsigned long phys = entry->phys_start;
0180             unsigned int size = entry->size;
0181 
0182             entry->kaddr = acpi_os_get_iomem(phys, size);
0183             if (!entry->kaddr) {
0184                 entry->kaddr = acpi_os_ioremap(phys, size);
0185                 entry->unmap = !!entry->kaddr;
0186             }
0187             if (!entry->kaddr) {
0188                 suspend_nvs_free();
0189                 return -ENOMEM;
0190             }
0191             memcpy(entry->data, entry->kaddr, entry->size);
0192         }
0193 
0194     return 0;
0195 }
0196 
0197 /**
0198  * suspend_nvs_restore - restore NVS memory regions
0199  *
0200  * This function is going to be called with interrupts disabled, so it
0201  * cannot iounmap the virtual addresses used to access the NVS region.
0202  */
0203 void suspend_nvs_restore(void)
0204 {
0205     struct nvs_page *entry;
0206 
0207     pr_info("Restoring platform NVS memory\n");
0208 
0209     list_for_each_entry(entry, &nvs_list, node)
0210         if (entry->data)
0211             memcpy(entry->kaddr, entry->data, entry->size);
0212 }
0213 #endif