From: Bixuan Cui cuibixuan@huawei.com
ascend inclusion category: feature bugzilla: NA CVE: NA
---------------------------------------------
Add Irqsoff tracer into itrace to trace the areas that disable interrupts Irqsoff 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/irqflags.h | 46 +++++++++++- include/linux/itrace.h | 31 ++++++++ kernel/irq/proc.c | 98 +++++++++++++++++++++++++ kernel/trace/Kconfig | 11 +++ kernel/trace/Makefile | 1 + kernel/trace/itrace_irqsoff.c | 132 ++++++++++++++++++++++++++++++++++ 6 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 kernel/trace/itrace_irqsoff.c
diff --git a/include/linux/irqflags.h b/include/linux/irqflags.h index 21619c92c3770..18f674deeb726 100644 --- a/include/linux/irqflags.h +++ b/include/linux/irqflags.h @@ -14,6 +14,7 @@
#include <linux/typecheck.h> #include <asm/irqflags.h> +#include <linux/itrace.h>
/* Currently trace_softirqs_on/off is used only by lockdep */ #ifdef CONFIG_PROVE_LOCKING @@ -69,7 +70,11 @@ do { \ extern void stop_critical_timings(void); extern void start_critical_timings(void); #else +#ifdef CONFIG_ITRACE_IRQSOFF +# define stop_critical_timings() itrace_hardirqs_ignore() +#else # define stop_critical_timings() do { } while (0) +#endif # define start_critical_timings() do { } while (0) #endif
@@ -135,7 +140,44 @@ do { \ } while (0)
-#else /* !CONFIG_TRACE_IRQFLAGS */ +#elif defined(CONFIG_ITRACE_IRQSOFF) /* CONFIG_ITRACE_IRQSOFF */ + +#define local_irq_enable() \ + do { \ + itrace_hardirqs_on(); \ + raw_local_irq_enable(); \ + } while (0) + +#define local_irq_disable() \ + do { \ + raw_local_irq_disable(); \ + itrace_hardirqs_off(); \ + } while (0) + +#define local_irq_save(flags) \ + do { \ + raw_local_irq_save(flags); \ + itrace_hardirqs_off(); \ + } while (0) + +#define local_irq_restore(flags) \ + do { \ + if (raw_irqs_disabled_flags(flags)) { \ + raw_local_irq_restore(flags); \ + itrace_hardirqs_off(); \ + } else { \ + itrace_hardirqs_on(); \ + raw_local_irq_restore(flags); \ + } \ + } while (0) + +#define safe_halt() \ + do { \ + itrace_hardirqs_on(); \ + raw_safe_halt(); \ + } while (0) + +#else
#define local_irq_enable() do { raw_local_irq_enable(); } while (0) #define local_irq_disable() do { raw_local_irq_disable(); } while (0) @@ -146,7 +188,7 @@ do { \ #define local_irq_restore(flags) do { raw_local_irq_restore(flags); } while (0) #define safe_halt() do { raw_safe_halt(); } while (0)
-#endif /* CONFIG_TRACE_IRQFLAGS */ +#endif /* CONFIG_TRACE_IRQFLAGS && CONFIG_ITRACE_IRQFLAGS */
#define local_save_flags(flags) raw_local_save_flags(flags)
diff --git a/include/linux/itrace.h b/include/linux/itrace.h index d7916581e03f2..aba6396857b38 100644 --- a/include/linux/itrace.h +++ b/include/linux/itrace.h @@ -15,6 +15,13 @@ #define IHANDLER_OFF 0 #define IHANDLER_ON 1
+#define CALLER_FUNC_LEN 50 +#define IRQSOFF_INFO_NUM_MIN 1 +#define IRQSOFF_INFO_NUM_MAX 30 +#define IRQSOFF_THRESHOLD_MAX 10000000 +#define IRQSOFF_OFF 0 +#define IRQSOFF_ON 1 + struct irq_handler_info { int irq; char name[IRQ_NAME_LEN]; @@ -28,6 +35,17 @@ struct Ihandler { struct irq_handler_info info[IHANDLER_INFO_NUM_MAX]; };
+struct irqsoff_info { + u64 t_max; + char caller[CALLER_FUNC_LEN]; +}; + +struct Irqsoff { + int front; + int num; + struct irqsoff_info info[IRQSOFF_INFO_NUM_MAX]; +}; + #ifdef CONFIG_ITRACE_IHANDLER extern void itrace_ihandler_entry(void); extern void itrace_ihandler_exit(int irq, const char *name); @@ -44,4 +62,17 @@ static inline void __maybe_unused itrace_ihandler_exit(int irq, const char *name }; #endif /* CONFIG_ITRACE_IHANDLER */
+#ifdef CONFIG_ITRACE_IRQSOFF +extern void itrace_hardirqs_on(void); +extern void itrace_hardirqs_off(void); +extern void itrace_hardirqs_ignore(void); +extern void itrace_irqsoff_set(u64 set); +extern void itrace_irqsoff_get(struct Irqsoff *is, int cpu); +extern void itrace_irqsoff_num_set(int set); +extern int itrace_irqsoff_num_get(void); +#else +# define itrace_hardirqs_on() do { } while (0) +# define itrace_hardirqs_on() do { } while (0) +#endif /* CONFIG_ITRACE_IRQSOFF */ + #endif /* __LINUX_ITRACE_H */ diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c index 1d5b4b0154720..1d50b5b855d21 100644 --- a/kernel/irq/proc.c +++ b/kernel/irq/proc.c @@ -393,6 +393,98 @@ static const struct file_operations itrace_ihandler_proc_fops = { }; #endif
+#ifdef CONFIG_ITRACE_IRQSOFF +static int itrace_irqsoff_num_show(struct seq_file *m, void *v) +{ + seq_printf(m, "%d\n", itrace_irqsoff_num_get()); + + return 0; +} + +static ssize_t itrace_irqsoff_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 > IRQSOFF_INFO_NUM_MAX || num < IRQSOFF_INFO_NUM_MIN) + return -EINVAL; + + itrace_irqsoff_num_set(num); + + return count; +} + +static int itrace_irqsoff_num_open(struct inode *inode, struct file *file) +{ + return single_open(file, itrace_irqsoff_num_show, PDE_DATA(inode)); +} + +static const struct file_operations itrace_irqsoff_num_proc_fops = { + .open = itrace_irqsoff_num_open, + .read = seq_read, + .release = single_release, + .write = itrace_irqsoff_num_write, +}; + +static int itrace_irqsoff_show(struct seq_file *m, void *v) +{ + unsigned int i, j; + int online_cpus = num_online_cpus(); + struct Irqsoff irqsoff; + + for (i = 0; i < online_cpus; i++) { + itrace_irqsoff_get(&irqsoff, i); + + /* print nothing while num is 0 */ + if (irqsoff.num == 0) + continue; + + seq_printf(m, "[irqsoff CPU%d]:\n", i); + for (j = 0; j < irqsoff.num; j++) + seq_printf(m, " max_time:%llu(us) caller:%s\n", + irqsoff.info[j].t_max / NSEC_PER_USEC, + irqsoff.info[j].caller); + } + + return 0; +} + +static ssize_t itrace_irqsoff_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 > IRQSOFF_THRESHOLD_MAX) + return -EINVAL; + + itrace_irqsoff_set(val); + + return count; +} + +static int itrace_irqsoff_open(struct inode *inode, struct file *file) +{ + return single_open(file, itrace_irqsoff_show, PDE_DATA(inode)); +} + +static const struct file_operations itrace_irqsoff_proc_fops = { + .open = itrace_irqsoff_open, + .read = seq_read, + .release = single_release, + .write = itrace_irqsoff_write, +}; +#endif + static int irq_spurious_proc_show(struct seq_file *m, void *v) { struct irq_desc *desc = irq_to_desc((long) m->private); @@ -558,6 +650,12 @@ void init_irq_proc(void) proc_create("irq/itrace_ihandler_num", 0644, NULL, &itrace_ihandler_num_proc_fops); #endif +#ifdef CONFIG_ITRACE_IRQSOFF + proc_create("irq/itrace_irqsoff", 0644, NULL, + &itrace_irqsoff_proc_fops); + proc_create("irq/itrace_irqsoff_num", 0644, NULL, + &itrace_irqsoff_num_proc_fops); +#endif }
#ifdef CONFIG_GENERIC_IRQ_SHOW diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig index 5dd2e37f1aae0..36d4ded6ae74b 100644 --- a/kernel/trace/Kconfig +++ b/kernel/trace/Kconfig @@ -808,6 +808,17 @@ config ITRACE_IHANDLER
Support dynamic disable and threshold setting.
+config ITRACE_IRQSOFF + bool "Support for tracing the irqsoff" + depends on !TRACE_IRQFLAGS && !IRQSOFF_TRACER && !PREEMPT_TRACER + default n + help + Irqsoff tracer is a lightweight tracing tool. It can trace + the areas that disable interrupts and saves the trace with + the latency. + + Support dynamic disable and threshold setting. + endif #ITRACE
endif # TRACING_SUPPORT diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile index 231ce3c22ea4e..c0e8ed93adf28 100644 --- a/kernel/trace/Makefile +++ b/kernel/trace/Makefile @@ -86,3 +86,4 @@ obj-$(CONFIG_TRACEPOINT_BENCHMARK) += trace_benchmark.o libftrace-y := ftrace.o
obj-$(CONFIG_ITRACE_IHANDLER) += itrace_ihandler.o +obj-$(CONFIG_ITRACE_IRQSOFF) += itrace_irqsoff.o diff --git a/kernel/trace/itrace_irqsoff.c b/kernel/trace/itrace_irqsoff.c new file mode 100644 index 0000000000000..15444755de712 --- /dev/null +++ b/kernel/trace/itrace_irqsoff.c @@ -0,0 +1,132 @@ +// 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> + +static u64 threshold_value; +static int irqsoff_info_num = 3; +static int irqsoff_enable; + +static DEFINE_PER_CPU(struct Irqsoff, irqsoff); +static DEFINE_PER_CPU(u64, irqsoff_off); + +/* Per-cpu variable to prevent redundant calls when IRQs already off */ +static DEFINE_PER_CPU(u64, irqsoff_flag); + +void itrace_hardirqs_on(void) +{ + u64 diff; + int front, cpu, num; + char *caller; + + if (irqsoff_enable == IRQSOFF_OFF) + return; + + if (this_cpu_read(irqsoff_flag)) { + cpu = smp_processor_id(); + diff = sched_clock() - per_cpu(irqsoff_off, cpu); + + if (diff > threshold_value) { + front = per_cpu(irqsoff, cpu).front; + num = per_cpu(irqsoff, cpu).num + 1; + caller = per_cpu(irqsoff, cpu).info[front].caller; + + per_cpu(irqsoff, cpu).info[front].t_max = diff; + snprintf(caller, CALLER_FUNC_LEN, "%pS", + __builtin_return_address(0)); + + per_cpu(irqsoff, cpu).front = (front + 1) % + irqsoff_info_num; + per_cpu(irqsoff, cpu).num = num > irqsoff_info_num ? + irqsoff_info_num : num; + } + + this_cpu_write(irqsoff_flag, 0); + } +} +EXPORT_SYMBOL(itrace_hardirqs_on); + +void itrace_hardirqs_off(void) +{ + if (irqsoff_enable == IRQSOFF_OFF) + return; + + if (!this_cpu_read(irqsoff_flag)) { + this_cpu_write(irqsoff_flag, 1); + + this_cpu_write(irqsoff_off, sched_clock()); + } +} +EXPORT_SYMBOL(itrace_hardirqs_off); + +void itrace_hardirqs_ignore(void) +{ + if (irqsoff_enable == IRQSOFF_OFF) + return; + + if (this_cpu_read(irqsoff_flag)) + this_cpu_write(irqsoff_flag, 0); +} +EXPORT_SYMBOL(itrace_hardirqs_ignore); + +void itrace_irqsoff_set(u64 set) +{ + unsigned int i, j; + int online_cpus = num_online_cpus(); + + /* disable tracer and update threshold_value first */ + irqsoff_enable = IRQSOFF_OFF; + threshold_value = set * NSEC_PER_USEC; + + for (i = 0; i < online_cpus; i++) { + + per_cpu(irqsoff, i).front = 0; + per_cpu(irqsoff, i).num = 0; + for (j = 0; j < IRQSOFF_INFO_NUM_MAX; j++) { + per_cpu(irqsoff, i).info[j].t_max = 0; + per_cpu(irqsoff, i).info[j].caller[0] = '\0'; + } + + /* enable tracer */ + if (set != 0) { + per_cpu(irqsoff_flag, i) = 0; + irqsoff_enable = IRQSOFF_ON; + } + } +} + +void itrace_irqsoff_get(struct Irqsoff *is, int cpu) +{ + unsigned int j; + char *caller; + + for (j = 0; j < irqsoff_info_num; j++) { + caller = per_cpu(irqsoff, cpu).info[j].caller; + + is->num = per_cpu(irqsoff, cpu).num; + is->info[j].t_max = per_cpu(irqsoff, cpu).info[j].t_max; + strncpy(is->info[j].caller, caller, CALLER_FUNC_LEN); + } +} + +void itrace_irqsoff_num_set(int set) +{ + irqsoff_info_num = set; + + /* clear irqsoff.num while reset info_num */ + itrace_irqsoff_set(threshold_value / NSEC_PER_USEC); +} + +int itrace_irqsoff_num_get(void) +{ + return irqsoff_info_num; +}