Linux Audio

Check our new training course

Embedded Linux training

Mar 10-20, 2025, special US time zones
Register
Loading...
v6.8
  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/sched/clock.h>
 13#include <linux/slab.h>
 
 
 
 14
 15struct 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	unsigned int *freq_table;
 
 23	unsigned int *trans_table;
 24
 25	/* Deferred reset */
 26	unsigned int reset_pending;
 27	unsigned long long reset_time;
 28};
 29
 30static void cpufreq_stats_update(struct cpufreq_stats *stats,
 31				 unsigned long long time)
 32{
 33	unsigned long long cur_time = local_clock();
 34
 35	stats->time_in_state[stats->last_index] += cur_time - time;
 
 36	stats->last_time = cur_time;
 37}
 38
 39static void cpufreq_stats_reset_table(struct cpufreq_stats *stats)
 40{
 41	unsigned int count = stats->max_state;
 42
 43	memset(stats->time_in_state, 0, count * sizeof(u64));
 44	memset(stats->trans_table, 0, count * count * sizeof(int));
 45	stats->last_time = local_clock();
 46	stats->total_trans = 0;
 47
 48	/* Adjust for the time elapsed since reset was requested */
 49	WRITE_ONCE(stats->reset_pending, 0);
 50	/*
 51	 * Prevent the reset_time read from being reordered before the
 52	 * reset_pending accesses in cpufreq_stats_record_transition().
 53	 */
 54	smp_rmb();
 55	cpufreq_stats_update(stats, READ_ONCE(stats->reset_time));
 56}
 57
 58static ssize_t show_total_trans(struct cpufreq_policy *policy, char *buf)
 59{
 60	struct cpufreq_stats *stats = policy->stats;
 61
 62	if (READ_ONCE(stats->reset_pending))
 63		return sprintf(buf, "%d\n", 0);
 64	else
 65		return sprintf(buf, "%u\n", stats->total_trans);
 66}
 67cpufreq_freq_attr_ro(total_trans);
 68
 69static ssize_t show_time_in_state(struct cpufreq_policy *policy, char *buf)
 70{
 71	struct cpufreq_stats *stats = policy->stats;
 72	bool pending = READ_ONCE(stats->reset_pending);
 73	unsigned long long time;
 74	ssize_t len = 0;
 75	int i;
 76
 
 77	for (i = 0; i < stats->state_num; i++) {
 78		if (pending) {
 79			if (i == stats->last_index) {
 80				/*
 81				 * Prevent the reset_time read from occurring
 82				 * before the reset_pending read above.
 83				 */
 84				smp_rmb();
 85				time = local_clock() - READ_ONCE(stats->reset_time);
 86			} else {
 87				time = 0;
 88			}
 89		} else {
 90			time = stats->time_in_state[i];
 91			if (i == stats->last_index)
 92				time += local_clock() - stats->last_time;
 93		}
 94
 95		len += sprintf(buf + len, "%u %llu\n", stats->freq_table[i],
 96			       nsec_to_clock_t(time));
 
 97	}
 98	return len;
 99}
100cpufreq_freq_attr_ro(time_in_state);
101
102/* We don't care what is written to the attribute */
103static ssize_t store_reset(struct cpufreq_policy *policy, const char *buf,
104			   size_t count)
105{
106	struct cpufreq_stats *stats = policy->stats;
107
108	/*
109	 * Defer resetting of stats to cpufreq_stats_record_transition() to
110	 * avoid races.
111	 */
112	WRITE_ONCE(stats->reset_time, local_clock());
113	/*
114	 * The memory barrier below is to prevent the readers of reset_time from
115	 * seeing a stale or partially updated value.
116	 */
117	smp_wmb();
118	WRITE_ONCE(stats->reset_pending, 1);
119
120	return count;
121}
122cpufreq_freq_attr_wo(reset);
123
 
