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(&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}
340static 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
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