/* * Copyright (c) 2013 Samsung Electronics Co., Ltd. * http://www.samsung.com * * Debug-SnapShot: Debug Framework for Ramdump based debugging method * The original code is Exynos-Snapshot for Exynos SoC * * Author: Hosung Kim * Author: Changki Kim * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "debug-snapshot-local.h" #include #ifdef CONFIG_SEC_DEBUG #include #endif /* CONFIG_SEC_DEBUG */ static void dbg_snapshot_soc_dummy_func(void *dummy) {return;} static int dbg_snapshot_soc_dummy_func_int(void *dummy) {return 0;} static int dbg_snapshot_soc_dummy_func_smc(unsigned long dummy1, unsigned long dummy2, unsigned long dummy3, unsigned long dummy4) {return 0;} static struct dbg_snapshot_helper_ops dss_soc_dummy_ops = { .soc_early_panic = dbg_snapshot_soc_dummy_func, .soc_prepare_panic_entry = dbg_snapshot_soc_dummy_func, .soc_prepare_panic_exit = dbg_snapshot_soc_dummy_func, .soc_post_panic_entry = dbg_snapshot_soc_dummy_func, .soc_post_panic_exit = dbg_snapshot_soc_dummy_func, .soc_post_reboot_entry = dbg_snapshot_soc_dummy_func, .soc_post_reboot_exit = dbg_snapshot_soc_dummy_func, .soc_save_context_entry = dbg_snapshot_soc_dummy_func, .soc_save_context_exit = dbg_snapshot_soc_dummy_func, .soc_save_core = dbg_snapshot_soc_dummy_func, .soc_save_system = dbg_snapshot_soc_dummy_func, .soc_dump_info = dbg_snapshot_soc_dummy_func, .soc_start_watchdog = dbg_snapshot_soc_dummy_func, .soc_expire_watchdog = dbg_snapshot_soc_dummy_func, .soc_stop_watchdog = dbg_snapshot_soc_dummy_func, .soc_kick_watchdog = dbg_snapshot_soc_dummy_func, .soc_is_power_cpu = dbg_snapshot_soc_dummy_func_int, .soc_smc_call = dbg_snapshot_soc_dummy_func_smc, }; struct dbg_snapshot_helper_ops *dss_soc_ops; void __iomem *dbg_snapshot_get_base_vaddr(void) { return (void __iomem *)(dss_base.vaddr); } void __iomem *dbg_snapshot_get_base_paddr(void) { return (void __iomem *)(dss_base.paddr); } static void dbg_snapshot_set_core_power_stat(unsigned int val, unsigned cpu) { if (dbg_snapshot_get_enable("header")) __raw_writel(val, (dbg_snapshot_get_base_vaddr() + DSS_OFFSET_CORE_POWER_STAT + cpu * 4)); } unsigned int dbg_snapshot_get_core_panic_stat(unsigned cpu) { if (dbg_snapshot_get_enable("header")) return __raw_readl(dbg_snapshot_get_base_vaddr() + DSS_OFFSET_PANIC_STAT + cpu * 4); else return 0; } void dbg_snapshot_set_core_panic_stat(unsigned int val, unsigned cpu) { if (dbg_snapshot_get_enable("header")) __raw_writel(val, (dbg_snapshot_get_base_vaddr() + DSS_OFFSET_PANIC_STAT + cpu * 4)); } static void dbg_snapshot_report_reason(unsigned int val) { if (dbg_snapshot_get_enable("header")) __raw_writel(val, dbg_snapshot_get_base_vaddr() + DSS_OFFSET_EMERGENCY_REASON); } void dbg_snapshot_set_debug_level_reg(void) { if (dbg_snapshot_get_enable("header")) __raw_writel(dss_desc.debug_level | DSS_DEBUG_LEVEL_PREFIX, dbg_snapshot_get_base_vaddr() + DSS_OFFSET_DEBUG_LEVEL); } int dbg_snapshot_get_debug_level_reg(void) { int ret = DSS_DEBUG_LEVEL_NONE; if (dbg_snapshot_get_enable("header")) { int val = __raw_readl(dbg_snapshot_get_base_vaddr() + DSS_OFFSET_DEBUG_LEVEL); if ((val & GENMASK(31, 16)) == DSS_DEBUG_LEVEL_PREFIX) ret = val & GENMASK(15, 0); } return ret; } void dbg_snapshot_set_sjtag_status(void) { int ret; ret = dss_soc_ops->soc_smc_call(SMC_CMD_GET_SJTAG_STATUS, 0x3, 0, 0); if (ret == true || ret == false) { dss_desc.sjtag_status = ret; pr_info("debug-snapshot: SJTAG is %sabled\n", ret == true ? "en" : "dis"); return; } dss_desc.sjtag_status = -1; } int dbg_snapshot_get_sjtag_status(void) { return dss_desc.sjtag_status; } void dbg_snapshot_scratch_reg(unsigned int val) { if (dbg_snapshot_get_enable("header")) __raw_writel(val, dbg_snapshot_get_base_vaddr() + DSS_OFFSET_SCRATCH); } bool dbg_snapshot_is_scratch(void) { return __raw_readl(dbg_snapshot_get_base_vaddr() + DSS_OFFSET_SCRATCH) == DSS_SIGN_SCRATCH; } unsigned long dbg_snapshot_get_last_pc_paddr(void) { /* * Basically we want to save the pc value to non-cacheable region * if ESS is enabled. But we should also consider cases that are not so. */ if (dbg_snapshot_get_enable("header")) return ((unsigned long)dbg_snapshot_get_base_paddr() + DSS_OFFSET_CORE_LAST_PC); else return virt_to_phys((void *)dss_desc.hardlockup_core_pc); } unsigned long dbg_snapshot_get_last_pc(unsigned int cpu) { if (dbg_snapshot_get_enable("header")) return __raw_readq(dbg_snapshot_get_base_vaddr() + DSS_OFFSET_CORE_LAST_PC + cpu * 8); else return dss_desc.hardlockup_core_pc[cpu]; } unsigned long dbg_snapshot_get_spare_vaddr(unsigned int offset) { return (unsigned long)(dbg_snapshot_get_base_vaddr() + DSS_OFFSET_SPARE_BASE + offset); } unsigned long dbg_snapshot_get_spare_paddr(unsigned int offset) { unsigned long base_vaddr = 0; unsigned long base_paddr = (unsigned long)dbg_snapshot_get_base_paddr(); if (base_paddr) base_vaddr = (unsigned long)(base_paddr + DSS_OFFSET_SPARE_BASE + offset); return base_vaddr; } unsigned int dbg_snapshot_get_item_size(char* name) { unsigned long i; for (i = 0; i < dss_desc.log_cnt; i++) { if (!strncmp(dss_items[i].name, name, strlen(name))) return dss_items[i].entry.size; } return 0; } EXPORT_SYMBOL(dbg_snapshot_get_item_size); unsigned long dbg_snapshot_get_item_vaddr(char *name) { unsigned long i; for (i = 0; i < dss_desc.log_cnt; i++) { if (!strncmp(dss_items[i].name, name, strlen(name))) return dss_items[i].entry.vaddr; } return 0; } unsigned int dbg_snapshot_get_item_paddr(char* name) { unsigned long i; for (i = 0; i < dss_desc.log_cnt; i++) { if (!strncmp(dss_items[i].name, name, strlen(name))) return dss_items[i].entry.paddr; } return 0; } EXPORT_SYMBOL(dbg_snapshot_get_item_paddr); int dbg_snapshot_get_hardlockup(void) { return dss_desc.hardlockup_detected; } EXPORT_SYMBOL(dbg_snapshot_get_hardlockup); int dbg_snapshot_set_hardlockup(int val) { unsigned long flags; if (unlikely(!dss_base.enabled)) return 0; raw_spin_lock_irqsave(&dss_desc.ctrl_lock, flags); dss_desc.hardlockup_detected = val; raw_spin_unlock_irqrestore(&dss_desc.ctrl_lock, flags); return 0; } EXPORT_SYMBOL(dbg_snapshot_set_hardlockup); int dbg_snapshot_early_panic(void) { dss_soc_ops->soc_early_panic(NULL); return 0; } int dbg_snapshot_prepare_panic(void) { unsigned long cpu; if (unlikely(!dss_base.enabled)) return 0; /* * kick watchdog to prevent unexpected reset during panic sequence * and it prevents the hang during panic sequence by watchedog */ dss_soc_ops->soc_start_watchdog(NULL); dss_soc_ops->soc_prepare_panic_entry(NULL); /* Again disable log_kevents */ dbg_snapshot_set_enable("log_kevents", false); for_each_possible_cpu(cpu) { if (dss_soc_ops->soc_is_power_cpu((void *)cpu)) dbg_snapshot_set_core_power_stat(DSS_SIGN_ALIVE, cpu); else dbg_snapshot_set_core_power_stat(DSS_SIGN_DEAD, cpu); } dss_soc_ops->soc_prepare_panic_exit(NULL); return 0; } EXPORT_SYMBOL(dbg_snapshot_prepare_panic); int dbg_snapshot_post_panic(void) { if (dss_base.enabled) { dbg_snapshot_recall_hardlockup_core(); #ifdef CONFIG_DEBUG_SNAPSHOT_PMU dbg_snapshot_dump_sfr(); #endif dbg_snapshot_save_context(NULL); dbg_snapshot_print_panic_report(); dss_soc_ops->soc_post_panic_entry(NULL); #ifdef CONFIG_DEBUG_SNAPSHOT_PANIC_REBOOT if (!dss_desc.no_wdt_dev && !dbg_snapshot_get_sjtag_status()) { #ifdef CONFIG_DEBUG_SNAPSHOT_WATCHDOG_RESET if (dss_desc.hardlockup_detected || num_online_cpus() > 1) { /* for stall cpu */ dbg_snapshot_spin_func(); } #endif } #endif } #ifdef CONFIG_SEC_DEBUG sec_debug_post_panic_handler(); #endif dss_soc_ops->soc_post_panic_exit(NULL); /* for stall cpu when not enabling panic reboot */ dbg_snapshot_spin_func(); /* Never run this function */ pr_emerg("debug-snapshot: %s DO NOT RUN this function (CPU:%d)\n", __func__, raw_smp_processor_id()); return 0; } EXPORT_SYMBOL(dbg_snapshot_post_panic); int dbg_snapshot_dump_panic(char *str, size_t len) { if (unlikely(!dss_base.enabled) || !dbg_snapshot_get_enable("header")) return 0; /* This function is only one which runs in panic funcion */ if (str && len && len < SZ_1K) memcpy(dbg_snapshot_get_base_vaddr() + DSS_OFFSET_PANIC_STRING, str, len); return 0; } EXPORT_SYMBOL(dbg_snapshot_dump_panic); int dbg_snapshot_post_reboot(char *cmd) { int cpu; if (unlikely(!dss_base.enabled)) return 0; dss_soc_ops->soc_post_reboot_entry(NULL); dbg_snapshot_report_reason(DSS_SIGN_NORMAL_REBOOT); if (!cmd) dbg_snapshot_scratch_reg(DSS_SIGN_RESET); else if (strcmp((char *)cmd, "bootloader") && strcmp((char *)cmd, "ramdump")) dbg_snapshot_scratch_reg(DSS_SIGN_RESET); pr_emerg("debug-snapshot: normal reboot done\n"); dbg_snapshot_save_context(NULL); /* clear DSS_SIGN_PANIC when normal reboot */ for_each_possible_cpu(cpu) { dbg_snapshot_set_core_panic_stat(DSS_SIGN_RESET, cpu); } dss_soc_ops->soc_post_reboot_exit(NULL); return 0; } EXPORT_SYMBOL(dbg_snapshot_post_reboot); static int dbg_snapshot_reboot_handler(struct notifier_block *nb, unsigned long l, void *p) { if (unlikely(!dss_base.enabled)) return 0; pr_emerg("debug-snapshot: normal reboot starting\n"); #ifdef CONFIG_SEC_DEBUG sec_debug_reboot_handler(p); #endif return 0; } static int dbg_snapshot_panic_handler(struct notifier_block *nb, unsigned long l, void *buf) { dbg_snapshot_report_reason(DSS_SIGN_PANIC); if (unlikely(!dss_base.enabled)) return 0; #ifdef CONFIG_DEBUG_SNAPSHOT_PANIC_REBOOT local_irq_disable(); pr_emerg("debug-snapshot: panic - reboot[%s]\n", __func__); #else pr_emerg("debug-snapshot: panic - normal[%s]\n", __func__); #endif dbg_snapshot_dump_task_info(); pr_emerg("linux_banner: %s\n", linux_banner); #ifdef CONFIG_SEC_DEBUG sec_debug_panic_handler(buf, true); #endif return 0; } static struct notifier_block nb_reboot_block = { .notifier_call = dbg_snapshot_reboot_handler }; static struct notifier_block nb_panic_block = { .notifier_call = dbg_snapshot_panic_handler, }; void dbg_snapshot_panic_handler_safe(void) { char *cpu_num[SZ_16] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; char text[SZ_32] = "safe panic handler at cpu "; int cpu = raw_smp_processor_id(); size_t len; if (unlikely(!dss_base.enabled)) return; strncat(text, cpu_num[cpu], 1); len = strnlen(text, SZ_32); dbg_snapshot_report_reason(DSS_SIGN_SAFE_FAULT); dbg_snapshot_dump_panic(text, len); #ifdef CONFIG_SEC_DEBUG dss_soc_ops->soc_expire_watchdog((void *)_RET_IP_); #else dss_soc_ops->soc_expire_watchdog((void *)NULL); #endif } void dbg_snapshot_register_soc_ops(struct dbg_snapshot_helper_ops *ops) { if (ops) dss_soc_ops = ops; } void __init dbg_snapshot_init_helper(void) { register_reboot_notifier(&nb_reboot_block); atomic_notifier_chain_register(&panic_notifier_list, &nb_panic_block); dss_soc_ops = &dss_soc_dummy_ops; /* hardlockup_detector function should be called before secondary booting */ dbg_snapshot_soc_helper_init(); }