ramdump: add ramdump support for kernel [4/6]
authortao zeng <tao.zeng@amlogic.com>
Fri, 18 May 2018 08:58:13 +0000 (16:58 +0800)
committerJianxin Pan <jianxin.pan@amlogic.com>
Sun, 3 Jun 2018 09:57:35 +0000 (02:57 -0700)
PD#165764: add ramdump support

1. Add Makefile and driver of ramdump;
2. Flush all cache before panic reboot;
3. Change panic reboot reason when ramdump is disabled;
4. Add dts support/open watch dog for chips.

Change-Id: Ieeb418f038bfda119c3156a7f8f8b05c8bc58ad2
Signed-off-by: tao zeng <tao.zeng@amlogic.com>
13 files changed:
MAINTAINERS
arch/arm64/boot/dts/amlogic/mesonaxg.dtsi
arch/arm64/boot/dts/amlogic/mesong12a.dtsi
arch/arm64/boot/dts/amlogic/mesongxl.dtsi
arch/arm64/boot/dts/amlogic/mesongxm.dtsi
arch/arm64/boot/dts/amlogic/mesontxlx.dtsi
arch/arm64/configs/meson64_defconfig
drivers/amlogic/memory_ext/Kconfig
drivers/amlogic/memory_ext/Makefile
drivers/amlogic/memory_ext/ram_dump.c [new file with mode: 0644]
drivers/amlogic/reboot/reboot.c
include/linux/amlogic/ramdump.h [new file with mode: 0644]
kernel/panic.c

index 88582bb..e36de2b 100644 (file)
@@ -13505,6 +13505,7 @@ F: drivers/amlogic/cpufreq/*
 AMLOGIC driver for memory extend
 M: Tao Zeng <tao.zeng@amlogic.com>
 F: drivers/amlogic/memory_ext/*
+F: include/linux/amlogic/ramdump.h
 
 AMLOGIC driver for memory extend
 M: Tao Zeng <tao.zeng@amlogic.com>
index 4a53384..3a1c674 100644 (file)
 
        wdt_ee: watchdog@0xffd0f0d0 {
                compatible = "amlogic, meson-wdt";
-               status = "disabled";
+               status = "okay";
                default_timeout=<10>;
                reset_watchdog_method=<1>; /* 0:sysfs,1:kernel */
                reset_watchdog_time=<2>;
                clocks = <&xtal>;
        };
 
+       ram-dump {
+               compatible = "amlogic, ram_dump";
+               status = "okay";
+       };
+
        pinctrl_aobus: pinctrl@ff800014{
                compatible = "amlogic,meson-axg-aobus-pinctrl";
                #address-cells = <2>;
index a45a59e..f58e504 100644 (file)
                clocks = <&xtal>;
        };
 
+       ram-dump {
+               compatible = "amlogic, ram_dump";
+               status = "okay";
+       };
+
        jtag {
                compatible = "amlogic, jtag";
                status = "okay";
                                &gpio       GPIOC_5     0>;
        };
 
