summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Lezcano <daniel.lezcano@linaro.org>2018-05-28 16:40:11 +0200
committerDaniel Lezcano <daniel.lezcano@linaro.org>2018-06-18 18:52:12 +0200
commit88fc4fb670cee7cab0edc1e6978b04eb663336c6 (patch)
treeef0557a55a7b7e9c43ca78c4b7769dc1ebb53a87
parent9175aff5c459656eb6e1cbef09033d1318dc4363 (diff)
This gives debugfs information for cpuidle Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
-rw-r--r--drivers/cpuidle/Kconfig4
-rw-r--r--drivers/cpuidle/Makefile1
-rw-r--r--drivers/cpuidle/cpuidle-debugfs.c317
-rw-r--r--drivers/cpuidle/cpuidle.c16
-rw-r--r--drivers/cpuidle/cpuidle.h23
-rw-r--r--include/linux/cpuidle.h5
6 files changed, 365 insertions, 1 deletions
diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig
index 7e48eb5bf0a7..668b4d86c7d9 100644
--- a/drivers/cpuidle/Kconfig
+++ b/drivers/cpuidle/Kconfig
@@ -26,6 +26,10 @@ config CPU_IDLE_GOV_MENU
config DT_IDLE_STATES
bool
+config CPU_IDLE_DEBUGFS
+ bool "Debug file system for statistics"
+ default n
+
menu "ARM CPU Idle Drivers"
depends on ARM || ARM64
source "drivers/cpuidle/Kconfig.arm"
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile
index 9d7176cee3d3..b9faf7c92681 100644
--- a/drivers/cpuidle/Makefile
+++ b/drivers/cpuidle/Makefile
@@ -7,6 +7,7 @@ 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_ARCH_HAS_CPU_RELAX) += poll_state.o
+obj-$(CONFIG_CPU_IDLE_DEBUGFS) += cpuidle-debugfs.o
##################################################################################
# ARM SoC drivers
diff --git a/drivers/cpuidle/cpuidle-debugfs.c b/drivers/cpuidle/cpuidle-debugfs.c
new file mode 100644
index 000000000000..237e3a5c57df
--- /dev/null
+++ b/drivers/cpuidle/cpuidle-debugfs.c
@@ -0,0 +1,317 @@
+#include <linux/atomic.h>
+#include <linux/cpuidle.h>
+#include <linux/cpumask.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+
+static struct dentry *top_dentry;
+static struct dentry *pred_dentry;
+static struct dentry *under_dentry;
+static struct dentry *over_dentry;
+static struct dentry *good_dentry;
+
+struct cpuidle_debugfs_stats {
+ struct dentry *under_dentry;
+ struct dentry *over_dentry;
+ struct dentry *good_dentry;
+ struct dentry *state_dentry;
+ unsigned long long good;
+ unsigned long long under;
+ unsigned long long over;
+};
+
+void cpuidle_debugfs_update(struct cpuidle_device *dev, int index)
+{
+ struct cpuidle_driver *drv;
+ int diff = dev->last_residency;
+
+ drv = cpuidle_get_cpu_driver(dev);
+ if (!drv)
+ return;
+
+ /*
+ * If the residency time is:
+ *
+ * - more or equal than the target residency's selected idle
+ * state and less than the next one, the prediction is correct
+ *
+ * - less than the the target residency's selected idle state,
+ * the prediction is too late
+ *
+ * - more than the the next idle's target residency, the
+ * prediction is too early
+ */
+ if (drv->states[index].target_residency <= diff) {
+ /* It is not the last state, check against the next one */
+ if (index < drv->state_count - 1) {
+ /* The prediction was shorter than expected */
+ if (drv->states[index + 1].target_residency <= diff)
+ dev->stats[index].under++;
+ else
+ dev->stats[index].good++;
+ } else {
+ dev->stats[index].good++;
+ }
+ } else {
+ dev->stats[index].over++;
+ }
+}
+
+void cpuidle_debugfs_remove_stats(struct cpuidle_debugfs_stats *stats)
+{
+ debugfs_remove(stats->under_dentry);
+ debugfs_remove(stats->good_dentry);
+ debugfs_remove(stats->over_dentry);
+}
+
+void cpuidle_debugfs_remove_device(struct cpuidle_device *dev)
+{
+ struct cpuidle_driver *drv;
+ int i;
+
+ drv = cpuidle_get_cpu_driver(dev);
+ if (!drv)
+ return;
+
+ for (i = 0; i < drv->state_count; i++) {
+ cpuidle_debugfs_remove_stats(&dev->stats[i]);
+ debugfs_remove(dev->stats[i].state_dentry);
+ }
+
+ debugfs_remove(dev->debug);
+}
+
+int cpuidle_debugfs_add_stats(struct cpuidle_debugfs_stats *stats)
+{
+ int ret = -ENOMEM;
+
+ stats->good_dentry = debugfs_create_u64("good", 0444,
+ stats->state_dentry,
+ &stats->good);
+ if (!stats->good_dentry)
+ goto out;
+
+ stats->under_dentry = debugfs_create_u64("under", 0444,
+ stats->state_dentry,
+ &stats->under);
+ if (!stats->under_dentry)
+ goto out_remove_good;
+
+ stats->over_dentry = debugfs_create_u64("over", 0444,
+ stats->state_dentry,
+ &stats->over);
+ if (!stats->over_dentry)
+ goto out_remove_under;
+
+ ret = 0;
+out:
+ return ret;
+out_remove_good:
+ debugfs_remove(stats->good_dentry);
+out_remove_under:
+ debugfs_remove(stats->under_dentry);
+ goto out;
+}
+
+int cpuidle_debugfs_add_device(struct cpuidle_device *dev)
+{
+ struct cpuidle_driver *drv;
+ int ret = -ENOMEM;
+ int i;
+ char *namedir;
+
+ drv = cpuidle_get_cpu_driver(dev);
+ if (!drv)
+ return -EINVAL;
+
+ dev->stats = kzalloc(sizeof(*dev->stats) * CPUIDLE_STATE_MAX,
+ GFP_KERNEL);
+ if (!dev->stats)
+ return -ENOMEM;
+
+ namedir = kasprintf(GFP_KERNEL, "cpu%d", dev->cpu);
+ if (!namedir)
+ goto out_free_stats;
+
+ dev->debug = debugfs_create_dir(namedir, top_dentry);
+
+ kfree(namedir);
+
+ if (!dev->debug)
+ goto out_remove_dir;
+
+ for (i = 0; i < drv->state_count; i++) {
+ namedir = kasprintf(GFP_KERNEL, "state%d", i);
+
+ dev->stats[i].state_dentry = debugfs_create_dir(namedir,
+ dev->debug);
+
+ kfree(namedir);
+
+ if (!dev->stats[i].state_dentry)
+ goto out_remove_states;
+
+ ret = cpuidle_debugfs_add_stats(&dev->stats[i]);
+ if (ret) {
+ debugfs_remove(dev->stats[i].state_dentry);
+ goto out_remove_states;
+ }
+ }
+
+ ret = 0;
+out:
+ return ret;
+
+out_remove_states:
+ for (i--; i >= 0; i--) {
+ cpuidle_debugfs_remove_stats(&dev->stats[i]);
+ debugfs_remove(dev->stats[i].state_dentry);
+ }
+out_remove_dir:
+ debugfs_remove(dev->debug);
+out_free_stats:
+ kfree(dev->stats);
+ goto out;
+}
+
+static int cpuidle_debugfs_under_show(struct seq_file *s, void *data)
+{
+ unsigned long long total = 0;
+ struct cpuidle_driver *drv;
+ struct cpuidle_device *dev;
+ int cpu, i;
+
+ for_each_possible_cpu(cpu) {
+
+ dev = per_cpu(cpuidle_devices, cpu);
+ if (!dev)
+ continue;
+
+ drv = cpuidle_get_cpu_driver(dev);
+ if (!drv)
+ return -EINVAL;
+
+ for (i = 0; i < drv->state_count; i++)
+ total += dev->stats[i].under;
+ }
+
+ seq_printf(s, "%llu\n", total);
+
+ return 0;
+}
+
+static int cpuidle_debugfs_over_show(struct seq_file *s, void *data)
+{
+ unsigned long long total = 0;
+ struct cpuidle_driver *drv;
+ struct cpuidle_device *dev;
+ int cpu, i;
+
+ for_each_possible_cpu(cpu) {
+
+ dev = per_cpu(cpuidle_devices, cpu);
+ if (!dev)
+ continue;
+
+ drv = cpuidle_get_cpu_driver(dev);
+ if (!drv)
+ return -EINVAL;
+
+ for (i = 0; i < drv->state_count; i++)
+ total += dev->stats[i].over;
+ }
+
+ seq_printf(s, "%llu\n", total);
+
+ return 0;
+}
+
+static int cpuidle_debugfs_good_show(struct seq_file *s, void *data)
+{
+ unsigned long long total = 0;
+ struct cpuidle_driver *drv;
+ struct cpuidle_device *dev;
+ int cpu, i;
+
+ for_each_possible_cpu(cpu) {
+
+ dev = per_cpu(cpuidle_devices, cpu);
+ if (!dev)
+ continue;
+
+ drv = cpuidle_get_cpu_driver(dev);
+ if (!drv)
+ return -EINVAL;
+
+ for (i = 0; i < drv->state_count; i++)
+ total += dev->stats[i].good;
+ }
+
+ seq_printf(s, "%llu\n", total);
+
+ return 0;
+}
+
+#define define_cpuidle_debugfs_open(name) \
+ static int cpuidle_debugfs_##name##_open(struct inode *inode, \
+ struct file *file) \
+ { \
+ return single_open(file, cpuidle_debugfs_##name##_show, \
+ inode->i_private); \
+ }
+
+define_cpuidle_debugfs_open(under);
+define_cpuidle_debugfs_open(over);
+define_cpuidle_debugfs_open(good);
+
+#define define_cpuidle_debugfs_fops(name) \
+static const struct file_operations cpuidle_debugfs_##name##_fops = { \
+ .open = cpuidle_debugfs_##name##_open, \
+ .read = seq_read, \
+ .llseek = seq_lseek, \
+ .release = single_release, \
+}
+
+define_cpuidle_debugfs_fops(under);
+define_cpuidle_debugfs_fops(over);
+define_cpuidle_debugfs_fops(good);
+
+#define cpuidle_debugfs_fops(name) cpuidle_debugfs_##name##_fops
+
+int cpuidle_debugfs_init(void)
+{
+ top_dentry = debugfs_create_dir("cpuidle", NULL);
+ if (!top_dentry)
+ return -ENOMEM;
+
+ pred_dentry = debugfs_create_dir("predictions", top_dentry);
+ if (!pred_dentry)
+ goto out_top_dentry;
+
+ under_dentry = debugfs_create_file("under", 0444, pred_dentry, NULL,
+ &cpuidle_debugfs_fops(under));
+ if (!under_dentry)
+ goto out_pred_dentry;
+
+ over_dentry = debugfs_create_file("over", 0444, pred_dentry, NULL,
+ &cpuidle_debugfs_fops(over));
+ if (!over_dentry)
+ goto out_under_dentry;
+
+ good_dentry = debugfs_create_file("good", 0444, pred_dentry, NULL,
+ &cpuidle_debugfs_fops(good));
+ if (!good_dentry)
+ goto out_over_dentry;
+
+ return 0;
+
+out_over_dentry:
+ debugfs_remove(over_dentry);
+out_under_dentry:
+ debugfs_remove(under_dentry);
+out_pred_dentry:
+ debugfs_remove(pred_dentry);
+out_top_dentry:
+ debugfs_remove(top_dentry);
+ return -ENOMEM;
+}
diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c
index 7c33193216ea..1850dfc9c9e3 100644
--- a/drivers/cpuidle/cpuidle.c
+++ b/drivers/cpuidle/cpuidle.c
@@ -251,6 +251,7 @@ int cpuidle_enter_state(struct cpuidle_device *dev, struct cpuidle_driver *drv,
*/
dev->states_usage[entered_state].time += dev->last_residency;
dev->states_usage[entered_state].usage++;
+ cpuidle_debugfs_update(dev, index);
} else {
dev->last_residency = 0;
}
@@ -406,9 +407,13 @@ int cpuidle_enable_device(struct cpuidle_device *dev)
if (ret)
return ret;
+ ret = cpuidle_debugfs_add_device(dev);
+ if (ret)
+ goto fail_sysfs;
+
if (cpuidle_curr_governor->enable &&
(ret = cpuidle_curr_governor->enable(drv, dev)))
- goto fail_sysfs;
+ goto fail_debugfs;
smp_wmb();
@@ -417,6 +422,8 @@ int cpuidle_enable_device(struct cpuidle_device *dev)
enabled_devices++;
return 0;
+fail_debugfs:
+ cpuidle_debugfs_remove_device(dev);
fail_sysfs:
cpuidle_remove_device_sysfs(dev);
@@ -448,6 +455,7 @@ void cpuidle_disable_device(struct cpuidle_device *dev)
cpuidle_curr_governor->disable(drv, dev);
cpuidle_remove_device_sysfs(dev);
+ cpuidle_debugfs_remove_device(dev);
enabled_devices--;
}
@@ -682,6 +690,12 @@ static int __init cpuidle_init(void)
if (ret)
return ret;
+ ret = cpuidle_debugfs_init();
+ if (ret) {
+ cpuidle_remove_interface(cpu_subsys.dev_root);
+ return ret;
+ }
+
latency_notifier_init(&cpuidle_latency_notifier);
return 0;
diff --git a/drivers/cpuidle/cpuidle.h b/drivers/cpuidle/cpuidle.h
index 2965ab32a583..dbab7959eae8 100644
--- a/drivers/cpuidle/cpuidle.h
+++ b/drivers/cpuidle/cpuidle.h
@@ -69,4 +69,27 @@ static inline void cpuidle_coupled_unregister_device(struct cpuidle_device *dev)
}
#endif
+#ifdef CONFIG_CPU_IDLE_DEBUGFS
+extern int cpuidle_debugfs_init(void);
+extern int cpuidle_debugfs_add_device(struct cpuidle_device *dev);
+extern void cpuidle_debugfs_remove_device(struct cpuidle_device *dev);
+extern void cpuidle_debugfs_update(struct cpuidle_device *dev, int index);
+#else
+static inline int cpuidle_debugfs_init(void)
+{
+ return 0;
+}
+static inline int cpuidle_debugfs_add_device(struct cpuidle_device *dev)
+{
+ return 0;
+}
+static inline void cpuidle_debugfs_remove_device(struct cpuidle_device *dev)
+{
+ ;
+}
+static inline void cpuidle_debugfs_update(struct cpuidle_device *dev, int index)
+{
+}
+#endif /* CPU_IDLE_DEBUGFS */
+
#endif /* __DRIVER_CPUIDLE_H */
diff --git a/include/linux/cpuidle.h b/include/linux/cpuidle.h
index 113a14833ad3..fd302d79847b 100644
--- a/include/linux/cpuidle.h
+++ b/include/linux/cpuidle.h
@@ -72,6 +72,7 @@ struct cpuidle_state {
struct cpuidle_device_kobj;
struct cpuidle_state_kobj;
struct cpuidle_driver_kobj;
+struct cpuidle_debugfs_stats;
struct cpuidle_device {
unsigned int registered:1;
@@ -90,6 +91,10 @@ struct cpuidle_device {
cpumask_t coupled_cpus;
struct cpuidle_coupled *coupled;
#endif
+#ifdef CONFIG_CPU_IDLE_DEBUGFS
+ struct dentry *debug;
+ struct cpuidle_debugfs_stats *stats;
+#endif
};
DECLARE_PER_CPU(struct cpuidle_device *, cpuidle_devices);