124static ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf)
125{
126	struct cpufreq_stats *stats = policy->stats;
127	bool pending = READ_ONCE(stats->reset_pending);
128	ssize_t len = 0;
129	int i, j, count;
130
131	len += sysfs_emit_at(buf, len, "   From  :    To\n");
132	len += sysfs_emit_at(buf, len, "         : ");
133	for (i = 0; i < stats->state_num; i++) {
134		if (len >= PAGE_SIZE - 1)
135			break;
136		len += sysfs_emit_at(buf, len, "%9u ", stats->freq_table[i]);
 
137	}
138	if (len >= PAGE_SIZE - 1)
139		return PAGE_SIZE - 1;
140
141	len += sysfs_emit_at(buf, len, "\n");
142
143	for (i = 0; i < stats->state_num; i++) {
144		if (len >= PAGE_SIZE - 1)
145			break;
146
147		len += sysfs_emit_at(buf, len, "%9u: ", stats->freq_table[i]);
 
148
149		for (j = 0; j < stats->state_num; j++) {
150			if (len >= PAGE_SIZE - 1)
151				break;
152
153			if (pending)
154				count = 0;
155			else
156				count = stats->trans_table[i * stats->max_state + j];
157
158			len += sysfs_emit_at(buf, len, "%9u ", count);
159		}
160		if (len >= PAGE_SIZE - 1)
161			break;
162		len += sysfs_emit_at(buf, len, "\n");
163	}
164
165	if (len >= PAGE_SIZE - 1) {
166		pr_warn_once("cpufreq transition table exceeds PAGE_SIZE. Disabling\n");
167		return -EFBIG;
168	}
 
 
169	return len;
170}
171cpufreq_freq_attr_ro(trans_table);
 
 
 
 
172
173static struct attribute *default_attrs[] = {
174	&total_trans.attr,
175	&time_in_state.attr,
176	&reset.attr,
177	&trans_table.attr,
 
178	NULL
179};
180static const struct attribute_group stats_attr_group = {
181	.attrs = default_attrs,
182	.name = "stats"
183};
184
185static int freq_table_get_index(struct cpufreq_stats *stats, unsigned int freq)
186{
187	int index;
188	for (index = 0; index < stats->max_state; index++)
189		if (stats->freq_table[index] == freq)
190			return index;
191	return -1;
192}
193
194void cpufreq_stats_free_table(struct cpufreq_policy *policy)
195{
196	struct cpufreq_stats *stats = policy->stats;
197
198	/* Already freed */
199	if (!stats)
200		return;
201
202	pr_debug("%s: Free stats table\n", __func__);
203
204	sysfs_remove_group(&policy->kobj, &stats_attr_group);
205	kfree(stats->time_in_state);
206	kfree(stats);
207	policy->stats = NULL;
208}
209
210void cpufreq_stats_create_table(struct cpufreq_policy *policy)
211{
212	unsigned int i = 0, count;
 
 
 
 
 
 
 
 
 
 
 
 
 
213	struct cpufreq_stats *stats;
214	unsigned int alloc_size;
215	struct cpufreq_frequency_table *pos;
 
216
217	count = cpufreq_table_count_valid_entries(policy);
218	if (!count)
219		return;
 
220
221	/* stats already initialized */
222	if (policy->stats)
223		return;
224
225	stats = kzalloc(sizeof(*stats), GFP_KERNEL);
226	if (!stats)
227		return;
 
 
 
 
228
229	alloc_size = count * sizeof(int) + count * sizeof(u64);
230
 
231	alloc_size += count * count * sizeof(int);
 
232
233	/* Allocate memory for time_in_state/freq_table/trans_table in one go */
234	stats->time_in_state = kzalloc(alloc_size, GFP_KERNEL);
235	if (!stats->time_in_state)
236		goto free_stat;
237
238	stats->freq_table = (unsigned int *)(stats->time_in_state + count);
239
 
240	stats->trans_table = stats->freq_table + count;
 
241
242	stats->max_state = count;
243
244	/* Find valid-unique entries */
245	cpufreq_for_each_valid_entry(pos, policy->freq_table)
246		if (policy->freq_table_sorted != CPUFREQ_TABLE_UNSORTED ||
247		    freq_table_get_index(stats, pos->frequency) == -1)
248			stats->freq_table[i++] = pos->frequency;
249
250	stats->state_num = i;
251	stats->last_time = local_clock();
252	stats->last_index = freq_table_get_index(stats, policy->cur);
253
254	policy->stats = stats;
255	if (!sysfs_create_group(&policy->kobj, &stats_attr_group))
256		return;
 
257
258	/* We failed, release resources */
259	policy->stats = NULL;
260	kfree(stats->time_in_state);
261free_stat:
262	kfree(stats);
 
 
263}
264
265void cpufreq_stats_record_transition(struct cpufreq_policy *policy,
266				     unsigned int new_freq)
267{
268	struct cpufreq_stats *stats = policy->stats;
269	int old_index, new_index;
270
271	if (unlikely(!stats))
 
 
 
 
 
272		return;
273
274	if (unlikely(READ_ONCE(stats->reset_pending)))
275		cpufreq_stats_reset_table(stats);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
277	old_index = stats->last_index;
278	new_index = freq_table_get_index(stats, new_freq);
279
280	/* We can't do stats->time_in_state[-1]= .. */
281	if (unlikely(old_index == -1 || new_index == -1 || old_index == new_index))
282		return;
 
 
 
283
284	cpufreq_stats_update(stats, stats->last_time);
285
286	stats->last_index = new_index;
 
287	stats->trans_table[old_index * stats->max_state + new_index]++;
 
288	stats->total_trans++;
 
 
 
 
289}
v4.6
 
  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);