Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0-only |
| 2 | /* |
| 3 | * Copyright 2020 Linaro Limited |
| 4 | * |
| 5 | * Author: Daniel Lezcano <daniel.lezcano@linaro.org> |
| 6 | * |
| 7 | * The DTPM CPU is based on the energy model. It hooks the CPU in the |
| 8 | * DTPM tree which in turns update the power number by propagating the |
| 9 | * power number from the CPU energy model information to the parents. |
| 10 | * |
| 11 | * The association between the power and the performance state, allows |
| 12 | * to set the power of the CPU at the OPP granularity. |
| 13 | * |
| 14 | * The CPU hotplug is supported and the power numbers will be updated |
| 15 | * if a CPU is hot plugged / unplugged. |
| 16 | */ |
Daniel Lezcano | 4570ddd | 2021-03-12 14:04:07 +0100 | [diff] [blame] | 17 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| 18 | |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 19 | #include <linux/cpumask.h> |
| 20 | #include <linux/cpufreq.h> |
| 21 | #include <linux/cpuhotplug.h> |
| 22 | #include <linux/dtpm.h> |
| 23 | #include <linux/energy_model.h> |
| 24 | #include <linux/pm_qos.h> |
| 25 | #include <linux/slab.h> |
| 26 | #include <linux/units.h> |
| 27 | |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 28 | struct dtpm_cpu { |
Daniel Lezcano | d2cdc6a | 2021-03-12 14:04:10 +0100 | [diff] [blame] | 29 | struct dtpm dtpm; |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 30 | struct freq_qos_request qos_req; |
| 31 | int cpu; |
| 32 | }; |
| 33 | |
Daniel Lezcano | d2cdc6a | 2021-03-12 14:04:10 +0100 | [diff] [blame] | 34 | static DEFINE_PER_CPU(struct dtpm_cpu *, dtpm_per_cpu); |
| 35 | |
| 36 | static struct dtpm_cpu *to_dtpm_cpu(struct dtpm *dtpm) |
| 37 | { |
| 38 | return container_of(dtpm, struct dtpm_cpu, dtpm); |
| 39 | } |
| 40 | |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 41 | static u64 set_pd_power_limit(struct dtpm *dtpm, u64 power_limit) |
| 42 | { |
Daniel Lezcano | d2cdc6a | 2021-03-12 14:04:10 +0100 | [diff] [blame] | 43 | struct dtpm_cpu *dtpm_cpu = to_dtpm_cpu(dtpm); |
Daniel Lezcano | 4570ddd | 2021-03-12 14:04:07 +0100 | [diff] [blame] | 44 | struct em_perf_domain *pd = em_cpu_get(dtpm_cpu->cpu); |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 45 | struct cpumask cpus; |
| 46 | unsigned long freq; |
| 47 | u64 power; |
| 48 | int i, nr_cpus; |
| 49 | |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 50 | cpumask_and(&cpus, cpu_online_mask, to_cpumask(pd->cpus)); |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 51 | nr_cpus = cpumask_weight(&cpus); |
| 52 | |
| 53 | for (i = 0; i < pd->nr_perf_states; i++) { |
| 54 | |
| 55 | power = pd->table[i].power * MICROWATT_PER_MILLIWATT * nr_cpus; |
| 56 | |
| 57 | if (power > power_limit) |
| 58 | break; |
| 59 | } |
| 60 | |
| 61 | freq = pd->table[i - 1].frequency; |
| 62 | |
| 63 | freq_qos_update_request(&dtpm_cpu->qos_req, freq); |
| 64 | |
| 65 | power_limit = pd->table[i - 1].power * |
| 66 | MICROWATT_PER_MILLIWATT * nr_cpus; |
| 67 | |
| 68 | return power_limit; |
| 69 | } |
| 70 | |
Daniel Lezcano | eb82bac | 2021-03-12 14:04:11 +0100 | [diff] [blame] | 71 | static u64 scale_pd_power_uw(struct cpumask *pd_mask, u64 power) |
| 72 | { |
| 73 | unsigned long max = 0, sum_util = 0; |
| 74 | int cpu; |
| 75 | |
| 76 | for_each_cpu_and(cpu, pd_mask, cpu_online_mask) { |
| 77 | |
| 78 | /* |
| 79 | * The capacity is the same for all CPUs belonging to |
| 80 | * the same perf domain, so a single call to |
| 81 | * arch_scale_cpu_capacity() is enough. However, we |
| 82 | * need the CPU parameter to be initialized by the |
| 83 | * loop, so the call ends up in this block. |
| 84 | * |
| 85 | * We can initialize 'max' with a cpumask_first() call |
| 86 | * before the loop but the bits computation is not |
| 87 | * worth given the arch_scale_cpu_capacity() just |
| 88 | * returns a value where the resulting assembly code |
| 89 | * will be optimized by the compiler. |
| 90 | */ |
| 91 | max = arch_scale_cpu_capacity(cpu); |
| 92 | sum_util += sched_cpu_util(cpu, max); |
| 93 | } |
| 94 | |
| 95 | /* |
| 96 | * In the improbable case where all the CPUs of the perf |
| 97 | * domain are offline, 'max' will be zero and will lead to an |
| 98 | * illegal operation with a zero division. |
| 99 | */ |
| 100 | return max ? (power * ((sum_util << 10) / max)) >> 10 : 0; |
| 101 | } |
| 102 | |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 103 | static u64 get_pd_power_uw(struct dtpm *dtpm) |
| 104 | { |
Daniel Lezcano | d2cdc6a | 2021-03-12 14:04:10 +0100 | [diff] [blame] | 105 | struct dtpm_cpu *dtpm_cpu = to_dtpm_cpu(dtpm); |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 106 | struct em_perf_domain *pd; |
Daniel Lezcano | eb82bac | 2021-03-12 14:04:11 +0100 | [diff] [blame] | 107 | struct cpumask *pd_mask; |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 108 | unsigned long freq; |
Daniel Lezcano | eb82bac | 2021-03-12 14:04:11 +0100 | [diff] [blame] | 109 | int i; |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 110 | |
| 111 | pd = em_cpu_get(dtpm_cpu->cpu); |
Daniel Lezcano | 4570ddd | 2021-03-12 14:04:07 +0100 | [diff] [blame] | 112 | |
Daniel Lezcano | eb82bac | 2021-03-12 14:04:11 +0100 | [diff] [blame] | 113 | pd_mask = em_span_cpus(pd); |
| 114 | |
| 115 | freq = cpufreq_quick_get(dtpm_cpu->cpu); |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 116 | |
| 117 | for (i = 0; i < pd->nr_perf_states; i++) { |
| 118 | |
| 119 | if (pd->table[i].frequency < freq) |
| 120 | continue; |
| 121 | |
Daniel Lezcano | eb82bac | 2021-03-12 14:04:11 +0100 | [diff] [blame] | 122 | return scale_pd_power_uw(pd_mask, pd->table[i].power * |
| 123 | MICROWATT_PER_MILLIWATT); |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 124 | } |
| 125 | |
| 126 | return 0; |
| 127 | } |
| 128 | |
Daniel Lezcano | 4570ddd | 2021-03-12 14:04:07 +0100 | [diff] [blame] | 129 | static int update_pd_power_uw(struct dtpm *dtpm) |
| 130 | { |
Daniel Lezcano | d2cdc6a | 2021-03-12 14:04:10 +0100 | [diff] [blame] | 131 | struct dtpm_cpu *dtpm_cpu = to_dtpm_cpu(dtpm); |
Daniel Lezcano | 4570ddd | 2021-03-12 14:04:07 +0100 | [diff] [blame] | 132 | struct em_perf_domain *em = em_cpu_get(dtpm_cpu->cpu); |
| 133 | struct cpumask cpus; |
| 134 | int nr_cpus; |
| 135 | |
| 136 | cpumask_and(&cpus, cpu_online_mask, to_cpumask(em->cpus)); |
| 137 | nr_cpus = cpumask_weight(&cpus); |
| 138 | |
| 139 | dtpm->power_min = em->table[0].power; |
| 140 | dtpm->power_min *= MICROWATT_PER_MILLIWATT; |
| 141 | dtpm->power_min *= nr_cpus; |
| 142 | |
| 143 | dtpm->power_max = em->table[em->nr_perf_states - 1].power; |
| 144 | dtpm->power_max *= MICROWATT_PER_MILLIWATT; |
| 145 | dtpm->power_max *= nr_cpus; |
| 146 | |
| 147 | return 0; |
| 148 | } |
| 149 | |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 150 | static void pd_release(struct dtpm *dtpm) |
| 151 | { |
Daniel Lezcano | d2cdc6a | 2021-03-12 14:04:10 +0100 | [diff] [blame] | 152 | struct dtpm_cpu *dtpm_cpu = to_dtpm_cpu(dtpm); |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 153 | |
| 154 | if (freq_qos_request_active(&dtpm_cpu->qos_req)) |
| 155 | freq_qos_remove_request(&dtpm_cpu->qos_req); |
| 156 | |
| 157 | kfree(dtpm_cpu); |
| 158 | } |
| 159 | |
| 160 | static struct dtpm_ops dtpm_ops = { |
Daniel Lezcano | 4570ddd | 2021-03-12 14:04:07 +0100 | [diff] [blame] | 161 | .set_power_uw = set_pd_power_limit, |
| 162 | .get_power_uw = get_pd_power_uw, |
| 163 | .update_power_uw = update_pd_power_uw, |
| 164 | .release = pd_release, |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 165 | }; |
| 166 | |
| 167 | static int cpuhp_dtpm_cpu_offline(unsigned int cpu) |
| 168 | { |
Daniel Lezcano | d2cdc6a | 2021-03-12 14:04:10 +0100 | [diff] [blame] | 169 | struct dtpm_cpu *dtpm_cpu; |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 170 | |
Daniel Lezcano | d2cdc6a | 2021-03-12 14:04:10 +0100 | [diff] [blame] | 171 | dtpm_cpu = per_cpu(dtpm_per_cpu, cpu); |
Daniel Lezcano | 4d1cd14 | 2021-11-08 07:23:44 +0100 | [diff] [blame] | 172 | if (dtpm_cpu) |
| 173 | dtpm_update_power(&dtpm_cpu->dtpm); |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 174 | |
Daniel Lezcano | 4d1cd14 | 2021-11-08 07:23:44 +0100 | [diff] [blame] | 175 | return 0; |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 176 | } |
| 177 | |
| 178 | static int cpuhp_dtpm_cpu_online(unsigned int cpu) |
| 179 | { |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 180 | struct dtpm_cpu *dtpm_cpu; |
| 181 | struct cpufreq_policy *policy; |
| 182 | struct em_perf_domain *pd; |
| 183 | char name[CPUFREQ_NAME_LEN]; |
| 184 | int ret = -ENOMEM; |
| 185 | |
| 186 | policy = cpufreq_cpu_get(cpu); |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 187 | if (!policy) |
| 188 | return 0; |
| 189 | |
| 190 | pd = em_cpu_get(cpu); |
| 191 | if (!pd) |
| 192 | return -EINVAL; |
| 193 | |
Daniel Lezcano | d2cdc6a | 2021-03-12 14:04:10 +0100 | [diff] [blame] | 194 | dtpm_cpu = per_cpu(dtpm_per_cpu, cpu); |
| 195 | if (dtpm_cpu) |
| 196 | return dtpm_update_power(&dtpm_cpu->dtpm); |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 197 | |
Colin Ian King | 66e713f | 2021-01-04 12:10:53 +0000 | [diff] [blame] | 198 | dtpm_cpu = kzalloc(sizeof(*dtpm_cpu), GFP_KERNEL); |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 199 | if (!dtpm_cpu) |
Daniel Lezcano | d2cdc6a | 2021-03-12 14:04:10 +0100 | [diff] [blame] | 200 | return -ENOMEM; |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 201 | |
Daniel Lezcano | d2cdc6a | 2021-03-12 14:04:10 +0100 | [diff] [blame] | 202 | dtpm_init(&dtpm_cpu->dtpm, &dtpm_ops); |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 203 | dtpm_cpu->cpu = cpu; |
| 204 | |
| 205 | for_each_cpu(cpu, policy->related_cpus) |
Daniel Lezcano | d2cdc6a | 2021-03-12 14:04:10 +0100 | [diff] [blame] | 206 | per_cpu(dtpm_per_cpu, cpu) = dtpm_cpu; |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 207 | |
Daniel Lezcano | 4570ddd | 2021-03-12 14:04:07 +0100 | [diff] [blame] | 208 | snprintf(name, sizeof(name), "cpu%d-cpufreq", dtpm_cpu->cpu); |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 209 | |
Daniel Lezcano | d2cdc6a | 2021-03-12 14:04:10 +0100 | [diff] [blame] | 210 | ret = dtpm_register(name, &dtpm_cpu->dtpm, NULL); |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 211 | if (ret) |
| 212 | goto out_kfree_dtpm_cpu; |
| 213 | |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 214 | ret = freq_qos_add_request(&policy->constraints, |
| 215 | &dtpm_cpu->qos_req, FREQ_QOS_MAX, |
| 216 | pd->table[pd->nr_perf_states - 1].frequency); |
| 217 | if (ret) |
Daniel Lezcano | 4570ddd | 2021-03-12 14:04:07 +0100 | [diff] [blame] | 218 | goto out_dtpm_unregister; |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 219 | |
| 220 | return 0; |
| 221 | |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 222 | out_dtpm_unregister: |
Daniel Lezcano | d2cdc6a | 2021-03-12 14:04:10 +0100 | [diff] [blame] | 223 | dtpm_unregister(&dtpm_cpu->dtpm); |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 224 | dtpm_cpu = NULL; |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 225 | |
| 226 | out_kfree_dtpm_cpu: |
| 227 | for_each_cpu(cpu, policy->related_cpus) |
| 228 | per_cpu(dtpm_per_cpu, cpu) = NULL; |
| 229 | kfree(dtpm_cpu); |
| 230 | |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 231 | return ret; |
| 232 | } |
| 233 | |
Daniel Lezcano | 7a89d7e | 2021-03-12 14:04:09 +0100 | [diff] [blame] | 234 | static int __init dtpm_cpu_init(void) |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 235 | { |
Daniel Lezcano | 4570ddd | 2021-03-12 14:04:07 +0100 | [diff] [blame] | 236 | int ret; |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 237 | |
Daniel Lezcano | 4570ddd | 2021-03-12 14:04:07 +0100 | [diff] [blame] | 238 | /* |
| 239 | * The callbacks at CPU hotplug time are calling |
| 240 | * dtpm_update_power() which in turns calls update_pd_power(). |
| 241 | * |
| 242 | * The function update_pd_power() uses the online mask to |
| 243 | * figure out the power consumption limits. |
| 244 | * |
| 245 | * At CPUHP_AP_ONLINE_DYN, the CPU is present in the CPU |
| 246 | * online mask when the cpuhp_dtpm_cpu_online function is |
| 247 | * called, but the CPU is still in the online mask for the |
| 248 | * tear down callback. So the power can not be updated when |
| 249 | * the CPU is unplugged. |
| 250 | * |
| 251 | * At CPUHP_AP_DTPM_CPU_DEAD, the situation is the opposite as |
| 252 | * above. The CPU online mask is not up to date when the CPU |
| 253 | * is plugged in. |
| 254 | * |
| 255 | * For this reason, we need to call the online and offline |
| 256 | * callbacks at different moments when the CPU online mask is |
| 257 | * consistent with the power numbers we want to update. |
| 258 | */ |
| 259 | ret = cpuhp_setup_state(CPUHP_AP_DTPM_CPU_DEAD, "dtpm_cpu:offline", |
| 260 | NULL, cpuhp_dtpm_cpu_offline); |
| 261 | if (ret < 0) |
| 262 | return ret; |
| 263 | |
| 264 | ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "dtpm_cpu:online", |
| 265 | cpuhp_dtpm_cpu_online, NULL); |
| 266 | if (ret < 0) |
| 267 | return ret; |
| 268 | |
| 269 | return 0; |
Daniel Lezcano | 0e8f68d | 2020-12-08 17:41:45 +0100 | [diff] [blame] | 270 | } |
Daniel Lezcano | 7a89d7e | 2021-03-12 14:04:09 +0100 | [diff] [blame] | 271 | |
| 272 | DTPM_DECLARE(dtpm_cpu, dtpm_cpu_init); |