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> 127854c752SViresh Kumar #include <linux/sched/clock.h> 135ff0a268SViresh Kumar #include <linux/slab.h> 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; 221da177e4SLinus Torvalds unsigned int *freq_table; 231da177e4SLinus Torvalds unsigned int *trans_table; 2440c3bd4cSViresh Kumar 2540c3bd4cSViresh Kumar /* Deferred reset */ 2640c3bd4cSViresh Kumar unsigned int reset_pending; 2740c3bd4cSViresh Kumar unsigned long long reset_time; 281da177e4SLinus Torvalds }; 291da177e4SLinus Torvalds 3040c3bd4cSViresh Kumar static void cpufreq_stats_update(struct cpufreq_stats *stats, 3140c3bd4cSViresh Kumar unsigned long long time) 321da177e4SLinus Torvalds { 337854c752SViresh Kumar unsigned long long cur_time = local_clock(); 3458f1df25SVenkatesh Pallipadi 3540c3bd4cSViresh Kumar stats->time_in_state[stats->last_index] += cur_time - time; 3650941607SViresh Kumar stats->last_time = cur_time; 371da177e4SLinus Torvalds } 381da177e4SLinus Torvalds 3940c3bd4cSViresh Kumar static void cpufreq_stats_reset_table(struct cpufreq_stats *stats) 40ee7930eeSMarkus Mayer { 41ee7930eeSMarkus Mayer unsigned int count = stats->max_state; 42ee7930eeSMarkus Mayer 43ee7930eeSMarkus Mayer memset(stats->time_in_state, 0, count * sizeof(u64)); 44ee7930eeSMarkus Mayer memset(stats->trans_table, 0, count * count * sizeof(int)); 457854c752SViresh Kumar stats->last_time = local_clock(); 46ee7930eeSMarkus Mayer stats->total_trans = 0; 4740c3bd4cSViresh Kumar 4840c3bd4cSViresh Kumar /* Adjust for the time elapsed since reset was requested */ 4940c3bd4cSViresh Kumar WRITE_ONCE(stats->reset_pending, 0); 50efad4240SRafael J. Wysocki /* 51efad4240SRafael J. Wysocki * Prevent the reset_time read from being reordered before the 52efad4240SRafael J. Wysocki * reset_pending accesses in cpufreq_stats_record_transition(). 53efad4240SRafael J. Wysocki */ 54efad4240SRafael J. Wysocki smp_rmb(); 5540c3bd4cSViresh Kumar cpufreq_stats_update(stats, READ_ONCE(stats->reset_time)); 56ee7930eeSMarkus Mayer } 57ee7930eeSMarkus Mayer 580a829c5aSDave Jones static ssize_t show_total_trans(struct cpufreq_policy *policy, char *buf) 591da177e4SLinus Torvalds { 6040c3bd4cSViresh Kumar struct cpufreq_stats *stats = policy->stats; 6140c3bd4cSViresh Kumar 6240c3bd4cSViresh Kumar if (READ_ONCE(stats->reset_pending)) 6340c3bd4cSViresh Kumar return sprintf(buf, "%d\n", 0); 6440c3bd4cSViresh Kumar else 65b7af6080SViresh Kumar return sprintf(buf, "%u\n", stats->total_trans); 661da177e4SLinus Torvalds } 6710b81821SViresh Kumar cpufreq_freq_attr_ro(total_trans); 681da177e4SLinus Torvalds 690a829c5aSDave Jones static ssize_t show_time_in_state(struct cpufreq_policy *policy, char *buf) 701da177e4SLinus Torvalds { 7150941607SViresh Kumar struct cpufreq_stats *stats = policy->stats; 7240c3bd4cSViresh Kumar bool pending = READ_ONCE(stats->reset_pending); 7340c3bd4cSViresh Kumar unsigned long long time; 741da177e4SLinus Torvalds ssize_t len = 0; 751da177e4SLinus Torvalds int i; 76a9aaf291SViresh Kumar 7750941607SViresh Kumar for (i = 0; i < stats->state_num; i++) { 7840c3bd4cSViresh Kumar if (pending) { 79efad4240SRafael J. Wysocki if (i == stats->last_index) { 80efad4240SRafael J. Wysocki /* 81efad4240SRafael J. Wysocki * Prevent the reset_time read from occurring 82efad4240SRafael J. Wysocki * before the reset_pending read above. 83efad4240SRafael J. Wysocki */ 84efad4240SRafael J. Wysocki smp_rmb(); 857854c752SViresh Kumar time = local_clock() - READ_ONCE(stats->reset_time); 86efad4240SRafael J. Wysocki } else { 8740c3bd4cSViresh Kumar time = 0; 88efad4240SRafael J. Wysocki } 8940c3bd4cSViresh Kumar } else { 9040c3bd4cSViresh Kumar time = stats->time_in_state[i]; 9140c3bd4cSViresh Kumar if (i == stats->last_index) 927854c752SViresh Kumar time += local_clock() - stats->last_time; 9340c3bd4cSViresh Kumar } 9440c3bd4cSViresh Kumar 9550941607SViresh Kumar len += sprintf(buf + len, "%u %llu\n", stats->freq_table[i], 967854c752SViresh Kumar nsec_to_clock_t(time)); 971da177e4SLinus Torvalds } 981da177e4SLinus Torvalds return len; 991da177e4SLinus Torvalds } 10010b81821SViresh Kumar cpufreq_freq_attr_ro(time_in_state); 1011da177e4SLinus Torvalds 10240c3bd4cSViresh Kumar /* We don't care what is written to the attribute */ 103ee7930eeSMarkus Mayer static ssize_t store_reset(struct cpufreq_policy *policy, const char *buf, 104ee7930eeSMarkus Mayer size_t count) 105ee7930eeSMarkus Mayer { 10640c3bd4cSViresh Kumar struct cpufreq_stats *stats = policy->stats; 10740c3bd4cSViresh Kumar 10840c3bd4cSViresh Kumar /* 10940c3bd4cSViresh Kumar * Defer resetting of stats to cpufreq_stats_record_transition() to 11040c3bd4cSViresh Kumar * avoid races. 11140c3bd4cSViresh Kumar */ 1127854c752SViresh Kumar WRITE_ONCE(stats->reset_time, local_clock()); 113efad4240SRafael J. Wysocki /* 114efad4240SRafael J. Wysocki * The memory barrier below is to prevent the readers of reset_time from 115efad4240SRafael J. Wysocki * seeing a stale or partially updated value. 116efad4240SRafael J. Wysocki */ 117efad4240SRafael J. Wysocki smp_wmb(); 11840c3bd4cSViresh Kumar WRITE_ONCE(stats->reset_pending, 1); 11940c3bd4cSViresh Kumar 120ee7930eeSMarkus Mayer return count; 121ee7930eeSMarkus Mayer } 12210b81821SViresh Kumar cpufreq_freq_attr_wo(reset); 123ee7930eeSMarkus Mayer 1240a829c5aSDave Jones static ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf) 1251da177e4SLinus Torvalds { 12650941607SViresh Kumar struct cpufreq_stats *stats = policy->stats; 12740c3bd4cSViresh Kumar bool pending = READ_ONCE(stats->reset_pending); 1281da177e4SLinus Torvalds ssize_t len = 0; 12940c3bd4cSViresh Kumar int i, j, count; 1301da177e4SLinus Torvalds 1313c0897c1STakashi Iwai len += scnprintf(buf + len, PAGE_SIZE - len, " From : To\n"); 1323c0897c1STakashi Iwai len += scnprintf(buf + len, PAGE_SIZE - len, " : "); 13350941607SViresh Kumar for (i = 0; i < stats->state_num; i++) { 1341da177e4SLinus Torvalds if (len >= PAGE_SIZE) 1351da177e4SLinus Torvalds break; 1363c0897c1STakashi Iwai len += scnprintf(buf + len, PAGE_SIZE - len, "%9u ", 13750941607SViresh Kumar stats->freq_table[i]); 13858f1df25SVenkatesh Pallipadi } 13958f1df25SVenkatesh Pallipadi if (len >= PAGE_SIZE) 14025aca347SCesar Eduardo Barros return PAGE_SIZE; 14158f1df25SVenkatesh Pallipadi 1423c0897c1STakashi Iwai len += scnprintf(buf + len, PAGE_SIZE - len, "\n"); 14358f1df25SVenkatesh Pallipadi 14450941607SViresh Kumar for (i = 0; i < stats->state_num; i++) { 14558f1df25SVenkatesh Pallipadi if (len >= PAGE_SIZE) 14658f1df25SVenkatesh Pallipadi break; 14758f1df25SVenkatesh Pallipadi 1483c0897c1STakashi Iwai len += scnprintf(buf + len, PAGE_SIZE - len, "%9u: ", 14950941607SViresh Kumar stats->freq_table[i]); 1501da177e4SLinus Torvalds 15150941607SViresh Kumar for (j = 0; j < stats->state_num; j++) { 1521da177e4SLinus Torvalds if (len >= PAGE_SIZE) 1531da177e4SLinus Torvalds break; 15440c3bd4cSViresh Kumar 15540c3bd4cSViresh Kumar if (pending) 15640c3bd4cSViresh Kumar count = 0; 15740c3bd4cSViresh Kumar else 15840c3bd4cSViresh Kumar count = stats->trans_table[i * stats->max_state + j]; 15940c3bd4cSViresh Kumar 16040c3bd4cSViresh Kumar len += scnprintf(buf + len, PAGE_SIZE - len, "%9u ", count); 1611da177e4SLinus Torvalds } 16225aca347SCesar Eduardo Barros if (len >= PAGE_SIZE) 16325aca347SCesar Eduardo Barros break; 1643c0897c1STakashi Iwai len += scnprintf(buf + len, PAGE_SIZE - len, "\n"); 1651da177e4SLinus Torvalds } 166f7bc9b20SGautham R. Shenoy 167f7bc9b20SGautham R. Shenoy if (len >= PAGE_SIZE) { 168f7bc9b20SGautham R. Shenoy pr_warn_once("cpufreq transition table exceeds PAGE_SIZE. Disabling\n"); 169f7bc9b20SGautham R. Shenoy return -EFBIG; 170f7bc9b20SGautham R. Shenoy } 1711da177e4SLinus Torvalds return len; 1721da177e4SLinus Torvalds } 173df18e504SViresh Kumar cpufreq_freq_attr_ro(trans_table); 1741da177e4SLinus Torvalds 1751da177e4SLinus Torvalds static struct attribute *default_attrs[] = { 176df18e504SViresh Kumar &total_trans.attr, 177df18e504SViresh Kumar &time_in_state.attr, 178ee7930eeSMarkus Mayer &reset.attr, 179df18e504SViresh Kumar &trans_table.attr, 1801da177e4SLinus Torvalds NULL 1811da177e4SLinus Torvalds }; 182402202e8SArvind Yadav static const struct attribute_group stats_attr_group = { 1831da177e4SLinus Torvalds .attrs = default_attrs, 1841da177e4SLinus Torvalds .name = "stats" 1851da177e4SLinus Torvalds }; 1861da177e4SLinus Torvalds 18750941607SViresh Kumar static int freq_table_get_index(struct cpufreq_stats *stats, unsigned int freq) 1881da177e4SLinus Torvalds { 1891da177e4SLinus Torvalds int index; 19050941607SViresh Kumar for (index = 0; index < stats->max_state; index++) 19150941607SViresh Kumar if (stats->freq_table[index] == freq) 1921da177e4SLinus Torvalds return index; 1931da177e4SLinus Torvalds return -1; 1941da177e4SLinus Torvalds } 1951da177e4SLinus Torvalds 1961aefc75bSRafael J. Wysocki void cpufreq_stats_free_table(struct cpufreq_policy *policy) 1971da177e4SLinus Torvalds { 19850941607SViresh Kumar struct cpufreq_stats *stats = policy->stats; 199b8eed8afSViresh Kumar 200a9aaf291SViresh Kumar /* Already freed */ 20150941607SViresh Kumar if (!stats) 2022d13594dSViresh Kumar return; 2032d13594dSViresh Kumar 20450941607SViresh Kumar pr_debug("%s: Free stats table\n", __func__); 2052d13594dSViresh Kumar 2062d13594dSViresh Kumar sysfs_remove_group(&policy->kobj, &stats_attr_group); 20750941607SViresh Kumar kfree(stats->time_in_state); 20850941607SViresh Kumar kfree(stats); 209a9aaf291SViresh Kumar policy->stats = NULL; 210b8eed8afSViresh Kumar } 21198586ed8Ssteven finney 2121aefc75bSRafael J. Wysocki void cpufreq_stats_create_table(struct cpufreq_policy *policy) 2131da177e4SLinus Torvalds { 214*5de12625SShaokun Zhang unsigned int i = 0, count; 21550941607SViresh Kumar struct cpufreq_stats *stats; 2161da177e4SLinus Torvalds unsigned int alloc_size; 21755d85293SViresh Kumar struct cpufreq_frequency_table *pos; 218ad4c2302SSaravana Kannan 21955d85293SViresh Kumar count = cpufreq_table_count_valid_entries(policy); 22055d85293SViresh Kumar if (!count) 2211aefc75bSRafael J. Wysocki return; 222ad4c2302SSaravana Kannan 223b8c67448SViresh Kumar /* stats already initialized */ 224a9aaf291SViresh Kumar if (policy->stats) 2251aefc75bSRafael J. Wysocki return; 226b8c67448SViresh Kumar 22750941607SViresh Kumar stats = kzalloc(sizeof(*stats), GFP_KERNEL); 228a685c6d0SViresh Kumar if (!stats) 2291aefc75bSRafael J. Wysocki return; 2301da177e4SLinus Torvalds 2311e7586a1SViresh Kumar alloc_size = count * sizeof(int) + count * sizeof(u64); 2321da177e4SLinus Torvalds 2331da177e4SLinus Torvalds alloc_size += count * count * sizeof(int); 234a685c6d0SViresh Kumar 235a685c6d0SViresh Kumar /* Allocate memory for time_in_state/freq_table/trans_table in one go */ 23650941607SViresh Kumar stats->time_in_state = kzalloc(alloc_size, GFP_KERNEL); 237a685c6d0SViresh Kumar if (!stats->time_in_state) 238a685c6d0SViresh Kumar goto free_stat; 239a685c6d0SViresh Kumar 24050941607SViresh Kumar stats->freq_table = (unsigned int *)(stats->time_in_state + count); 2411da177e4SLinus Torvalds 24250941607SViresh Kumar stats->trans_table = stats->freq_table + count; 243a685c6d0SViresh Kumar 244a685c6d0SViresh Kumar stats->max_state = count; 245a685c6d0SViresh Kumar 246a685c6d0SViresh Kumar /* Find valid-unique entries */ 24755d85293SViresh Kumar cpufreq_for_each_valid_entry(pos, policy->freq_table) 24850941607SViresh Kumar if (freq_table_get_index(stats, pos->frequency) == -1) 24950941607SViresh Kumar stats->freq_table[i++] = pos->frequency; 250a685c6d0SViresh Kumar 251490285c6SViresh Kumar stats->state_num = i; 2527854c752SViresh Kumar stats->last_time = local_clock(); 25350941607SViresh Kumar stats->last_index = freq_table_get_index(stats, policy->cur); 254a685c6d0SViresh Kumar 255a685c6d0SViresh Kumar policy->stats = stats; 256*5de12625SShaokun Zhang if (!sysfs_create_group(&policy->kobj, &stats_attr_group)) 2571aefc75bSRafael J. Wysocki return; 258a685c6d0SViresh Kumar 259a685c6d0SViresh Kumar /* We failed, release resources */ 260a9aaf291SViresh Kumar policy->stats = NULL; 261a685c6d0SViresh Kumar kfree(stats->time_in_state); 262a685c6d0SViresh Kumar free_stat: 263a685c6d0SViresh Kumar kfree(stats); 2641da177e4SLinus Torvalds } 2651da177e4SLinus Torvalds 2661aefc75bSRafael J. Wysocki void cpufreq_stats_record_transition(struct cpufreq_policy *policy, 2671aefc75bSRafael J. Wysocki unsigned int new_freq) 268b3f9ff88SViresh Kumar { 2691aefc75bSRafael J. Wysocki struct cpufreq_stats *stats = policy->stats; 2701da177e4SLinus Torvalds int old_index, new_index; 2711da177e4SLinus Torvalds 2724958b46eSViresh Kumar if (unlikely(!stats)) 2731aefc75bSRafael J. Wysocki return; 27440c3bd4cSViresh Kumar 27540c3bd4cSViresh Kumar if (unlikely(READ_ONCE(stats->reset_pending))) 27640c3bd4cSViresh Kumar cpufreq_stats_reset_table(stats); 277a9aaf291SViresh Kumar 27850941607SViresh Kumar old_index = stats->last_index; 2791aefc75bSRafael J. Wysocki new_index = freq_table_get_index(stats, new_freq); 2801da177e4SLinus Torvalds 28150941607SViresh Kumar /* We can't do stats->time_in_state[-1]= .. */ 2824958b46eSViresh Kumar if (unlikely(old_index == -1 || new_index == -1 || old_index == new_index)) 2831aefc75bSRafael J. Wysocki return; 2848edc59d9SVenkatesh Pallipadi 28540c3bd4cSViresh Kumar cpufreq_stats_update(stats, stats->last_time); 286e7347694SViresh Kumar 28750941607SViresh Kumar stats->last_index = new_index; 28850941607SViresh Kumar stats->trans_table[old_index * stats->max_state + new_index]++; 28950941607SViresh Kumar stats->total_trans++; 2901da177e4SLinus Torvalds } 291