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/sched/clock.h> |
13 | #include <linux/slab.h> |
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 | unsigned int *freq_table; |
23 | unsigned int *trans_table; |
24 | |
25 | /* Deferred reset */ |
26 | unsigned int reset_pending; |
27 | unsigned long long reset_time; |
28 | }; |
29 | |
30 | static void cpufreq_stats_update(struct cpufreq_stats *stats, |
31 | unsigned long long time) |
32 | { |
33 | unsigned long long cur_time = local_clock(); |
34 | |
35 | stats->time_in_state[stats->last_index] += cur_time - time; |
36 | stats->last_time = cur_time; |
37 | } |
38 | |
39 | static void cpufreq_stats_reset_table(struct cpufreq_stats *stats) |
40 | { |
41 | unsigned int count = stats->max_state; |
42 | |
43 | memset(stats->time_in_state, 0, count * sizeof(u64)); |
44 | memset(stats->trans_table, 0, count * count * sizeof(int)); |
45 | stats->last_time = local_clock(); |
46 | stats->total_trans = 0; |
47 | |
48 | /* Adjust for the time elapsed since reset was requested */ |
49 | WRITE_ONCE(stats->reset_pending, 0); |
50 | /* |
51 | * Prevent the reset_time read from being reordered before the |
52 | * reset_pending accesses in cpufreq_stats_record_transition(). |
53 | */ |
54 | smp_rmb(); |
55 | cpufreq_stats_update(stats, READ_ONCE(stats->reset_time)); |
56 | } |
57 | |
58 | static ssize_t show_total_trans(struct cpufreq_policy *policy, char *buf) |
59 | { |
60 | struct cpufreq_stats *stats = policy->stats; |
61 | |
62 | if (READ_ONCE(stats->reset_pending)) |
63 | return sprintf(buf, fmt: "%d\n" , 0); |
64 | else |
65 | return sprintf(buf, fmt: "%u\n" , stats->total_trans); |
66 | } |
67 | cpufreq_freq_attr_ro(total_trans); |
68 | |
69 | static ssize_t show_time_in_state(struct cpufreq_policy *policy, char *buf) |
70 | { |
71 | struct cpufreq_stats *stats = policy->stats; |
72 | bool pending = READ_ONCE(stats->reset_pending); |
73 | unsigned long long time; |
74 | ssize_t len = 0; |
75 | int i; |
76 | |
77 | for (i = 0; i < stats->state_num; i++) { |
78 | if (pending) { |
79 | if (i == stats->last_index) { |
80 | /* |
81 | * Prevent the reset_time read from occurring |
82 | * before the reset_pending read above. |
83 | */ |
84 | smp_rmb(); |
85 | time = local_clock() - READ_ONCE(stats->reset_time); |
86 | } else { |
87 | time = 0; |
88 | } |
89 | } else { |
90 | time = stats->time_in_state[i]; |
91 | if (i == stats->last_index) |
92 | time += local_clock() - stats->last_time; |
93 | } |
94 | |
95 | len += sprintf(buf: buf + len, fmt: "%u %llu\n" , stats->freq_table[i], |
96 | nsec_to_clock_t(x: time)); |
97 | } |
98 | return len; |
99 | } |
100 | cpufreq_freq_attr_ro(time_in_state); |
101 | |
102 | /* We don't care what is written to the attribute */ |
103 | static ssize_t store_reset(struct cpufreq_policy *policy, const char *buf, |
104 | size_t count) |
105 | { |
106 | struct cpufreq_stats *stats = policy->stats; |
107 | |
108 | /* |
109 | * Defer resetting of stats to cpufreq_stats_record_transition() to |
110 | * avoid races. |
111 | */ |
112 | WRITE_ONCE(stats->reset_time, local_clock()); |
113 | /* |
114 | * The memory barrier below is to prevent the readers of reset_time from |
115 | * seeing a stale or partially updated value. |
116 | */ |
117 | smp_wmb(); |
118 | WRITE_ONCE(stats->reset_pending, 1); |
119 | |
120 | return count; |
121 | } |
122 | cpufreq_freq_attr_wo(reset); |
123 | |
124 | static ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf) |
125 | { |
126 | struct cpufreq_stats *stats = policy->stats; |
127 | bool pending = READ_ONCE(stats->reset_pending); |
128 | ssize_t len = 0; |
129 | int i, j, count; |
130 | |
131 | len += sysfs_emit_at(buf, at: len, fmt: " From : To\n" ); |
132 | len += sysfs_emit_at(buf, at: len, fmt: " : " ); |
133 | for (i = 0; i < stats->state_num; i++) { |
134 | if (len >= PAGE_SIZE - 1) |
135 | break; |
136 | len += sysfs_emit_at(buf, at: len, fmt: "%9u " , stats->freq_table[i]); |
137 | } |
138 | if (len >= PAGE_SIZE - 1) |
139 | return PAGE_SIZE - 1; |
140 | |
141 | len += sysfs_emit_at(buf, at: len, fmt: "\n" ); |
142 | |
143 | for (i = 0; i < stats->state_num; i++) { |
144 | if (len >= PAGE_SIZE - 1) |
145 | break; |
146 | |
147 | len += sysfs_emit_at(buf, at: len, fmt: "%9u: " , stats->freq_table[i]); |
148 | |
149 | for (j = 0; j < stats->state_num; j++) { |
150 | if (len >= PAGE_SIZE - 1) |
151 | break; |
152 | |
153 | if (pending) |
154 | count = 0; |
155 | else |
156 | count = stats->trans_table[i * stats->max_state + j]; |
157 | |
158 | len += sysfs_emit_at(buf, at: len, fmt: "%9u " , count); |
159 | } |
160 | if (len >= PAGE_SIZE - 1) |
161 | break; |
162 | len += sysfs_emit_at(buf, at: len, fmt: "\n" ); |
163 | } |
164 | |
165 | if (len >= PAGE_SIZE - 1) { |
166 | pr_warn_once("cpufreq transition table exceeds PAGE_SIZE. Disabling\n" ); |
167 | return -EFBIG; |
168 | } |
169 | return len; |
170 | } |
171 | cpufreq_freq_attr_ro(trans_table); |
172 | |
173 | static struct attribute *default_attrs[] = { |
174 | &total_trans.attr, |
175 | &time_in_state.attr, |
176 | &reset.attr, |
177 | &trans_table.attr, |
178 | NULL |
179 | }; |
180 | static const struct attribute_group stats_attr_group = { |
181 | .attrs = default_attrs, |
182 | .name = "stats" |
183 | }; |
184 | |
185 | static int freq_table_get_index(struct cpufreq_stats *stats, unsigned int freq) |
186 | { |
187 | int index; |
188 | for (index = 0; index < stats->max_state; index++) |
189 | if (stats->freq_table[index] == freq) |
190 | return index; |
191 | return -1; |
192 | } |
193 | |
194 | void cpufreq_stats_free_table(struct cpufreq_policy *policy) |
195 | { |
196 | struct cpufreq_stats *stats = policy->stats; |
197 | |
198 | /* Already freed */ |
199 | if (!stats) |
200 | return; |
201 | |
202 | pr_debug("%s: Free stats table\n" , __func__); |
203 | |
204 | sysfs_remove_group(kobj: &policy->kobj, grp: &stats_attr_group); |
205 | kfree(objp: stats->time_in_state); |
206 | kfree(objp: stats); |
207 | policy->stats = NULL; |
208 | } |
209 | |
210 | void cpufreq_stats_create_table(struct cpufreq_policy *policy) |
211 | { |
212 | unsigned int i = 0, count; |
213 | struct cpufreq_stats *stats; |
214 | unsigned int alloc_size; |
215 | struct cpufreq_frequency_table *pos; |
216 | |
217 | count = cpufreq_table_count_valid_entries(policy); |
218 | if (!count) |
219 | return; |
220 | |
221 | /* stats already initialized */ |
222 | if (policy->stats) |
223 | return; |
224 | |
225 | stats = kzalloc(size: sizeof(*stats), GFP_KERNEL); |
226 | if (!stats) |
227 | return; |
228 | |
229 | alloc_size = count * sizeof(int) + count * sizeof(u64); |
230 | |
231 | alloc_size += count * count * sizeof(int); |
232 | |
233 | /* Allocate memory for time_in_state/freq_table/trans_table in one go */ |
234 | stats->time_in_state = kzalloc(size: alloc_size, GFP_KERNEL); |
235 | if (!stats->time_in_state) |
236 | goto free_stat; |
237 | |
238 | stats->freq_table = (unsigned int *)(stats->time_in_state + count); |
239 | |
240 | stats->trans_table = stats->freq_table + count; |
241 | |
242 | stats->max_state = count; |
243 | |
244 | /* Find valid-unique entries */ |
245 | cpufreq_for_each_valid_entry(pos, policy->freq_table) |
246 | if (policy->freq_table_sorted != CPUFREQ_TABLE_UNSORTED || |
247 | freq_table_get_index(stats, freq: pos->frequency) == -1) |
248 | stats->freq_table[i++] = pos->frequency; |
249 | |
250 | stats->state_num = i; |
251 | stats->last_time = local_clock(); |
252 | stats->last_index = freq_table_get_index(stats, freq: policy->cur); |
253 | |
254 | policy->stats = stats; |
255 | if (!sysfs_create_group(kobj: &policy->kobj, grp: &stats_attr_group)) |
256 | return; |
257 | |
258 | /* We failed, release resources */ |
259 | policy->stats = NULL; |
260 | kfree(objp: stats->time_in_state); |
261 | free_stat: |
262 | kfree(objp: stats); |
263 | } |
264 | |
265 | void cpufreq_stats_record_transition(struct cpufreq_policy *policy, |
266 | unsigned int new_freq) |
267 | { |
268 | struct cpufreq_stats *stats = policy->stats; |
269 | int old_index, new_index; |
270 | |
271 | if (unlikely(!stats)) |
272 | return; |
273 | |
274 | if (unlikely(READ_ONCE(stats->reset_pending))) |
275 | cpufreq_stats_reset_table(stats); |
276 | |
277 | old_index = stats->last_index; |
278 | new_index = freq_table_get_index(stats, freq: new_freq); |
279 | |
280 | /* We can't do stats->time_in_state[-1]= .. */ |
281 | if (unlikely(old_index == -1 || new_index == -1 || old_index == new_index)) |
282 | return; |
283 | |
284 | cpufreq_stats_update(stats, time: stats->last_time); |
285 | |
286 | stats->last_index = new_index; |
287 | stats->trans_table[old_index * stats->max_state + new_index]++; |
288 | stats->total_trans++; |
289 | } |
290 | |