18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * nvs.c - Routines for saving and restoring ACPI NVS memory region 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2008-2011 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/io.h> 98c2ecf20Sopenharmony_ci#include <linux/kernel.h> 108c2ecf20Sopenharmony_ci#include <linux/list.h> 118c2ecf20Sopenharmony_ci#include <linux/mm.h> 128c2ecf20Sopenharmony_ci#include <linux/slab.h> 138c2ecf20Sopenharmony_ci#include <linux/acpi.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include "internal.h" 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci/* ACPI NVS regions, APEI may use it */ 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_cistruct nvs_region { 208c2ecf20Sopenharmony_ci __u64 phys_start; 218c2ecf20Sopenharmony_ci __u64 size; 228c2ecf20Sopenharmony_ci struct list_head node; 238c2ecf20Sopenharmony_ci}; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_cistatic LIST_HEAD(nvs_region_list); 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#ifdef CONFIG_ACPI_SLEEP 288c2ecf20Sopenharmony_cistatic int suspend_nvs_register(unsigned long start, unsigned long size); 298c2ecf20Sopenharmony_ci#else 308c2ecf20Sopenharmony_cistatic inline int suspend_nvs_register(unsigned long a, unsigned long b) 318c2ecf20Sopenharmony_ci{ 328c2ecf20Sopenharmony_ci return 0; 338c2ecf20Sopenharmony_ci} 348c2ecf20Sopenharmony_ci#endif 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ciint acpi_nvs_register(__u64 start, __u64 size) 378c2ecf20Sopenharmony_ci{ 388c2ecf20Sopenharmony_ci struct nvs_region *region; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci region = kmalloc(sizeof(*region), GFP_KERNEL); 418c2ecf20Sopenharmony_ci if (!region) 428c2ecf20Sopenharmony_ci return -ENOMEM; 438c2ecf20Sopenharmony_ci region->phys_start = start; 448c2ecf20Sopenharmony_ci region->size = size; 458c2ecf20Sopenharmony_ci list_add_tail(®ion->node, &nvs_region_list); 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci return suspend_nvs_register(start, size); 488c2ecf20Sopenharmony_ci} 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ciint acpi_nvs_for_each_region(int (*func)(__u64 start, __u64 size, void *data), 518c2ecf20Sopenharmony_ci void *data) 528c2ecf20Sopenharmony_ci{ 538c2ecf20Sopenharmony_ci int rc; 548c2ecf20Sopenharmony_ci struct nvs_region *region; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci list_for_each_entry(region, &nvs_region_list, node) { 578c2ecf20Sopenharmony_ci rc = func(region->phys_start, region->size, data); 588c2ecf20Sopenharmony_ci if (rc) 598c2ecf20Sopenharmony_ci return rc; 608c2ecf20Sopenharmony_ci } 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci return 0; 638c2ecf20Sopenharmony_ci} 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci#ifdef CONFIG_ACPI_SLEEP 678c2ecf20Sopenharmony_ci/* 688c2ecf20Sopenharmony_ci * Platforms, like ACPI, may want us to save some memory used by them during 698c2ecf20Sopenharmony_ci * suspend and to restore the contents of this memory during the subsequent 708c2ecf20Sopenharmony_ci * resume. The code below implements a mechanism allowing us to do that. 718c2ecf20Sopenharmony_ci */ 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistruct nvs_page { 748c2ecf20Sopenharmony_ci unsigned long phys_start; 758c2ecf20Sopenharmony_ci unsigned int size; 768c2ecf20Sopenharmony_ci void *kaddr; 778c2ecf20Sopenharmony_ci void *data; 788c2ecf20Sopenharmony_ci bool unmap; 798c2ecf20Sopenharmony_ci struct list_head node; 808c2ecf20Sopenharmony_ci}; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic LIST_HEAD(nvs_list); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci/** 858c2ecf20Sopenharmony_ci * suspend_nvs_register - register platform NVS memory region to save 868c2ecf20Sopenharmony_ci * @start - physical address of the region 878c2ecf20Sopenharmony_ci * @size - size of the region 888c2ecf20Sopenharmony_ci * 898c2ecf20Sopenharmony_ci * The NVS region need not be page-aligned (both ends) and we arrange 908c2ecf20Sopenharmony_ci * things so that the data from page-aligned addresses in this region will 918c2ecf20Sopenharmony_ci * be copied into separate RAM pages. 928c2ecf20Sopenharmony_ci */ 938c2ecf20Sopenharmony_cistatic int suspend_nvs_register(unsigned long start, unsigned long size) 948c2ecf20Sopenharmony_ci{ 958c2ecf20Sopenharmony_ci struct nvs_page *entry, *next; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci pr_info("PM: Registering ACPI NVS region [mem %#010lx-%#010lx] (%ld bytes)\n", 988c2ecf20Sopenharmony_ci start, start + size - 1, size); 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci while (size > 0) { 1018c2ecf20Sopenharmony_ci unsigned int nr_bytes; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci entry = kzalloc(sizeof(struct nvs_page), GFP_KERNEL); 1048c2ecf20Sopenharmony_ci if (!entry) 1058c2ecf20Sopenharmony_ci goto Error; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci list_add_tail(&entry->node, &nvs_list); 1088c2ecf20Sopenharmony_ci entry->phys_start = start; 1098c2ecf20Sopenharmony_ci nr_bytes = PAGE_SIZE - (start & ~PAGE_MASK); 1108c2ecf20Sopenharmony_ci entry->size = (size < nr_bytes) ? size : nr_bytes; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci start += entry->size; 1138c2ecf20Sopenharmony_ci size -= entry->size; 1148c2ecf20Sopenharmony_ci } 1158c2ecf20Sopenharmony_ci return 0; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci Error: 1188c2ecf20Sopenharmony_ci list_for_each_entry_safe(entry, next, &nvs_list, node) { 1198c2ecf20Sopenharmony_ci list_del(&entry->node); 1208c2ecf20Sopenharmony_ci kfree(entry); 1218c2ecf20Sopenharmony_ci } 1228c2ecf20Sopenharmony_ci return -ENOMEM; 1238c2ecf20Sopenharmony_ci} 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci/** 1268c2ecf20Sopenharmony_ci * suspend_nvs_free - free data pages allocated for saving NVS regions 1278c2ecf20Sopenharmony_ci */ 1288c2ecf20Sopenharmony_civoid suspend_nvs_free(void) 1298c2ecf20Sopenharmony_ci{ 1308c2ecf20Sopenharmony_ci struct nvs_page *entry; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci list_for_each_entry(entry, &nvs_list, node) 1338c2ecf20Sopenharmony_ci if (entry->data) { 1348c2ecf20Sopenharmony_ci free_page((unsigned long)entry->data); 1358c2ecf20Sopenharmony_ci entry->data = NULL; 1368c2ecf20Sopenharmony_ci if (entry->kaddr) { 1378c2ecf20Sopenharmony_ci if (entry->unmap) { 1388c2ecf20Sopenharmony_ci iounmap(entry->kaddr); 1398c2ecf20Sopenharmony_ci entry->unmap = false; 1408c2ecf20Sopenharmony_ci } else { 1418c2ecf20Sopenharmony_ci acpi_os_unmap_iomem(entry->kaddr, 1428c2ecf20Sopenharmony_ci entry->size); 1438c2ecf20Sopenharmony_ci } 1448c2ecf20Sopenharmony_ci entry->kaddr = NULL; 1458c2ecf20Sopenharmony_ci } 1468c2ecf20Sopenharmony_ci } 1478c2ecf20Sopenharmony_ci} 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci/** 1508c2ecf20Sopenharmony_ci * suspend_nvs_alloc - allocate memory necessary for saving NVS regions 1518c2ecf20Sopenharmony_ci */ 1528c2ecf20Sopenharmony_ciint suspend_nvs_alloc(void) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci struct nvs_page *entry; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci list_for_each_entry(entry, &nvs_list, node) { 1578c2ecf20Sopenharmony_ci entry->data = (void *)__get_free_page(GFP_KERNEL); 1588c2ecf20Sopenharmony_ci if (!entry->data) { 1598c2ecf20Sopenharmony_ci suspend_nvs_free(); 1608c2ecf20Sopenharmony_ci return -ENOMEM; 1618c2ecf20Sopenharmony_ci } 1628c2ecf20Sopenharmony_ci } 1638c2ecf20Sopenharmony_ci return 0; 1648c2ecf20Sopenharmony_ci} 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci/** 1678c2ecf20Sopenharmony_ci * suspend_nvs_save - save NVS memory regions 1688c2ecf20Sopenharmony_ci */ 1698c2ecf20Sopenharmony_ciint suspend_nvs_save(void) 1708c2ecf20Sopenharmony_ci{ 1718c2ecf20Sopenharmony_ci struct nvs_page *entry; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci printk(KERN_INFO "PM: Saving platform NVS memory\n"); 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci list_for_each_entry(entry, &nvs_list, node) 1768c2ecf20Sopenharmony_ci if (entry->data) { 1778c2ecf20Sopenharmony_ci unsigned long phys = entry->phys_start; 1788c2ecf20Sopenharmony_ci unsigned int size = entry->size; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci entry->kaddr = acpi_os_get_iomem(phys, size); 1818c2ecf20Sopenharmony_ci if (!entry->kaddr) { 1828c2ecf20Sopenharmony_ci entry->kaddr = acpi_os_ioremap(phys, size); 1838c2ecf20Sopenharmony_ci entry->unmap = !!entry->kaddr; 1848c2ecf20Sopenharmony_ci } 1858c2ecf20Sopenharmony_ci if (!entry->kaddr) { 1868c2ecf20Sopenharmony_ci suspend_nvs_free(); 1878c2ecf20Sopenharmony_ci return -ENOMEM; 1888c2ecf20Sopenharmony_ci } 1898c2ecf20Sopenharmony_ci memcpy(entry->data, entry->kaddr, entry->size); 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci return 0; 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci/** 1968c2ecf20Sopenharmony_ci * suspend_nvs_restore - restore NVS memory regions 1978c2ecf20Sopenharmony_ci * 1988c2ecf20Sopenharmony_ci * This function is going to be called with interrupts disabled, so it 1998c2ecf20Sopenharmony_ci * cannot iounmap the virtual addresses used to access the NVS region. 2008c2ecf20Sopenharmony_ci */ 2018c2ecf20Sopenharmony_civoid suspend_nvs_restore(void) 2028c2ecf20Sopenharmony_ci{ 2038c2ecf20Sopenharmony_ci struct nvs_page *entry; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci printk(KERN_INFO "PM: Restoring platform NVS memory\n"); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci list_for_each_entry(entry, &nvs_list, node) 2088c2ecf20Sopenharmony_ci if (entry->data) 2098c2ecf20Sopenharmony_ci memcpy(entry->kaddr, entry->data, entry->size); 2108c2ecf20Sopenharmony_ci} 2118c2ecf20Sopenharmony_ci#endif 212