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 18 static spinlock_t cpufreq_stats_lock; 19 20 struct 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 33 static 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 44 static ssize_t show_total_trans(struct cpufreq_policy *policy, char *buf) 45 { 46 return sprintf(buf, "%d\n", policy->stats->total_trans); 47 } 48 49 static 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 65 static 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 } 105 cpufreq_freq_attr_ro(trans_table); 106 #endif 107 108 cpufreq_freq_attr_ro(total_trans); 109 cpufreq_freq_attr_ro(time_in_state); 110 111 static 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 }; 119 static struct attribute_group stats_attr_group = { 120 .attrs = default_attrs, 121 .name = "stats" 122 }; 123 124 static 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 133 static 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 149 static 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 162 static 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); 223 free_stat: 224 kfree(stats); 225 226 return ret; 227 } 228 229 static 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 246 static 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 260 static 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 301 put_policy: 302 cpufreq_cpu_put(policy); 303 return 0; 304 } 305 306 static struct notifier_block notifier_policy_block = { 307 .notifier_call = cpufreq_stat_notifier_policy 308 }; 309 310 static struct notifier_block notifier_trans_block = { 311 .notifier_call = cpufreq_stat_notifier_trans 312 }; 313 314 static 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 } 340 static 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 352 MODULE_AUTHOR("Zou Nan hai <nanhai.zou@intel.com>"); 353 MODULE_DESCRIPTION("Export cpufreq stats via sysfs"); 354 MODULE_LICENSE("GPL"); 355 356 module_init(cpufreq_stats_init); 357 module_exit(cpufreq_stats_exit); 358