From: Bixuan Cui cuibixuan@huawei.com
ascend inclusion category: feature bugzilla: NA CVE: NA
---------------------------------------------
Itrace (Interrupt Tracing) is a lightweight interrupt tracing tool. It supports the following functions: * Ihandler(Interrupt Handler) tracer can trace and calculate the time consumed of the hardware irq function and list the name, number and duration of the interrupt. * Ihandler supports dynamic disable and threshold setting.
Signed-off-by: Bixuan Cui cuibixuan@huawei.com Reviewed-by: Ding Tianhong dingtianhong@huawei.com Reviewed-by: Hanjun Guo guohanjun@huawei.com Signed-off-by: Yang Yingliang yangyingliang@huawei.com --- include/linux/itrace.h | 47 ++++++++++ kernel/irq/handle.c | 3 + kernel/irq/proc.c | 100 +++++++++++++++++++++ kernel/trace/Kconfig | 22 +++++ kernel/trace/Makefile | 2 + kernel/trace/itrace_ihandler.c | 154 +++++++++++++++++++++++++++++++++ 6 files changed, 328 insertions(+) create mode 100644 include/linux/itrace.h create mode 100644 kernel/trace/itrace_ihandler.c
diff --git a/include/linux/itrace.h b/include/linux/itrace.h new file mode 100644 index 0000000000000..d7916581e03f2 --- /dev/null +++ b/include/linux/itrace.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright(c) 2021 Huawei Technologies Co., Ltd + * Author: Bixuan Cui cuibixuan@huawei.com + */ + +#ifndef __LINUX_ITRACE_H +#define __LINUX_ITRACE_H + +#define IRQ_NAME_LEN 20 +#define IHANDLER_INFO_NUM_MIN 1 +#define IHANDLER_INFO_NUM_MAX 30 +#define IHANDLER_INFO_CT_MAX 99999 +#define IHANDLER_THRESHOLD_MAX 10000000 +#define IHANDLER_OFF 0 +#define IHANDLER_ON 1 + +struct irq_handler_info { + int irq; + char name[IRQ_NAME_LEN]; + u64 t_max; + unsigned int ct; +}; + +struct Ihandler { + int front; + int num; + struct irq_handler_info info[IHANDLER_INFO_NUM_MAX]; +}; + +#ifdef CONFIG_ITRACE_IHANDLER +extern void itrace_ihandler_entry(void); +extern void itrace_ihandler_exit(int irq, const char *name); +extern void itrace_ihandler_set(u64 set); +extern void itrace_ihandler_get(struct Ihandler *ih, int cpu); +extern void itrace_ihandler_num_set(int set); +extern int itrace_ihandler_num_get(void); +#else +static inline void __maybe_unused itrace_ihandler_entry(void) +{ +}; +static inline void __maybe_unused itrace_ihandler_exit(int irq, const char *name) +{ +}; +#endif /* CONFIG_ITRACE_IHANDLER */ + +#endif /* __LINUX_ITRACE_H */ diff --git a/kernel/irq/handle.c b/kernel/irq/handle.c index 38554bc353755..95caa394df603 100644 --- a/kernel/irq/handle.c +++ b/kernel/irq/handle.c @@ -13,6 +13,7 @@ #include <linux/sched.h> #include <linux/interrupt.h> #include <linux/kernel_stat.h> +#include <linux/itrace.h>
#include <trace/events/irq.h>
@@ -146,7 +147,9 @@ irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags irqreturn_t res;
trace_irq_handler_entry(irq, action); + itrace_ihandler_entry(); res = action->handler(irq, action->dev_id); + itrace_ihandler_exit(irq, action->name); trace_irq_handler_exit(irq, action, res);
if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n", diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c index e8c655b7a4308..1d5b4b0154720 100644 --- a/kernel/irq/proc.c +++ b/kernel/irq/proc.c @@ -12,6 +12,7 @@ #include <linux/interrupt.h> #include <linux/kernel_stat.h> #include <linux/mutex.h> +#include <linux/itrace.h>
#include "internals.h"
@@ -299,6 +300,99 @@ static int irq_node_proc_show(struct seq_file *m, void *v) } #endif
+#ifdef CONFIG_ITRACE_IHANDLER +static int itrace_ihandler_num_show(struct seq_file *m, void *v) +{ + seq_printf(m, "%d\n", itrace_ihandler_num_get()); + + return 0; +} + +static ssize_t itrace_ihandler_num_write(struct file *file, + const char __user *buffer, size_t count, loff_t *ppos) +{ + int ret; + int num; + + ret = kstrtoint_from_user(buffer, count, 10, &num); + if (ret) + return ret; + + if (num > IHANDLER_INFO_NUM_MAX || num < IHANDLER_INFO_NUM_MIN) + return -EINVAL; + + itrace_ihandler_num_set(num); + + return count; +} + +static int itrace_ihandler_num_open(struct inode *inode, struct file *file) +{ + return single_open(file, itrace_ihandler_num_show, PDE_DATA(inode)); +} + +static const struct file_operations itrace_ihandler_num_proc_fops = { + .open = itrace_ihandler_num_open, + .read = seq_read, + .release = single_release, + .write = itrace_ihandler_num_write, +}; + +static int itrace_ihandler_show(struct seq_file *m, void *v) +{ + unsigned int i, j; + struct Ihandler ihandler; + int online_cpus = num_online_cpus(); + + for (i = 0; i < online_cpus; i++) { + itrace_ihandler_get(&ihandler, i); + + /* print nothing while num is 0 */ + if (ihandler.num == 0) + return 0; + + seq_printf(m, "[irq_handler CPU%d]:\n", i); + for (j = 0; j < ihandler.num; j++) + seq_printf(m, " irq:%d name:%s max_time:%llu(us) count:%u\n", + ihandler.info[j].irq, ihandler.info[j].name, + ihandler.info[j].t_max / NSEC_PER_USEC, + ihandler.info[j].ct); + } + + return 0; +} + +static ssize_t itrace_ihandler_write(struct file *file, + const char __user *buffer, size_t count, loff_t *ppos) +{ + int ret; + u64 val; + + ret = kstrtoull_from_user(buffer, count, 10, &val); + if (ret) + return ret; + + if (val > IHANDLER_THRESHOLD_MAX) + return -EINVAL; + + itrace_ihandler_set(val); + + return count; +} + +static int itrace_ihandler_open(struct inode *inode, struct file *file) +{ + return single_open(file, itrace_ihandler_show, PDE_DATA(inode)); +} + +static const struct file_operations itrace_ihandler_proc_fops = { + .open = itrace_ihandler_open, + .read = seq_read, + .release = single_release, + .write = itrace_ihandler_write, +}; +#endif + static int irq_spurious_proc_show(struct seq_file *m, void *v) { struct irq_desc *desc = irq_to_desc((long) m->private); @@ -458,6 +552,12 @@ void init_irq_proc(void) */ for_each_irq_desc(irq, desc) register_irq_proc(irq, desc); +#ifdef CONFIG_ITRACE_IHANDLER + proc_create("irq/itrace_ihandler", 0644, NULL, + &itrace_ihandler_proc_fops); + proc_create("irq/itrace_ihandler_num", 0644, NULL, + &itrace_ihandler_num_proc_fops); +#endif }
#ifdef CONFIG_GENERIC_IRQ_SHOW diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig index e656d1e232da4..5dd2e37f1aae0 100644 --- a/kernel/trace/Kconfig +++ b/kernel/trace/Kconfig @@ -788,5 +788,27 @@ config GCOV_PROFILE_FTRACE
endif # FTRACE
+menuconfig ITRACE + bool "Itrace Support" + help + Itrace(Interrupt Tracing) is a lightweight interrupt tracing + tool. + It can trace the areas that interrupt handler function and + disable interrupts. + +if ITRACE + +config ITRACE_IHANDLER + bool "Support for tracing the interrupt handler function" + default n + help + Ihandler(Interrupt Handler) tracer can trace and calculate + the time consumed of the hardware irq function and list the + name, number and duration of the interrupt. + + Support dynamic disable and threshold setting. + +endif #ITRACE + endif # TRACING_SUPPORT
diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile index f81dadbc7c4ac..231ce3c22ea4e 100644 --- a/kernel/trace/Makefile +++ b/kernel/trace/Makefile @@ -84,3 +84,5 @@ obj-$(CONFIG_UPROBE_EVENTS) += trace_uprobe.o obj-$(CONFIG_TRACEPOINT_BENCHMARK) += trace_benchmark.o
libftrace-y := ftrace.o + +obj-$(CONFIG_ITRACE_IHANDLER) += itrace_ihandler.o diff --git a/kernel/trace/itrace_ihandler.c b/kernel/trace/itrace_ihandler.c new file mode 100644 index 0000000000000..906e2e0b1a0f8 --- /dev/null +++ b/kernel/trace/itrace_ihandler.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(c) 2021 Huawei Technologies Co., Ltd + * Author: Bixuan Cui cuibixuan@huawei.com + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/sched/clock.h> +#include <linux/itrace.h> +#include <linux/time64.h> + +static u64 threshold_value; +static int ihandler_info_num = 5; +static int ihandler_enable; + +static DEFINE_PER_CPU(struct Ihandler, ihandler); +static DEFINE_PER_CPU(u64, ihandler_entry); + +/* Per-cpu variable to prevent redundant calls when already entry handler */ +static DEFINE_PER_CPU(int, ihandle_flag); + +void itrace_ihandler_entry(void) +{ + if (ihandler_enable == IHANDLER_OFF) + return; + + if (!this_cpu_read(ihandle_flag)) { + this_cpu_write(ihandle_flag, 1); + + this_cpu_write(ihandler_entry, sched_clock()); + } +} + +static void itrace_insert_diff(int cpu, u64 diff, int irq, const char *name) +{ + int j, index, find = 0; + char *ihandler_name; + unsigned int ct; + u64 t_max; + int front = per_cpu(ihandler, cpu).front; + int num = per_cpu(ihandler, cpu).num; + + for (j = 0; j < num; j++) { + index = (front + j) % num; + if (per_cpu(ihandler, cpu).info[index].irq == irq) { + find = 1; + break; + } + } + + if (find != 0) { + t_max = per_cpu(ihandler, cpu).info[index].t_max; + ct = per_cpu(ihandler, cpu).info[index].ct + 1; + + per_cpu(ihandler, cpu).info[index].t_max = diff > t_max ? + diff : t_max; + per_cpu(ihandler, cpu).info[index].ct = ct > + IHANDLER_INFO_CT_MAX ? IHANDLER_INFO_CT_MAX : ct; + } else { + num = num + 1; + ihandler_name = per_cpu(ihandler, cpu).info[front].name; + + per_cpu(ihandler, cpu).info[front].irq = irq; + strncpy(ihandler_name, name, IRQ_NAME_LEN - 1); + ihandler_name[IRQ_NAME_LEN - 1] = '\0'; + per_cpu(ihandler, cpu).info[front].t_max = diff; + per_cpu(ihandler, cpu).front = (front + 1) % + ihandler_info_num; + per_cpu(ihandler, cpu).num = num > ihandler_info_num ? + ihandler_info_num : num; + per_cpu(ihandler, cpu).info[front].ct += 1; + + } +} + +void itrace_ihandler_exit(int irq, const char *name) +{ + u64 diff; + int cpu; + + if (ihandler_enable == IHANDLER_OFF) + return; + + if (this_cpu_read(ihandle_flag)) { + cpu = smp_processor_id(); + diff = sched_clock() - per_cpu(ihandler_entry, cpu); + + if (diff > threshold_value) + itrace_insert_diff(cpu, diff, irq, name); + + this_cpu_write(ihandle_flag, 0); + } +} + +void itrace_ihandler_set(u64 set) +{ + unsigned int i, j; + int online_cpus = num_online_cpus(); + + /* disable tracer and update threshold_value first */ + ihandler_enable = IHANDLER_OFF; + threshold_value = set * NSEC_PER_USEC; + + for (i = 0; i < online_cpus; i++) { + + per_cpu(ihandler, i).front = 0; + per_cpu(ihandler, i).num = 0; + for (j = 0; j < IHANDLER_INFO_NUM_MAX; j++) { + per_cpu(ihandler, i).info[j].irq = 0; + per_cpu(ihandler, i).info[j].name[0] = '\0'; + per_cpu(ihandler, i).info[j].t_max = 0; + per_cpu(ihandler, i).info[j].ct = 0; + } + + /* enable tracer */ + if (set != 0) { + per_cpu(ihandle_flag, i) = 0; + ihandler_enable = IHANDLER_ON; + } + } +} + +void itrace_ihandler_get(struct Ihandler *ih, int cpu) +{ + unsigned int j; + char *ihandler_name; + + for (j = 0; j < ihandler_info_num; j++) { + ihandler_name = per_cpu(ihandler, cpu).info[j].name; + + ih->num = per_cpu(ihandler, cpu).num; + ih->info[j].irq = per_cpu(ihandler, cpu).info[j].irq; + strncpy(ih->info[j].name, ihandler_name, IRQ_NAME_LEN); + ih->info[j].t_max = per_cpu(ihandler, cpu).info[j].t_max; + ih->info[j].ct = per_cpu(ihandler, cpu).info[j].ct; + } +} + +void itrace_ihandler_num_set(int set) +{ + ihandler_info_num = set; + + /* clear ihandler of per cpu while reset info_num */ + itrace_ihandler_set(threshold_value / NSEC_PER_USEC); +} + +int itrace_ihandler_num_get(void) +{ + return ihandler_info_num; +}