summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Lezcano <daniel.lezcano@linaro.org>2015-11-13 11:52:05 +0100
committerDaniel Lezcano <daniel.lezcano@linaro.org>2015-11-13 11:52:05 +0100
commit74a0ecdfef6a309a35da55d0a245033914fab6bf (patch)
treea288bf93a56cf4d5b353c45a5a7291c077c141db
parentf75115975f4fe1f5d6682f3ac32c109a301439ce (diff)
Add debug information for cpuidle and irq timings
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
-rw-r--r--drivers/cpuidle/Kconfig7
-rw-r--r--drivers/cpuidle/Makefile2
-rw-r--r--drivers/cpuidle/cpuidle.c13
-rw-r--r--drivers/cpuidle/debugfs.c168
-rw-r--r--drivers/cpuidle/debugfs.h19
-rw-r--r--kernel/irq/timings.c106
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);