]>
Commit | Line | Data |
---|---|---|
a20d0ef9 DL |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Copyright 2020 Linaro Limited | |
4 | * | |
5 | * Author: Daniel Lezcano <[email protected]> | |
6 | * | |
7 | * The powercap based Dynamic Thermal Power Management framework | |
8 | * provides to the userspace a consistent API to set the power limit | |
9 | * on some devices. | |
10 | * | |
11 | * DTPM defines the functions to create a tree of constraints. Each | |
12 | * parent node is a virtual description of the aggregation of the | |
13 | * children. It propagates the constraints set at its level to its | |
14 | * children and collect the children power information. The leaves of | |
15 | * the tree are the real devices which have the ability to get their | |
16 | * current power consumption and set their power limit. | |
17 | */ | |
18 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
19 | ||
20 | #include <linux/dtpm.h> | |
21 | #include <linux/init.h> | |
22 | #include <linux/kernel.h> | |
23 | #include <linux/powercap.h> | |
24 | #include <linux/slab.h> | |
25 | #include <linux/mutex.h> | |
26 | ||
2185c230 | 27 | #define DTPM_POWER_LIMIT_FLAG 0 |
a20d0ef9 DL |
28 | |
29 | static const char *constraint_name[] = { | |
30 | "Instantaneous", | |
31 | }; | |
32 | ||
33 | static DEFINE_MUTEX(dtpm_lock); | |
34 | static struct powercap_control_type *pct; | |
35 | static struct dtpm *root; | |
36 | ||
37 | static int get_time_window_us(struct powercap_zone *pcz, int cid, u64 *window) | |
38 | { | |
39 | return -ENOSYS; | |
40 | } | |
41 | ||
42 | static int set_time_window_us(struct powercap_zone *pcz, int cid, u64 window) | |
43 | { | |
44 | return -ENOSYS; | |
45 | } | |
46 | ||
47 | static int get_max_power_range_uw(struct powercap_zone *pcz, u64 *max_power_uw) | |
48 | { | |
49 | struct dtpm *dtpm = to_dtpm(pcz); | |
50 | ||
51 | mutex_lock(&dtpm_lock); | |
52 | *max_power_uw = dtpm->power_max - dtpm->power_min; | |
53 | mutex_unlock(&dtpm_lock); | |
54 | ||
55 | return 0; | |
56 | } | |
57 | ||
58 | static int __get_power_uw(struct dtpm *dtpm, u64 *power_uw) | |
59 | { | |
60 | struct dtpm *child; | |
61 | u64 power; | |
62 | int ret = 0; | |
63 | ||
64 | if (dtpm->ops) { | |
65 | *power_uw = dtpm->ops->get_power_uw(dtpm); | |
66 | return 0; | |
67 | } | |
68 | ||
69 | *power_uw = 0; | |
70 | ||
71 | list_for_each_entry(child, &dtpm->children, sibling) { | |
72 | ret = __get_power_uw(child, &power); | |
73 | if (ret) | |
74 | break; | |
75 | *power_uw += power; | |
76 | } | |
77 | ||
78 | return ret; | |
79 | } | |
80 | ||
81 | static int get_power_uw(struct powercap_zone *pcz, u64 *power_uw) | |
82 | { | |
83 | struct dtpm *dtpm = to_dtpm(pcz); | |
84 | int ret; | |
85 | ||
86 | mutex_lock(&dtpm_lock); | |
87 | ret = __get_power_uw(dtpm, power_uw); | |
88 | mutex_unlock(&dtpm_lock); | |
89 | ||
90 | return ret; | |
91 | } | |
92 | ||
93 | static void __dtpm_rebalance_weight(struct dtpm *dtpm) | |
94 | { | |
95 | struct dtpm *child; | |
96 | ||
97 | list_for_each_entry(child, &dtpm->children, sibling) { | |
98 | ||
99 | pr_debug("Setting weight '%d' for '%s'\n", | |
100 | child->weight, child->zone.name); | |
101 | ||
8f50db4b DL |
102 | child->weight = DIV64_U64_ROUND_CLOSEST( |
103 | child->power_max * 1024, dtpm->power_max); | |
a20d0ef9 DL |
104 | |
105 | __dtpm_rebalance_weight(child); | |
106 | } | |
107 | } | |
108 | ||
109 | static void __dtpm_sub_power(struct dtpm *dtpm) | |
110 | { | |
111 | struct dtpm *parent = dtpm->parent; | |
112 | ||
113 | while (parent) { | |
114 | parent->power_min -= dtpm->power_min; | |
115 | parent->power_max -= dtpm->power_max; | |
116 | parent->power_limit -= dtpm->power_limit; | |
117 | parent = parent->parent; | |
118 | } | |
a20d0ef9 DL |
119 | } |
120 | ||
121 | static void __dtpm_add_power(struct dtpm *dtpm) | |
122 | { | |
123 | struct dtpm *parent = dtpm->parent; | |
124 | ||
125 | while (parent) { | |
126 | parent->power_min += dtpm->power_min; | |
127 | parent->power_max += dtpm->power_max; | |
128 | parent->power_limit += dtpm->power_limit; | |
129 | parent = parent->parent; | |
130 | } | |
4570ddda DL |
131 | } |
132 | ||
133 | static int __dtpm_update_power(struct dtpm *dtpm) | |
134 | { | |
135 | int ret; | |
136 | ||
137 | __dtpm_sub_power(dtpm); | |
a20d0ef9 | 138 | |
4570ddda DL |
139 | ret = dtpm->ops->update_power_uw(dtpm); |
140 | if (ret) | |
141 | pr_err("Failed to update power for '%s': %d\n", | |
142 | dtpm->zone.name, ret); | |
143 | ||
144 | if (!test_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags)) | |
145 | dtpm->power_limit = dtpm->power_max; | |
146 | ||
147 | __dtpm_add_power(dtpm); | |
148 | ||
149 | if (root) | |
150 | __dtpm_rebalance_weight(root); | |
151 | ||
152 | return ret; | |
a20d0ef9 DL |
153 | } |
154 | ||
155 | /** | |
156 | * dtpm_update_power - Update the power on the dtpm | |
157 | * @dtpm: a pointer to a dtpm structure to update | |
a20d0ef9 DL |
158 | * |
159 | * Function to update the power values of the dtpm node specified in | |
160 | * parameter. These new values will be propagated to the tree. | |
161 | * | |
162 | * Return: zero on success, -EINVAL if the values are inconsistent | |
163 | */ | |
4570ddda | 164 | int dtpm_update_power(struct dtpm *dtpm) |
a20d0ef9 | 165 | { |
4570ddda | 166 | int ret; |
0fe1329b | 167 | |
a20d0ef9 | 168 | mutex_lock(&dtpm_lock); |
4570ddda | 169 | ret = __dtpm_update_power(dtpm); |
a20d0ef9 DL |
170 | mutex_unlock(&dtpm_lock); |
171 | ||
0fe1329b | 172 | return ret; |
a20d0ef9 DL |
173 | } |
174 | ||
175 | /** | |
176 | * dtpm_release_zone - Cleanup when the node is released | |
177 | * @pcz: a pointer to a powercap_zone structure | |
178 | * | |
179 | * Do some housecleaning and update the weight on the tree. The | |
180 | * release will be denied if the node has children. This function must | |
181 | * be called by the specific release callback of the different | |
182 | * backends. | |
183 | * | |
184 | * Return: 0 on success, -EBUSY if there are children | |
185 | */ | |
186 | int dtpm_release_zone(struct powercap_zone *pcz) | |
187 | { | |
188 | struct dtpm *dtpm = to_dtpm(pcz); | |
189 | struct dtpm *parent = dtpm->parent; | |
190 | ||
191 | mutex_lock(&dtpm_lock); | |
192 | ||
0fe1329b DC |
193 | if (!list_empty(&dtpm->children)) { |
194 | mutex_unlock(&dtpm_lock); | |
a20d0ef9 | 195 | return -EBUSY; |
0fe1329b | 196 | } |
a20d0ef9 DL |
197 | |
198 | if (parent) | |
199 | list_del(&dtpm->sibling); | |
200 | ||
201 | __dtpm_sub_power(dtpm); | |
202 | ||
203 | mutex_unlock(&dtpm_lock); | |
204 | ||
205 | if (dtpm->ops) | |
206 | dtpm->ops->release(dtpm); | |
207 | ||
f3c14105 DL |
208 | if (root == dtpm) |
209 | root = NULL; | |
210 | ||
a20d0ef9 DL |
211 | kfree(dtpm); |
212 | ||
213 | return 0; | |
214 | } | |
215 | ||
216 | static int __get_power_limit_uw(struct dtpm *dtpm, int cid, u64 *power_limit) | |
217 | { | |
218 | *power_limit = dtpm->power_limit; | |
219 | return 0; | |
220 | } | |
221 | ||
222 | static int get_power_limit_uw(struct powercap_zone *pcz, | |
223 | int cid, u64 *power_limit) | |
224 | { | |
225 | struct dtpm *dtpm = to_dtpm(pcz); | |
226 | int ret; | |
227 | ||
228 | mutex_lock(&dtpm_lock); | |
229 | ret = __get_power_limit_uw(dtpm, cid, power_limit); | |
230 | mutex_unlock(&dtpm_lock); | |
231 | ||
232 | return ret; | |
233 | } | |
234 | ||
235 | /* | |
236 | * Set the power limit on the nodes, the power limit is distributed | |
237 | * given the weight of the children. | |
238 | * | |
239 | * The dtpm node lock must be held when calling this function. | |
240 | */ | |
241 | static int __set_power_limit_uw(struct dtpm *dtpm, int cid, u64 power_limit) | |
242 | { | |
243 | struct dtpm *child; | |
244 | int ret = 0; | |
245 | u64 power; | |
246 | ||
247 | /* | |
248 | * A max power limitation means we remove the power limit, | |
249 | * otherwise we set a constraint and flag the dtpm node. | |
250 | */ | |
251 | if (power_limit == dtpm->power_max) { | |
252 | clear_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags); | |
253 | } else { | |
254 | set_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags); | |
255 | } | |
256 | ||
257 | pr_debug("Setting power limit for '%s': %llu uW\n", | |
258 | dtpm->zone.name, power_limit); | |
259 | ||
260 | /* | |
261 | * Only leaves of the dtpm tree has ops to get/set the power | |
262 | */ | |
263 | if (dtpm->ops) { | |
264 | dtpm->power_limit = dtpm->ops->set_power_uw(dtpm, power_limit); | |
265 | } else { | |
266 | dtpm->power_limit = 0; | |
267 | ||
268 | list_for_each_entry(child, &dtpm->children, sibling) { | |
269 | ||
270 | /* | |
271 | * Integer division rounding will inevitably | |
272 | * lead to a different min or max value when | |
273 | * set several times. In order to restore the | |
274 | * initial value, we force the child's min or | |
275 | * max power every time if the constraint is | |
276 | * at the boundaries. | |
277 | */ | |
278 | if (power_limit == dtpm->power_max) { | |
279 | power = child->power_max; | |
280 | } else if (power_limit == dtpm->power_min) { | |
281 | power = child->power_min; | |
282 | } else { | |
8f50db4b | 283 | power = DIV_ROUND_CLOSEST_ULL( |
a20d0ef9 DL |
284 | power_limit * child->weight, 1024); |
285 | } | |
286 | ||
287 | pr_debug("Setting power limit for '%s': %llu uW\n", | |
288 | child->zone.name, power); | |
289 | ||
290 | ret = __set_power_limit_uw(child, cid, power); | |
291 | if (!ret) | |
292 | ret = __get_power_limit_uw(child, cid, &power); | |
293 | ||
294 | if (ret) | |
295 | break; | |
296 | ||
297 | dtpm->power_limit += power; | |
298 | } | |
299 | } | |
300 | ||
301 | return ret; | |
302 | } | |
303 | ||
304 | static int set_power_limit_uw(struct powercap_zone *pcz, | |
305 | int cid, u64 power_limit) | |
306 | { | |
307 | struct dtpm *dtpm = to_dtpm(pcz); | |
308 | int ret; | |
309 | ||
310 | mutex_lock(&dtpm_lock); | |
311 | ||
312 | /* | |
313 | * Don't allow values outside of the power range previously | |
314 | * set when initializing the power numbers. | |
315 | */ | |
316 | power_limit = clamp_val(power_limit, dtpm->power_min, dtpm->power_max); | |
317 | ||
318 | ret = __set_power_limit_uw(dtpm, cid, power_limit); | |
319 | ||
320 | pr_debug("%s: power limit: %llu uW, power max: %llu uW\n", | |
321 | dtpm->zone.name, dtpm->power_limit, dtpm->power_max); | |
322 | ||
323 | mutex_unlock(&dtpm_lock); | |
324 | ||
325 | return ret; | |
326 | } | |
327 | ||
328 | static const char *get_constraint_name(struct powercap_zone *pcz, int cid) | |
329 | { | |
330 | return constraint_name[cid]; | |
331 | } | |
332 | ||
333 | static int get_max_power_uw(struct powercap_zone *pcz, int id, u64 *max_power) | |
334 | { | |
335 | struct dtpm *dtpm = to_dtpm(pcz); | |
336 | ||
337 | mutex_lock(&dtpm_lock); | |
338 | *max_power = dtpm->power_max; | |
339 | mutex_unlock(&dtpm_lock); | |
340 | ||
341 | return 0; | |
342 | } | |
343 | ||
344 | static struct powercap_zone_constraint_ops constraint_ops = { | |
345 | .set_power_limit_uw = set_power_limit_uw, | |
346 | .get_power_limit_uw = get_power_limit_uw, | |
347 | .set_time_window_us = set_time_window_us, | |
348 | .get_time_window_us = get_time_window_us, | |
349 | .get_max_power_uw = get_max_power_uw, | |
350 | .get_name = get_constraint_name, | |
351 | }; | |
352 | ||
353 | static struct powercap_zone_ops zone_ops = { | |
354 | .get_max_power_range_uw = get_max_power_range_uw, | |
355 | .get_power_uw = get_power_uw, | |
356 | .release = dtpm_release_zone, | |
357 | }; | |
358 | ||
359 | /** | |
d2cdc6ad DL |
360 | * dtpm_init - Allocate and initialize a dtpm struct |
361 | * @dtpm: The dtpm struct pointer to be initialized | |
362 | * @ops: The dtpm device specific ops, NULL for a virtual node | |
a20d0ef9 | 363 | */ |
d2cdc6ad | 364 | void dtpm_init(struct dtpm *dtpm, struct dtpm_ops *ops) |
a20d0ef9 | 365 | { |
a20d0ef9 DL |
366 | if (dtpm) { |
367 | INIT_LIST_HEAD(&dtpm->children); | |
368 | INIT_LIST_HEAD(&dtpm->sibling); | |
369 | dtpm->weight = 1024; | |
370 | dtpm->ops = ops; | |
371 | } | |
a20d0ef9 DL |
372 | } |
373 | ||
374 | /** | |
375 | * dtpm_unregister - Unregister a dtpm node from the hierarchy tree | |
376 | * @dtpm: a pointer to a dtpm structure corresponding to the node to be removed | |
377 | * | |
378 | * Call the underlying powercap unregister function. That will call | |
379 | * the release callback of the powercap zone. | |
380 | */ | |
381 | void dtpm_unregister(struct dtpm *dtpm) | |
382 | { | |
383 | powercap_unregister_zone(pct, &dtpm->zone); | |
384 | ||
385 | pr_info("Unregistered dtpm node '%s'\n", dtpm->zone.name); | |
386 | } | |
387 | ||
388 | /** | |
389 | * dtpm_register - Register a dtpm node in the hierarchy tree | |
390 | * @name: a string specifying the name of the node | |
391 | * @dtpm: a pointer to a dtpm structure corresponding to the new node | |
392 | * @parent: a pointer to a dtpm structure corresponding to the parent node | |
393 | * | |
394 | * Create a dtpm node in the tree. If no parent is specified, the node | |
395 | * is the root node of the hierarchy. If the root node already exists, | |
396 | * then the registration will fail. The powercap controller must be | |
397 | * initialized before calling this function. | |
398 | * | |
399 | * The dtpm structure must be initialized with the power numbers | |
400 | * before calling this function. | |
401 | * | |
402 | * Return: zero on success, a negative value in case of error: | |
403 | * -EAGAIN: the function is called before the framework is initialized. | |
404 | * -EBUSY: the root node is already inserted | |
405 | * -EINVAL: * there is no root node yet and @parent is specified | |
406 | * * no all ops are defined | |
407 | * * parent have ops which are reserved for leaves | |
408 | * Other negative values are reported back from the powercap framework | |
409 | */ | |
410 | int dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent) | |
411 | { | |
412 | struct powercap_zone *pcz; | |
413 | ||
414 | if (!pct) | |
415 | return -EAGAIN; | |
416 | ||
417 | if (root && !parent) | |
418 | return -EBUSY; | |
419 | ||
420 | if (!root && parent) | |
421 | return -EINVAL; | |
422 | ||
423 | if (parent && parent->ops) | |
424 | return -EINVAL; | |
425 | ||
426 | if (!dtpm) | |
427 | return -EINVAL; | |
428 | ||
429 | if (dtpm->ops && !(dtpm->ops->set_power_uw && | |
430 | dtpm->ops->get_power_uw && | |
4570ddda | 431 | dtpm->ops->update_power_uw && |
a20d0ef9 DL |
432 | dtpm->ops->release)) |
433 | return -EINVAL; | |
434 | ||
435 | pcz = powercap_register_zone(&dtpm->zone, pct, name, | |
436 | parent ? &parent->zone : NULL, | |
437 | &zone_ops, MAX_DTPM_CONSTRAINTS, | |
438 | &constraint_ops); | |
439 | if (IS_ERR(pcz)) | |
440 | return PTR_ERR(pcz); | |
441 | ||
442 | mutex_lock(&dtpm_lock); | |
443 | ||
444 | if (parent) { | |
445 | list_add_tail(&dtpm->sibling, &parent->children); | |
446 | dtpm->parent = parent; | |
447 | } else { | |
448 | root = dtpm; | |
449 | } | |
450 | ||
5d8cb8db | 451 | if (dtpm->ops && !dtpm->ops->update_power_uw(dtpm)) { |
4570ddda | 452 | __dtpm_add_power(dtpm); |
5d8cb8db DL |
453 | dtpm->power_limit = dtpm->power_max; |
454 | } | |
a20d0ef9 DL |
455 | |
456 | pr_info("Registered dtpm node '%s' / %llu-%llu uW, \n", | |
457 | dtpm->zone.name, dtpm->power_min, dtpm->power_max); | |
458 | ||
459 | mutex_unlock(&dtpm_lock); | |
460 | ||
461 | return 0; | |
462 | } | |
463 | ||
d2cdc6ad | 464 | static int __init init_dtpm(void) |
a20d0ef9 | 465 | { |
7a89d7ea | 466 | struct dtpm_descr *dtpm_descr; |
a20d0ef9 DL |
467 | |
468 | pct = powercap_register_control_type(NULL, "dtpm", NULL); | |
f8f706ad | 469 | if (IS_ERR(pct)) { |
a20d0ef9 | 470 | pr_err("Failed to register control type\n"); |
f8f706ad | 471 | return PTR_ERR(pct); |
a20d0ef9 DL |
472 | } |
473 | ||
a20d0ef9 DL |
474 | return 0; |
475 | } | |
d2cdc6ad | 476 | late_initcall(init_dtpm); |