198 lines
5.2 KiB
C
198 lines
5.2 KiB
C
#include <linux/irq.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kprobes.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irqdesc.h>
|
|
|
|
#define CREATE_TRACE_POINTS
|
|
#include <trace/events/nvt_profiler.h>
|
|
|
|
struct nvt_profiler_params {
|
|
bool full_feature;
|
|
};
|
|
|
|
struct nvt_irq_kretprobe_data {
|
|
struct irq_desc *desc;
|
|
ktime_t entry_stamp;
|
|
};
|
|
|
|
struct nvt_profiler_params params = {
|
|
.full_feature = true,
|
|
};
|
|
|
|
DEFINE_STATIC_KEY_FALSE(full_feature_key);
|
|
|
|
static int __init early_param_nprofile_irq_duration(char *p)
|
|
{
|
|
if (!strncmp(p, "on", 2))
|
|
params.full_feature = true;
|
|
else
|
|
params.full_feature = false;
|
|
return 0;
|
|
}
|
|
early_param("nprofile_irq_duration", early_param_nprofile_irq_duration);
|
|
|
|
static int __init set_nprofile_irqsoff(char *str)
|
|
{
|
|
int nprofile_irqsoff_enable(char *str) __init;
|
|
|
|
return nprofile_irqsoff_enable(str);
|
|
}
|
|
__setup("nprofile_irqsoff=", set_nprofile_irqsoff);
|
|
|
|
static int nvt_irq_kret_probe_entry(struct kretprobe_instance *ri,
|
|
struct pt_regs *regs)
|
|
{
|
|
struct nvt_irq_kretprobe_data *data;
|
|
#if defined(CONFIG_ARM)
|
|
struct irq_desc *desc = (struct irq_desc*)(regs->ARM_r0);
|
|
#elif defined(CONFIG_ARM64)
|
|
struct irq_desc *desc = (struct irq_desc*)(regs->regs[0]);
|
|
#else
|
|
#error "CPU architecture not supported"
|
|
#endif
|
|
ktime_t current_stamp = ktime_get();
|
|
|
|
data = (struct nvt_irq_kretprobe_data *)ri->data;
|
|
data->entry_stamp = current_stamp;
|
|
data->desc = desc;
|
|
|
|
if (static_branch_unlikely(&full_feature_key)) {
|
|
/* write out last calculation */
|
|
if (ktime_ms_delta(current_stamp, desc->start_stamp) > 1000) {
|
|
desc->last_stamp = desc->start_stamp;
|
|
desc->last_sum = desc->cur_sum;
|
|
desc->last_count = desc->cur_count;
|
|
desc->start_stamp = current_stamp;
|
|
desc->cur_sum = 0;
|
|
desc->cur_count = 0;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define ns_to_ms(ns) ((ns) / 1000000)
|
|
static int nvt_irq_kret_probe_ret(struct kretprobe_instance *ri,
|
|
struct pt_regs *regs)
|
|
{
|
|
struct nvt_irq_kretprobe_data *data =
|
|
(struct nvt_irq_kretprobe_data *)ri->data;
|
|
struct irq_desc *desc = data->desc;
|
|
|
|
ktime_t cur = ktime_get();
|
|
ktime_t start = desc->start_stamp;
|
|
ktime_t entry = data->entry_stamp;
|
|
s64 duration = ktime_to_ns(ktime_sub(cur, entry));
|
|
|
|
/* for last_ns and max_in in /proc/interrupts */
|
|
desc->last_duration = duration;
|
|
if (unlikely(duration > desc->max_duration))
|
|
desc->max_duration = duration;
|
|
|
|
/* for irq_duration trace_event */
|
|
trace_irq_duration(desc->irq_data.irq, duration);
|
|
|
|
/* for cnt/sec and ns/sec in /proc/interrupts */
|
|
if (static_branch_unlikely(&full_feature_key)) {
|
|
|
|
/*
|
|
* Case 1:
|
|
* start start+1s start+2s
|
|
* |-------+--------+-----|----------------------|
|
|
* entry cur
|
|
* Case 2:
|
|
* start start+1s start+2s
|
|
* |-------+--------------|----------+-----------|
|
|
* entry cur
|
|
* Case 3:
|
|
* start start+1s start+2s
|
|
* |-------+--------------|----------------------|
|
|
* entry cur
|
|
*/
|
|
if (ktime_to_ms(ktime_sub(cur, start)) < 1000) {
|
|
/* case 1 */
|
|
desc->cur_count++;
|
|
desc->cur_sum += duration;
|
|
} else {
|
|
/* case 2,3 */
|
|
ktime_t start_1 = ktime_add_ms(start, 1000);
|
|
|
|
/* write out last calculation */
|
|
desc->last_stamp = start;
|
|
desc->last_sum = desc->cur_sum +
|
|
ktime_to_ns(ktime_sub(start_1, entry));
|
|
desc->last_count = desc->cur_count + 1;
|
|
|
|
/* start new calculation */
|
|
desc->start_stamp = cur;
|
|
/* check for case 2 or case 3 */
|
|
desc->cur_sum = ktime_to_ns(ktime_sub(cur, start_1));
|
|
desc->cur_count = desc->cur_sum ? 1: 0;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct kretprobe nvt_irq_kretprobe = {
|
|
.handler = nvt_irq_kret_probe_ret,
|
|
.entry_handler = nvt_irq_kret_probe_entry,
|
|
.data_size = sizeof(struct nvt_irq_kretprobe_data),
|
|
/* Probe up to 20 instances concurrently. */
|
|
.maxactive = 20,
|
|
};
|
|
|
|
static int __init nvt_irq_kretprobe_init(void)
|
|
{
|
|
static const char func_name[] = "__irq_action_handler";
|
|
int ret;
|
|
|
|
nvt_irq_kretprobe.kp.symbol_name = func_name;
|
|
ret = register_kretprobe(&nvt_irq_kretprobe);
|
|
if (ret < 0) {
|
|
pr_err("Novatek IRQ probe init failed: %d\n", ret);
|
|
return -1;
|
|
}
|
|
|
|
if (params.full_feature)
|
|
static_branch_enable(&full_feature_key);
|
|
|
|
pr_info("Novatek IRQ probe at %s %s\n",
|
|
nvt_irq_kretprobe.kp.symbol_name,
|
|
!params.full_feature ? "" : "(lite)");
|
|
|
|
return 0;
|
|
}
|
|
module_init(nvt_irq_kretprobe_init)
|
|
|
|
static void __exit nvt_irq_kretprobe_exit(void)
|
|
{
|
|
unregister_kretprobe(&nvt_irq_kretprobe);
|
|
/* nmissed > 0 suggests that maxactive was set too low. */
|
|
pr_info("Novatek IRQ probe missed probing %d instances of %s\n",
|
|
nvt_irq_kretprobe.nmissed, nvt_irq_kretprobe.kp.symbol_name);
|
|
}
|
|
module_exit(nvt_irq_kretprobe_exit)
|
|
|
|
void nvt_profiler_proc_intr_head(struct seq_file *p)
|
|
{
|
|
seq_printf(p, "%7s %10s %10s %10s %10s", "last_ns", "max_ns", "cnt/sec",
|
|
"ns/sec", "timestamp");
|
|
}
|
|
|
|
void nvt_profiler_proc_intr_val(struct seq_file *p, struct irq_desc *desc)
|
|
{
|
|
u64 ts = ktime_to_ms(desc->last_stamp);
|
|
u32 ms = do_div(ts, 1000);
|
|
|
|
seq_printf(p, " %10lld %10lld %10lld %10lld %6llu.%03u",
|
|
desc->last_duration,
|
|
desc->max_duration,
|
|
desc->last_count,
|
|
desc->last_sum,
|
|
ts, ms);
|
|
}
|