diff options
Diffstat (limited to 'drivers/cpufreq/cpufreq_stats.c')
-rw-r--r-- | drivers/cpufreq/cpufreq_stats.c | 405 |
1 files changed, 378 insertions, 27 deletions
diff --git a/drivers/cpufreq/cpufreq_stats.c b/drivers/cpufreq/cpufreq_stats.c index 0cd9b4dcef99..a2f1d41820b8 100644 --- a/drivers/cpufreq/cpufreq_stats.c +++ b/drivers/cpufreq/cpufreq_stats.c @@ -13,6 +13,9 @@ #include <linux/cpufreq.h> #include <linux/module.h> #include <linux/slab.h> +#include <linux/sort.h> +#include <linux/of.h> +#include <linux/sched.h> #include <linux/cputime.h> static spinlock_t cpufreq_stats_lock; @@ -31,7 +34,28 @@ struct cpufreq_stats { #endif }; +struct all_cpufreq_stats { + unsigned int state_num; + cputime64_t *time_in_state; + unsigned int *freq_table; +}; + +struct cpufreq_power_stats { + unsigned int state_num; + unsigned int *curr; + unsigned int *freq_table; +}; + +struct all_freq_table { + unsigned int *freq_table; + unsigned int table_size; +}; + +static struct all_freq_table *all_freq_table; + +static DEFINE_PER_CPU(struct all_cpufreq_stats *, all_cpufreq_stats); static DEFINE_PER_CPU(struct cpufreq_stats *, cpufreq_stats_table); +static DEFINE_PER_CPU(struct cpufreq_power_stats *, cpufreq_power_stats); struct cpufreq_stats_attribute { struct attribute attr; @@ -41,14 +65,24 @@ struct cpufreq_stats_attribute { static int cpufreq_stats_update(unsigned int cpu) { struct cpufreq_stats *stat; + struct all_cpufreq_stats *all_stat; unsigned long long cur_time; cur_time = get_jiffies_64(); spin_lock(&cpufreq_stats_lock); stat = per_cpu(cpufreq_stats_table, cpu); - if (stat->time_in_state) + all_stat = per_cpu(all_cpufreq_stats, cpu); + if (!stat) { + spin_unlock(&cpufreq_stats_lock); + return 0; + } + if (stat->time_in_state) { stat->time_in_state[stat->last_index] += cur_time - stat->last_time; + if (all_stat) + all_stat->time_in_state[stat->last_index] += + cur_time - stat->last_time; + } stat->last_time = cur_time; spin_unlock(&cpufreq_stats_lock); return 0; @@ -79,6 +113,104 @@ static ssize_t show_time_in_state(struct cpufreq_policy *policy, char *buf) return len; } +static int get_index_all_cpufreq_stat(struct all_cpufreq_stats *all_stat, + unsigned int freq) +{ + int i; + if (!all_stat) + return -1; + for (i = 0; i < all_stat->state_num; i++) { + if (all_stat->freq_table[i] == freq) + return i; + } + return -1; +} + +void acct_update_power(struct task_struct *task, cputime_t cputime) { + struct cpufreq_power_stats *powerstats; + struct cpufreq_stats *stats; + unsigned int cpu_num, curr; + + if (!task) + return; + cpu_num = task_cpu(task); + powerstats = per_cpu(cpufreq_power_stats, cpu_num); + stats = per_cpu(cpufreq_stats_table, cpu_num); + if (!powerstats || !stats) + return; + + curr = powerstats->curr[stats->last_index]; + if (task->cpu_power != ULLONG_MAX) + task->cpu_power += curr * cputime_to_usecs(cputime); +} +EXPORT_SYMBOL_GPL(acct_update_power); + +static ssize_t show_current_in_state(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + ssize_t len = 0; + unsigned int i, cpu; + struct cpufreq_power_stats *powerstats; + + spin_lock(&cpufreq_stats_lock); + for_each_possible_cpu(cpu) { + powerstats = per_cpu(cpufreq_power_stats, cpu); + if (!powerstats) + continue; + len += scnprintf(buf + len, PAGE_SIZE - len, "CPU%d:", cpu); + for (i = 0; i < powerstats->state_num; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, + "%d=%d ", powerstats->freq_table[i], + powerstats->curr[i]); + len += scnprintf(buf + len, PAGE_SIZE - len, "\n"); + } + spin_unlock(&cpufreq_stats_lock); + return len; +} + +static ssize_t show_all_time_in_state(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + ssize_t len = 0; + unsigned int i, cpu, freq, index; + struct all_cpufreq_stats *all_stat; + struct cpufreq_policy *policy; + + len += scnprintf(buf + len, PAGE_SIZE - len, "freq\t\t"); + for_each_possible_cpu(cpu) { + len += scnprintf(buf + len, PAGE_SIZE - len, "cpu%d\t\t", cpu); + if (cpu_online(cpu)) + cpufreq_stats_update(cpu); + } + + if (!all_freq_table) + goto out; + for (i = 0; i < all_freq_table->table_size; i++) { + freq = all_freq_table->freq_table[i]; + len += scnprintf(buf + len, PAGE_SIZE - len, "\n%u\t\t", freq); + for_each_possible_cpu(cpu) { + policy = cpufreq_cpu_get(cpu); + if (policy == NULL) + continue; + all_stat = per_cpu(all_cpufreq_stats, policy->cpu); + index = get_index_all_cpufreq_stat(all_stat, freq); + if (index != -1) { + len += scnprintf(buf + len, PAGE_SIZE - len, + "%llu\t\t", (unsigned long long) + cputime64_to_clock_t(all_stat->time_in_state[index])); + } else { + len += scnprintf(buf + len, PAGE_SIZE - len, + "N/A\t\t"); + } + cpufreq_cpu_put(policy); + } + } + +out: + len += scnprintf(buf + len, PAGE_SIZE - len, "\n"); + return len; +} + #ifdef CONFIG_CPU_FREQ_STAT_DETAILS static ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf) { @@ -142,6 +274,12 @@ static struct attribute_group stats_attr_group = { .name = "stats" }; +static struct kobj_attribute _attr_all_time_in_state = __ATTR(all_time_in_state, + 0444, show_all_time_in_state, NULL); + +static struct kobj_attribute _attr_current_in_state = __ATTR(current_in_state, + 0444, show_current_in_state, NULL); + static int freq_table_get_index(struct cpufreq_stats *stat, unsigned int freq) { int index; @@ -180,17 +318,54 @@ static void cpufreq_stats_free_table(unsigned int cpu) cpufreq_cpu_put(policy); } -static int __cpufreq_stats_create_table(struct cpufreq_policy *policy) +static void cpufreq_allstats_free(void) +{ + int cpu; + struct all_cpufreq_stats *all_stat; + + sysfs_remove_file(cpufreq_global_kobject, + &_attr_all_time_in_state.attr); + + for_each_possible_cpu(cpu) { + all_stat = per_cpu(all_cpufreq_stats, cpu); + if (!all_stat) + continue; + kfree(all_stat->time_in_state); + kfree(all_stat); + per_cpu(all_cpufreq_stats, cpu) = NULL; + } + if (all_freq_table) { + kfree(all_freq_table->freq_table); + kfree(all_freq_table); + all_freq_table = NULL; + } +} + +static void cpufreq_powerstats_free(void) +{ + int cpu; + struct cpufreq_power_stats *powerstats; + + sysfs_remove_file(cpufreq_global_kobject, &_attr_current_in_state.attr); + + for_each_possible_cpu(cpu) { + powerstats = per_cpu(cpufreq_power_stats, cpu); + if (!powerstats) + continue; + kfree(powerstats->curr); + kfree(powerstats); + per_cpu(cpufreq_power_stats, cpu) = NULL; + } +} + +static int __cpufreq_stats_create_table(struct cpufreq_policy *policy, + struct cpufreq_frequency_table *table, int count) { - unsigned int i, count = 0, ret = 0; + unsigned int i, ret = 0; struct cpufreq_stats *stat; unsigned int alloc_size; unsigned int cpu = policy->cpu; - struct cpufreq_frequency_table *pos, *table; - - table = cpufreq_frequency_get_table(cpu); - if (unlikely(!table)) - return 0; + struct cpufreq_frequency_table *pos; if (per_cpu(cpufreq_stats_table, cpu)) return -EBUSY; @@ -205,9 +380,6 @@ static int __cpufreq_stats_create_table(struct cpufreq_policy *policy) stat->cpu = cpu; per_cpu(cpufreq_stats_table, cpu) = stat; - cpufreq_for_each_valid_entry(pos, table) - count++; - alloc_size = count * sizeof(int) + count * sizeof(u64); #ifdef CONFIG_CPU_FREQ_STAT_DETAILS @@ -242,10 +414,159 @@ error_out: return ret; } +static void cpufreq_stats_update_policy_cpu(struct cpufreq_policy *policy) +{ + struct cpufreq_stats *stat = per_cpu(cpufreq_stats_table, + policy->last_cpu); + + pr_debug("Updating stats_table for new_cpu %u from last_cpu %u\n", + policy->cpu, policy->last_cpu); + per_cpu(cpufreq_stats_table, policy->cpu) = per_cpu(cpufreq_stats_table, + policy->last_cpu); + per_cpu(cpufreq_stats_table, policy->last_cpu) = NULL; + stat->cpu = policy->cpu; +} + +static void cpufreq_powerstats_create(unsigned int cpu, + struct cpufreq_frequency_table *table, int count) { + unsigned int alloc_size, i = 0, ret = 0; + struct cpufreq_power_stats *powerstats; + struct cpufreq_frequency_table *pos; + struct device_node *cpu_node; + char device_path[16]; + + powerstats = kzalloc(sizeof(struct cpufreq_power_stats), + GFP_KERNEL); + if (!powerstats) + return; + + /* Allocate memory for freq table per cpu as well as clockticks per + * freq*/ + alloc_size = count * sizeof(unsigned int) + + count * sizeof(unsigned int); + powerstats->curr = kzalloc(alloc_size, GFP_KERNEL); + if (!powerstats->curr) { + kfree(powerstats); + return; + } + powerstats->freq_table = powerstats->curr + count; + + spin_lock(&cpufreq_stats_lock); + i = 0; + cpufreq_for_each_valid_entry(pos, table) + powerstats->freq_table[i++] = pos->frequency; + powerstats->state_num = i; + + snprintf(device_path, sizeof(device_path), "/cpus/cpu@%d", cpu); + cpu_node = of_find_node_by_path(device_path); + if (cpu_node) { + ret = of_property_read_u32_array(cpu_node, "current", + powerstats->curr, count); + if (ret) { + kfree(powerstats->curr); + kfree(powerstats); + powerstats = NULL; + } + } + per_cpu(cpufreq_power_stats, cpu) = powerstats; + spin_unlock(&cpufreq_stats_lock); +} + +static int compare_for_sort(const void *lhs_ptr, const void *rhs_ptr) +{ + unsigned int lhs = *(const unsigned int *)(lhs_ptr); + unsigned int rhs = *(const unsigned int *)(rhs_ptr); + if (lhs < rhs) + return -1; + if (lhs > rhs) + return 1; + return 0; +} + +static bool check_all_freq_table(unsigned int freq) +{ + int i; + for (i = 0; i < all_freq_table->table_size; i++) { + if (freq == all_freq_table->freq_table[i]) + return true; + } + return false; +} + +static void create_all_freq_table(void) +{ + all_freq_table = kzalloc(sizeof(struct all_freq_table), + GFP_KERNEL); + if (!all_freq_table) + pr_warn("could not allocate memory for all_freq_table\n"); + return; +} + +static void add_all_freq_table(unsigned int freq) +{ + unsigned int size; + size = sizeof(unsigned int) * (all_freq_table->table_size + 1); + all_freq_table->freq_table = krealloc(all_freq_table->freq_table, + size, GFP_ATOMIC); + if (IS_ERR(all_freq_table->freq_table)) { + pr_warn("Could not reallocate memory for freq_table\n"); + all_freq_table->freq_table = NULL; + return; + } + all_freq_table->freq_table[all_freq_table->table_size++] = freq; +} + +static void cpufreq_allstats_create(unsigned int cpu, + struct cpufreq_frequency_table *table, int count) +{ + int i , j = 0; + unsigned int alloc_size; + struct all_cpufreq_stats *all_stat; + bool sort_needed = false; + + all_stat = kzalloc(sizeof(struct all_cpufreq_stats), + GFP_KERNEL); + if (!all_stat) { + pr_warn("Cannot allocate memory for cpufreq stats\n"); + return; + } + + /*Allocate memory for freq table per cpu as well as clockticks per freq*/ + alloc_size = count * sizeof(int) + count * sizeof(cputime64_t); + all_stat->time_in_state = kzalloc(alloc_size, GFP_KERNEL); + if (!all_stat->time_in_state) { + pr_warn("Cannot allocate memory for cpufreq time_in_state\n"); + kfree(all_stat); + all_stat = NULL; + return; + } + all_stat->freq_table = (unsigned int *) + (all_stat->time_in_state + count); + + spin_lock(&cpufreq_stats_lock); + for (i = 0; table[i].frequency != CPUFREQ_TABLE_END; i++) { + unsigned int freq = table[i].frequency; + if (freq == CPUFREQ_ENTRY_INVALID) + continue; + all_stat->freq_table[j++] = freq; + if (all_freq_table && !check_all_freq_table(freq)) { + add_all_freq_table(freq); + sort_needed = true; + } + } + if (sort_needed) + sort(all_freq_table->freq_table, all_freq_table->table_size, + sizeof(unsigned int), &compare_for_sort, NULL); + all_stat->state_num = j; + per_cpu(all_cpufreq_stats, cpu) = all_stat; + spin_unlock(&cpufreq_stats_lock); +} + static void cpufreq_stats_create_table(unsigned int cpu) { struct cpufreq_policy *policy; - + struct cpufreq_frequency_table *table, *pos; + int count = 0; /* * "likely(!policy)" because normally cpufreq_stats will be registered * before cpufreq driver @@ -254,37 +575,52 @@ static void cpufreq_stats_create_table(unsigned int cpu) if (likely(!policy)) return; - __cpufreq_stats_create_table(policy); + table = cpufreq_frequency_get_table(policy->cpu); + if (likely(table)) { + cpufreq_for_each_valid_entry(pos, table) + count++; - cpufreq_cpu_put(policy); -} + if (!per_cpu(all_cpufreq_stats, cpu)) + cpufreq_allstats_create(cpu, table, count); -static void cpufreq_stats_update_policy_cpu(struct cpufreq_policy *policy) -{ - struct cpufreq_stats *stat = per_cpu(cpufreq_stats_table, - policy->last_cpu); + if (!per_cpu(cpufreq_power_stats, cpu)) + cpufreq_powerstats_create(cpu, table, count); - pr_debug("Updating stats_table for new_cpu %u from last_cpu %u\n", - policy->cpu, policy->last_cpu); - per_cpu(cpufreq_stats_table, policy->cpu) = per_cpu(cpufreq_stats_table, - policy->last_cpu); - per_cpu(cpufreq_stats_table, policy->last_cpu) = NULL; - stat->cpu = policy->cpu; + __cpufreq_stats_create_table(policy, table, count); + } + cpufreq_cpu_put(policy); } static int cpufreq_stat_notifier_policy(struct notifier_block *nb, unsigned long val, void *data) { - int ret = 0; + int ret = 0, count = 0; struct cpufreq_policy *policy = data; + struct cpufreq_frequency_table *table, *pos; + unsigned int cpu_num, cpu = policy->cpu; if (val == CPUFREQ_UPDATE_POLICY_CPU) { cpufreq_stats_update_policy_cpu(policy); return 0; } + table = cpufreq_frequency_get_table(cpu); + if (!table) + return 0; + + cpufreq_for_each_valid_entry(pos, table) + count++; + + if (!per_cpu(all_cpufreq_stats, cpu)) + cpufreq_allstats_create(cpu, table, count); + + for_each_possible_cpu(cpu_num) { + if (!per_cpu(cpufreq_power_stats, cpu_num)) + cpufreq_powerstats_create(cpu_num, table, count); + } + if (val == CPUFREQ_CREATE_POLICY) - ret = __cpufreq_stats_create_table(policy); + ret = __cpufreq_stats_create_table(policy, table, count); else if (val == CPUFREQ_REMOVE_POLICY) __cpufreq_stats_free_table(policy); @@ -359,6 +695,18 @@ static int __init cpufreq_stats_init(void) return ret; } + create_all_freq_table(); + WARN_ON(cpufreq_get_global_kobject()); + ret = sysfs_create_file(cpufreq_global_kobject, + &_attr_all_time_in_state.attr); + if (ret) + pr_warn("Cannot create sysfs file for cpufreq stats\n"); + + ret = sysfs_create_file(cpufreq_global_kobject, + &_attr_current_in_state.attr); + if (ret) + pr_warn("Cannot create sysfs file for cpufreq current stats\n"); + return 0; } static void __exit cpufreq_stats_exit(void) @@ -371,6 +719,9 @@ static void __exit cpufreq_stats_exit(void) CPUFREQ_TRANSITION_NOTIFIER); for_each_online_cpu(cpu) cpufreq_stats_free_table(cpu); + cpufreq_allstats_free(); + cpufreq_powerstats_free(); + cpufreq_put_global_kobject(); } MODULE_AUTHOR("Zou Nan hai <nanhai.zou@intel.com>"); |