diff options
author | Daniel Lezcano <daniel.lezcano@linaro.org> | 2015-11-13 11:52:05 +0100 |
---|---|---|
committer | Daniel Lezcano <daniel.lezcano@linaro.org> | 2015-11-13 11:52:05 +0100 |
commit | 74a0ecdfef6a309a35da55d0a245033914fab6bf (patch) | |
tree | a288bf93a56cf4d5b353c45a5a7291c077c141db | |
parent | f75115975f4fe1f5d6682f3ac32c109a301439ce (diff) |
Add debug information for cpuidle and irq timings
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
-rw-r--r-- | drivers/cpuidle/Kconfig | 7 | ||||
-rw-r--r-- | drivers/cpuidle/Makefile | 2 | ||||
-rw-r--r-- | drivers/cpuidle/cpuidle.c | 13 | ||||
-rw-r--r-- | drivers/cpuidle/debugfs.c | 168 | ||||
-rw-r--r-- | drivers/cpuidle/debugfs.h | 19 | ||||
-rw-r--r-- | kernel/irq/timings.c | 106 |
6 files changed, 314 insertions, 1 deletions
diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index c58796f3d624..6579e81ec579 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig @@ -29,6 +29,13 @@ config CPU_IDLE_GOV_IRQ bool "Irq governor (for tickless system)" default y +config CPU_IDLE_DEBUG + bool "Debug information about cpuidle" + select DEBUG_FS + help + Enables the debug filesystem showing information about the governor + predictions. + config DT_IDLE_STATES bool diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 3ba81b1dffad..cc2f750cdab9 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -6,6 +6,8 @@ obj-y += cpuidle.o driver.o governor.o sysfs.o governors/ obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.o obj-$(CONFIG_DT_IDLE_STATES) += dt_idle_states.o +obj-$(CONFIG_CPU_IDLE_DEBUG) += debugfs.o + ################################################################################## # ARM SoC drivers obj-$(CONFIG_ARM_MVEBU_V7_CPUIDLE) += cpuidle-mvebu-v7.o diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c index f9617f1d8587..6948d65daf76 100644 --- a/drivers/cpuidle/cpuidle.c +++ b/drivers/cpuidle/cpuidle.c @@ -23,6 +23,7 @@ #include <linux/tick.h> #include <trace/events/power.h> +#include "debugfs.h" #include "cpuidle.h" DEFINE_PER_CPU(struct cpuidle_device *, cpuidle_devices); @@ -237,6 +238,14 @@ int cpuidle_enter_state(struct cpuidle_device *dev, struct cpuidle_driver *drv, * We want to account the number of times the governor * did a correct prediction. */ + if (diff < drv->states[entered_state].target_residency) + cpuidle_debugfs_early_inc(dev->cpu); + else if (entered_state == drv->state_count - 1) + cpuidle_debugfs_correct_inc(dev->cpu); + else if (diff < drv->states[entered_state + 1].target_residency) + cpuidle_debugfs_late_inc(dev->cpu); + else cpuidle_debugfs_correct_inc(dev->cpu); + if ((diff >= drv->states[entered_state].target_residency) && ((entered_state == drv->state_count - 1) || (diff < drv->states[entered_state + 1].target_residency))) @@ -659,6 +668,10 @@ static int __init cpuidle_init(void) { int ret; + ret = cpuidle_debugfs_init(); + if (ret) + return ret; + if (cpuidle_disabled()) return -ENODEV; diff --git a/drivers/cpuidle/debugfs.c b/drivers/cpuidle/debugfs.c new file mode 100644 index 000000000000..16a01c411fb4 --- /dev/null +++ b/drivers/cpuidle/debugfs.c @@ -0,0 +1,168 @@ +/* + * Debugfs support for cpuidle governors + * + * Copyright (C) 2015 Linaro : daniel.lezcano@linaro.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/cpu.h> +#include <linux/cpuidle.h> +#include <linux/debugfs.h> +#include <linux/atomic.h> + +#include "cpuidle.h" + +static atomic_t correct; +static atomic_t failed; +static atomic_t early; +static atomic_t late; +static atomic_t total; + +static DEFINE_PER_CPU(atomic_t, _correct); +static DEFINE_PER_CPU(atomic_t, _failed); +static DEFINE_PER_CPU(atomic_t, _early); +static DEFINE_PER_CPU(atomic_t, _late); +static DEFINE_PER_CPU(atomic_t, _total); + +void cpuidle_debugfs_reset(void) +{ + int cpu; + + atomic_set(&correct, 0); + atomic_set(&failed, 0); + atomic_set(&early, 0); + atomic_set(&late, 0); + atomic_set(&total, 0); + + for_each_possible_cpu(cpu) { + atomic_set(&per_cpu(_correct, cpu), 0); + atomic_set(&per_cpu(_failed, cpu), 0); + atomic_set(&per_cpu(_early, cpu), 0); + atomic_set(&per_cpu(_late, cpu), 0); + atomic_set(&per_cpu(_total, cpu), 0); + } +} + +void cpuidle_debugfs_correct_inc(int cpu) +{ + atomic_inc(&per_cpu(_total, cpu)); + atomic_inc(&per_cpu(_correct, cpu)); + atomic_inc(&correct); + atomic_inc(&total); +} + +void cpuidle_debugfs_early_inc(int cpu) +{ + atomic_inc(&per_cpu(_total, cpu)); + atomic_inc(&per_cpu(_early, cpu)); + atomic_inc(&per_cpu(_failed, cpu)); + atomic_inc(&early); + atomic_inc(&failed); + atomic_inc(&total); +} + +void cpuidle_debugfs_late_inc(int cpu) +{ + atomic_inc(&per_cpu(_total, cpu)); + atomic_inc(&per_cpu(_late, cpu)); + atomic_inc(&per_cpu(_failed, cpu)); + atomic_inc(&late); + atomic_inc(&failed); + atomic_inc(&total); +} + +ssize_t cpuidle_debugfs_write_reset(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + ssize_t size; + bool reset = false; + + file->private_data = &reset; + + size = debugfs_write_file_bool(file, user_buf, count, ppos); + if (size > 0 && reset) + cpuidle_debugfs_reset(); + + return size; +} + +static const struct file_operations reset_fops = { + .open = simple_open, + .write = cpuidle_debugfs_write_reset, + .llseek = seq_lseek, +}; + +static int cpuidle_debugfs_stats_show(struct seq_file *m, void *v) +{ + int cpu; + + seq_printf(m, "#cpu\tcorrect\tearly\tlate\tfailed\ttotal\n"); + + seq_printf(m, "all\t"); + seq_printf(m, "%d\t", atomic_read(&correct)); + seq_printf(m, "%d\t", atomic_read(&early)); + seq_printf(m, "%d\t", atomic_read(&late)); + seq_printf(m, "%d\t", atomic_read(&failed)); + seq_printf(m, "%d\n", atomic_read(&total)); + + for_each_online_cpu(cpu) { + seq_printf(m, "%d\t", cpu); + seq_printf(m, "%d\t", atomic_read(&per_cpu(_correct, cpu))); + seq_printf(m, "%d\t", atomic_read(&per_cpu(_early, cpu))); + seq_printf(m, "%d\t", atomic_read(&per_cpu(_late, cpu))); + seq_printf(m, "%d\t", atomic_read(&per_cpu(_failed, cpu))); + seq_printf(m, "%d\n", atomic_read(&per_cpu(_total, cpu))); + } + + return 0; +} + +static int cpuidle_debugfs_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, cpuidle_debugfs_stats_show, + &inode->i_private); +} + +static const struct file_operations stats_fops = { + .open = cpuidle_debugfs_stats_open, + .read = seq_read, + .llseek = seq_lseek, +}; + +int __init cpuidle_debugfs_init(void) +{ + struct dentry *top_dentry; + int ret = -ENOMEM; + + /* + * Topmost directory. The reference is kept in order to do a + * recursive remove in case of an error. + */ + top_dentry = debugfs_create_dir("cpuidle", NULL); + if (!top_dentry) + return -ENOMEM; + + /* + * Overall statistics for all cpus + */ + if (!debugfs_create_file("stats", 0400, + top_dentry, NULL, &stats_fops)) + goto out; + + /* + * Reset statistics file + */ + if (!debugfs_create_file("reset", 0200, + top_dentry, NULL, &reset_fops)) + goto out; + + ret = 0; +out: + if (ret) + debugfs_remove_recursive(top_dentry); + + return ret; +} diff --git a/drivers/cpuidle/debugfs.h b/drivers/cpuidle/debugfs.h new file mode 100644 index 000000000000..20fd719e53d6 --- /dev/null +++ b/drivers/cpuidle/debugfs.h @@ -0,0 +1,19 @@ +#ifndef __CPUIDLE_DEBUGFS_H +#define __CPUIDLE_DEBUGFS_H + +#ifdef CONFIG_CPU_IDLE_DEBUG +extern int __init cpuidle_debugfs_init(void); +extern void cpuidle_debugfs_reset(void); +extern void cpuidle_debugfs_correct_inc(int cpu); +extern void cpuidle_debugfs_early_inc(int cpu); +extern void cpuidle_debugfs_late_inc(int cpu); +#else +static int inline cpuidle_debugfs_init(void) { return 0; } +static inline void cpuidle_debugfs_reset(void) { return; } +static inline void cpuidle_debugfs_correct_inc(int cpu) {} +static inline void cpuidle_debugfs_early_inc(int cpu) {} +static inline void cpuidle_debugfs_late_inc(int cpu) {} + +#endif /* CONFIG_CPU_IDLE_DEBUG */ + +#endif diff --git a/kernel/irq/timings.c b/kernel/irq/timings.c index 27994cea4a99..d8f11ceae18c 100644 --- a/kernel/irq/timings.c +++ b/kernel/irq/timings.c @@ -15,7 +15,7 @@ #include <linux/math64.h> #include <linux/slab.h> #include <linux/spinlock.h> - +#include <linux/debugfs.h> #include "internals.h" #include <trace/events/irq.h> @@ -51,6 +51,105 @@ struct irqt_stat { static DEFINE_PER_CPU(struct list_head, irqt_predictions); static DEFINE_PER_CPU(raw_spinlock_t, irqt_predictions_lock); +#ifdef CONFIG_DEBUG_FS + +struct irqt_stats { + atomic_t correct; + atomic_t early; + atomic_t late; + atomic_t total; +}; + +static DEFINE_PER_CPU(struct irqt_stats, irqt_stats[NR_IRQS]); +static unsigned irqt_registered[NR_IRQS]; + +static int irqt_debugfs_stats_show(struct seq_file *m, void *v) +{ + int i, cpu; + struct irqt_stats *s; + + seq_printf(m, "# cpu\t irq\tcorrect\tearly\tlate\ttotal\n"); + + for_each_online_cpu(cpu) { + + for (i = 0; i < NR_IRQS; i++) { + + if (!irqt_registered[i]) + continue; + + s = &per_cpu(irqt_stats[i], cpu); + + if (!atomic_read(&s->total)) + continue; + + seq_printf(m, "%d\t%d\t", cpu, i); + seq_printf(m, "%d\t", atomic_read(&s->correct)); + seq_printf(m, "%d\t", atomic_read(&s->early)); + seq_printf(m, "%d\t", atomic_read(&s->late)); + seq_printf(m, "%d\n", atomic_read(&s->total)); + } + } + + return 0; +} + +static int irqt_debugfs_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, irqt_debugfs_stats_show, + &inode->i_private); +} + +static const struct file_operations stats_fops = { + .open = irqt_debugfs_stats_open, + .read = seq_read, + .llseek = seq_lseek, +}; + +static void irqt_debugfs_correct_inc(int cpu, int irq) +{ + struct irqt_stats *s = &per_cpu(irqt_stats[irq], cpu); + atomic_inc(&s->correct); + atomic_inc(&s->total); +} + +static void irqt_debugfs_early_inc(int cpu, int irq) +{ + struct irqt_stats *s = &per_cpu(irqt_stats[irq], cpu); + atomic_inc(&s->early); + atomic_inc(&s->total); +} + +static void irqt_debugfs_late_inc(int cpu, int irq) +{ + struct irqt_stats *s = &per_cpu(irqt_stats[irq], cpu); + atomic_inc(&s->late); + atomic_inc(&s->total); +} + +static int __init irqt_debugfs_init(void) +{ + struct dentry *top; + int ret = -ENOMEM; + + top = debugfs_create_dir("irqt", NULL); + if (!top) + return -ENOMEM; + + if (!debugfs_create_file("stats", 0400, top, NULL, &stats_fops)) + goto out; + + ret = 0; +out: + if (ret) + debugfs_remove_recursive(top); + + return ret; +} + +late_initcall(irqt_debugfs_init); + +#endif + void __init irqt_init(void) { int cpu; @@ -261,7 +360,10 @@ void irqt_process(unsigned int irq, struct irqt_stat *s) s->predictable++; if (s->predictable >= IRQT_INTERVAL_WINDOW) irqt_enqueue_prediction(now, s); + irqt_debugfs_correct_inc(smp_processor_id(), irq); } else { + irqt_debugfs_early_inc(smp_processor_id(), irq); + irqt_debugfs_late_inc(smp_processor_id(), irq); s->predictable = 0; s->unpredictable++; } @@ -303,6 +405,7 @@ int irqt_register(struct irq_desc *desc) ret = -ENOSYS; } else { desc->irq_timings = s; + irqt_registered[desc->action->irq] = 1; s = NULL; ret = 0; } @@ -327,6 +430,7 @@ void irqt_unregister(struct irq_desc *desc) return; s = desc->irq_timings; desc->irq_timings = NULL; + irqt_registered[desc->action->irq] = 0; cpu = s->prediction.cpu; if (cpu != -1) { lock = &per_cpu(irqt_predictions_lock, cpu); |