-
        saradc:saradc {
                compatible = "amlogic,meson-g12a-saradc";
                status = "okay";
index 079ba18..d196261 100644 (file)
 
        watchdog {
                compatible = "amlogic, meson-wdt";
-               status = "disabled";
+               status = "okay";
                default_timeout=<10>;
                reset_watchdog_method=<1>; /* 0:sysfs,1:kernel */
                reset_watchdog_time=<2>;
                clocks = <&xtal>;
        };
 
+       ram-dump {
+               compatible = "amlogic, ram_dump";
+               status = "okay";
+       };
+
        jtag {
                compatible = "amlogic, jtag";
                status = "okay";
index 627b956..a1ca12b 100644 (file)
 
        watchdog {
                compatible = "amlogic, meson-wdt";
-               status = "disabled";
+               status = "okay";
                default_timeout=<10>;
                reset_watchdog_method=<1>; /* 0:sysfs,1:kernel */
                reset_watchdog_time=<2>;
                clocks = <&xtal>;
        };
 
+       ram-dump {
+               compatible = "amlogic, ram_dump";
+               status = "okay";
+       };
+
        jtag {
                compatible = "amlogic, jtag";
                status = "disabled";
index ed61152..6517935 100644 (file)
 
        wdt_ee: watchdog@0xffd0f0d0 {
                compatible = "amlogic, meson-wdt";
-               status = "disabled";
+               status = "watch";
                default_timeout=<10>;
                reset_watchdog_method=<1>; /* 0:sysfs,1:kernel */
                reset_watchdog_time=<2>;
                clocks = <&xtal>;
        };
 
+       ram-dump {
+               compatible = "amlogic, ram_dump";
+               status = "okay";
+       };
+
        amlogic-jtag {
                compatible = "amlogic, jtag";
                status = "okay";
index 1ca2f8f..62ad862 100644 (file)
@@ -344,6 +344,7 @@ CONFIG_AMLOGIC_SARADC=y
 CONFIG_AMLOGIC_DDR_TOOL=y
 CONFIG_AMLOGIC_DDR_BANDWIDTH=y
 CONFIG_AMLOGIC_TEE=y
+CONFIG_AMLOGIC_RAMDUMP=y
 CONFIG_AMLOGIC_GPIO_IRQ=y
 CONFIG_AMLOGIC_ATV_DEMOD=y
 CONFIG_AMLOGIC_DEBUG=y
index 8a1433a..cef724a 100644 (file)
@@ -19,6 +19,17 @@ config AMLOGIC_PAGE_TRACE
                with allocate page count information of each caller functions from
                /proc/pagetrace
 
+config AMLOGIC_RAMDUMP
+       bool "Amlogic RAM DUMP support"
+       depends on AMLOGIC_MEMORY_EXTEND
+       depends on PANIC_TIMEOUT != 0
+       default n
+       help
+               RAM dump support for amlogic platform, when system is rebooted
+               after panic/watch dog reboot, you may need this option to save
+               whole ram to /data/ partition. Which can be used for analyze
+               crash reason.
+
 config AMLOGIC_CMA
        bool "Amlogic CMA change"
        depends on AMLOGIC_MEMORY_EXTEND
index 93b0037..f3c1216 100644 (file)
@@ -2,3 +2,4 @@
 obj-$(CONFIG_AMLOGIC_PAGE_TRACE)  += page_trace.o
 obj-$(CONFIG_AMLOGIC_CMA)         += aml_cma.o
 obj-$(CONFIG_AMLOGIC_SLUB_DEBUG)  += aml_slub_debug.o
+obj-$(CONFIG_AMLOGIC_RAMDUMP)     += ram_dump.o
diff --git a/drivers/amlogic/memory_ext/ram_dump.c b/drivers/amlogic/memory_ext/ram_dump.c
new file mode 100644 (file)
index 0000000..3a836c4
--- /dev/null
@@ -0,0 +1,357 @@
+/*
+ * 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/reboot.h>
+#include <linux/memblock.h>
+#include <linux/amlogic/ramdump.h>
+#include <linux/amlogic/reboot.h>
+#include <linux/arm-smccc.h>
+#include <asm/cacheflush.h>
+
+static unsigned long ramdump_base      __initdata;
+static unsigned long ramdump_size      __initdata;
+static bool ramdump_disable            __initdata;
+
+struct ramdump {
+       void __iomem            *mem_base;
+       unsigned long           mem_size;
+       struct mutex            lock;
+       struct kobject          *kobj;
+       struct work_struct      clear_work;
+       int                     disable;
+};
+
+static struct ramdump *ram;
+
+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);
+       return;
+}
+
+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;
+               }
+       }
+       if (ramdump_disable)
+               meson_set_reboot_reason(MESON_NORMAL_BOOT);
+
+       return 0;
+}
+early_param("ramdump", early_ramdump_para);
+
+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)
+               return 0;
+
+       if (off + count > ram->mem_size)
+               count = ram->mem_size - off;
+
+       p = 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 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 = S_IRUGO | S_IWUSR,
+       },
+       .read  = ramdump_bin_read,
+       .write = ramdump_bin_write,
+};
+
+/*
+ * 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
+
+static int ramdump_probe(struct platform_device *pdev)
+{
+       void __iomem *p;
+
+       ram = kzalloc(sizeof(struct ramdump), GFP_KERNEL);
+       if (!ram)
+               return -ENOMEM;
+
+       if (ramdump_disable)
+               ram->disable = 1;
+
+       if (!ramdump_base || !ramdump_size) {
+               pr_info("NO valid ramdump args:%lx %lx\n",
+                       ramdump_base, ramdump_size);
+       } else {
+               p = ioremap_cache(ramdump_base, ramdump_size);
+               ram->mem_base = p;
+               ram->mem_size = ramdump_size;
+               pr_info("%s, mem_base:%p, %lx, size:%lx\n",
+                       __func__, p, ramdump_base, ramdump_size);
+       }
+       ram->kobj = kobject_create_and_add("mdump", kernel_kobj);
+       if (!ram->kobj) {
+               pr_err("%s, create sysfs failed\n", __func__);
+               goto err;
+       }
+       ramdump_attr.size = ram->mem_size;
+       if (sysfs_create_bin_file(ram->kobj, &ramdump_attr)) {
+               pr_err("%s, create sysfs1 failed\n", __func__);
+               goto err1;
+       }
+       mutex_init(&ram->lock);
+       if (!ram->disable && !ram->mem_size) {
+               INIT_WORK(&ram->clear_work, lazy_clear_work);
+               schedule_work(&ram->clear_work);
+       }
+       return 0;
+
+err1:
+       kobject_put(ram->kobj);
+err:
+       if (ram->mem_base)
+               iounmap(ram->mem_base);
+       kfree(ram);
+
+       return -EINVAL;
+}
+
+static int ramdump_remove(struct platform_device *pdev)
+{
+       sysfs_remove_bin_file(ram->kobj, &ramdump_attr);
+       iounmap(ram->mem_base);
+       kobject_put(ram->kobj);
+       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
+       },
+       .probe  = ramdump_probe,
+       .remove = ramdump_remove,
+};
+
+static int __init ramdump_init(void)
+{
+       int ret;
+
+       ret = platform_driver_register(&ramdump_driver);
+
+       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");
index e7dc27b..e26119d 100644 (file)
 #include <asm/compiler.h>
 #include <linux/kdebug.h>
 #include <linux/arm-smccc.h>
+#ifdef CONFIG_AMLOGIC_RAMDUMP
+#include <linux/amlogic/ramdump.h>
+#define RAMDUMP_REPLACE_MSG    "ramdump disabled, replase panic to normal\n"
+#endif /* CONFIG_AMLOGIC_RAMDUMP */
 
 static u32 psci_function_id_restart;
 static u32 psci_function_id_poweroff;
@@ -59,8 +63,17 @@ static u32 parse_reason(const char *cmd)
                        reboot_reason = MESON_UBOOT_SUSPEND;
        } else {
                if (kernel_panic) {
-                       if (strcmp(kernel_panic, "kernel_panic") == 0)
+                       if (strcmp(kernel_panic, "kernel_panic") == 0) {
+                       #ifdef CONFIG_AMLOGIC_RAMDUMP
+                               if (ramdump_disabled()) {
+                                       reboot_reason = MESON_NORMAL_BOOT;
+                                       pr_info(RAMDUMP_REPLACE_MSG);
+                               } else
+                                       reboot_reason = MESON_KERNEL_PANIC;
+                       #else
                                reboot_reason = MESON_KERNEL_PANIC;
+                       #endif
+                       }
                }
 
        }
diff --git a/include/linux/amlogic/ramdump.h b/include/linux/amlogic/ramdump.h
new file mode 100644 (file)
index 0000000..5dc0c42
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * include/linux/amlogic/ramdump.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef __RAMDUMP_H__
+#define __RAMDUMP_H__
+
+#define SET_REBOOT_REASON              0x82000049
+
+extern int ramdump_disabled(void);
+extern void ramdump_sync_data(void);
+
+#endif /* __RAMDUMP_H__ */
index dbec387..535ee50 100644 (file)
@@ -25,6 +25,9 @@
 #include <linux/nmi.h>
 #include <linux/console.h>
 #include <linux/bug.h>
+#ifdef CONFIG_AMLOGIC_RAMDUMP
+#include <linux/amlogic/ramdump.h>
+#endif
 
 #define PANIC_TIMER_STEP 100
 #define PANIC_BLINK_SPD 18
@@ -179,6 +182,9 @@ void panic(const char *fmt, ...)
                dump_stack();
 #endif
 
+#ifdef CONFIG_AMLOGIC_RAMDUMP
+       ramdump_sync_data();
+#endif
        /*
         * If we have crashed and we have a crash kernel loaded let it handle
         * everything else.