xref: /linux/drivers/cpufreq/cpufreq_stats.c (revision e0bf6c5ca2d3281f231c5f0c9bf145e9513644de)
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(&notifier_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(&notifier_trans_block,
329 				CPUFREQ_TRANSITION_NOTIFIER);
330 	if (ret) {
331 		cpufreq_unregister_notifier(&notifier_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(&notifier_policy_block,
345 			CPUFREQ_POLICY_NOTIFIER);
346 	cpufreq_unregister_notifier(&notifier_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