mirror of
https://github.com/hardkernel/kernel_common_drivers.git
synced 2026-06-25 12:03:48 +09:00
c4f04df833
PD#SWPL-132538 Problem: The core-dump file cannot be saved to /data in Android. Solution: a. Fix BUG for bad page state when lazy_clear_work is running. b. Print the parameter infor required for the crash tools. c. Support for storing crashdump files in Android /data/vender/ramdump/ 1) When a kernel panic occurs, flush cache all. 2) Restart to uboot stage and compress the full DDR. 3) Set kernel dts rsv mem based on compressed infor. 4) Crop reserved memory sub nodes to reduce mem usage. 5) Kernel stage parsing dts to configure rsv mem. 6) Android init.rc read sysnode, and generate crash file. Verify: sc2_ah212 Change-Id: Ic6fba2c305f9e3171445f3b89afa02c75c27a080 Signed-off-by: dongqing.li <dongqing.li@amlogic.com> Signed-off-by: Luan Yuan <luan.yuan@amlogic.com>
504 lines
12 KiB
C
504 lines
12 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* 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.
|
|
*
|
|
*/
|
|
#define DEBUG
|
|
#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/sched/clock.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>
|
|
#include <linux/amlogic/gki_module.h>
|
|
#include <trace/hooks/debug.h>
|
|
#include <linux/panic_notifier.h>
|
|
#include <linux/of_address.h>
|
|
|
|
static unsigned long ramdump_base;
|
|
static unsigned long ramdump_size;
|
|
static bool ramdump_disable = 1;
|
|
|
|
#define WAIT_TIMEOUT (40ULL * 1000 * 1000 * 1000)
|
|
#define SAVE_DATA_BY_INIT_RC_SHELL 1
|
|
#define RESERVE_MEM_BY_RAMDUMP_DTS_NODE 1
|
|
|
|
struct ramdump {
|
|
unsigned long mem_size;
|
|
unsigned long mem_base;
|
|
#ifdef SAVE_DATA_BY_INIT_RC_SHELL
|
|
//void __iomem *mem_base;
|
|
struct mutex lock;
|
|
struct kobject *kobj;
|
|
#else
|
|
unsigned long long tick;
|
|
void *mnt_buf;
|
|
const char *storage_device;
|
|
#endif
|
|
struct delayed_work work;
|
|
int disable;
|
|
};
|
|
|
|
static struct ramdump *ram;
|
|
|
|
static void ramdump_parse_info(void)
|
|
{
|
|
#if !IS_MODULE(CONFIG_AMLOGIC_MEMORY_DEBUG)
|
|
pr_info("%s, .text : 0x%px - 0x%px, pa(.text): 0x%lx\n",
|
|
__func__, (unsigned long *)_text,
|
|
(unsigned long *)_etext, (unsigned long)__pa_symbol(_text));
|
|
pr_info("%s, kimage_voffset = 0x%px\n",
|
|
__func__, (unsigned long)_text - (unsigned long)__pa_symbol(_text));
|
|
#endif
|
|
|
|
#ifdef CONFIG_ARM64
|
|
pr_info("%s, kimage_vaddr: 0x%px, v2p: 0x%lx\n",
|
|
__func__, (unsigned long *)kimage_vaddr,
|
|
(unsigned long)__pa_symbol(kimage_vaddr));
|
|
pr_info("%s, KIMAGE_VADDR: 0x%px, v2p: 0x%lx\n",
|
|
__func__, (unsigned long *)KIMAGE_VADDR,
|
|
(unsigned long)__pa_symbol(KIMAGE_VADDR));
|
|
pr_info("%s, kimage_voffset = 0x%lx\n", __func__,
|
|
(unsigned long)kimage_vaddr - (unsigned long)__pa_symbol(kimage_vaddr));
|
|
pr_info("%s, kaslr_offset = 0x%lx\n", __func__, kaslr_offset());
|
|
pr_info("%s, vabits_actual = %d\n", __func__, (unsigned int)vabits_actual);
|
|
#endif
|
|
}
|
|
|
|
static int early_ramdump_para(char *buf)
|
|
{
|
|
int ret;
|
|
#ifdef RESERVE_MEM_BY_RAMDUMP_DTS_NODE
|
|
struct device_node *node;
|
|
struct resource res;
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
ramdump_disable = 0;
|
|
pr_info("%s, base:%lx, size:%lx\n",
|
|
__func__, ramdump_base, ramdump_size);
|
|
#ifdef RESERVE_MEM_BY_RAMDUMP_DTS_NODE
|
|
node = of_find_node_by_path("/reserved-memory/ramdump_bl33z");
|
|
ret = of_address_to_resource(node, 0, &res);
|
|
pr_info("%s, dts reserved-memory %lx - %lx\n",
|
|
__func__, (unsigned long)res.start, (unsigned long)res.end);
|
|
#else
|
|
ret = memblock_reserve(ramdump_base, PAGE_ALIGN(ramdump_size));
|
|
if (ret < 0) {
|
|
pr_info("%s, reserve memblock %lx - %lx failed\n",
|
|
__func__, ramdump_base,
|
|
ramdump_base + PAGE_ALIGN(ramdump_size));
|
|
ramdump_disable = 1;
|
|
} else {
|
|
pr_info("%s, reserve memblock %lx - %lx OK\n",
|
|
__func__, ramdump_base,
|
|
ramdump_base + PAGE_ALIGN(ramdump_size));
|
|
}
|
|
#endif
|
|
ramdump_parse_info();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
early_param("ramdump", early_ramdump_para);
|
|
|
|
#ifdef SAVE_DATA_BY_INIT_RC_SHELL
|
|
static ssize_t ramdump_bin_read(struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *attr,
|
|
char *buf, loff_t off, size_t count)
|
|
{
|
|
void *p = NULL;
|
|
|
|
if (!ram->mem_base || off >= ram->mem_size) {
|
|
pr_info("%s, crash sysfsnode data err.\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
if (off + count > ram->mem_size)
|
|
count = ram->mem_size - off;
|
|
|
|
p = (void *)phys_to_virt(ram->mem_base + off);
|
|
|
|
mutex_lock(&ram->lock);
|
|
memcpy(buf, p, count);
|
|
mutex_unlock(&ram->lock);
|
|
|
|
/* debug when read end */
|
|
if (off + count >= ram->mem_size)
|
|
pr_info("%s, p=%p %p, off:%lli, c:%zi\n",
|
|
__func__, buf, p, off, count);
|
|
|
|
return count;
|
|
}
|
|
|
|
int ramdump_disabled(void)
|
|
{
|
|
if (ram)
|
|
return ram->disable;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(ramdump_disabled);
|
|
|
|
static void meson_set_reboot_reason(int reboot_reason)
|
|
{
|
|
struct arm_smccc_res smccc;
|
|
|
|
arm_smccc_smc(SET_REBOOT_REASON,
|
|
reboot_reason, 0, 0, 0, 0, 0, 0, &smccc);
|
|
}
|
|
|
|
static ssize_t ramdump_bin_write(struct file *filp,
|
|
struct kobject *kobj,
|
|
struct bin_attribute *bin_attr,
|
|
char *buf, loff_t off, size_t count)
|
|
{
|
|
if (ram->mem_base && !strncmp("reboot", buf, 6))
|
|
kernel_restart("RAM-DUMP finished\n");
|
|
|
|
if (!strncmp("disable", buf, 7)) {
|
|
ram->disable = 1;
|
|
meson_set_reboot_reason(MESON_NORMAL_BOOT);
|
|
}
|
|
if (!strncmp("enable", buf, 6)) {
|
|
ram->disable = 0;
|
|
meson_set_reboot_reason(MESON_KERNEL_PANIC);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static struct bin_attribute ramdump_attr = {
|
|
.attr = {
|
|
.name = "compmsg",
|
|
.mode = 0664,
|
|
},
|
|
.read = ramdump_bin_read,
|
|
.write = ramdump_bin_write,
|
|
};
|
|
|
|
#ifdef CONFIG_ARM64
|
|
noinline void ramdump_sync_data(void)
|
|
{
|
|
/*
|
|
* back port from old kernel version 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
|
|
noinline void ramdump_sync_data(void)
|
|
{
|
|
flush_cache_all();
|
|
}
|
|
#endif
|
|
|
|
#endif /* end SAVE_DATA_BY_INIT_RC_SHELL */
|
|
|
|
/*
|
|
* clear memory to avoid large amount of memory not used.
|
|
* for random 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 - 3;
|
|
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("ramdump, clear:%lx, free:%lx, tick:%ld us\n",
|
|
clear, free, tick / 1000);
|
|
}
|
|
|
|
#if defined(CONFIG_TRACEPOINTS) && defined(CONFIG_ANDROID_VENDOR_HOOKS)
|
|
static void do_flush_cpu_cache(void)
|
|
{
|
|
int cpu = smp_processor_id();
|
|
|
|
pr_info("ramdump: CPU-%d flush cache ...\n", cpu);
|
|
ramdump_sync_data();
|
|
pr_info("ramdump: CPU-%d flush cache finish.\n", cpu);
|
|
}
|
|
|
|
static void flush_all_cache_hook(void *data, struct pt_regs *regs)
|
|
{
|
|
do_flush_cpu_cache();
|
|
}
|
|
|
|
static int panic_notify(struct notifier_block *self,
|
|
unsigned long cmd, void *ptr)
|
|
{
|
|
do_flush_cpu_cache();
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block panic_notifier = {
|
|
.notifier_call = panic_notify,
|
|
};
|
|
#endif
|
|
|
|
static int __init ramdump_probe(struct platform_device *pdev)
|
|
{
|
|
unsigned long total_mem;
|
|
struct resource *res;
|
|
unsigned int dump_set;
|
|
void __iomem *base;
|
|
void *vaddr = NULL;
|
|
int ret = 0;
|
|
|
|
total_mem = get_num_physpages() << PAGE_SHIFT;
|
|
pr_info("ramdump, Total Memory:[%ld MB]\n", total_mem / 1024 / 1024);
|
|
|
|
#if defined(CONFIG_TRACEPOINTS) && defined(CONFIG_ANDROID_VENDOR_HOOKS)
|
|
if (!ramdump_disable) {
|
|
/* flush cache for online cpu */
|
|
ret = register_trace_android_vh_ipi_stop(flush_all_cache_hook, NULL);
|
|
if (ret)
|
|
pr_err("%s, register_trace_android_vh_ipi_stop err(%d).\n", __func__, ret);
|
|
|
|
/* flush cache for panic cpu */
|
|
ret = atomic_notifier_chain_register(&panic_notifier_list, &panic_notifier);
|
|
if (ret)
|
|
pr_err("%s, register panic_notifier err(%d).\n", __func__, ret);
|
|
}
|
|
#endif
|
|
|
|
ram = kzalloc(sizeof(*ram), GFP_KERNEL);
|
|
if (!ram)
|
|
return -ENOMEM;
|
|
|
|
if (ramdump_disable)
|
|
ram->disable = 1;
|
|
|
|
ram->mem_base = 0;
|
|
ram->mem_size = ramdump_size;
|
|
if (!ramdump_base || !ramdump_size) {
|
|
pr_info("NO valid ramdump args:%lx %lx\n",
|
|
ramdump_base, ramdump_size);
|
|
} else {
|
|
pr_info("%s, memremap start, paddr area: 0x%08lx - 0x%08lx\n",
|
|
__func__, ramdump_base, ramdump_base + PAGE_ALIGN(ramdump_size));
|
|
//vaddr = ioremap_cache(ramdump_base, PAGE_ALIGN(ramdump_size));
|
|
vaddr = memremap(ramdump_base, PAGE_ALIGN(ramdump_size), MEMREMAP_WB);
|
|
if (vaddr)
|
|
ram->mem_base = (unsigned long)vaddr;
|
|
|
|
pr_info("%s, memremap end, vaddr_base:%lx, size:%lx\n",
|
|
__func__, ram->mem_base, ram->mem_size);
|
|
}
|
|
|
|
if (!ram->disable) {
|
|
if (!ram->mem_base) { /* No compressed data */
|
|
INIT_DELAYED_WORK(&ram->work, lazy_clear_work);
|
|
schedule_delayed_work(&ram->work, msecs_to_jiffies(100));
|
|
} else { /* with compressed data */
|
|
#ifdef SAVE_DATA_BY_INIT_RC_SHELL
|
|
pr_info("%s, SAVE_DATA_BY_INIT_RC_SHELL\n", __func__);
|
|
ram->kobj = kobject_create_and_add("mdump", kernel_kobj);
|
|
if (!ram->kobj) {
|
|
pr_err("%s, create sysfs /mdump failed\n", __func__);
|
|
goto err;
|
|
}
|
|
|
|
ramdump_attr.size = ram->mem_size;
|
|
pr_info("%s, creat /sys/kernel/mdump/compmsg\n", __func__);
|
|
if (sysfs_create_bin_file(ram->kobj, &ramdump_attr)) {
|
|
pr_err("%s, create sysfs compmsg failed\n", __func__);
|
|
goto err1;
|
|
}
|
|
#endif /* end SAVE_DATA_BY_INIT_RC_SHELL */
|
|
}
|
|
|
|
/* if ramdump is disabled in env, no need to set sticky reg */
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
|
"SYSCTRL_STICKY_REG6");
|
|
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(0x%08x) to 0x%x\n",
|
|
__func__, (unsigned int)res->start, dump_set);
|
|
}
|
|
}
|
|
return ret;
|
|
#ifdef SAVE_DATA_BY_INIT_RC_SHELL
|
|
err1:
|
|
kobject_put(ram->kobj);
|
|
#endif
|
|
|
|
err:
|
|
kfree(ram);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int ramdump_remove(struct platform_device *pdev)
|
|
{
|
|
#ifdef SAVE_DATA_BY_INIT_RC_SHELL
|
|
sysfs_remove_bin_file(ram->kobj, &ramdump_attr);
|
|
iounmap((void *)ram->mem_base);
|
|
kobject_put(ram->kobj);
|
|
#endif
|
|
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,
|
|
};
|
|
|
|
int __init ramdump_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = platform_driver_probe(&ramdump_driver, ramdump_probe);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void __exit ramdump_uninit(void)
|
|
{
|
|
platform_driver_unregister(&ramdump_driver);
|
|
}
|
|
|