From: Bixuan Cui <cuibixuan(a)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(a)huawei.com>
Reviewed-by: Ding Tianhong <dingtianhong(a)huawei.com>
Reviewed-by: Hanjun Guo <guohanjun(a)huawei.com>
Signed-off-by: Yang Yingliang <yangyingliang(a)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(a)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(a)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;
+}
--
2.25.1