Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0-only 2 : : /* 3 : : * drivers/cpufreq/cpufreq_stats.c 4 : : * 5 : : * Copyright (C) 2003-2004 Venkatesh Pallipadi <venkatesh.pallipadi@intel.com>. 6 : : * (C) 2004 Zou Nan hai <nanhai.zou@intel.com>. 7 : : */ 8 : : 9 : : #include <linux/cpu.h> 10 : : #include <linux/cpufreq.h> 11 : : #include <linux/module.h> 12 : : #include <linux/slab.h> 13 : : 14 : : 15 : : struct cpufreq_stats { 16 : : unsigned int total_trans; 17 : : unsigned long long last_time; 18 : : unsigned int max_state; 19 : : unsigned int state_num; 20 : : unsigned int last_index; 21 : : u64 *time_in_state; 22 : : spinlock_t lock; 23 : : unsigned int *freq_table; 24 : : unsigned int *trans_table; 25 : : }; 26 : : 27 : 2 : static void cpufreq_stats_update(struct cpufreq_stats *stats) 28 : : { 29 : 2 : unsigned long long cur_time = get_jiffies_64(); 30 : : 31 : 2 : stats->time_in_state[stats->last_index] += cur_time - stats->last_time; 32 : 2 : stats->last_time = cur_time; 33 : 2 : } 34 : : 35 : 0 : static void cpufreq_stats_clear_table(struct cpufreq_stats *stats) 36 : : { 37 : 0 : unsigned int count = stats->max_state; 38 : : 39 : : spin_lock(&stats->lock); 40 : 0 : memset(stats->time_in_state, 0, count * sizeof(u64)); 41 : 0 : memset(stats->trans_table, 0, count * count * sizeof(int)); 42 : 0 : stats->last_time = get_jiffies_64(); 43 : 0 : stats->total_trans = 0; 44 : : spin_unlock(&stats->lock); 45 : 0 : } 46 : : 47 : 0 : static ssize_t show_total_trans(struct cpufreq_policy *policy, char *buf) 48 : : { 49 : 0 : return sprintf(buf, "%d\n", policy->stats->total_trans); 50 : : } 51 : : cpufreq_freq_attr_ro(total_trans); 52 : : 53 : 0 : static ssize_t show_time_in_state(struct cpufreq_policy *policy, char *buf) 54 : : { 55 : 0 : struct cpufreq_stats *stats = policy->stats; 56 : : ssize_t len = 0; 57 : : int i; 58 : : 59 : 0 : if (policy->fast_switch_enabled) 60 : : return 0; 61 : : 62 : : spin_lock(&stats->lock); 63 : 0 : cpufreq_stats_update(stats); 64 : : spin_unlock(&stats->lock); 65 : : 66 : 0 : for (i = 0; i < stats->state_num; i++) { 67 : 0 : len += sprintf(buf + len, "%u %llu\n", stats->freq_table[i], 68 : : (unsigned long long) 69 : 0 : jiffies_64_to_clock_t(stats->time_in_state[i])); 70 : : } 71 : 0 : return len; 72 : : } 73 : : cpufreq_freq_attr_ro(time_in_state); 74 : : 75 : 0 : static ssize_t store_reset(struct cpufreq_policy *policy, const char *buf, 76 : : size_t count) 77 : : { 78 : : /* We don't care what is written to the attribute. */ 79 : 0 : cpufreq_stats_clear_table(policy->stats); 80 : 0 : return count; 81 : : } 82 : : cpufreq_freq_attr_wo(reset); 83 : : 84 : 0 : static ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf) 85 : : { 86 : 0 : struct cpufreq_stats *stats = policy->stats; 87 : : ssize_t len = 0; 88 : : int i, j; 89 : : 90 : 0 : if (policy->fast_switch_enabled) 91 : : return 0; 92 : : 93 : 0 : len += snprintf(buf + len, PAGE_SIZE - len, " From : To\n"); 94 : 0 : len += snprintf(buf + len, PAGE_SIZE - len, " : "); 95 : 0 : for (i = 0; i < stats->state_num; i++) { 96 : 0 : if (len >= PAGE_SIZE) 97 : : break; 98 : 0 : len += snprintf(buf + len, PAGE_SIZE - len, "%9u ", 99 : 0 : stats->freq_table[i]); 100 : : } 101 : 0 : if (len >= PAGE_SIZE) 102 : : return PAGE_SIZE; 103 : : 104 : 0 : len += snprintf(buf + len, PAGE_SIZE - len, "\n"); 105 : : 106 : 0 : for (i = 0; i < stats->state_num; i++) { 107 : 0 : if (len >= PAGE_SIZE) 108 : : break; 109 : : 110 : 0 : len += snprintf(buf + len, PAGE_SIZE - len, "%9u: ", 111 : 0 : stats->freq_table[i]); 112 : : 113 : 0 : for (j = 0; j < stats->state_num; j++) { 114 : 0 : if (len >= PAGE_SIZE) 115 : : break; 116 : 0 : len += snprintf(buf + len, PAGE_SIZE - len, "%9u ", 117 : 0 : stats->trans_table[i*stats->max_state+j]); 118 : : } 119 : 0 : if (len >= PAGE_SIZE) 120 : : break; 121 : 0 : len += snprintf(buf + len, PAGE_SIZE - len, "\n"); 122 : : } 123 : : 124 : 0 : if (len >= PAGE_SIZE) { 125 : 0 : pr_warn_once("cpufreq transition table exceeds PAGE_SIZE. Disabling\n"); 126 : : return -EFBIG; 127 : : } 128 : : return len; 129 : : } 130 : : cpufreq_freq_attr_ro(trans_table); 131 : : 132 : : static struct attribute *default_attrs[] = { 133 : : &total_trans.attr, 134 : : &time_in_state.attr, 135 : : &reset.attr, 136 : : &trans_table.attr, 137 : : NULL 138 : : }; 139 : : static const struct attribute_group stats_attr_group = { 140 : : .attrs = default_attrs, 141 : : .name = "stats" 142 : : }; 143 : : 144 : : static int freq_table_get_index(struct cpufreq_stats *stats, unsigned int freq) 145 : : { 146 : : int index; 147 : 2 : for (index = 0; index < stats->max_state; index++) 148 : 2 : if (stats->freq_table[index] == freq) 149 : 2 : return index; 150 : : return -1; 151 : : } 152 : : 153 : 0 : void cpufreq_stats_free_table(struct cpufreq_policy *policy) 154 : : { 155 : 0 : struct cpufreq_stats *stats = policy->stats; 156 : : 157 : : /* Already freed */ 158 : 0 : if (!stats) 159 : 0 : return; 160 : : 161 : : pr_debug("%s: Free stats table\n", __func__); 162 : : 163 : 0 : sysfs_remove_group(&policy->kobj, &stats_attr_group); 164 : 0 : kfree(stats->time_in_state); 165 : 0 : kfree(stats); 166 : 0 : policy->stats = NULL; 167 : : } 168 : : 169 : 2 : void cpufreq_stats_create_table(struct cpufreq_policy *policy) 170 : : { 171 : : unsigned int i = 0, count = 0, ret = -ENOMEM; 172 : : struct cpufreq_stats *stats; 173 : : unsigned int alloc_size; 174 : : struct cpufreq_frequency_table *pos; 175 : : 176 : 2 : count = cpufreq_table_count_valid_entries(policy); 177 : 2 : if (!count) 178 : : return; 179 : : 180 : : /* stats already initialized */ 181 : 2 : if (policy->stats) 182 : : return; 183 : : 184 : 2 : stats = kzalloc(sizeof(*stats), GFP_KERNEL); 185 : 2 : if (!stats) 186 : : return; 187 : : 188 : 2 : alloc_size = count * sizeof(int) + count * sizeof(u64); 189 : : 190 : 2 : alloc_size += count * count * sizeof(int); 191 : : 192 : : /* Allocate memory for time_in_state/freq_table/trans_table in one go */ 193 : 2 : stats->time_in_state = kzalloc(alloc_size, GFP_KERNEL); 194 : 2 : if (!stats->time_in_state) 195 : : goto free_stat; 196 : : 197 : 2 : stats->freq_table = (unsigned int *)(stats->time_in_state + count); 198 : : 199 : 2 : stats->trans_table = stats->freq_table + count; 200 : : 201 : 2 : stats->max_state = count; 202 : : 203 : : /* Find valid-unique entries */ 204 : 2 : cpufreq_for_each_valid_entry(pos, policy->freq_table) 205 : 2 : if (freq_table_get_index(stats, pos->frequency) == -1) 206 : 2 : stats->freq_table[i++] = pos->frequency; 207 : : 208 : 2 : stats->state_num = i; 209 : 2 : stats->last_time = get_jiffies_64(); 210 : 2 : stats->last_index = freq_table_get_index(stats, policy->cur); 211 : 2 : spin_lock_init(&stats->lock); 212 : : 213 : 2 : policy->stats = stats; 214 : 2 : ret = sysfs_create_group(&policy->kobj, &stats_attr_group); 215 : 2 : if (!ret) 216 : : return; 217 : : 218 : : /* We failed, release resources */ 219 : 0 : policy->stats = NULL; 220 : 0 : kfree(stats->time_in_state); 221 : : free_stat: 222 : 0 : kfree(stats); 223 : : } 224 : : 225 : 2 : void cpufreq_stats_record_transition(struct cpufreq_policy *policy, 226 : : unsigned int new_freq) 227 : : { 228 : 2 : struct cpufreq_stats *stats = policy->stats; 229 : : int old_index, new_index; 230 : : 231 : 2 : if (!stats) { 232 : : pr_debug("%s: No stats found\n", __func__); 233 : : return; 234 : : } 235 : : 236 : 2 : old_index = stats->last_index; 237 : : new_index = freq_table_get_index(stats, new_freq); 238 : : 239 : : /* We can't do stats->time_in_state[-1]= .. */ 240 : 2 : if (old_index == -1 || new_index == -1 || old_index == new_index) 241 : : return; 242 : : 243 : : spin_lock(&stats->lock); 244 : 2 : cpufreq_stats_update(stats); 245 : : 246 : 2 : stats->last_index = new_index; 247 : 2 : stats->trans_table[old_index * stats->max_state + new_index]++; 248 : 2 : stats->total_trans++; 249 : : spin_unlock(&stats->lock); 250 : 0 : }