]>
Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
1da177e4 LT |
2 | /* |
3 | * drivers/cpufreq/cpufreq_stats.c | |
4 | * | |
5 | * Copyright (C) 2003-2004 Venkatesh Pallipadi <venkatesh.pallipadi@intel.com>. | |
0a829c5a | 6 | * (C) 2004 Zou Nan hai <nanhai.zou@intel.com>. |
1da177e4 LT |
7 | */ |
8 | ||
1da177e4 | 9 | #include <linux/cpu.h> |
1da177e4 | 10 | #include <linux/cpufreq.h> |
5c720d37 | 11 | #include <linux/module.h> |
7854c752 | 12 | #include <linux/sched/clock.h> |
5ff0a268 | 13 | #include <linux/slab.h> |
1da177e4 | 14 | |
1da177e4 | 15 | struct cpufreq_stats { |
1da177e4 | 16 | unsigned int total_trans; |
bb176f7d | 17 | unsigned long long last_time; |
1da177e4 LT |
18 | unsigned int max_state; |
19 | unsigned int state_num; | |
20 | unsigned int last_index; | |
1e7586a1 | 21 | u64 *time_in_state; |
1da177e4 | 22 | unsigned int *freq_table; |
1da177e4 | 23 | unsigned int *trans_table; |
40c3bd4c VK |
24 | |
25 | /* Deferred reset */ | |
26 | unsigned int reset_pending; | |
27 | unsigned long long reset_time; | |
1da177e4 LT |
28 | }; |
29 | ||
40c3bd4c VK |
30 | static void cpufreq_stats_update(struct cpufreq_stats *stats, |
31 | unsigned long long time) | |
1da177e4 | 32 | { |
7854c752 | 33 | unsigned long long cur_time = local_clock(); |
58f1df25 | 34 | |
40c3bd4c | 35 | stats->time_in_state[stats->last_index] += cur_time - time; |
50941607 | 36 | stats->last_time = cur_time; |
1da177e4 LT |
37 | } |
38 | ||
40c3bd4c | 39 | static void cpufreq_stats_reset_table(struct cpufreq_stats *stats) |
ee7930ee MM |
40 | { |
41 | unsigned int count = stats->max_state; | |
42 | ||
43 | memset(stats->time_in_state, 0, count * sizeof(u64)); | |
ee7930ee | 44 | memset(stats->trans_table, 0, count * count * sizeof(int)); |
7854c752 | 45 | stats->last_time = local_clock(); |
ee7930ee | 46 | stats->total_trans = 0; |
40c3bd4c VK |
47 | |
48 | /* Adjust for the time elapsed since reset was requested */ | |
49 | WRITE_ONCE(stats->reset_pending, 0); | |
efad4240 RW |
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(); | |
40c3bd4c | 55 | cpufreq_stats_update(stats, READ_ONCE(stats->reset_time)); |
ee7930ee MM |
56 | } |
57 | ||
0a829c5a | 58 | static ssize_t show_total_trans(struct cpufreq_policy *policy, char *buf) |
1da177e4 | 59 | { |
40c3bd4c VK |
60 | struct cpufreq_stats *stats = policy->stats; |
61 | ||
62 | if (READ_ONCE(stats->reset_pending)) | |
63 | return sprintf(buf, "%d\n", 0); | |
64 | else | |
b7af6080 | 65 | return sprintf(buf, "%u\n", stats->total_trans); |
1da177e4 | 66 | } |
10b81821 | 67 | cpufreq_freq_attr_ro(total_trans); |
1da177e4 | 68 | |
0a829c5a | 69 | static ssize_t show_time_in_state(struct cpufreq_policy *policy, char *buf) |
1da177e4 | 70 | { |
50941607 | 71 | struct cpufreq_stats *stats = policy->stats; |
40c3bd4c VK |
72 | bool pending = READ_ONCE(stats->reset_pending); |
73 | unsigned long long time; | |
1da177e4 LT |
74 | ssize_t len = 0; |
75 | int i; | |
a9aaf291 | 76 | |
50941607 | 77 | for (i = 0; i < stats->state_num; i++) { |
40c3bd4c | 78 | if (pending) { |
efad4240 RW |
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(); | |
7854c752 | 85 | time = local_clock() - READ_ONCE(stats->reset_time); |
efad4240 | 86 | } else { |
40c3bd4c | 87 | time = 0; |
efad4240 | 88 | } |
40c3bd4c VK |
89 | } else { |
90 | time = stats->time_in_state[i]; | |
91 | if (i == stats->last_index) | |
7854c752 | 92 | time += local_clock() - stats->last_time; |
40c3bd4c VK |
93 | } |
94 | ||
50941607 | 95 | len += sprintf(buf + len, "%u %llu\n", stats->freq_table[i], |
7854c752 | 96 | nsec_to_clock_t(time)); |
1da177e4 LT |
97 | } |
98 | return len; | |
99 | } | |
10b81821 | 100 | cpufreq_freq_attr_ro(time_in_state); |
1da177e4 | 101 | |
40c3bd4c | 102 | /* We don't care what is written to the attribute */ |
ee7930ee MM |
103 | static ssize_t store_reset(struct cpufreq_policy *policy, const char *buf, |
104 | size_t count) | |
105 | { | |
40c3bd4c VK |
106 | struct cpufreq_stats *stats = policy->stats; |
107 | ||
108 | /* | |
109 | * Defer resetting of stats to cpufreq_stats_record_transition() to | |
110 | * avoid races. | |
111 | */ | |
7854c752 | 112 | WRITE_ONCE(stats->reset_time, local_clock()); |
efad4240 RW |
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(); | |
40c3bd4c VK |
118 | WRITE_ONCE(stats->reset_pending, 1); |
119 | ||
ee7930ee MM |
120 | return count; |
121 | } | |
10b81821 | 122 | cpufreq_freq_attr_wo(reset); |
ee7930ee | 123 | |
0a829c5a | 124 | static ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf) |
1da177e4 | 125 | { |
50941607 | 126 | struct cpufreq_stats *stats = policy->stats; |
40c3bd4c | 127 | bool pending = READ_ONCE(stats->reset_pending); |
1da177e4 | 128 | ssize_t len = 0; |
40c3bd4c | 129 | int i, j, count; |
1da177e4 | 130 | |
3c0897c1 TI |
131 | len += scnprintf(buf + len, PAGE_SIZE - len, " From : To\n"); |
132 | len += scnprintf(buf + len, PAGE_SIZE - len, " : "); | |
50941607 | 133 | for (i = 0; i < stats->state_num; i++) { |
58f1df25 VP |
134 | if (len >= PAGE_SIZE) |
135 | break; | |
3c0897c1 | 136 | len += scnprintf(buf + len, PAGE_SIZE - len, "%9u ", |
50941607 | 137 | stats->freq_table[i]); |
58f1df25 VP |
138 | } |
139 | if (len >= PAGE_SIZE) | |
25aca347 | 140 | return PAGE_SIZE; |
58f1df25 | 141 | |
3c0897c1 | 142 | len += scnprintf(buf + len, PAGE_SIZE - len, "\n"); |
58f1df25 | 143 | |
50941607 | 144 | for (i = 0; i < stats->state_num; i++) { |
1da177e4 LT |
145 | if (len >= PAGE_SIZE) |
146 | break; | |
58f1df25 | 147 | |
3c0897c1 | 148 | len += scnprintf(buf + len, PAGE_SIZE - len, "%9u: ", |
50941607 | 149 | stats->freq_table[i]); |
1da177e4 | 150 | |
50941607 | 151 | for (j = 0; j < stats->state_num; j++) { |
1da177e4 LT |
152 | if (len >= PAGE_SIZE) |
153 | break; | |
40c3bd4c VK |
154 | |
155 | if (pending) | |
156 | count = 0; | |
157 | else | |
158 | count = stats->trans_table[i * stats->max_state + j]; | |
159 | ||
160 | len += scnprintf(buf + len, PAGE_SIZE - len, "%9u ", count); | |
1da177e4 | 161 | } |
25aca347 CEB |
162 | if (len >= PAGE_SIZE) |
163 | break; | |
3c0897c1 | 164 | len += scnprintf(buf + len, PAGE_SIZE - len, "\n"); |
1da177e4 | 165 | } |
f7bc9b20 GS |
166 | |
167 | if (len >= PAGE_SIZE) { | |
168 | pr_warn_once("cpufreq transition table exceeds PAGE_SIZE. Disabling\n"); | |
169 | return -EFBIG; | |
170 | } | |
1da177e4 LT |
171 | return len; |
172 | } | |
df18e504 | 173 | cpufreq_freq_attr_ro(trans_table); |
1da177e4 | 174 | |
1da177e4 | 175 | static struct attribute *default_attrs[] = { |
df18e504 VK |
176 | &total_trans.attr, |
177 | &time_in_state.attr, | |
ee7930ee | 178 | &reset.attr, |
df18e504 | 179 | &trans_table.attr, |
1da177e4 LT |
180 | NULL |
181 | }; | |
402202e8 | 182 | static const struct attribute_group stats_attr_group = { |
1da177e4 LT |
183 | .attrs = default_attrs, |
184 | .name = "stats" | |
185 | }; | |
186 | ||
50941607 | 187 | static int freq_table_get_index(struct cpufreq_stats *stats, unsigned int freq) |
1da177e4 LT |
188 | { |
189 | int index; | |
50941607 VK |
190 | for (index = 0; index < stats->max_state; index++) |
191 | if (stats->freq_table[index] == freq) | |
1da177e4 LT |
192 | return index; |
193 | return -1; | |
194 | } | |
195 | ||
1aefc75b | 196 | void cpufreq_stats_free_table(struct cpufreq_policy *policy) |
1da177e4 | 197 | { |
50941607 | 198 | struct cpufreq_stats *stats = policy->stats; |
b8eed8af | 199 | |
a9aaf291 | 200 | /* Already freed */ |
50941607 | 201 | if (!stats) |
2d13594d VK |
202 | return; |
203 | ||
50941607 | 204 | pr_debug("%s: Free stats table\n", __func__); |
2d13594d VK |
205 | |
206 | sysfs_remove_group(&policy->kobj, &stats_attr_group); | |
50941607 VK |
207 | kfree(stats->time_in_state); |
208 | kfree(stats); | |
a9aaf291 | 209 | policy->stats = NULL; |
98586ed8 | 210 | } |
211 | ||
1aefc75b | 212 | void cpufreq_stats_create_table(struct cpufreq_policy *policy) |
1da177e4 | 213 | { |
5de12625 | 214 | unsigned int i = 0, count; |
50941607 | 215 | struct cpufreq_stats *stats; |
1da177e4 | 216 | unsigned int alloc_size; |
55d85293 | 217 | struct cpufreq_frequency_table *pos; |
ad4c2302 | 218 | |
55d85293 VK |
219 | count = cpufreq_table_count_valid_entries(policy); |
220 | if (!count) | |
1aefc75b | 221 | return; |
ad4c2302 | 222 | |
b8c67448 | 223 | /* stats already initialized */ |
a9aaf291 | 224 | if (policy->stats) |
1aefc75b | 225 | return; |
b8c67448 | 226 | |
50941607 | 227 | stats = kzalloc(sizeof(*stats), GFP_KERNEL); |
a685c6d0 | 228 | if (!stats) |
1aefc75b | 229 | return; |
1da177e4 | 230 | |
1e7586a1 | 231 | alloc_size = count * sizeof(int) + count * sizeof(u64); |
1da177e4 | 232 | |
1da177e4 | 233 | alloc_size += count * count * sizeof(int); |
a685c6d0 VK |
234 | |
235 | /* Allocate memory for time_in_state/freq_table/trans_table in one go */ | |
50941607 | 236 | stats->time_in_state = kzalloc(alloc_size, GFP_KERNEL); |
a685c6d0 VK |
237 | if (!stats->time_in_state) |
238 | goto free_stat; | |
239 | ||
50941607 | 240 | stats->freq_table = (unsigned int *)(stats->time_in_state + count); |
1da177e4 | 241 | |
50941607 | 242 | stats->trans_table = stats->freq_table + count; |
a685c6d0 VK |
243 | |
244 | stats->max_state = count; | |
245 | ||
246 | /* Find valid-unique entries */ | |
55d85293 | 247 | cpufreq_for_each_valid_entry(pos, policy->freq_table) |
50941607 VK |
248 | if (freq_table_get_index(stats, pos->frequency) == -1) |
249 | stats->freq_table[i++] = pos->frequency; | |
a685c6d0 | 250 | |
490285c6 | 251 | stats->state_num = i; |
7854c752 | 252 | stats->last_time = local_clock(); |
50941607 | 253 | stats->last_index = freq_table_get_index(stats, policy->cur); |
a685c6d0 VK |
254 | |
255 | policy->stats = stats; | |
5de12625 | 256 | if (!sysfs_create_group(&policy->kobj, &stats_attr_group)) |
1aefc75b | 257 | return; |
a685c6d0 VK |
258 | |
259 | /* We failed, release resources */ | |
a9aaf291 | 260 | policy->stats = NULL; |
a685c6d0 VK |
261 | kfree(stats->time_in_state); |
262 | free_stat: | |
263 | kfree(stats); | |
b3f9ff88 VK |
264 | } |
265 | ||
1aefc75b RW |
266 | void cpufreq_stats_record_transition(struct cpufreq_policy *policy, |
267 | unsigned int new_freq) | |
1da177e4 | 268 | { |
1aefc75b | 269 | struct cpufreq_stats *stats = policy->stats; |
1da177e4 LT |
270 | int old_index, new_index; |
271 | ||
4958b46e | 272 | if (unlikely(!stats)) |
1aefc75b | 273 | return; |
40c3bd4c VK |
274 | |
275 | if (unlikely(READ_ONCE(stats->reset_pending))) | |
276 | cpufreq_stats_reset_table(stats); | |
a9aaf291 | 277 | |
50941607 | 278 | old_index = stats->last_index; |
1aefc75b | 279 | new_index = freq_table_get_index(stats, new_freq); |
1da177e4 | 280 | |
50941607 | 281 | /* We can't do stats->time_in_state[-1]= .. */ |
4958b46e | 282 | if (unlikely(old_index == -1 || new_index == -1 || old_index == new_index)) |
1aefc75b | 283 | return; |
8edc59d9 | 284 | |
40c3bd4c | 285 | cpufreq_stats_update(stats, stats->last_time); |
e7347694 | 286 | |
50941607 | 287 | stats->last_index = new_index; |
50941607 | 288 | stats->trans_table[old_index * stats->max_state + new_index]++; |
50941607 | 289 | stats->total_trans++; |
1da177e4 | 290 | } |