1/* 2 * drivers/cpufreq/cpufreq_stats.c 3 * 4 * Copyright (C) 2003-2004 Venkatesh Pallipadi <venkatesh.pallipadi@intel.com>. 5 * (C) 2004 Zou Nan hai <nanhai.zou@intel.com>. 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 2 as 9 * published by the Free Software Foundation. 10 */ 11 12#include <linux/cpu.h> 13#include <linux/cpufreq.h> 14#include <linux/module.h> 15#include <linux/slab.h> 16#include <linux/cputime.h> 17 18static spinlock_t cpufreq_stats_lock; 19 20struct cpufreq_stats { 21 unsigned int total_trans; 22 unsigned long long last_time; 23 unsigned int max_state; 24 unsigned int state_num; 25 unsigned int last_index; 26 u64 *time_in_state; 27 unsigned int *freq_table; 28#ifdef CONFIG_CPU_FREQ_STAT_DETAILS 29 unsigned int *trans_table; 30#endif 31}; 32 33static int cpufreq_stats_update(struct cpufreq_stats *stats) 34{ 35 unsigned long long cur_time = get_jiffies_64(); 36 37 spin_lock(&cpufreq_stats_lock); 38 stats->time_in_state[stats->last_index] += cur_time - stats->last_time; 39 stats->last_time = cur_time; 40 spin_unlock(&cpufreq_stats_lock); 41 return 0; 42} 43 44static ssize_t show_total_trans(struct cpufreq_policy *policy, char *buf) 45{ 46 return sprintf(buf, "%d\n", policy->stats->total_trans); 47} 48 49static ssize_t show_time_in_state(struct cpufreq_policy *policy, char *buf) 50{ 51 struct cpufreq_stats *stats = policy->stats; 52 ssize_t len = 0; 53 int i; 54 55 cpufreq_stats_update(stats); 56 for (i = 0; i < stats->state_num; i++) { 57 len += sprintf(buf + len, "%u %llu\n", stats->freq_table[i], 58 (unsigned long long) 59 jiffies_64_to_clock_t(stats->time_in_state[i])); 60 } 61 return len; 62} 63 64#ifdef CONFIG_CPU_FREQ_STAT_DETAILS 65static ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf) 66{ 67 struct cpufreq_stats *stats = policy->stats; 68 ssize_t len = 0; 69 int i, j; 70 71 len += snprintf(buf + len, PAGE_SIZE - len, " From : To\n"); 72 len += snprintf(buf + len, PAGE_SIZE - len, " : "); 73 for (i = 0; i < stats->state_num; i++) { 74 if (len >= PAGE_SIZE) 75 break; 76 len += snprintf(buf + len, PAGE_SIZE - len, "%9u ", 77 stats->freq_table[i]); 78 } 79 if (len >= PAGE_SIZE) 80 return PAGE_SIZE; 81 82 len += snprintf(buf + len, PAGE_SIZE - len, "\n"); 83 84 for (i = 0; i < stats->state_num; i++) { 85 if (len >= PAGE_SIZE) 86 break; 87 88 len += snprintf(buf + len, PAGE_SIZE - len, "%9u: ", 89 stats->freq_table[i]); 90 91 for (j = 0; j < stats->state_num; j++) { 92 if (len >= PAGE_SIZE) 93 break; 94 len += snprintf(buf + len, PAGE_SIZE - len, "%9u ", 95 stats->trans_table[i*stats->max_state+j]); 96 } 97 if (len >= PAGE_SIZE) 98 break; 99 len += snprintf(buf + len, PAGE_SIZE - len, "\n"); 100 } 101 if (len >= PAGE_SIZE) 102 return PAGE_SIZE; 103 return len; 104} 105cpufreq_freq_attr_ro(trans_table); 106#endif 107 108cpufreq_freq_attr_ro(total_trans); 109cpufreq_freq_attr_ro(time_in_state); 110 111static struct attribute *default_attrs[] = { 112 &total_trans.attr, 113 &time_in_state.attr, 114#ifdef CONFIG_CPU_FREQ_STAT_DETAILS 115 &trans_table.attr, 116#endif 117 NULL 118}; 119static struct attribute_group stats_attr_group = { 120 .attrs = default_attrs, 121 .name = "stats" 122}; 123 124static int freq_table_get_index(struct cpufreq_stats *stats, unsigned int freq) 125{ 126 int index; 127 for (index = 0; index < stats->max_state; index++) 128 if (stats->freq_table[index] == freq) 129 return index; 130 return -1; 131} 132 133static void __cpufreq_stats_free_table(struct cpufreq_policy *policy) 134{ 135 struct cpufreq_stats *stats = policy->stats; 136 137 /* Already freed */ 138 if (!stats) 139 return; 140 141 pr_debug("%s: Free stats table\n", __func__); 142 143 sysfs_remove_group(&policy->kobj, &stats_attr_group); 144 kfree(stats->time_in_state); 145 kfree(stats); 146 policy->stats = NULL; 147} 148 149static void cpufreq_stats_free_table(unsigned int cpu) 150{ 151 struct cpufreq_policy *policy; 152 153 policy = cpufreq_cpu_get(cpu); 154 if (!policy) 155 return; 156 157 __cpufreq_stats_free_table(policy); 158 159 cpufreq_cpu_put(policy); 160} 161 162static int __cpufreq_stats_create_table(struct cpufreq_policy *policy) 163{ 164 unsigned int i = 0, count = 0, ret = -ENOMEM; 165 struct cpufreq_stats *stats; 166 unsigned int alloc_size; 167 unsigned int cpu = policy->cpu; 168 struct cpufreq_frequency_table *pos, *table; 169 170 /* We need cpufreq table for creating stats table */ 171 table = cpufreq_frequency_get_table(cpu); 172 if (unlikely(!table)) 173 return 0; 174 175 /* stats already initialized */ 176 if (policy->stats) 177 return -EEXIST; 178 179 stats = kzalloc(sizeof(*stats), GFP_KERNEL); 180 if (!stats) 181 return -ENOMEM; 182 183 /* Find total allocation size */ 184 cpufreq_for_each_valid_entry(pos, table) 185 count++; 186 187 alloc_size = count * sizeof(int) + count * sizeof(u64); 188 189#ifdef CONFIG_CPU_FREQ_STAT_DETAILS 190 alloc_size += count * count * sizeof(int); 191#endif 192 193 /* Allocate memory for time_in_state/freq_table/trans_table in one go */ 194 stats->time_in_state = kzalloc(alloc_size, GFP_KERNEL); 195 if (!stats->time_in_state) 196 goto free_stat; 197 198 stats->freq_table = (unsigned int *)(stats->time_in_state + count); 199 200#ifdef CONFIG_CPU_FREQ_STAT_DETAILS 201 stats->trans_table = stats->freq_table + count; 202#endif 203 204 stats->max_state = count; 205 206 /* Find valid-unique entries */ 207 cpufreq_for_each_valid_entry(pos, table) 208 if (freq_table_get_index(stats, pos->frequency) == -1) 209 stats->freq_table[i++] = pos->frequency; 210 211 stats->state_num = i; 212 stats->last_time = get_jiffies_64(); 213 stats->last_index = freq_table_get_index(stats, policy->cur); 214 215 policy->stats = stats; 216 ret = sysfs_create_group(&policy->kobj, &stats_attr_group); 217 if (!ret) 218 return 0; 219 220 /* We failed, release resources */ 221 policy->stats = NULL; 222 kfree(stats->time_in_state); 223free_stat: 224 kfree(stats); 225 226 return ret; 227} 228 229static void cpufreq_stats_create_table(unsigned int cpu) 230{ 231 struct cpufreq_policy *policy; 232 233 /* 234 * "likely(!policy)" because normally cpufreq_stats will be registered 235 * before cpufreq driver 236 */ 237 policy = cpufreq_cpu_get(cpu); 238 if (likely(!policy)) 239 return; 240 241 __cpufreq_stats_create_table(policy); 242 243 cpufreq_cpu_put(policy); 244} 245 246static int cpufreq_stat_notifier_policy(struct notifier_block *nb, 247 unsigned long val, void *data) 248{ 249 int ret = 0; 250 struct cpufreq_policy *policy = data; 251 252 if (val == CPUFREQ_CREATE_POLICY) 253 ret = __cpufreq_stats_create_table(policy); 254 else if (val == CPUFREQ_REMOVE_POLICY) 255 __cpufreq_stats_free_table(policy); 256 257 return ret; 258} 259 260static int cpufreq_stat_notifier_trans(struct notifier_block *nb, 261 unsigned long val, void *data) 262{ 263 struct cpufreq_freqs *freq = data; 264 struct cpufreq_policy *policy = cpufreq_cpu_get(freq->cpu); 265 struct cpufreq_stats *stats; 266 int old_index, new_index; 267 268 if (!policy) { 269 pr_err("%s: No policy found\n", __func__); 270 return 0; 271 } 272 273 if (val != CPUFREQ_POSTCHANGE) 274 goto put_policy; 275 276 if (!policy->stats) { 277 pr_debug("%s: No stats found\n", __func__); 278 goto put_policy; 279 } 280 281 stats = policy->stats; 282 283 old_index = stats->last_index; 284 new_index = freq_table_get_index(stats, freq->new); 285 286 /* We can't do stats->time_in_state[-1]= .. */ 287 if (old_index == -1 || new_index == -1) 288 goto put_policy; 289 290 if (old_index == new_index) 291 goto put_policy; 292 293 cpufreq_stats_update(stats); 294 295 stats->last_index = new_index; 296#ifdef CONFIG_CPU_FREQ_STAT_DETAILS 297 stats->trans_table[old_index * stats->max_state + new_index]++; 298#endif 299 stats->total_trans++; 300 301put_policy: 302 cpufreq_cpu_put(policy); 303 return 0; 304} 305 306static struct notifier_block notifier_policy_block = { 307 .notifier_call = cpufreq_stat_notifier_policy 308}; 309 310static struct notifier_block notifier_trans_block = { 311 .notifier_call = cpufreq_stat_notifier_trans 312}; 313 314static int __init cpufreq_stats_init(void) 315{ 316 int ret; 317 unsigned int cpu; 318 319 spin_lock_init(&cpufreq_stats_lock); 320 ret = cpufreq_register_notifier(¬ifier_policy_block, 321 CPUFREQ_POLICY_NOTIFIER); 322 if (ret) 323 return ret; 324 325 for_each_online_cpu(cpu) 326 cpufreq_stats_create_table(cpu); 327 328 ret = cpufreq_register_notifier(¬ifier_trans_block, 329 CPUFREQ_TRANSITION_NOTIFIER); 330 if (ret) { 331 cpufreq_unregister_notifier(¬ifier_policy_block, 332 CPUFREQ_POLICY_NOTIFIER); 333 for_each_online_cpu(cpu) 334 cpufreq_stats_free_table(cpu); 335 return ret; 336 } 337 338 return 0; 339} 340static void __exit cpufreq_stats_exit(void) 341{ 342 unsigned int cpu; 343 344 cpufreq_unregister_notifier(¬ifier_policy_block, 345 CPUFREQ_POLICY_NOTIFIER); 346 cpufreq_unregister_notifier(¬ifier_trans_block, 347 CPUFREQ_TRANSITION_NOTIFIER); 348 for_each_online_cpu(cpu) 349 cpufreq_stats_free_table(cpu); 350} 351 352MODULE_AUTHOR("Zou Nan hai <nanhai.zou@intel.com>"); 353MODULE_DESCRIPTION("Export cpufreq stats via sysfs"); 354MODULE_LICENSE("GPL"); 355 356module_init(cpufreq_stats_init); 357module_exit(cpufreq_stats_exit); 358