diff options
author | Daniel Lezcano <daniel.lezcano@linaro.org> | 2018-05-28 16:40:11 +0200 |
---|---|---|
committer | Daniel Lezcano <daniel.lezcano@linaro.org> | 2018-05-31 16:27:27 +0200 |
commit | 415abd3e6d4c652908c6edf596f0901ed2b30ac5 (patch) | |
tree | 094512cd36349f02f9e731a0018af1e3c7712f41 | |
parent | 2d3b267ac0d336f85f8d39b1a357f4cc0b7cdb36 (diff) |
Add debugfs for cpuidlelkft
This gives debugfs information for cpuidle
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
-rw-r--r-- | drivers/cpuidle/Kconfig | 4 | ||||
-rw-r--r-- | drivers/cpuidle/Makefile | 1 | ||||
-rw-r--r-- | drivers/cpuidle/cpuidle-debugfs.c | 317 | ||||
-rw-r--r-- | drivers/cpuidle/cpuidle.c | 16 | ||||
-rw-r--r-- | drivers/cpuidle/cpuidle.h | 23 | ||||
-rw-r--r-- | include/linux/cpuidle.h | 5 |
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); |