| .. | .. |
|---|
| 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]++; |
|---|