.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * drivers/cpufreq/cpufreq_stats.c |
---|
3 | 4 | * |
---|
4 | 5 | * Copyright (C) 2003-2004 Venkatesh Pallipadi <venkatesh.pallipadi@intel.com>. |
---|
5 | 6 | * (C) 2004 Zou Nan hai <nanhai.zou@intel.com>. |
---|
6 | | - * |
---|
7 | | - * This program is free software; you can redistribute it and/or modify |
---|
8 | | - * it under the terms of the GNU General Public License version 2 as |
---|
9 | | - * published by the Free Software Foundation. |
---|
10 | 7 | */ |
---|
11 | 8 | |
---|
12 | 9 | #include <linux/cpu.h> |
---|
.. | .. |
---|
14 | 11 | #include <linux/module.h> |
---|
15 | 12 | #include <linux/slab.h> |
---|
16 | 13 | |
---|
17 | | -static DEFINE_SPINLOCK(cpufreq_stats_lock); |
---|
18 | 14 | |
---|
19 | 15 | struct cpufreq_stats { |
---|
20 | 16 | unsigned int total_trans; |
---|
.. | .. |
---|
25 | 21 | u64 *time_in_state; |
---|
26 | 22 | unsigned int *freq_table; |
---|
27 | 23 | unsigned int *trans_table; |
---|
| 24 | + |
---|
| 25 | + /* Deferred reset */ |
---|
| 26 | + unsigned int reset_pending; |
---|
| 27 | + unsigned long long reset_time; |
---|
28 | 28 | }; |
---|
29 | 29 | |
---|
30 | | -static void cpufreq_stats_update(struct cpufreq_stats *stats) |
---|
| 30 | +static void cpufreq_stats_update(struct cpufreq_stats *stats, |
---|
| 31 | + unsigned long long time) |
---|
31 | 32 | { |
---|
32 | 33 | unsigned long long cur_time = get_jiffies_64(); |
---|
33 | 34 | |
---|
34 | | - spin_lock(&cpufreq_stats_lock); |
---|
35 | | - stats->time_in_state[stats->last_index] += cur_time - stats->last_time; |
---|
| 35 | + stats->time_in_state[stats->last_index] += cur_time - time; |
---|
36 | 36 | stats->last_time = cur_time; |
---|
37 | | - spin_unlock(&cpufreq_stats_lock); |
---|
38 | 37 | } |
---|
39 | 38 | |
---|
40 | | -static void cpufreq_stats_clear_table(struct cpufreq_stats *stats) |
---|
| 39 | +static void cpufreq_stats_reset_table(struct cpufreq_stats *stats) |
---|
41 | 40 | { |
---|
42 | 41 | unsigned int count = stats->max_state; |
---|
43 | 42 | |
---|
.. | .. |
---|
45 | 44 | memset(stats->trans_table, 0, count * count * sizeof(int)); |
---|
46 | 45 | stats->last_time = get_jiffies_64(); |
---|
47 | 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)); |
---|
48 | 56 | } |
---|
49 | 57 | |
---|
50 | 58 | static ssize_t show_total_trans(struct cpufreq_policy *policy, char *buf) |
---|
51 | 59 | { |
---|
52 | | - return sprintf(buf, "%d\n", policy->stats->total_trans); |
---|
| 60 | + struct cpufreq_stats *stats = policy->stats; |
---|
| 61 | + |
---|
| 62 | + if (READ_ONCE(stats->reset_pending)) |
---|
| 63 | + return sprintf(buf, "%d\n", 0); |
---|
| 64 | + else |
---|
| 65 | + return sprintf(buf, "%u\n", stats->total_trans); |
---|
53 | 66 | } |
---|
| 67 | +cpufreq_freq_attr_ro(total_trans); |
---|
54 | 68 | |
---|
55 | 69 | static ssize_t show_time_in_state(struct cpufreq_policy *policy, char *buf) |
---|
56 | 70 | { |
---|
57 | 71 | struct cpufreq_stats *stats = policy->stats; |
---|
| 72 | + bool pending = READ_ONCE(stats->reset_pending); |
---|
| 73 | + unsigned long long time; |
---|
58 | 74 | ssize_t len = 0; |
---|
59 | 75 | int i; |
---|
60 | 76 | |
---|
61 | | - if (policy->fast_switch_enabled) |
---|
62 | | - return 0; |
---|
63 | | - |
---|
64 | | - cpufreq_stats_update(stats); |
---|
65 | 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 = get_jiffies_64() - 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 += get_jiffies_64() - stats->last_time; |
---|
| 93 | + } |
---|
| 94 | + |
---|
66 | 95 | len += sprintf(buf + len, "%u %llu\n", stats->freq_table[i], |
---|
67 | | - (unsigned long long) |
---|
68 | | - jiffies_64_to_clock_t(stats->time_in_state[i])); |
---|
| 96 | + jiffies_64_to_clock_t(time)); |
---|
69 | 97 | } |
---|
70 | 98 | return len; |
---|
71 | 99 | } |
---|
| 100 | +cpufreq_freq_attr_ro(time_in_state); |
---|
72 | 101 | |
---|
| 102 | +/* We don't care what is written to the attribute */ |
---|
73 | 103 | static ssize_t store_reset(struct cpufreq_policy *policy, const char *buf, |
---|
74 | 104 | size_t count) |
---|
75 | 105 | { |
---|
76 | | - /* We don't care what is written to the attribute. */ |
---|
77 | | - cpufreq_stats_clear_table(policy->stats); |
---|
| 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, get_jiffies_64()); |
---|
| 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 | + |
---|
78 | 120 | return count; |
---|
79 | 121 | } |
---|
| 122 | +cpufreq_freq_attr_wo(reset); |
---|
80 | 123 | |
---|
81 | 124 | static ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf) |
---|
82 | 125 | { |
---|
83 | 126 | struct cpufreq_stats *stats = policy->stats; |
---|
| 127 | + bool pending = READ_ONCE(stats->reset_pending); |
---|
84 | 128 | ssize_t len = 0; |
---|
85 | | - int i, j; |
---|
| 129 | + int i, j, count; |
---|
86 | 130 | |
---|
87 | | - if (policy->fast_switch_enabled) |
---|
88 | | - return 0; |
---|
89 | | - |
---|
90 | | - len += snprintf(buf + len, PAGE_SIZE - len, " From : To\n"); |
---|
91 | | - len += snprintf(buf + len, PAGE_SIZE - len, " : "); |
---|
| 131 | + len += scnprintf(buf + len, PAGE_SIZE - len, " From : To\n"); |
---|
| 132 | + len += scnprintf(buf + len, PAGE_SIZE - len, " : "); |
---|
92 | 133 | for (i = 0; i < stats->state_num; i++) { |
---|
93 | 134 | if (len >= PAGE_SIZE) |
---|
94 | 135 | break; |
---|
95 | | - len += snprintf(buf + len, PAGE_SIZE - len, "%9u ", |
---|
| 136 | + len += scnprintf(buf + len, PAGE_SIZE - len, "%9u ", |
---|
96 | 137 | stats->freq_table[i]); |
---|
97 | 138 | } |
---|
98 | 139 | if (len >= PAGE_SIZE) |
---|
99 | 140 | return PAGE_SIZE; |
---|
100 | 141 | |
---|
101 | | - len += snprintf(buf + len, PAGE_SIZE - len, "\n"); |
---|
| 142 | + len += scnprintf(buf + len, PAGE_SIZE - len, "\n"); |
---|
102 | 143 | |
---|
103 | 144 | for (i = 0; i < stats->state_num; i++) { |
---|
104 | 145 | if (len >= PAGE_SIZE) |
---|
105 | 146 | break; |
---|
106 | 147 | |
---|
107 | | - len += snprintf(buf + len, PAGE_SIZE - len, "%9u: ", |
---|
| 148 | + len += scnprintf(buf + len, PAGE_SIZE - len, "%9u: ", |
---|
108 | 149 | stats->freq_table[i]); |
---|
109 | 150 | |
---|
110 | 151 | for (j = 0; j < stats->state_num; j++) { |
---|
111 | 152 | if (len >= PAGE_SIZE) |
---|
112 | 153 | break; |
---|
113 | | - len += snprintf(buf + len, PAGE_SIZE - len, "%9u ", |
---|
114 | | - stats->trans_table[i*stats->max_state+j]); |
---|
| 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); |
---|
115 | 161 | } |
---|
116 | 162 | if (len >= PAGE_SIZE) |
---|
117 | 163 | break; |
---|
118 | | - len += snprintf(buf + len, PAGE_SIZE - len, "\n"); |
---|
| 164 | + len += scnprintf(buf + len, PAGE_SIZE - len, "\n"); |
---|
119 | 165 | } |
---|
120 | 166 | |
---|
121 | 167 | if (len >= PAGE_SIZE) { |
---|
.. | .. |
---|
125 | 171 | return len; |
---|
126 | 172 | } |
---|
127 | 173 | cpufreq_freq_attr_ro(trans_table); |
---|
128 | | - |
---|
129 | | -cpufreq_freq_attr_ro(total_trans); |
---|
130 | | -cpufreq_freq_attr_ro(time_in_state); |
---|
131 | | -cpufreq_freq_attr_wo(reset); |
---|
132 | 174 | |
---|
133 | 175 | static struct attribute *default_attrs[] = { |
---|
134 | 176 | &total_trans.attr, |
---|
.. | .. |
---|
228 | 270 | struct cpufreq_stats *stats = policy->stats; |
---|
229 | 271 | int old_index, new_index; |
---|
230 | 272 | |
---|
231 | | - if (!stats) { |
---|
232 | | - pr_debug("%s: No stats found\n", __func__); |
---|
| 273 | + if (unlikely(!stats)) |
---|
233 | 274 | return; |
---|
234 | | - } |
---|
| 275 | + |
---|
| 276 | + if (unlikely(READ_ONCE(stats->reset_pending))) |
---|
| 277 | + cpufreq_stats_reset_table(stats); |
---|
235 | 278 | |
---|
236 | 279 | old_index = stats->last_index; |
---|
237 | 280 | new_index = freq_table_get_index(stats, new_freq); |
---|
238 | 281 | |
---|
239 | 282 | /* We can't do stats->time_in_state[-1]= .. */ |
---|
240 | | - if (old_index == -1 || new_index == -1 || old_index == new_index) |
---|
| 283 | + if (unlikely(old_index == -1 || new_index == -1 || old_index == new_index)) |
---|
241 | 284 | return; |
---|
242 | 285 | |
---|
243 | | - cpufreq_stats_update(stats); |
---|
| 286 | + cpufreq_stats_update(stats, stats->last_time); |
---|
244 | 287 | |
---|
245 | 288 | stats->last_index = new_index; |
---|
246 | 289 | stats->trans_table[old_index * stats->max_state + new_index]++; |
---|