blob: a4354fa399fe07ecbed7ac1dc0aeda76d916ebba [file] [log] [blame]
/*
* drivers/amlogic/memory_ext/ram_dump.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/version.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/reboot.h>
#include <linux/memblock.h>
#include <linux/amlogic/ramdump.h>
#include <linux/amlogic/reboot.h>
#include <linux/arm-smccc.h>
#include <linux/of.h>
#include <linux/highmem.h>
#include <linux/syscalls.h>
#include <linux/fs_struct.h>
#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/capability.h>
#include <asm/cacheflush.h>
static unsigned long ramdump_base __initdata;
static unsigned long ramdump_size __initdata;
static bool ramdump_disable __initdata;
#define WAIT_TIMEOUT (40ULL * 1000 * 1000 * 1000)
struct ramdump {
unsigned long mem_base;
unsigned long mem_size;
unsigned long long tick;
void *mnt_buf;
const char *storage_device;
struct delayed_work work;
int disable;
};
static struct ramdump *ram;
static int __init early_ramdump_para(char *buf)
{
int ret;
if (!buf)
return -EINVAL;
pr_info("%s:%s\n", __func__, buf);
if (strcmp(buf, "disabled") == 0) {
ramdump_disable = 1;
} else {
ret = sscanf(buf, "%lx,%lx", &ramdump_base, &ramdump_size);
if (ret != 2) {
pr_err("invalid boot args\n");
ramdump_disable = 1;
}
pr_info("%s, base:%lx, size:%lx\n",
__func__, ramdump_base, ramdump_size);
ret = memblock_reserve(ramdump_base, PAGE_ALIGN(ramdump_size));
if (ret < 0) {
pr_info("reserve memblock %lx - %lx failed\n",
ramdump_base,
ramdump_base + PAGE_ALIGN(ramdump_size));
ramdump_disable = 1;
}
}
return 0;
}
early_param("ramdump", early_ramdump_para);
/*
* clear memory to avoid large amount of memory not used.
* for ramdom data, it's hard to compress
*/
static void lazy_clear_work(struct work_struct *work)
{
struct page *page;
struct list_head head, *pos, *next;
void *virt;
int order;
gfp_t flags = __GFP_NORETRY |
__GFP_NOWARN |
__GFP_MOVABLE;
unsigned long clear = 0, size = 0, free = 0, tick;
INIT_LIST_HEAD(&head);
order = MAX_ORDER - 1;
tick = sched_clock();
do {
page = alloc_pages(flags, order);
if (page) {
list_add(&page->lru, &head);
virt = page_address(page);
size = (1 << order) * PAGE_SIZE;
memset(virt, 0, size);
clear += size;
}
} while (page);
tick = sched_clock() - tick;
list_for_each_safe(pos, next, &head) {
page = list_entry(pos, struct page, lru);
list_del(&page->lru);
__free_pages(page, order);
free += size;
}
pr_info("clear:%lx, free:%lx, tick:%ld us\n",
clear, free, tick / 1000);
}
#ifdef CONFIG_ARM64
void ramdump_sync_data(void)
{
/*
* back port from old kernel verion for function
* flush_cache_all(), we need it for ram dump
*/
asm volatile (
"mov x12, x30 \n"
"dsb sy \n"
"mrs x0, clidr_el1 \n"
"and x3, x0, #0x7000000 \n"
"lsr x3, x3, #23 \n"
"cbz x3, finished \n"
"mov x10, #0 \n"
"loop1: \n"
"add x2, x10, x10, lsr #1 \n"
"lsr x1, x0, x2 \n"
"and x1, x1, #7 \n"
"cmp x1, #2 \n"
"b.lt skip \n"
"mrs x9, daif \n"
"msr daifset, #2 \n"
"msr csselr_el1, x10 \n"
"isb \n"
"mrs x1, ccsidr_el1 \n"
"msr daif, x9 \n"
"and x2, x1, #7 \n"
"add x2, x2, #4 \n"
"mov x4, #0x3ff \n"
"and x4, x4, x1, lsr #3 \n"
"clz w5, w4 \n"
"mov x7, #0x7fff \n"
"and x7, x7, x1, lsr #13 \n"
"loop2: \n"
"mov x9, x4 \n"
"loop3: \n"
"lsl x6, x9, x5 \n"
"orr x11, x10, x6 \n"
"lsl x6, x7, x2 \n"
"orr x11, x11, x6 \n"
"dc cisw, x11 \n"
"subs x9, x9, #1 \n"
"b.ge loop3 \n"
"subs x7, x7, #1 \n"
"b.ge loop2 \n"
"skip: \n"
"add x10, x10, #2 \n"
"cmp x3, x10 \n"
"b.gt loop1 \n"
"finished: \n"
"mov x10, #0 \n"
"msr csselr_el1, x10 \n"
"dsb sy \n"
"isb \n"
"mov x0, #0 \n"
"ic ialluis \n"
"ret x12 \n"
);
}
#else
void ramdump_sync_data(void)
{
flush_cache_all();
}
#endif
#ifdef CONFIG_64BIT
static void free_reserved_highmem(unsigned long start, unsigned long end)
{
}
#else
static void free_reserved_highmem(unsigned long start, unsigned long end)
{
for (; start < end; ) {
free_highmem_page(phys_to_page(start));
start += PAGE_SIZE;
}
}
#endif
static void free_reserved_mem(unsigned long start, unsigned long size)
{
unsigned long end = PAGE_ALIGN(start + size);
struct page *page, *epage;
page = phys_to_page(start);
if (PageHighMem(page)) {
free_reserved_highmem(start, end);
} else {
epage = phys_to_page(end);
if (!PageHighMem(epage)) {
free_reserved_area(__va(start),
__va(end), 0, "ramdump");
} else {
/* reserved area cross zone */
struct zone *zone;
unsigned long bound;
zone = page_zone(page);
bound = zone_end_pfn(zone);
free_reserved_area(__va(start),
__va(bound << PAGE_SHIFT),
0, "ramdump");
zone = page_zone(epage);
bound = zone->zone_start_pfn;
free_reserved_highmem(bound << PAGE_SHIFT, end);
}
}
}
static int check_storage_mounted(char **root)
{
int fd, cnt, ret = 0;
char mnt_dev[64] = {}, *mnt_ptr, *root_dir;
fd = sys_open("/proc/mounts", O_RDONLY, 0);
if (!fd) {
pr_debug("%s, open mounts failed:%d\n", __func__, fd);
return -EINVAL;
}
cnt = sys_read(fd, ram->mnt_buf, PAGE_SIZE);
if (cnt < 0) {
pr_debug("%s, read mounts failed:%d\n", __func__, cnt);
ret = -ENODEV;
goto exit;
}
pr_debug("read:%d, %s\n", cnt, (char *)ram->mnt_buf);
sprintf(mnt_dev, "/dev/block/%s", ram->storage_device);
mnt_ptr = strstr((char *)ram->mnt_buf, mnt_dev);
if (mnt_ptr) {
pr_debug("%s, find %s in buffer, ptr:%p\n",
__func__, mnt_dev, mnt_ptr);
root_dir = strstr(mnt_ptr, " ");
root_dir++;
*root = root_dir;
pr_debug("mount:%s root:%s\n", mnt_ptr, root_dir);
} else
ret = -ENODEV;
exit:
sys_close(fd);
return ret;
}
static size_t save_data(int fd)
{
unsigned long saved = 0, off = 0, s = 0, e;
void *buffer;
int block = (1 << (PAGE_SHIFT + MAX_ORDER - 1)), wsize = 0, ret, i;
struct vm_struct *area;
struct page *page, **pages = NULL;
area = get_vm_area(block, VM_ALLOC);
if (!area) {
pr_err("%s, get vma failed\n", __func__);
return -ENOMEM;
}
pages = kzalloc(sizeof(unsigned long) * MAX_ORDER_NR_PAGES, GFP_KERNEL);
if (!pages)
goto out;
buffer = area->addr;
while (saved < ram->mem_size) {
s = ram->mem_size - saved;
if (s >= block)
wsize = block;
else
wsize = s;
s = ram->mem_base + off;
e = s + PAGE_ALIGN(wsize);
page = phys_to_page(s);
for (i = 0; i < MAX_ORDER_NR_PAGES; i++) {
pages[i] = page;
page++;
}
ret = map_kernel_range_noflush((unsigned long)buffer,
PAGE_ALIGN(wsize),
PAGE_KERNEL,
pages);
if (!ret) {
pr_err("map page:%lx failed\n", page_to_pfn(page));
goto out;
}
ret = sys_write(fd, buffer, wsize);
if (ret != wsize) {
unmap_kernel_range((unsigned long)buffer,
PAGE_ALIGN(wsize));
pr_err("%s, write failed\n", __func__);
goto out;
}
unmap_kernel_range((unsigned long)buffer, PAGE_ALIGN(wsize));
free_reserved_mem(s, PAGE_ALIGN(wsize));
saved += wsize;
off += wsize;
pr_debug("%s, write %08lx, size:%08x, saved:%08lx, off:%lx\n",
__func__, s, wsize, saved, off);
}
out:
free_vm_area(area);
kfree(pages);
pr_info("%s, write %08lx, size:%08x, saved:%08lx, off:%lx\n",
__func__, s, wsize, saved, off);
return saved;
}
#define OPEN_FLAGS (O_WRONLY | O_CREAT | O_DSYNC | O_TRUNC)
static void wait_to_save(struct work_struct *work)
{
char *root;
int fd, ret = 0;
int need_reboot = 0;
if ((sched_clock() - ram->tick) >= WAIT_TIMEOUT) {
pr_err("can't find mounted device, free saved data\n");
need_reboot = 3;
goto exit;
}
if (!check_storage_mounted(&root)) {
char wname[64] = {}, *next_token;
/* write compressed data to storage device */
next_token = strstr(root, " ");
if (next_token)
*next_token = '\0';
sprintf(wname, "%s/crashdump-1.bin", root);
fd = sys_open(wname, OPEN_FLAGS, 0644);
if (fd < 0) {
pr_info("open %s failed:%d\n", wname, fd);
need_reboot = 3;
goto exit;
}
ret = save_data(fd);
if (ret != ram->mem_size) {
pr_err("write size %d not match %ld\n",
ret, ram->mem_size);
}
sys_fsync(fd);
sys_close(fd);
sys_sync();
need_reboot = 1;
} else
schedule_delayed_work(&ram->work, 500);
exit:
/* Nomatter what happened, reboot must be done in this function */
if (need_reboot) {
if (need_reboot & 0x1)
kfree(ram->mnt_buf);
if (need_reboot & 0x02)
free_reserved_mem(ram->mem_base, ram->mem_size);
kernel_restart("RAM-DUMP finished");
}
}
static int __init ramdump_probe(struct platform_device *pdev)
{
void __iomem *p = NULL;
struct device_node *np;
unsigned long dts_memory[2] = {0}, total_mem;
struct resource *res;
unsigned int dump_set;
int ret;
void __iomem *base;
const char *dev_name = NULL;
np = of_find_node_by_name(NULL, "memory");
if (!np)
return -EINVAL;
#ifdef CONFIG_64BIT
ret = of_property_read_u64_array(np, "linux,usable-memory",
(u64 *)&dts_memory, 2);
if (ret)
ret = of_property_read_u64_array(np, "reg",
(u64 *)&dts_memory, 2);
#else
ret = of_property_read_u32_array(np, "linux,usable-memory",
(u32 *)&dts_memory, 2);
if (ret)
ret = of_property_read_u32_array(np, "reg",
(u32 *)&dts_memory, 2);
#endif
if (ret)
pr_info("can't get dts memory\n");
else
pr_info("MEMORY:[%lx+%lx]\n", dts_memory[0], dts_memory[1]);
of_node_put(np);
/*
* memory in dts is [start_addr size] patten. For amlogic soc,
* ddr address range is started from 0x0, usually start_addr in
* dts should be started with 0x0, but some soc must reserve a
* small framgment of memory at 0x0 for start up code. So start_addr
* can be 0x100000/0x1000000. But we always using 0x0 to get real
* DDR size for ramdump. So we using following formula to get total
* DDR size.
*/
total_mem = dts_memory[0] + dts_memory[1];
ram = kzalloc(sizeof(struct ramdump), GFP_KERNEL);
if (!ram)
return -ENOMEM;
if (ramdump_disable)
ram->disable = 1;
np = pdev->dev.of_node;
ret = of_property_read_string(np, "store_device", &dev_name);
if (!ret) {
ram->storage_device = dev_name;
pr_info("%s, storage device:%s\n", __func__, dev_name);
}
if (!ramdump_base || !ramdump_size) {
pr_info("NO valid ramdump args:%lx %lx\n",
ramdump_base, ramdump_size);
} else {
ram->mem_base = ramdump_base;
ram->mem_size = ramdump_size;
pr_info("%s, mem_base:%p, %lx, size:%lx\n",
__func__, p, ramdump_base, ramdump_size);
}
if (!ram->disable) {
if (!ram->mem_base) { /* No compressed data */
INIT_DELAYED_WORK(&ram->work, lazy_clear_work);
} else { /* with compressed data */
INIT_DELAYED_WORK(&ram->work, wait_to_save);
ram->tick = sched_clock();
ram->mnt_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
WARN_ON(!ram->mnt_buf);
}
schedule_delayed_work(&ram->work, 1);
/* if ramdump is disabled in env, no need to set sticky reg */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"PREG_STICKY_REG8");
if (res) {
base = devm_ioremap(&pdev->dev, res->start,
res->end - res->start);
if (!base) {
pr_err("%s, map reg failed\n", __func__);
goto err;
}
dump_set = readl(base);
dump_set &= ~RAMDUMP_STICKY_DATA_MASK;
dump_set |= ((total_mem >> 20) | AMLOGIC_KERNEL_BOOTED);
writel(dump_set, base);
pr_info("%s, set sticky to %x\n", __func__, dump_set);
}
}
return 0;
err:
kfree(ram);
return -EINVAL;
}
static int ramdump_remove(struct platform_device *pdev)
{
kfree(ram);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id ramdump_dt_match[] = {
{
.compatible = "amlogic, ram_dump",
},
{},
};
#endif
static struct platform_driver ramdump_driver = {
.driver = {
.name = "mdump",
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = ramdump_dt_match,
#endif
},
.remove = ramdump_remove,
};
static int __init ramdump_init(void)
{
int ret;
ret = platform_driver_probe(&ramdump_driver, ramdump_probe);
return ret;
}
static void __exit ramdump_uninit(void)
{
platform_driver_unregister(&ramdump_driver);
}
subsys_initcall(ramdump_init);
module_exit(ramdump_uninit);
MODULE_DESCRIPTION("AMLOGIC ramdump driver");
MODULE_LICENSE("GPL");