1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds  *  drivers/cpufreq/cpufreq_stats.c
41da177e4SLinus Torvalds  *
51da177e4SLinus Torvalds  *  Copyright (C) 2003-2004 Venkatesh Pallipadi <venkatesh.pallipadi@intel.com>.
61da177e4SLinus Torvalds  *  (C) 2004 Zou Nan hai <nanhai.zou@intel.com>.
71da177e4SLinus Torvalds  */
81da177e4SLinus Torvalds 
91da177e4SLinus Torvalds #include <linux/cpu.h>
101da177e4SLinus Torvalds #include <linux/cpufreq.h>
115c720d37SPaul Gortmaker #include <linux/module.h>
125ff0a268SViresh Kumar #include <linux/slab.h>
131da177e4SLinus Torvalds 
141da177e4SLinus Torvalds 
151da177e4SLinus Torvalds struct cpufreq_stats {
161da177e4SLinus Torvalds 	unsigned int total_trans;
171da177e4SLinus Torvalds 	unsigned long long last_time;
181da177e4SLinus Torvalds 	unsigned int max_state;
191da177e4SLinus Torvalds 	unsigned int state_num;
201da177e4SLinus Torvalds 	unsigned int last_index;
211e7586a1SViresh Kumar 	u64 *time_in_state;
22fcccc5c8SKyle Lin 	spinlock_t lock;
231da177e4SLinus Torvalds 	unsigned int *freq_table;
241da177e4SLinus Torvalds 	unsigned int *trans_table;
2540c3bd4cSViresh Kumar 
2640c3bd4cSViresh Kumar 	/* Deferred reset */
2740c3bd4cSViresh Kumar 	unsigned int reset_pending;
2840c3bd4cSViresh Kumar 	unsigned long long reset_time;
291da177e4SLinus Torvalds };
301da177e4SLinus Torvalds 
3140c3bd4cSViresh Kumar static void cpufreq_stats_update(struct cpufreq_stats *stats,
3240c3bd4cSViresh Kumar 				 unsigned long long time)
331da177e4SLinus Torvalds {
349531347cSViresh Kumar 	unsigned long long cur_time = get_jiffies_64();
3558f1df25SVenkatesh Pallipadi 
3640c3bd4cSViresh Kumar 	stats->time_in_state[stats->last_index] += cur_time - time;
3750941607SViresh Kumar 	stats->last_time = cur_time;
381da177e4SLinus Torvalds }
391da177e4SLinus Torvalds 
4040c3bd4cSViresh Kumar static void cpufreq_stats_reset_table(struct cpufreq_stats *stats)
41ee7930eeSMarkus Mayer {
42ee7930eeSMarkus Mayer 	unsigned int count = stats->max_state;
43ee7930eeSMarkus Mayer 
44fcccc5c8SKyle Lin 	spin_lock(&stats->lock);
45ee7930eeSMarkus Mayer 	memset(stats->time_in_state, 0, count * sizeof(u64));
46ee7930eeSMarkus Mayer 	memset(stats->trans_table, 0, count * count * sizeof(int));
47ee7930eeSMarkus Mayer 	stats->last_time = get_jiffies_64();
48ee7930eeSMarkus Mayer 	stats->total_trans = 0;
4940c3bd4cSViresh Kumar 
5040c3bd4cSViresh Kumar 	/* Adjust for the time elapsed since reset was requested */
5140c3bd4cSViresh Kumar 	WRITE_ONCE(stats->reset_pending, 0);
5240c3bd4cSViresh Kumar 	cpufreq_stats_update(stats, READ_ONCE(stats->reset_time));
53fcccc5c8SKyle Lin 	spin_unlock(&stats->lock);
54ee7930eeSMarkus Mayer }
55ee7930eeSMarkus Mayer 
560a829c5aSDave Jones static ssize_t show_total_trans(struct cpufreq_policy *policy, char *buf)
571da177e4SLinus Torvalds {
5840c3bd4cSViresh Kumar 	struct cpufreq_stats *stats = policy->stats;
5940c3bd4cSViresh Kumar 
6040c3bd4cSViresh Kumar 	if (READ_ONCE(stats->reset_pending))
6140c3bd4cSViresh Kumar 		return sprintf(buf, "%d\n", 0);
6240c3bd4cSViresh Kumar 	else
6340c3bd4cSViresh Kumar 		return sprintf(buf, "%d\n", stats->total_trans);
641da177e4SLinus Torvalds }
6510b81821SViresh Kumar cpufreq_freq_attr_ro(total_trans);
661da177e4SLinus Torvalds 
670a829c5aSDave Jones static ssize_t show_time_in_state(struct cpufreq_policy *policy, char *buf)
681da177e4SLinus Torvalds {
6950941607SViresh Kumar 	struct cpufreq_stats *stats = policy->stats;
7040c3bd4cSViresh Kumar 	bool pending = READ_ONCE(stats->reset_pending);
7140c3bd4cSViresh Kumar 	unsigned long long time;
721da177e4SLinus Torvalds 	ssize_t len = 0;
731da177e4SLinus Torvalds 	int i;
74a9aaf291SViresh Kumar 
751aefc75bSRafael J. Wysocki 	if (policy->fast_switch_enabled)
761aefc75bSRafael J. Wysocki 		return 0;
771aefc75bSRafael J. Wysocki 
7850941607SViresh Kumar 	for (i = 0; i < stats->state_num; i++) {
7940c3bd4cSViresh Kumar 		if (pending) {
8040c3bd4cSViresh Kumar 			if (i == stats->last_index)
8140c3bd4cSViresh Kumar 				time = get_jiffies_64() - READ_ONCE(stats->reset_time);
8240c3bd4cSViresh Kumar 			else
8340c3bd4cSViresh Kumar 				time = 0;
8440c3bd4cSViresh Kumar 		} else {
8540c3bd4cSViresh Kumar 			time = stats->time_in_state[i];
8640c3bd4cSViresh Kumar 			if (i == stats->last_index)
8740c3bd4cSViresh Kumar 				time += get_jiffies_64() - stats->last_time;
8840c3bd4cSViresh Kumar 		}
8940c3bd4cSViresh Kumar 
9050941607SViresh Kumar 		len += sprintf(buf + len, "%u %llu\n", stats->freq_table[i],
9140c3bd4cSViresh Kumar 			       jiffies_64_to_clock_t(time));
921da177e4SLinus Torvalds 	}
931da177e4SLinus Torvalds 	return len;
941da177e4SLinus Torvalds }
9510b81821SViresh Kumar cpufreq_freq_attr_ro(time_in_state);
961da177e4SLinus Torvalds 
9740c3bd4cSViresh Kumar /* We don't care what is written to the attribute */
98ee7930eeSMarkus Mayer static ssize_t store_reset(struct cpufreq_policy *policy, const char *buf,
99ee7930eeSMarkus Mayer 			   size_t count)
100ee7930eeSMarkus Mayer {
10140c3bd4cSViresh Kumar 	struct cpufreq_stats *stats = policy->stats;
10240c3bd4cSViresh Kumar 
10340c3bd4cSViresh Kumar 	/*
10440c3bd4cSViresh Kumar 	 * Defer resetting of stats to cpufreq_stats_record_transition() to
10540c3bd4cSViresh Kumar 	 * avoid races.
10640c3bd4cSViresh Kumar 	 */
10740c3bd4cSViresh Kumar 	WRITE_ONCE(stats->reset_time, get_jiffies_64());
10840c3bd4cSViresh Kumar 	WRITE_ONCE(stats->reset_pending, 1);
10940c3bd4cSViresh Kumar 
110ee7930eeSMarkus Mayer 	return count;
111ee7930eeSMarkus Mayer }
11210b81821SViresh Kumar cpufreq_freq_attr_wo(reset);
113ee7930eeSMarkus Mayer 
1140a829c5aSDave Jones static ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf)
1151da177e4SLinus Torvalds {
11650941607SViresh Kumar 	struct cpufreq_stats *stats = policy->stats;
11740c3bd4cSViresh Kumar 	bool pending = READ_ONCE(stats->reset_pending);
1181da177e4SLinus Torvalds 	ssize_t len = 0;
11940c3bd4cSViresh Kumar 	int i, j, count;
1201da177e4SLinus Torvalds 
1211aefc75bSRafael J. Wysocki 	if (policy->fast_switch_enabled)
1221aefc75bSRafael J. Wysocki 		return 0;
1231aefc75bSRafael J. Wysocki 
1243c0897c1STakashi Iwai 	len += scnprintf(buf + len, PAGE_SIZE - len, "   From  :    To\n");
1253c0897c1STakashi Iwai 	len += scnprintf(buf + len, PAGE_SIZE - len, "         : ");
12650941607SViresh Kumar 	for (i = 0; i < stats->state_num; i++) {
1271da177e4SLinus Torvalds 		if (len >= PAGE_SIZE)
1281da177e4SLinus Torvalds 			break;
1293c0897c1STakashi Iwai 		len += scnprintf(buf + len, PAGE_SIZE - len, "%9u ",
13050941607SViresh Kumar 				stats->freq_table[i]);
13158f1df25SVenkatesh Pallipadi 	}
13258f1df25SVenkatesh Pallipadi 	if (len >= PAGE_SIZE)
13325aca347SCesar Eduardo Barros 		return PAGE_SIZE;
13458f1df25SVenkatesh Pallipadi 
1353c0897c1STakashi Iwai 	len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
13658f1df25SVenkatesh Pallipadi 
13750941607SViresh Kumar 	for (i = 0; i < stats->state_num; i++) {
13858f1df25SVenkatesh Pallipadi 		if (len >= PAGE_SIZE)
13958f1df25SVenkatesh Pallipadi 			break;
14058f1df25SVenkatesh Pallipadi 
1413c0897c1STakashi Iwai 		len += scnprintf(buf + len, PAGE_SIZE - len, "%9u: ",
14250941607SViresh Kumar 				stats->freq_table[i]);
1431da177e4SLinus Torvalds 
14450941607SViresh Kumar 		for (j = 0; j < stats->state_num; j++) {
1451da177e4SLinus Torvalds 			if (len >= PAGE_SIZE)
1461da177e4SLinus Torvalds 				break;
14740c3bd4cSViresh Kumar 
14840c3bd4cSViresh Kumar 			if (pending)
14940c3bd4cSViresh Kumar 				count = 0;
15040c3bd4cSViresh Kumar 			else
15140c3bd4cSViresh Kumar 				count = stats->trans_table[i * stats->max_state + j];
15240c3bd4cSViresh Kumar 
15340c3bd4cSViresh Kumar 			len += scnprintf(buf + len, PAGE_SIZE - len, "%9u ", count);
1541da177e4SLinus Torvalds 		}
15525aca347SCesar Eduardo Barros 		if (len >= PAGE_SIZE)
15625aca347SCesar Eduardo Barros 			break;
1573c0897c1STakashi Iwai 		len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
1581da177e4SLinus Torvalds 	}
159f7bc9b20SGautham R. Shenoy 
160f7bc9b20SGautham R. Shenoy 	if (len >= PAGE_SIZE) {
161f7bc9b20SGautham R. Shenoy 		pr_warn_once("cpufreq transition table exceeds PAGE_SIZE. Disabling\n");
162f7bc9b20SGautham R. Shenoy 		return -EFBIG;
163f7bc9b20SGautham R. Shenoy 	}
1641da177e4SLinus Torvalds 	return len;
1651da177e4SLinus Torvalds }
166df18e504SViresh Kumar cpufreq_freq_attr_ro(trans_table);
1671da177e4SLinus Torvalds 
1681da177e4SLinus Torvalds static struct attribute *default_attrs[] = {
169df18e504SViresh Kumar 	&total_trans.attr,
170df18e504SViresh Kumar 	&time_in_state.attr,
171ee7930eeSMarkus Mayer 	&reset.attr,
172df18e504SViresh Kumar 	&trans_table.attr,
1731da177e4SLinus Torvalds 	NULL
1741da177e4SLinus Torvalds };
175402202e8SArvind Yadav static const struct attribute_group stats_attr_group = {
1761da177e4SLinus Torvalds 	.attrs = default_attrs,
1771da177e4SLinus Torvalds 	.name = "stats"
1781da177e4SLinus Torvalds };
1791da177e4SLinus Torvalds 
18050941607SViresh Kumar static int freq_table_get_index(struct cpufreq_stats *stats, unsigned int freq)
1811da177e4SLinus Torvalds {
1821da177e4SLinus Torvalds 	int index;
18350941607SViresh Kumar 	for (index = 0; index < stats->max_state; index++)
18450941607SViresh Kumar 		if (stats->freq_table[index] == freq)
1851da177e4SLinus Torvalds 			return index;
1861da177e4SLinus Torvalds 	return -1;
1871da177e4SLinus Torvalds }
1881da177e4SLinus Torvalds 
1891aefc75bSRafael J. Wysocki void cpufreq_stats_free_table(struct cpufreq_policy *policy)
1901da177e4SLinus Torvalds {
19150941607SViresh Kumar 	struct cpufreq_stats *stats = policy->stats;
192b8eed8afSViresh Kumar 
193a9aaf291SViresh Kumar 	/* Already freed */
19450941607SViresh Kumar 	if (!stats)
1952d13594dSViresh Kumar 		return;
1962d13594dSViresh Kumar 
19750941607SViresh Kumar 	pr_debug("%s: Free stats table\n", __func__);
1982d13594dSViresh Kumar 
1992d13594dSViresh Kumar 	sysfs_remove_group(&policy->kobj, &stats_attr_group);
20050941607SViresh Kumar 	kfree(stats->time_in_state);
20150941607SViresh Kumar 	kfree(stats);
202a9aaf291SViresh Kumar 	policy->stats = NULL;
203b8eed8afSViresh Kumar }
20498586ed8Ssteven finney 
2051aefc75bSRafael J. Wysocki void cpufreq_stats_create_table(struct cpufreq_policy *policy)
2061da177e4SLinus Torvalds {
207a685c6d0SViresh Kumar 	unsigned int i = 0, count = 0, ret = -ENOMEM;
20850941607SViresh Kumar 	struct cpufreq_stats *stats;
2091da177e4SLinus Torvalds 	unsigned int alloc_size;
21055d85293SViresh Kumar 	struct cpufreq_frequency_table *pos;
211ad4c2302SSaravana Kannan 
21255d85293SViresh Kumar 	count = cpufreq_table_count_valid_entries(policy);
21355d85293SViresh Kumar 	if (!count)
2141aefc75bSRafael J. Wysocki 		return;
215ad4c2302SSaravana Kannan 
216b8c67448SViresh Kumar 	/* stats already initialized */
217a9aaf291SViresh Kumar 	if (policy->stats)
2181aefc75bSRafael J. Wysocki 		return;
219b8c67448SViresh Kumar 
22050941607SViresh Kumar 	stats = kzalloc(sizeof(*stats), GFP_KERNEL);
221a685c6d0SViresh Kumar 	if (!stats)
2221aefc75bSRafael J. Wysocki 		return;
2231da177e4SLinus Torvalds 
2241e7586a1SViresh Kumar 	alloc_size = count * sizeof(int) + count * sizeof(u64);
2251da177e4SLinus Torvalds 
2261da177e4SLinus Torvalds 	alloc_size += count * count * sizeof(int);
227a685c6d0SViresh Kumar 
228a685c6d0SViresh Kumar 	/* Allocate memory for time_in_state/freq_table/trans_table in one go */
22950941607SViresh Kumar 	stats->time_in_state = kzalloc(alloc_size, GFP_KERNEL);
230a685c6d0SViresh Kumar 	if (!stats->time_in_state)
231a685c6d0SViresh Kumar 		goto free_stat;
232a685c6d0SViresh Kumar 
23350941607SViresh Kumar 	stats->freq_table = (unsigned int *)(stats->time_in_state + count);
2341da177e4SLinus Torvalds 
23550941607SViresh Kumar 	stats->trans_table = stats->freq_table + count;
236a685c6d0SViresh Kumar 
237a685c6d0SViresh Kumar 	stats->max_state = count;
238a685c6d0SViresh Kumar 
239a685c6d0SViresh Kumar 	/* Find valid-unique entries */
24055d85293SViresh Kumar 	cpufreq_for_each_valid_entry(pos, policy->freq_table)
24150941607SViresh Kumar 		if (freq_table_get_index(stats, pos->frequency) == -1)
24250941607SViresh Kumar 			stats->freq_table[i++] = pos->frequency;
243a685c6d0SViresh Kumar 
244490285c6SViresh Kumar 	stats->state_num = i;
24550941607SViresh Kumar 	stats->last_time = get_jiffies_64();
24650941607SViresh Kumar 	stats->last_index = freq_table_get_index(stats, policy->cur);
247fcccc5c8SKyle Lin 	spin_lock_init(&stats->lock);
248a685c6d0SViresh Kumar 
249a685c6d0SViresh Kumar 	policy->stats = stats;
250a685c6d0SViresh Kumar 	ret = sysfs_create_group(&policy->kobj, &stats_attr_group);
251a685c6d0SViresh Kumar 	if (!ret)
2521aefc75bSRafael J. Wysocki 		return;
253a685c6d0SViresh Kumar 
254a685c6d0SViresh Kumar 	/* We failed, release resources */
255a9aaf291SViresh Kumar 	policy->stats = NULL;
256a685c6d0SViresh Kumar 	kfree(stats->time_in_state);
257a685c6d0SViresh Kumar free_stat:
258a685c6d0SViresh Kumar 	kfree(stats);
2591da177e4SLinus Torvalds }
2601da177e4SLinus Torvalds 
2611aefc75bSRafael J. Wysocki void cpufreq_stats_record_transition(struct cpufreq_policy *policy,
2621aefc75bSRafael J. Wysocki 				     unsigned int new_freq)
263b3f9ff88SViresh Kumar {
2641aefc75bSRafael J. Wysocki 	struct cpufreq_stats *stats = policy->stats;
2651da177e4SLinus Torvalds 	int old_index, new_index;
2661da177e4SLinus Torvalds 
26740c3bd4cSViresh Kumar 	if (!stats)
2681aefc75bSRafael J. Wysocki 		return;
26940c3bd4cSViresh Kumar 
27040c3bd4cSViresh Kumar 	if (unlikely(READ_ONCE(stats->reset_pending)))
27140c3bd4cSViresh Kumar 		cpufreq_stats_reset_table(stats);
272a9aaf291SViresh Kumar 
27350941607SViresh Kumar 	old_index = stats->last_index;
2741aefc75bSRafael J. Wysocki 	new_index = freq_table_get_index(stats, new_freq);
2751da177e4SLinus Torvalds 
27650941607SViresh Kumar 	/* We can't do stats->time_in_state[-1]= .. */
2771aefc75bSRafael J. Wysocki 	if (old_index == -1 || new_index == -1 || old_index == new_index)
2781aefc75bSRafael J. Wysocki 		return;
2798edc59d9SVenkatesh Pallipadi 
280fcccc5c8SKyle Lin 	spin_lock(&stats->lock);
28140c3bd4cSViresh Kumar 	cpufreq_stats_update(stats, stats->last_time);
282e7347694SViresh Kumar 
28350941607SViresh Kumar 	stats->last_index = new_index;
28450941607SViresh Kumar 	stats->trans_table[old_index * stats->max_state + new_index]++;
28550941607SViresh Kumar 	stats->total_trans++;
286fcccc5c8SKyle Lin 	spin_unlock(&stats->lock);
2871da177e4SLinus Torvalds }
288