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 DEFINE_SPINLOCK(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 void cpufreq_stats_clear_table(struct cpufreq_stats *stats) 45 { 46 unsigned int count = stats->max_state; 47 48 memset(stats->time_in_state, 0, count * sizeof(u64)); 49 #ifdef CONFIG_CPU_FREQ_STAT_DETAILS 50 memset(stats->trans_table, 0, count * count * sizeof(int)); 51 #endif 52 stats->last_time = get_jiffies_64(); 53 stats->total_trans = 0; 54 } 55 56 static ssize_t show_total_trans(struct cpufreq_policy *policy, char *buf) 57 { 58 return sprintf(buf, "%d\n", policy->stats->total_trans); 59 } 60 61 static ssize_t show_time_in_state(struct cpufreq_policy *policy, char *buf) 62 { 63 struct cpufreq_stats *stats = policy->stats; 64 ssize_t len = 0; 65 int i; 66 67 if (policy->fast_switch_enabled) 68 return 0; 69 70 cpufreq_stats_update(stats); 71 for (i = 0; i < stats->state_num; i++) { 72 len += sprintf(buf + len, "%u %llu\n", stats->freq_table[i], 73 (unsigned long long) 74 jiffies_64_to_clock_t(stats->time_in_state[i])); 75 } 76 return len; 77 } 78 79 static ssize_t store_reset(struct cpufreq_policy *policy, const char *buf, 80 size_t count) 81 { 82 /* We don't care what is written to the attribute. */ 83 cpufreq_stats_clear_table(policy->stats); 84 return count; 85 } 86 87 #ifdef CONFIG_CPU_FREQ_STAT_DETAILS 88 static ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf) 89 { 90 struct cpufreq_stats *stats = policy->stats; 91 ssize_t len = 0; 92 int i, j; 93 94 if (policy->fast_switch_enabled) 95 return 0; 96 97 len += snprintf(buf + len, PAGE_SIZE - len, " From : To\n"); 98 len += snprintf(buf + len, PAGE_SIZE - len, " : "); 99 for (i = 0; i < stats->state_num; i++) { 100 if (len >= PAGE_SIZE) 101 break; 102 len += snprintf(buf + len, PAGE_SIZE - len, "%9u ", 103 stats->freq_table[i]); 104 } 105 if (len >= PAGE_SIZE) 106 return PAGE_SIZE; 107 108 len += snprintf(buf + len, PAGE_SIZE - len, "\n"); 109 110 for (i = 0; i < stats->state_num; i++) { 111 if (len >= PAGE_SIZE) 112 break; 113 114 len += snprintf(buf + len, PAGE_SIZE - len, "%9u: ", 115 stats->freq_table[i]); 116 117 for (j = 0; j < stats->state_num; j++) { 118 if (len >= PAGE_SIZE) 119 break; 120 len += snprintf(buf + len, PAGE_SIZE - len, "%9u ", 121 stats->trans_table[i*stats->max_state+j]); 122 } 123 if (len >= PAGE_SIZE) 124 break; 125 len += snprintf(buf + len, PAGE_SIZE - len, "\n"); 126 } 127 if (len >= PAGE_SIZE) 128 return PAGE_SIZE; 129 return len; 130 } 131 cpufreq_freq_attr_ro(trans_table); 132 #endif 133 134 cpufreq_freq_attr_ro(total_trans); 135 cpufreq_freq_attr_ro(time_in_state); 136 cpufreq_freq_attr_wo(reset); 137 138 static struct attribute *default_attrs[] = { 139 &total_trans.attr, 140 &time_in_state.attr, 141 &reset.attr, 142 #ifdef CONFIG_CPU_FREQ_STAT_DETAILS 143 &trans_table.attr, 144 #endif 145 NULL 146 }; 147 static struct attribute_group stats_attr_group = { 148 .attrs = default_attrs, 149 .name = "stats" 150 }; 151 152 static int freq_table_get_index(struct cpufreq_stats *stats, unsigned int freq) 153 { 154 int index; 155 for (index = 0; index < stats->max_state; index++) 156 if (stats->freq_table[index] == freq) 157 return index; 158 return -1; 159 } 160 161 void cpufreq_stats_free_table(struct cpufreq_policy *policy) 162 { 163 struct cpufreq_stats *stats = policy->stats; 164 165 /* Already freed */ 166 if (!stats) 167 return; 168 169 pr_debug("%s: Free stats table\n", __func__); 170 171 sysfs_remove_group(&policy->kobj, &stats_attr_group); 172 kfree(stats->time_in_state); 173 kfree(stats); 174 policy->stats = NULL; 175 } 176 177 void cpufreq_stats_create_table(struct cpufreq_policy *policy) 178 { 179 unsigned int i = 0, count = 0, ret = -ENOMEM; 180 struct cpufreq_stats *stats; 181 unsigned int alloc_size; 182 struct cpufreq_frequency_table *pos, *table; 183 184 /* We need cpufreq table for creating stats table */ 185 table = policy->freq_table; 186 if (unlikely(!table)) 187 return; 188 189 /* stats already initialized */ 190 if (policy->stats) 191 return; 192 193 stats = kzalloc(sizeof(*stats), GFP_KERNEL); 194 if (!stats) 195 return; 196 197 /* Find total allocation size */ 198 cpufreq_for_each_valid_entry(pos, table) 199 count++; 200 201 alloc_size = count * sizeof(int) + count * sizeof(u64); 202 203 #ifdef CONFIG_CPU_FREQ_STAT_DETAILS 204 alloc_size += count * count * sizeof(int); 205 #endif 206 207 /* Allocate memory for time_in_state/freq_table/trans_table in one go */ 208 stats->time_in_state = kzalloc(alloc_size, GFP_KERNEL); 209 if (!stats->time_in_state) 210 goto free_stat; 211 212 stats->freq_table = (unsigned int *)(stats->time_in_state + count); 213 214 #ifdef CONFIG_CPU_FREQ_STAT_DETAILS 215 stats->trans_table = stats->freq_table + count; 216 #endif 217 218 stats->max_state = count; 219 220 /* Find valid-unique entries */ 221 cpufreq_for_each_valid_entry(pos, table) 222 if (freq_table_get_index(stats, pos->frequency) == -1) 223 stats->freq_table[i++] = pos->frequency; 224 225 stats->state_num = i; 226 stats->last_time = get_jiffies_64(); 227 stats->last_index = freq_table_get_index(stats, policy->cur); 228 229 policy->stats = stats; 230 ret = sysfs_create_group(&policy->kobj, &stats_attr_group); 231 if (!ret) 232 return; 233 234 /* We failed, release resources */ 235 policy->stats = NULL; 236 kfree(stats->time_in_state); 237 free_stat: 238 kfree(stats); 239 } 240 241 void cpufreq_stats_record_transition(struct cpufreq_policy *policy, 242 unsigned int new_freq) 243 { 244 struct cpufreq_stats *stats = policy->stats; 245 int old_index, new_index; 246 247 if (!stats) { 248 pr_debug("%s: No stats found\n", __func__); 249 return; 250 } 251 252 old_index = stats->last_index; 253 new_index = freq_table_get_index(stats, new_freq); 254 255 /* We can't do stats->time_in_state[-1]= .. */ 256 if (old_index == -1 || new_index == -1 || old_index == new_index) 257 return; 258 259 cpufreq_stats_update(stats); 260 261 stats->last_index = new_index; 262 #ifdef CONFIG_CPU_FREQ_STAT_DETAILS 263 stats->trans_table[old_index * stats->max_state + new_index]++; 264 #endif 265 stats->total_trans++; 266 } 267