Branch data Line data Source code
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/slab.h>
13 : :
14 : :
15 : : struct 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 : : spinlock_t lock;
23 : : unsigned int *freq_table;
24 : : unsigned int *trans_table;
25 : : };
26 : :
27 : 0 : static void cpufreq_stats_update(struct cpufreq_stats *stats)
28 : : {
29 : 0 : unsigned long long cur_time = get_jiffies_64();
30 : :
31 : 0 : stats->time_in_state[stats->last_index] += cur_time - stats->last_time;
32 : 0 : stats->last_time = cur_time;
33 : 0 : }
34 : :
35 : 0 : static void cpufreq_stats_clear_table(struct cpufreq_stats *stats)
36 : : {
37 : 0 : unsigned int count = stats->max_state;
38 : :
39 : : spin_lock(&stats->lock);
40 : 0 : memset(stats->time_in_state, 0, count * sizeof(u64));
41 : 0 : memset(stats->trans_table, 0, count * count * sizeof(int));
42 : 0 : stats->last_time = get_jiffies_64();
43 : 0 : stats->total_trans = 0;
44 : : spin_unlock(&stats->lock);
45 : 0 : }
46 : :
47 : 0 : static ssize_t show_total_trans(struct cpufreq_policy *policy, char *buf)
48 : : {
49 : 0 : return sprintf(buf, "%d\n", policy->stats->total_trans);
50 : : }
51 : : cpufreq_freq_attr_ro(total_trans);
52 : :
53 : 0 : static ssize_t show_time_in_state(struct cpufreq_policy *policy, char *buf)
54 : : {
55 : 0 : struct cpufreq_stats *stats = policy->stats;
56 : : ssize_t len = 0;
57 : : int i;
58 : :
59 [ # # ]: 0 : if (policy->fast_switch_enabled)
60 : : return 0;
61 : :
62 : : spin_lock(&stats->lock);
63 : 0 : cpufreq_stats_update(stats);
64 : : spin_unlock(&stats->lock);
65 : :
66 [ # # ]: 0 : for (i = 0; i < stats->state_num; i++) {
67 : 0 : len += sprintf(buf + len, "%u %llu\n", stats->freq_table[i],
68 : : (unsigned long long)
69 : 0 : jiffies_64_to_clock_t(stats->time_in_state[i]));
70 : : }
71 : 0 : return len;
72 : : }
73 : : cpufreq_freq_attr_ro(time_in_state);
74 : :
75 : 0 : static ssize_t store_reset(struct cpufreq_policy *policy, const char *buf,
76 : : size_t count)
77 : : {
78 : : /* We don't care what is written to the attribute. */
79 : 0 : cpufreq_stats_clear_table(policy->stats);
80 : 0 : return count;
81 : : }
82 : : cpufreq_freq_attr_wo(reset);
83 : :
84 : 0 : static ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf)
85 : : {
86 : 0 : struct cpufreq_stats *stats = policy->stats;
87 : : ssize_t len = 0;
88 : : int i, j;
89 : :
90 [ # # ]: 0 : if (policy->fast_switch_enabled)
91 : : return 0;
92 : :
93 : 0 : len += snprintf(buf + len, PAGE_SIZE - len, " From : To\n");
94 : 0 : len += snprintf(buf + len, PAGE_SIZE - len, " : ");
95 [ # # ]: 0 : for (i = 0; i < stats->state_num; i++) {
96 [ # # ]: 0 : if (len >= PAGE_SIZE)
97 : : break;
98 : 0 : len += snprintf(buf + len, PAGE_SIZE - len, "%9u ",
99 : 0 : stats->freq_table[i]);
100 : : }
101 [ # # ]: 0 : if (len >= PAGE_SIZE)
102 : : return PAGE_SIZE;
103 : :
104 : 0 : len += snprintf(buf + len, PAGE_SIZE - len, "\n");
105 : :
106 [ # # ]: 0 : for (i = 0; i < stats->state_num; i++) {
107 [ # # ]: 0 : if (len >= PAGE_SIZE)
108 : : break;
109 : :
110 : 0 : len += snprintf(buf + len, PAGE_SIZE - len, "%9u: ",
111 : 0 : stats->freq_table[i]);
112 : :
113 [ # # ]: 0 : for (j = 0; j < stats->state_num; j++) {
114 [ # # ]: 0 : if (len >= PAGE_SIZE)
115 : : break;
116 : 0 : len += snprintf(buf + len, PAGE_SIZE - len, "%9u ",
117 : 0 : stats->trans_table[i*stats->max_state+j]);
118 : : }
119 [ # # ]: 0 : if (len >= PAGE_SIZE)
120 : : break;
121 : 0 : len += snprintf(buf + len, PAGE_SIZE - len, "\n");
122 : : }
123 : :
124 [ # # ]: 0 : if (len >= PAGE_SIZE) {
125 [ # # ]: 0 : pr_warn_once("cpufreq transition table exceeds PAGE_SIZE. Disabling\n");
126 : : return -EFBIG;
127 : : }
128 : : return len;
129 : : }
130 : : cpufreq_freq_attr_ro(trans_table);
131 : :
132 : : static struct attribute *default_attrs[] = {
133 : : &total_trans.attr,
134 : : &time_in_state.attr,
135 : : &reset.attr,
136 : : &trans_table.attr,
137 : : NULL
138 : : };
139 : : static const struct attribute_group stats_attr_group = {
140 : : .attrs = default_attrs,
141 : : .name = "stats"
142 : : };
143 : :
144 : : static int freq_table_get_index(struct cpufreq_stats *stats, unsigned int freq)
145 : : {
146 : : int index;
147 [ # # # # : 0 : for (index = 0; index < stats->max_state; index++)
# # ]
148 [ # # # # : 0 : if (stats->freq_table[index] == freq)
# # ]
149 : 0 : return index;
150 : : return -1;
151 : : }
152 : :
153 : 0 : void cpufreq_stats_free_table(struct cpufreq_policy *policy)
154 : : {
155 : 0 : struct cpufreq_stats *stats = policy->stats;
156 : :
157 : : /* Already freed */
158 [ # # ]: 0 : if (!stats)
159 : 0 : return;
160 : :
161 : : pr_debug("%s: Free stats table\n", __func__);
162 : :
163 : 0 : sysfs_remove_group(&policy->kobj, &stats_attr_group);
164 : 0 : kfree(stats->time_in_state);
165 : 0 : kfree(stats);
166 : 0 : policy->stats = NULL;
167 : : }
168 : :
169 : 0 : void cpufreq_stats_create_table(struct cpufreq_policy *policy)
170 : : {
171 : : unsigned int i = 0, count = 0, ret = -ENOMEM;
172 : : struct cpufreq_stats *stats;
173 : : unsigned int alloc_size;
174 : : struct cpufreq_frequency_table *pos;
175 : :
176 : 0 : count = cpufreq_table_count_valid_entries(policy);
177 [ # # ]: 0 : if (!count)
178 : : return;
179 : :
180 : : /* stats already initialized */
181 [ # # ]: 0 : if (policy->stats)
182 : : return;
183 : :
184 : 0 : stats = kzalloc(sizeof(*stats), GFP_KERNEL);
185 [ # # ]: 0 : if (!stats)
186 : : return;
187 : :
188 : 0 : alloc_size = count * sizeof(int) + count * sizeof(u64);
189 : :
190 : 0 : alloc_size += count * count * sizeof(int);
191 : :
192 : : /* Allocate memory for time_in_state/freq_table/trans_table in one go */
193 : 0 : stats->time_in_state = kzalloc(alloc_size, GFP_KERNEL);
194 [ # # ]: 0 : if (!stats->time_in_state)
195 : : goto free_stat;
196 : :
197 : 0 : stats->freq_table = (unsigned int *)(stats->time_in_state + count);
198 : :
199 : 0 : stats->trans_table = stats->freq_table + count;
200 : :
201 : 0 : stats->max_state = count;
202 : :
203 : : /* Find valid-unique entries */
204 [ # # # # ]: 0 : cpufreq_for_each_valid_entry(pos, policy->freq_table)
205 [ # # ]: 0 : if (freq_table_get_index(stats, pos->frequency) == -1)
206 : 0 : stats->freq_table[i++] = pos->frequency;
207 : :
208 : 0 : stats->state_num = i;
209 : 0 : stats->last_time = get_jiffies_64();
210 : 0 : stats->last_index = freq_table_get_index(stats, policy->cur);
211 : 0 : spin_lock_init(&stats->lock);
212 : :
213 : 0 : policy->stats = stats;
214 : 0 : ret = sysfs_create_group(&policy->kobj, &stats_attr_group);
215 [ # # ]: 0 : if (!ret)
216 : : return;
217 : :
218 : : /* We failed, release resources */
219 : 0 : policy->stats = NULL;
220 : 0 : kfree(stats->time_in_state);
221 : : free_stat:
222 : 0 : kfree(stats);
223 : : }
224 : :
225 : 0 : void cpufreq_stats_record_transition(struct cpufreq_policy *policy,
226 : : unsigned int new_freq)
227 : : {
228 : 0 : struct cpufreq_stats *stats = policy->stats;
229 : : int old_index, new_index;
230 : :
231 [ # # ]: 0 : if (!stats) {
232 : : pr_debug("%s: No stats found\n", __func__);
233 : : return;
234 : : }
235 : :
236 : 0 : old_index = stats->last_index;
237 : : new_index = freq_table_get_index(stats, new_freq);
238 : :
239 : : /* We can't do stats->time_in_state[-1]= .. */
240 [ # # # # ]: 0 : if (old_index == -1 || new_index == -1 || old_index == new_index)
241 : : return;
242 : :
243 : : spin_lock(&stats->lock);
244 : 0 : cpufreq_stats_update(stats);
245 : :
246 : 0 : stats->last_index = new_index;
247 : 0 : stats->trans_table[old_index * stats->max_state + new_index]++;
248 : 0 : stats->total_trans++;
249 : : spin_unlock(&stats->lock);
250 : 0 : }
|