| /* |
| * drivers/amlogic/debug/debug_lockup.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/stacktrace.h> |
| #include <linux/export.h> |
| #include <linux/types.h> |
| #include <linux/smp.h> |
| #include <linux/irqflags.h> |
| #include <linux/sched.h> |
| #include "../kernel/sched/sched.h" |
| #include <linux/moduleparam.h> |
| #include <linux/debugfs.h> |
| #include <linux/module.h> |
| #include <linux/uaccess.h> |
| #include <linux/amlogic/debug_ftrace_ramoops.h> |
| |
| /*isr trace*/ |
| #define ns2ms (1000 * 1000) |
| #define LONG_ISR (500 * ns2ms) |
| #define LONG_SIRQ (500 * ns2ms) |
| #define CHK_WINDOW (1000 * ns2ms) |
| #define IRQ_CNT 256 |
| #define CCCNT_WARN 15000 |
| #define CPU 8 |
| static unsigned long long t_base[IRQ_CNT]; |
| static unsigned long long t_isr[IRQ_CNT]; |
| static unsigned long long t_total[IRQ_CNT]; |
| static unsigned int cnt_total[IRQ_CNT]; |
| static void *cpu_action[CPU]; |
| static int cpu_irq[CPU] = {0}; |
| static void *cpu_sirq[CPU] = {NULL}; |
| |
| /*irq disable trace*/ |
| #define LONG_IRQDIS (1000 * 1000000)/*1000 ms*/ |
| #define OUT_WIN (500 * 1000000) /*500 ms*/ |
| #define LONG_IDLE (5000000000) /*5 sec*/ |
| #define ENTRY 10 |
| static unsigned long long t_i_d[CPU]; |
| static int irq_flg; |
| static struct stack_trace irq_trace[CPU]; |
| static unsigned long t_entrys[CPU][ENTRY]; |
| static unsigned long long t_idle[CPU] = { 0 }; |
| static unsigned long long t_d_out; |
| |
| static unsigned long isr_thr = LONG_ISR; |
| core_param(isr_thr, isr_thr, ulong, 0644); |
| static unsigned long irq_dis_thr = LONG_IRQDIS; |
| core_param(irq_dis_thr, irq_dis_thr, ulong, 0644); |
| static unsigned long sirq_thr = LONG_SIRQ; |
| core_param(sirq_thr, sirq_thr, ulong, 0644); |
| static int irq_check_en; |
| core_param(irq_check_en, irq_check_en, int, 0644); |
| static int isr_check_en = 1; |
| core_param(isr_check_en, isr_check_en, int, 0644); |
| static unsigned long out_thr = OUT_WIN; |
| core_param(out_thr, out_thr, ulong, 0644); |
| |
| void notrace isr_in_hook(unsigned int cpu, unsigned long long *tin, |
| unsigned int irq, void *act) |
| { |
| if (irq >= IRQ_CNT || !isr_check_en || oops_in_progress) |
| return; |
| |
| pstore_ftrace_io_tag((unsigned long)irq, (unsigned long)act); |
| |
| cpu_irq[cpu] = irq; |
| cpu_action[cpu] = act; |
| *tin = sched_clock(); |
| if (*tin >= CHK_WINDOW + t_base[irq]) { |
| t_base[irq] = *tin; |
| t_isr[irq] = 0; |
| t_total[irq] = 0; |
| cnt_total[irq] = 0; |
| } |
| } |
| |
| void notrace isr_out_hook(unsigned int cpu, unsigned long long tin, |
| unsigned int irq) |
| { |
| unsigned long long tout; |
| unsigned long long ratio = 0; |
| unsigned long long t_diff; |
| unsigned long long t_isr_tmp; |
| unsigned long long t_total_tmp; |
| |
| if (!isr_check_en || oops_in_progress) |
| return; |
| if (irq >= IRQ_CNT || cpu_irq[cpu] <= 0) |
| return; |
| cpu_irq[cpu] = 0; |
| tout = sched_clock(); |
| t_isr[irq] += (tout > tin) ? (tout-tin) : 0; |
| t_total[irq] = (tout-t_base[irq]); |
| cnt_total[irq]++; |
| |
| pstore_ftrace_io_tag(0xFFF1, (unsigned long)irq); |
| |
| if (tout > isr_thr + tin) { |
| t_diff = tout - tin; |
| do_div(t_diff, ns2ms); |
| pr_err("ISR_Long___ERR. irq:%d tout-tin:%llu ms\n", |
| irq, t_diff); |
| } |
| |
| if (t_total[irq] < CHK_WINDOW) |
| return; |
| |
| if (t_isr[irq] * 100 >= 35 * t_total[irq] || cnt_total[irq] > CCCNT_WARN) { |
| t_isr_tmp = t_isr[irq]; |
| do_div(t_isr_tmp, ns2ms); |
| t_total_tmp = t_total[irq]; |
| do_div(t_total_tmp, ns2ms); |
| ratio = t_isr_tmp * 100; |
| do_div(ratio, t_total_tmp); |
| pr_err("IRQRatio___ERR.irq:%d ratio:%llu\n", irq, ratio); |
| pr_err("t_isr:%llu t_total:%llu, cnt:%d, %pf t:%lld\n", |
| t_isr_tmp, t_total_tmp, cnt_total[irq], cpu_action[cpu], tout - tin); |
| } |
| t_base[irq] = sched_clock(); |
| t_isr[irq] = 0; |
| t_total[irq] = 0; |
| cnt_total[irq] = 0; |
| cpu_action[cpu] = NULL; |
| } |
| |
| void notrace sirq_in_hook(unsigned int cpu, unsigned long long *tin, void *p) |
| { |
| cpu_sirq[cpu] = p; |
| *tin = sched_clock(); |
| pstore_ftrace_io_tag(0xFFE0, (unsigned long)p); |
| } |
| void notrace sirq_out_hook(unsigned int cpu, unsigned long long tin, void *p) |
| { |
| unsigned long long tout = sched_clock(); |
| unsigned long long t_diff; |
| |
| pstore_ftrace_io_tag(0xFFE1, (unsigned long)p); |
| |
| if (cpu_sirq[cpu] && tin && (tout > tin + sirq_thr) && |
| !oops_in_progress) { |
| t_diff = tout - tin; |
| do_div(t_diff, ns2ms); |
| pr_err("SIRQLong___ERR. sirq:%p tout-tin:%llu ms\n", |
| p, t_diff); |
| } |
| cpu_sirq[cpu] = NULL; |
| } |
| |
| void notrace irq_trace_start(unsigned long flags) |
| { |
| unsigned int cpu; |
| int softirq = 0; |
| |
| if (!irq_flg || !irq_check_en || oops_in_progress) |
| return; |
| |
| if (arch_irqs_disabled_flags(flags)) |
| return; |
| |
| cpu = get_cpu(); |
| put_cpu(); |
| softirq = task_thread_info(current)->preempt_count & SOFTIRQ_MASK; |
| if ((t_idle[cpu] && !softirq) || t_i_d[cpu] || cpu_is_offline(cpu) || |
| (softirq_count() && !cpu_sirq[cpu])) |
| return; |
| |
| memset(&irq_trace[cpu], 0, sizeof(irq_trace[cpu])); |
| memset(&t_entrys[cpu][0], 0, sizeof(t_entrys[cpu][0])*ENTRY); |
| irq_trace[cpu].entries = &t_entrys[cpu][0]; |
| irq_trace[cpu].max_entries = ENTRY; |
| irq_trace[cpu].skip = 2; |
| irq_trace[cpu].nr_entries = 0; |
| t_i_d[cpu] = sched_clock(); |
| |
| save_stack_trace(&irq_trace[cpu]); |
| } |
| EXPORT_SYMBOL(irq_trace_start); |
| |
| void notrace irq_trace_stop(unsigned long flag) |
| { |
| unsigned long long t_i_e, t; |
| unsigned int cpu; |
| int softirq = 0; |
| static int out_cnt; |
| |
| if (!irq_check_en || !irq_flg || oops_in_progress) |
| return; |
| |
| if (arch_irqs_disabled_flags(flag)) |
| return; |
| |
| cpu = get_cpu(); |
| put_cpu(); |
| if (!t_i_d[cpu] || |
| !arch_irqs_disabled_flags(arch_local_save_flags())) { |
| t_i_d[cpu] = 0; |
| return; |
| } |
| |
| t_i_e = sched_clock(); |
| if (t_i_e < t_i_d[cpu]) { |
| t_i_d[cpu] = 0; |
| return; |
| } |
| |
| t = (t_i_e - t_i_d[cpu]); |
| softirq = task_thread_info(current)->preempt_count & SOFTIRQ_MASK; |
| |
| if (!(t_idle[cpu] && !softirq) && (t > irq_dis_thr) && t_i_d[cpu] && |
| !(softirq_count() && !cpu_sirq[cpu])) { |
| out_cnt++; |
| if (t_i_e >= t_d_out + out_thr) { |
| t_d_out = t_i_e; |
| do_div(t, ns2ms); |
| do_div(t_i_e, ns2ms); |
| do_div(t_i_d[cpu], ns2ms); |
| pr_err("\n\nDisIRQ___ERR:%llu ms <%llu %llu> %d:\n", |
| t, t_i_e, t_i_d[cpu], out_cnt); |
| print_stack_trace(&irq_trace[cpu], 0); |
| dump_stack(); |
| } |
| } |
| t_i_d[cpu] = 0; |
| } |
| EXPORT_SYMBOL(irq_trace_stop); |
| |
| void __attribute__((weak)) lockup_hook(int cpu) |
| { |
| } |
| |
| void notrace __arch_cpu_idle_enter(void) |
| { |
| int cpu; |
| |
| if ((!irq_check_en || !irq_flg) && !isr_check_en) |
| return; |
| cpu = get_cpu(); |
| put_cpu(); |
| t_idle[cpu] = local_clock(); |
| } |
| void notrace __arch_cpu_idle_exit(void) |
| { |
| int cpu; |
| |
| if ((!irq_check_en || !irq_flg) && !isr_check_en) |
| return; |
| cpu = get_cpu(); |
| put_cpu(); |
| t_idle[cpu] = 0; |
| } |
| |
| void pr_lockup_info(int c) |
| { |
| int cpu; |
| int virq = irq_check_en; |
| int visr = isr_check_en; |
| unsigned long long t_idle_diff; |
| unsigned long long t_idle_tmp; |
| unsigned long long t_i_diff; |
| unsigned long long t_i_tmp; |
| |
| irq_flg = 0; |
| irq_check_en = 0; |
| isr_check_en = 0; |
| console_loglevel = 7; |
| pr_err("\n\n\nHARDLOCKUP____ERR.CPU[%d] <irqen:%d isren%d>START\n", |
| c, virq, visr); |
| for_each_online_cpu(cpu) { |
| unsigned long long t_cur = sched_clock(); |
| struct task_struct *p = (cpu_rq(cpu)->curr); |
| int preempt = task_thread_info(p)->preempt_count; |
| |
| pr_err("\ndump_cpu[%d] irq:%3d preempt:%x %s\n", |
| cpu, cpu_irq[cpu], preempt, p->comm); |
| pr_err("IRQ %pf, %x\n", cpu_action[cpu], |
| (unsigned int)(preempt & HARDIRQ_MASK)); |
| pr_err("SotIRQ %pf, %x\n", cpu_sirq[cpu], |
| (unsigned int)(preempt & SOFTIRQ_MASK)); |
| |
| if (t_i_d[cpu]) { |
| t_i_diff = t_cur-t_i_d[cpu]; |
| do_div(t_i_diff, ns2ms); |
| t_i_tmp = t_i_d[cpu]; |
| do_div(t_i_tmp, ns2ms); |
| pr_err("IRQ____ERR[%d]. <%llu %llu>.\n", |
| cpu, t_i_tmp, t_i_diff); |
| print_stack_trace(&irq_trace[cpu], 0); |
| } |
| if (t_idle[cpu] && (t_idle[cpu] > LONG_IDLE + t_cur)) { |
| t_idle_diff = t_cur - t_idle[cpu]; |
| do_div(t_idle_diff, ns2ms); |
| do_div(t_cur, ns2ms); |
| t_idle_tmp = t_idle[cpu]; |
| do_div(t_idle_tmp, ns2ms); |
| pr_err("CPU[%d] IdleLong____ERR:%llu ms <%llu %llu>\n", |
| cpu, t_idle_diff, t_cur, t_idle_tmp); |
| } |
| dump_cpu_task(cpu); |
| lockup_hook(cpu); |
| } |
| pr_err("\nHARDLOCKUP____ERR.END\n\n"); |
| } |
| |
| |
| |
| static struct dentry *debug_lockup; |
| #define debug_fs(x) \ |
| static ssize_t x##_write(struct file *file, const char __user *userbuf,\ |
| size_t count, loff_t *ppos) \ |
| { \ |
| char buf[20]; \ |
| unsigned long val; \ |
| int ret; \ |
| count = min_t(size_t, count, (sizeof(buf)-1)); \ |
| if (copy_from_user(buf, userbuf, count)) \ |
| return -EFAULT; \ |
| buf[count] = 0; \ |
| ret = kstrtoul(buf, 0, &val); \ |
| if (ret) \ |
| return -1; \ |
| x = (typeof(x))val; \ |
| if (irq_check_en || isr_check_en) \ |
| aml_wdt_disable_dbg(); \ |
| pr_info("%s:%ld\n", __func__, (unsigned long)x); \ |
| return count; \ |
| } \ |
| static ssize_t x##_read(struct file *file, char __user *userbuf, \ |
| size_t count, loff_t *ppos) \ |
| { \ |
| char buf[20]; \ |
| unsigned long val; \ |
| ssize_t len; \ |
| val = (unsigned long)x; \ |
| len = snprintf(buf, sizeof(buf), "%ld\n", val); \ |
| pr_info("%s:%ld\n", __func__, val); \ |
| return simple_read_from_buffer(userbuf, count, ppos, buf, len);\ |
| } \ |
| static const struct file_operations x##_debug_ops = { \ |
| .open = simple_open, \ |
| .read = x##_read, \ |
| .write = x##_write, \ |
| } |
| debug_fs(isr_thr); |
| debug_fs(irq_dis_thr); |
| debug_fs(sirq_thr); |
| debug_fs(irq_check_en); |
| debug_fs(isr_check_en); |
| debug_fs(out_thr); |
| |
| static int __init debug_lockup_init(void) |
| { |
| debug_lockup = debugfs_create_dir("lockup", NULL); |
| if (IS_ERR_OR_NULL(debug_lockup)) { |
| pr_warn("failed to create debug_lockup\n"); |
| debug_lockup = NULL; |
| return -1; |
| } |
| debugfs_create_file("isr_thr", S_IFREG | 0664, |
| debug_lockup, NULL, &isr_thr_debug_ops); |
| debugfs_create_file("irq_dis_thr", S_IFREG | 0664, |
| debug_lockup, NULL, &irq_dis_thr_debug_ops); |
| debugfs_create_file("sirq_thr", S_IFREG | 0664, |
| debug_lockup, NULL, &sirq_thr_debug_ops); |
| debugfs_create_file("out_thr", S_IFREG | 0664, |
| debug_lockup, NULL, &out_thr_debug_ops); |
| debugfs_create_file("isr_check_en", S_IFREG | 0664, |
| debug_lockup, NULL, &isr_check_en_debug_ops); |
| debugfs_create_file("irq_check_en", S_IFREG | 0664, |
| debug_lockup, NULL, &irq_check_en_debug_ops); |
| if (irq_check_en || isr_check_en) |
| aml_wdt_disable_dbg(); |
| irq_flg = 1; |
| return 0; |
| } |
| late_initcall(debug_lockup_init); |
| |
| MODULE_DESCRIPTION("Amlogic debug lockup module"); |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Jianxin Pan <jianxin.pan@amlogic.com>"); |