#define pr_fmt(fmt) "ACPI: PM: " fmt
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/acpi.h>
#include "internal.h"
struct nvs_region {
__u64 phys_start;
__u64 size;
struct list_head node;
};
static LIST_HEAD(nvs_region_list);
#ifdef CONFIG_ACPI_SLEEP
static int suspend_nvs_register(unsigned long start, unsigned long size);
#else
static inline int suspend_nvs_register(unsigned long a, unsigned long b)
{
return 0;
}
#endif
int acpi_nvs_register(__u64 start, __u64 size)
{
struct nvs_region *region;
region = kmalloc(sizeof(*region), GFP_KERNEL);
if (!region)
return -ENOMEM;
region->phys_start = start;
region->size = size;
list_add_tail(®ion->node, &nvs_region_list);
return suspend_nvs_register(start, size);
}
int acpi_nvs_for_each_region(int (*func)(__u64 start, __u64 size, void *data),
void *data)
{
int rc;
struct nvs_region *region;
list_for_each_entry(region, &nvs_region_list, node) {
rc = func(region->phys_start, region->size, data);
if (rc)
return rc;
}
return 0;
}
#ifdef CONFIG_ACPI_SLEEP
struct nvs_page {
unsigned long phys_start;
unsigned int size;
void *kaddr;
void *data;
bool unmap;
struct list_head node;
};
static LIST_HEAD(nvs_list);
static int suspend_nvs_register(unsigned long start, unsigned long size)
{
struct nvs_page *entry, *next;
pr_info("Registering ACPI NVS region [mem %#010lx-%#010lx] (%ld bytes)\n",
start, start + size - 1, size);
while (size > 0) {
unsigned int nr_bytes;
entry = kzalloc(sizeof(struct nvs_page), GFP_KERNEL);
if (!entry)
goto Error;
list_add_tail(&entry->node, &nvs_list);
entry->phys_start = start;
nr_bytes = PAGE_SIZE - (start & ~PAGE_MASK);
entry->size = (size < nr_bytes) ? size : nr_bytes;
start += entry->size;
size -= entry->size;
}
return 0;
Error:
list_for_each_entry_safe(entry, next, &nvs_list, node) {
list_del(&entry->node);
kfree(entry);
}
return -ENOMEM;
}
void suspend_nvs_free(void)
{
struct nvs_page *entry;
list_for_each_entry(entry, &nvs_list, node)
if (entry->data) {
free_page((unsigned long)entry->data);
entry->data = NULL;
if (entry->kaddr) {
if (entry->unmap) {
iounmap(entry->kaddr);
entry->unmap = false;
} else {
acpi_os_unmap_iomem(entry->kaddr,
entry->size);
}
entry->kaddr = NULL;
}
}
}
int suspend_nvs_alloc(void)
{
struct nvs_page *entry;
list_for_each_entry(entry, &nvs_list, node) {
entry->data = (void *)__get_free_page(GFP_KERNEL);
if (!entry->data) {
suspend_nvs_free();
return -ENOMEM;
}
}
return 0;
}
int suspend_nvs_save(void)
{
struct nvs_page *entry;
pr_info("Saving platform NVS memory\n");
list_for_each_entry(entry, &nvs_list, node)
if (entry->data) {
unsigned long phys = entry->phys_start;
unsigned int size = entry->size;
entry->kaddr = acpi_os_get_iomem(phys, size);
if (!entry->kaddr) {
entry->kaddr = acpi_os_ioremap(phys, size);
entry->unmap = !!entry->kaddr;
}
if (!entry->kaddr) {
suspend_nvs_free();
return -ENOMEM;
}
memcpy(entry->data, entry->kaddr, entry->size);
}
return 0;
}
void suspend_nvs_restore(void)
{
struct nvs_page *entry;
pr_info("Restoring platform NVS memory\n");
list_for_each_entry(entry, &nvs_list, node)
if (entry->data)
memcpy(entry->kaddr, entry->data, entry->size);
}
#endif