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