18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  drivers/cpufreq/cpufreq_stats.c
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Copyright (C) 2003-2004 Venkatesh Pallipadi <venkatesh.pallipadi@intel.com>.
68c2ecf20Sopenharmony_ci *  (C) 2004 Zou Nan hai <nanhai.zou@intel.com>.
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/cpu.h>
108c2ecf20Sopenharmony_ci#include <linux/cpufreq.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/slab.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_cistruct cpufreq_stats {
168c2ecf20Sopenharmony_ci	unsigned int total_trans;
178c2ecf20Sopenharmony_ci	unsigned long long last_time;
188c2ecf20Sopenharmony_ci	unsigned int max_state;
198c2ecf20Sopenharmony_ci	unsigned int state_num;
208c2ecf20Sopenharmony_ci	unsigned int last_index;
218c2ecf20Sopenharmony_ci	u64 *time_in_state;
228c2ecf20Sopenharmony_ci	unsigned int *freq_table;
238c2ecf20Sopenharmony_ci	unsigned int *trans_table;
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci	/* Deferred reset */
268c2ecf20Sopenharmony_ci	unsigned int reset_pending;
278c2ecf20Sopenharmony_ci	unsigned long long reset_time;
288c2ecf20Sopenharmony_ci};
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_cistatic void cpufreq_stats_update(struct cpufreq_stats *stats,
318c2ecf20Sopenharmony_ci				 unsigned long long time)
328c2ecf20Sopenharmony_ci{
338c2ecf20Sopenharmony_ci	unsigned long long cur_time = get_jiffies_64();
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	stats->time_in_state[stats->last_index] += cur_time - time;
368c2ecf20Sopenharmony_ci	stats->last_time = cur_time;
378c2ecf20Sopenharmony_ci}
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistatic void cpufreq_stats_reset_table(struct cpufreq_stats *stats)
408c2ecf20Sopenharmony_ci{
418c2ecf20Sopenharmony_ci	unsigned int count = stats->max_state;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	memset(stats->time_in_state, 0, count * sizeof(u64));
448c2ecf20Sopenharmony_ci	memset(stats->trans_table, 0, count * count * sizeof(int));
458c2ecf20Sopenharmony_ci	stats->last_time = get_jiffies_64();
468c2ecf20Sopenharmony_ci	stats->total_trans = 0;
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	/* Adjust for the time elapsed since reset was requested */
498c2ecf20Sopenharmony_ci	WRITE_ONCE(stats->reset_pending, 0);
508c2ecf20Sopenharmony_ci	/*
518c2ecf20Sopenharmony_ci	 * Prevent the reset_time read from being reordered before the
528c2ecf20Sopenharmony_ci	 * reset_pending accesses in cpufreq_stats_record_transition().
538c2ecf20Sopenharmony_ci	 */
548c2ecf20Sopenharmony_ci	smp_rmb();
558c2ecf20Sopenharmony_ci	cpufreq_stats_update(stats, READ_ONCE(stats->reset_time));
568c2ecf20Sopenharmony_ci}
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_cistatic ssize_t show_total_trans(struct cpufreq_policy *policy, char *buf)
598c2ecf20Sopenharmony_ci{
608c2ecf20Sopenharmony_ci	struct cpufreq_stats *stats = policy->stats;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	if (READ_ONCE(stats->reset_pending))
638c2ecf20Sopenharmony_ci		return sprintf(buf, "%d\n", 0);
648c2ecf20Sopenharmony_ci	else
658c2ecf20Sopenharmony_ci		return sprintf(buf, "%u\n", stats->total_trans);
668c2ecf20Sopenharmony_ci}
678c2ecf20Sopenharmony_cicpufreq_freq_attr_ro(total_trans);
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic ssize_t show_time_in_state(struct cpufreq_policy *policy, char *buf)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	struct cpufreq_stats *stats = policy->stats;
728c2ecf20Sopenharmony_ci	bool pending = READ_ONCE(stats->reset_pending);
738c2ecf20Sopenharmony_ci	unsigned long long time;
748c2ecf20Sopenharmony_ci	ssize_t len = 0;
758c2ecf20Sopenharmony_ci	int i;
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	for (i = 0; i < stats->state_num; i++) {
788c2ecf20Sopenharmony_ci		if (pending) {
798c2ecf20Sopenharmony_ci			if (i == stats->last_index) {
808c2ecf20Sopenharmony_ci				/*
818c2ecf20Sopenharmony_ci				 * Prevent the reset_time read from occurring
828c2ecf20Sopenharmony_ci				 * before the reset_pending read above.
838c2ecf20Sopenharmony_ci				 */
848c2ecf20Sopenharmony_ci				smp_rmb();
858c2ecf20Sopenharmony_ci				time = get_jiffies_64() - READ_ONCE(stats->reset_time);
868c2ecf20Sopenharmony_ci			} else {
878c2ecf20Sopenharmony_ci				time = 0;
888c2ecf20Sopenharmony_ci			}
898c2ecf20Sopenharmony_ci		} else {
908c2ecf20Sopenharmony_ci			time = stats->time_in_state[i];
918c2ecf20Sopenharmony_ci			if (i == stats->last_index)
928c2ecf20Sopenharmony_ci				time += get_jiffies_64() - stats->last_time;
938c2ecf20Sopenharmony_ci		}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci		len += sprintf(buf + len, "%u %llu\n", stats->freq_table[i],
968c2ecf20Sopenharmony_ci			       jiffies_64_to_clock_t(time));
978c2ecf20Sopenharmony_ci	}
988c2ecf20Sopenharmony_ci	return len;
998c2ecf20Sopenharmony_ci}
1008c2ecf20Sopenharmony_cicpufreq_freq_attr_ro(time_in_state);
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci/* We don't care what is written to the attribute */
1038c2ecf20Sopenharmony_cistatic ssize_t store_reset(struct cpufreq_policy *policy, const char *buf,
1048c2ecf20Sopenharmony_ci			   size_t count)
1058c2ecf20Sopenharmony_ci{
1068c2ecf20Sopenharmony_ci	struct cpufreq_stats *stats = policy->stats;
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	/*
1098c2ecf20Sopenharmony_ci	 * Defer resetting of stats to cpufreq_stats_record_transition() to
1108c2ecf20Sopenharmony_ci	 * avoid races.
1118c2ecf20Sopenharmony_ci	 */
1128c2ecf20Sopenharmony_ci	WRITE_ONCE(stats->reset_time, get_jiffies_64());
1138c2ecf20Sopenharmony_ci	/*
1148c2ecf20Sopenharmony_ci	 * The memory barrier below is to prevent the readers of reset_time from
1158c2ecf20Sopenharmony_ci	 * seeing a stale or partially updated value.
1168c2ecf20Sopenharmony_ci	 */
1178c2ecf20Sopenharmony_ci	smp_wmb();
1188c2ecf20Sopenharmony_ci	WRITE_ONCE(stats->reset_pending, 1);
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	return count;
1218c2ecf20Sopenharmony_ci}
1228c2ecf20Sopenharmony_cicpufreq_freq_attr_wo(reset);
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_cistatic ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf)
1258c2ecf20Sopenharmony_ci{
1268c2ecf20Sopenharmony_ci	struct cpufreq_stats *stats = policy->stats;
1278c2ecf20Sopenharmony_ci	bool pending = READ_ONCE(stats->reset_pending);
1288c2ecf20Sopenharmony_ci	ssize_t len = 0;
1298c2ecf20Sopenharmony_ci	int i, j, count;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	len += scnprintf(buf + len, PAGE_SIZE - len, "   From  :    To\n");
1328c2ecf20Sopenharmony_ci	len += scnprintf(buf + len, PAGE_SIZE - len, "         : ");
1338c2ecf20Sopenharmony_ci	for (i = 0; i < stats->state_num; i++) {
1348c2ecf20Sopenharmony_ci		if (len >= PAGE_SIZE - 1)
1358c2ecf20Sopenharmony_ci			break;
1368c2ecf20Sopenharmony_ci		len += scnprintf(buf + len, PAGE_SIZE - len, "%9u ",
1378c2ecf20Sopenharmony_ci				stats->freq_table[i]);
1388c2ecf20Sopenharmony_ci	}
1398c2ecf20Sopenharmony_ci	if (len >= PAGE_SIZE - 1)
1408c2ecf20Sopenharmony_ci		return PAGE_SIZE - 1;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	for (i = 0; i < stats->state_num; i++) {
1458c2ecf20Sopenharmony_ci		if (len >= PAGE_SIZE - 1)
1468c2ecf20Sopenharmony_ci			break;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci		len += scnprintf(buf + len, PAGE_SIZE - len, "%9u: ",
1498c2ecf20Sopenharmony_ci				stats->freq_table[i]);
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci		for (j = 0; j < stats->state_num; j++) {
1528c2ecf20Sopenharmony_ci			if (len >= PAGE_SIZE - 1)
1538c2ecf20Sopenharmony_ci				break;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci			if (pending)
1568c2ecf20Sopenharmony_ci				count = 0;
1578c2ecf20Sopenharmony_ci			else
1588c2ecf20Sopenharmony_ci				count = stats->trans_table[i * stats->max_state + j];
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci			len += scnprintf(buf + len, PAGE_SIZE - len, "%9u ", count);
1618c2ecf20Sopenharmony_ci		}
1628c2ecf20Sopenharmony_ci		if (len >= PAGE_SIZE - 1)
1638c2ecf20Sopenharmony_ci			break;
1648c2ecf20Sopenharmony_ci		len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
1658c2ecf20Sopenharmony_ci	}
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	if (len >= PAGE_SIZE - 1) {
1688c2ecf20Sopenharmony_ci		pr_warn_once("cpufreq transition table exceeds PAGE_SIZE. Disabling\n");
1698c2ecf20Sopenharmony_ci		return -EFBIG;
1708c2ecf20Sopenharmony_ci	}
1718c2ecf20Sopenharmony_ci	return len;
1728c2ecf20Sopenharmony_ci}
1738c2ecf20Sopenharmony_cicpufreq_freq_attr_ro(trans_table);
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_cistatic struct attribute *default_attrs[] = {
1768c2ecf20Sopenharmony_ci	&total_trans.attr,
1778c2ecf20Sopenharmony_ci	&time_in_state.attr,
1788c2ecf20Sopenharmony_ci	&reset.attr,
1798c2ecf20Sopenharmony_ci	&trans_table.attr,
1808c2ecf20Sopenharmony_ci	NULL
1818c2ecf20Sopenharmony_ci};
1828c2ecf20Sopenharmony_cistatic const struct attribute_group stats_attr_group = {
1838c2ecf20Sopenharmony_ci	.attrs = default_attrs,
1848c2ecf20Sopenharmony_ci	.name = "stats"
1858c2ecf20Sopenharmony_ci};
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_cistatic int freq_table_get_index(struct cpufreq_stats *stats, unsigned int freq)
1888c2ecf20Sopenharmony_ci{
1898c2ecf20Sopenharmony_ci	int index;
1908c2ecf20Sopenharmony_ci	for (index = 0; index < stats->max_state; index++)
1918c2ecf20Sopenharmony_ci		if (stats->freq_table[index] == freq)
1928c2ecf20Sopenharmony_ci			return index;
1938c2ecf20Sopenharmony_ci	return -1;
1948c2ecf20Sopenharmony_ci}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_civoid cpufreq_stats_free_table(struct cpufreq_policy *policy)
1978c2ecf20Sopenharmony_ci{
1988c2ecf20Sopenharmony_ci	struct cpufreq_stats *stats = policy->stats;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	/* Already freed */
2018c2ecf20Sopenharmony_ci	if (!stats)
2028c2ecf20Sopenharmony_ci		return;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	pr_debug("%s: Free stats table\n", __func__);
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	sysfs_remove_group(&policy->kobj, &stats_attr_group);
2078c2ecf20Sopenharmony_ci	kfree(stats->time_in_state);
2088c2ecf20Sopenharmony_ci	kfree(stats);
2098c2ecf20Sopenharmony_ci	policy->stats = NULL;
2108c2ecf20Sopenharmony_ci}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_civoid cpufreq_stats_create_table(struct cpufreq_policy *policy)
2138c2ecf20Sopenharmony_ci{
2148c2ecf20Sopenharmony_ci	unsigned int i = 0, count = 0, ret = -ENOMEM;
2158c2ecf20Sopenharmony_ci	struct cpufreq_stats *stats;
2168c2ecf20Sopenharmony_ci	unsigned int alloc_size;
2178c2ecf20Sopenharmony_ci	struct cpufreq_frequency_table *pos;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	count = cpufreq_table_count_valid_entries(policy);
2208c2ecf20Sopenharmony_ci	if (!count)
2218c2ecf20Sopenharmony_ci		return;
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	/* stats already initialized */
2248c2ecf20Sopenharmony_ci	if (policy->stats)
2258c2ecf20Sopenharmony_ci		return;
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	stats = kzalloc(sizeof(*stats), GFP_KERNEL);
2288c2ecf20Sopenharmony_ci	if (!stats)
2298c2ecf20Sopenharmony_ci		return;
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	alloc_size = count * sizeof(int) + count * sizeof(u64);
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	alloc_size += count * count * sizeof(int);
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	/* Allocate memory for time_in_state/freq_table/trans_table in one go */
2368c2ecf20Sopenharmony_ci	stats->time_in_state = kzalloc(alloc_size, GFP_KERNEL);
2378c2ecf20Sopenharmony_ci	if (!stats->time_in_state)
2388c2ecf20Sopenharmony_ci		goto free_stat;
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	stats->freq_table = (unsigned int *)(stats->time_in_state + count);
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	stats->trans_table = stats->freq_table + count;
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	stats->max_state = count;
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	/* Find valid-unique entries */
2478c2ecf20Sopenharmony_ci	cpufreq_for_each_valid_entry(pos, policy->freq_table)
2488c2ecf20Sopenharmony_ci		if (freq_table_get_index(stats, pos->frequency) == -1)
2498c2ecf20Sopenharmony_ci			stats->freq_table[i++] = pos->frequency;
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	stats->state_num = i;
2528c2ecf20Sopenharmony_ci	stats->last_time = get_jiffies_64();
2538c2ecf20Sopenharmony_ci	stats->last_index = freq_table_get_index(stats, policy->cur);
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci	policy->stats = stats;
2568c2ecf20Sopenharmony_ci	ret = sysfs_create_group(&policy->kobj, &stats_attr_group);
2578c2ecf20Sopenharmony_ci	if (!ret)
2588c2ecf20Sopenharmony_ci		return;
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_ci	/* We failed, release resources */
2618c2ecf20Sopenharmony_ci	policy->stats = NULL;
2628c2ecf20Sopenharmony_ci	kfree(stats->time_in_state);
2638c2ecf20Sopenharmony_cifree_stat:
2648c2ecf20Sopenharmony_ci	kfree(stats);
2658c2ecf20Sopenharmony_ci}
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_civoid cpufreq_stats_record_transition(struct cpufreq_policy *policy,
2688c2ecf20Sopenharmony_ci				     unsigned int new_freq)
2698c2ecf20Sopenharmony_ci{
2708c2ecf20Sopenharmony_ci	struct cpufreq_stats *stats = policy->stats;
2718c2ecf20Sopenharmony_ci	int old_index, new_index;
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	if (unlikely(!stats))
2748c2ecf20Sopenharmony_ci		return;
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	if (unlikely(READ_ONCE(stats->reset_pending)))
2778c2ecf20Sopenharmony_ci		cpufreq_stats_reset_table(stats);
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	old_index = stats->last_index;
2808c2ecf20Sopenharmony_ci	new_index = freq_table_get_index(stats, new_freq);
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	/* We can't do stats->time_in_state[-1]= .. */
2838c2ecf20Sopenharmony_ci	if (unlikely(old_index == -1 || new_index == -1 || old_index == new_index))
2848c2ecf20Sopenharmony_ci		return;
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci	cpufreq_stats_update(stats, stats->last_time);
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	stats->last_index = new_index;
2898c2ecf20Sopenharmony_ci	stats->trans_table[old_index * stats->max_state + new_index]++;
2908c2ecf20Sopenharmony_ci	stats->total_trans++;
2918c2ecf20Sopenharmony_ci}
292