xref: /linux/drivers/powercap/dtpm.c (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
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 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 #include <linux/of.h>
27 
28 #include "dtpm_subsys.h"
29 
30 #define DTPM_POWER_LIMIT_FLAG 0
31 
32 static const char *constraint_name[] = {
33 	"Instantaneous",
34 };
35 
36 static DEFINE_MUTEX(dtpm_lock);
37 static struct powercap_control_type *pct;
38 static struct dtpm *root;
39 
40 static int get_time_window_us(struct powercap_zone *pcz, int cid, u64 *window)
41 {
42 	return -ENOSYS;
43 }
44 
45 static int set_time_window_us(struct powercap_zone *pcz, int cid, u64 window)
46 {
47 	return -ENOSYS;
48 }
49 
50 static int get_max_power_range_uw(struct powercap_zone *pcz, u64 *max_power_uw)
51 {
52 	struct dtpm *dtpm = to_dtpm(pcz);
53 
54 	*max_power_uw = dtpm->power_max - dtpm->power_min;
55 
56 	return 0;
57 }
58 
59 static int __get_power_uw(struct dtpm *dtpm, u64 *power_uw)
60 {
61 	struct dtpm *child;
62 	u64 power;
63 	int ret = 0;
64 
65 	if (dtpm->ops) {
66 		*power_uw = dtpm->ops->get_power_uw(dtpm);
67 		return 0;
68 	}
69 
70 	*power_uw = 0;
71 
72 	list_for_each_entry(child, &dtpm->children, sibling) {
73 		ret = __get_power_uw(child, &power);
74 		if (ret)
75 			break;
76 		*power_uw += power;
77 	}
78 
79 	return ret;
80 }
81 
82 static int get_power_uw(struct powercap_zone *pcz, u64 *power_uw)
83 {
84 	return __get_power_uw(to_dtpm(pcz), power_uw);
85 }
86 
87 static void __dtpm_rebalance_weight(struct dtpm *dtpm)
88 {
89 	struct dtpm *child;
90 
91 	list_for_each_entry(child, &dtpm->children, sibling) {
92 
93 		pr_debug("Setting weight '%d' for '%s'\n",
94 			 child->weight, child->zone.name);
95 
96 		child->weight = DIV64_U64_ROUND_CLOSEST(
97 			child->power_max * 1024, dtpm->power_max);
98 
99 		__dtpm_rebalance_weight(child);
100 	}
101 }
102 
103 static void __dtpm_sub_power(struct dtpm *dtpm)
104 {
105 	struct dtpm *parent = dtpm->parent;
106 
107 	while (parent) {
108 		parent->power_min -= dtpm->power_min;
109 		parent->power_max -= dtpm->power_max;
110 		parent->power_limit -= dtpm->power_limit;
111 		parent = parent->parent;
112 	}
113 }
114 
115 static void __dtpm_add_power(struct dtpm *dtpm)
116 {
117 	struct dtpm *parent = dtpm->parent;
118 
119 	while (parent) {
120 		parent->power_min += dtpm->power_min;
121 		parent->power_max += dtpm->power_max;
122 		parent->power_limit += dtpm->power_limit;
123 		parent = parent->parent;
124 	}
125 }
126 
127 /**
128  * dtpm_update_power - Update the power on the dtpm
129  * @dtpm: a pointer to a dtpm structure to update
130  *
131  * Function to update the power values of the dtpm node specified in
132  * parameter. These new values will be propagated to the tree.
133  *
134  * Return: zero on success, -EINVAL if the values are inconsistent
135  */
136 int dtpm_update_power(struct dtpm *dtpm)
137 {
138 	int ret;
139 
140 	__dtpm_sub_power(dtpm);
141 
142 	ret = dtpm->ops->update_power_uw(dtpm);
143 	if (ret)
144 		pr_err("Failed to update power for '%s': %d\n",
145 		       dtpm->zone.name, ret);
146 
147 	if (!test_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags))
148 		dtpm->power_limit = dtpm->power_max;
149 
150 	__dtpm_add_power(dtpm);
151 
152 	if (root)
153 		__dtpm_rebalance_weight(root);
154 
155 	return ret;
156 }
157 
158 /**
159  * dtpm_release_zone - Cleanup when the node is released
160  * @pcz: a pointer to a powercap_zone structure
161  *
162  * Do some housecleaning and update the weight on the tree. The
163  * release will be denied if the node has children. This function must
164  * be called by the specific release callback of the different
165  * backends.
166  *
167  * Return: 0 on success, -EBUSY if there are children
168  */
169 int dtpm_release_zone(struct powercap_zone *pcz)
170 {
171 	struct dtpm *dtpm = to_dtpm(pcz);
172 	struct dtpm *parent = dtpm->parent;
173 
174 	if (!list_empty(&dtpm->children))
175 		return -EBUSY;
176 
177 	if (parent)
178 		list_del(&dtpm->sibling);
179 
180 	__dtpm_sub_power(dtpm);
181 
182 	if (dtpm->ops)
183 		dtpm->ops->release(dtpm);
184 	else
185 		kfree(dtpm);
186 
187 	return 0;
188 }
189 
190 static int get_power_limit_uw(struct powercap_zone *pcz,
191 			      int cid, u64 *power_limit)
192 {
193 	*power_limit = to_dtpm(pcz)->power_limit;
194 
195 	return 0;
196 }
197 
198 /*
199  * Set the power limit on the nodes, the power limit is distributed
200  * given the weight of the children.
201  *
202  * The dtpm node lock must be held when calling this function.
203  */
204 static int __set_power_limit_uw(struct dtpm *dtpm, int cid, u64 power_limit)
205 {
206 	struct dtpm *child;
207 	int ret = 0;
208 	u64 power;
209 
210 	/*
211 	 * A max power limitation means we remove the power limit,
212 	 * otherwise we set a constraint and flag the dtpm node.
213 	 */
214 	if (power_limit == dtpm->power_max) {
215 		clear_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags);
216 	} else {
217 		set_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags);
218 	}
219 
220 	pr_debug("Setting power limit for '%s': %llu uW\n",
221 		 dtpm->zone.name, power_limit);
222 
223 	/*
224 	 * Only leaves of the dtpm tree has ops to get/set the power
225 	 */
226 	if (dtpm->ops) {
227 		dtpm->power_limit = dtpm->ops->set_power_uw(dtpm, power_limit);
228 	} else {
229 		dtpm->power_limit = 0;
230 
231 		list_for_each_entry(child, &dtpm->children, sibling) {
232 
233 			/*
234 			 * Integer division rounding will inevitably
235 			 * lead to a different min or max value when
236 			 * set several times. In order to restore the
237 			 * initial value, we force the child's min or
238 			 * max power every time if the constraint is
239 			 * at the boundaries.
240 			 */
241 			if (power_limit == dtpm->power_max) {
242 				power = child->power_max;
243 			} else if (power_limit == dtpm->power_min) {
244 				power = child->power_min;
245 			} else {
246 				power = DIV_ROUND_CLOSEST_ULL(
247 					power_limit * child->weight, 1024);
248 			}
249 
250 			pr_debug("Setting power limit for '%s': %llu uW\n",
251 				 child->zone.name, power);
252 
253 			ret = __set_power_limit_uw(child, cid, power);
254 			if (!ret)
255 				ret = get_power_limit_uw(&child->zone, cid, &power);
256 
257 			if (ret)
258 				break;
259 
260 			dtpm->power_limit += power;
261 		}
262 	}
263 
264 	return ret;
265 }
266 
267 static int set_power_limit_uw(struct powercap_zone *pcz,
268 			      int cid, u64 power_limit)
269 {
270 	struct dtpm *dtpm = to_dtpm(pcz);
271 	int ret;
272 
273 	/*
274 	 * Don't allow values outside of the power range previously
275 	 * set when initializing the power numbers.
276 	 */
277 	power_limit = clamp_val(power_limit, dtpm->power_min, dtpm->power_max);
278 
279 	ret = __set_power_limit_uw(dtpm, cid, power_limit);
280 
281 	pr_debug("%s: power limit: %llu uW, power max: %llu uW\n",
282 		 dtpm->zone.name, dtpm->power_limit, dtpm->power_max);
283 
284 	return ret;
285 }
286 
287 static const char *get_constraint_name(struct powercap_zone *pcz, int cid)
288 {
289 	return constraint_name[cid];
290 }
291 
292 static int get_max_power_uw(struct powercap_zone *pcz, int id, u64 *max_power)
293 {
294 	*max_power = to_dtpm(pcz)->power_max;
295 
296 	return 0;
297 }
298 
299 static struct powercap_zone_constraint_ops constraint_ops = {
300 	.set_power_limit_uw = set_power_limit_uw,
301 	.get_power_limit_uw = get_power_limit_uw,
302 	.set_time_window_us = set_time_window_us,
303 	.get_time_window_us = get_time_window_us,
304 	.get_max_power_uw = get_max_power_uw,
305 	.get_name = get_constraint_name,
306 };
307 
308 static struct powercap_zone_ops zone_ops = {
309 	.get_max_power_range_uw = get_max_power_range_uw,
310 	.get_power_uw = get_power_uw,
311 	.release = dtpm_release_zone,
312 };
313 
314 /**
315  * dtpm_init - Allocate and initialize a dtpm struct
316  * @dtpm: The dtpm struct pointer to be initialized
317  * @ops: The dtpm device specific ops, NULL for a virtual node
318  */
319 void dtpm_init(struct dtpm *dtpm, struct dtpm_ops *ops)
320 {
321 	if (dtpm) {
322 		INIT_LIST_HEAD(&dtpm->children);
323 		INIT_LIST_HEAD(&dtpm->sibling);
324 		dtpm->weight = 1024;
325 		dtpm->ops = ops;
326 	}
327 }
328 
329 /**
330  * dtpm_unregister - Unregister a dtpm node from the hierarchy tree
331  * @dtpm: a pointer to a dtpm structure corresponding to the node to be removed
332  *
333  * Call the underlying powercap unregister function. That will call
334  * the release callback of the powercap zone.
335  */
336 void dtpm_unregister(struct dtpm *dtpm)
337 {
338 	powercap_unregister_zone(pct, &dtpm->zone);
339 
340 	pr_debug("Unregistered dtpm node '%s'\n", dtpm->zone.name);
341 }
342 
343 /**
344  * dtpm_register - Register a dtpm node in the hierarchy tree
345  * @name: a string specifying the name of the node
346  * @dtpm: a pointer to a dtpm structure corresponding to the new node
347  * @parent: a pointer to a dtpm structure corresponding to the parent node
348  *
349  * Create a dtpm node in the tree. If no parent is specified, the node
350  * is the root node of the hierarchy. If the root node already exists,
351  * then the registration will fail. The powercap controller must be
352  * initialized before calling this function.
353  *
354  * The dtpm structure must be initialized with the power numbers
355  * before calling this function.
356  *
357  * Return: zero on success, a negative value in case of error:
358  *  -EAGAIN: the function is called before the framework is initialized.
359  *  -EBUSY: the root node is already inserted
360  *  -EINVAL: * there is no root node yet and @parent is specified
361  *           * no all ops are defined
362  *           * parent have ops which are reserved for leaves
363  *   Other negative values are reported back from the powercap framework
364  */
365 int dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent)
366 {
367 	struct powercap_zone *pcz;
368 
369 	if (!pct)
370 		return -EAGAIN;
371 
372 	if (root && !parent)
373 		return -EBUSY;
374 
375 	if (!root && parent)
376 		return -EINVAL;
377 
378 	if (parent && parent->ops)
379 		return -EINVAL;
380 
381 	if (!dtpm)
382 		return -EINVAL;
383 
384 	if (dtpm->ops && !(dtpm->ops->set_power_uw &&
385 			   dtpm->ops->get_power_uw &&
386 			   dtpm->ops->update_power_uw &&
387 			   dtpm->ops->release))
388 		return -EINVAL;
389 
390 	pcz = powercap_register_zone(&dtpm->zone, pct, name,
391 				     parent ? &parent->zone : NULL,
392 				     &zone_ops, MAX_DTPM_CONSTRAINTS,
393 				     &constraint_ops);
394 	if (IS_ERR(pcz))
395 		return PTR_ERR(pcz);
396 
397 	if (parent) {
398 		list_add_tail(&dtpm->sibling, &parent->children);
399 		dtpm->parent = parent;
400 	} else {
401 		root = dtpm;
402 	}
403 
404 	if (dtpm->ops && !dtpm->ops->update_power_uw(dtpm)) {
405 		__dtpm_add_power(dtpm);
406 		dtpm->power_limit = dtpm->power_max;
407 	}
408 
409 	pr_debug("Registered dtpm node '%s' / %llu-%llu uW, \n",
410 		 dtpm->zone.name, dtpm->power_min, dtpm->power_max);
411 
412 	return 0;
413 }
414 
415 static struct dtpm *dtpm_setup_virtual(const struct dtpm_node *hierarchy,
416 				       struct dtpm *parent)
417 {
418 	struct dtpm *dtpm;
419 	int ret;
420 
421 	dtpm = kzalloc(sizeof(*dtpm), GFP_KERNEL);
422 	if (!dtpm)
423 		return ERR_PTR(-ENOMEM);
424 	dtpm_init(dtpm, NULL);
425 
426 	ret = dtpm_register(hierarchy->name, dtpm, parent);
427 	if (ret) {
428 		pr_err("Failed to register dtpm node '%s': %d\n",
429 		       hierarchy->name, ret);
430 		kfree(dtpm);
431 		return ERR_PTR(ret);
432 	}
433 
434 	return dtpm;
435 }
436 
437 static struct dtpm *dtpm_setup_dt(const struct dtpm_node *hierarchy,
438 				  struct dtpm *parent)
439 {
440 	struct device_node *np;
441 	int i, ret;
442 
443 	np = of_find_node_by_path(hierarchy->name);
444 	if (!np) {
445 		pr_err("Failed to find '%s'\n", hierarchy->name);
446 		return ERR_PTR(-ENXIO);
447 	}
448 
449 	for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
450 
451 		if (!dtpm_subsys[i]->setup)
452 			continue;
453 
454 		ret = dtpm_subsys[i]->setup(parent, np);
455 		if (ret) {
456 			pr_err("Failed to setup '%s': %d\n", dtpm_subsys[i]->name, ret);
457 			of_node_put(np);
458 			return ERR_PTR(ret);
459 		}
460 	}
461 
462 	of_node_put(np);
463 
464 	/*
465 	 * By returning a NULL pointer, we let know the caller there
466 	 * is no child for us as we are a leaf of the tree
467 	 */
468 	return NULL;
469 }
470 
471 typedef struct dtpm * (*dtpm_node_callback_t)(const struct dtpm_node *, struct dtpm *);
472 
473 static dtpm_node_callback_t dtpm_node_callback[] = {
474 	[DTPM_NODE_VIRTUAL] = dtpm_setup_virtual,
475 	[DTPM_NODE_DT] = dtpm_setup_dt,
476 };
477 
478 static int dtpm_for_each_child(const struct dtpm_node *hierarchy,
479 			       const struct dtpm_node *it, struct dtpm *parent)
480 {
481 	struct dtpm *dtpm;
482 	int i, ret;
483 
484 	for (i = 0; hierarchy[i].name; i++) {
485 
486 		if (hierarchy[i].parent != it)
487 			continue;
488 
489 		dtpm = dtpm_node_callback[hierarchy[i].type](&hierarchy[i], parent);
490 
491 		/*
492 		 * A NULL pointer means there is no children, hence we
493 		 * continue without going deeper in the recursivity.
494 		 */
495 		if (!dtpm)
496 			continue;
497 
498 		/*
499 		 * There are multiple reasons why the callback could
500 		 * fail. The generic glue is abstracting the backend
501 		 * and therefore it is not possible to report back or
502 		 * take a decision based on the error.  In any case,
503 		 * if this call fails, it is not critical in the
504 		 * hierarchy creation, we can assume the underlying
505 		 * service is not found, so we continue without this
506 		 * branch in the tree but with a warning to log the
507 		 * information the node was not created.
508 		 */
509 		if (IS_ERR(dtpm)) {
510 			pr_warn("Failed to create '%s' in the hierarchy\n",
511 				hierarchy[i].name);
512 			continue;
513 		}
514 
515 		ret = dtpm_for_each_child(hierarchy, &hierarchy[i], dtpm);
516 		if (ret)
517 			return ret;
518 	}
519 
520 	return 0;
521 }
522 
523 /**
524  * dtpm_create_hierarchy - Create the dtpm hierarchy
525  * @dtpm_match_table: Pointer to the array of device ID structures
526  *
527  * The function is called by the platform specific code with the
528  * description of the different node in the hierarchy. It creates the
529  * tree in the sysfs filesystem under the powercap dtpm entry.
530  *
531  * The expected tree has the format:
532  *
533  * struct dtpm_node hierarchy[] = {
534  *	[0] { .name = "topmost", type =  DTPM_NODE_VIRTUAL },
535  *	[1] { .name = "package", .type = DTPM_NODE_VIRTUAL, .parent = &hierarchy[0] },
536  *	[2] { .name = "/cpus/cpu0", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
537  *	[3] { .name = "/cpus/cpu1", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
538  *	[4] { .name = "/cpus/cpu2", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
539  *	[5] { .name = "/cpus/cpu3", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
540  *	[6] { }
541  * };
542  *
543  * The last element is always an empty one and marks the end of the
544  * array.
545  *
546  * Return: zero on success, a negative value in case of error. Errors
547  * are reported back from the underlying functions.
548  */
549 int dtpm_create_hierarchy(struct of_device_id *dtpm_match_table)
550 {
551 	const struct of_device_id *match;
552 	const struct dtpm_node *hierarchy;
553 	struct device_node *np;
554 	int i, ret;
555 
556 	mutex_lock(&dtpm_lock);
557 
558 	if (pct) {
559 		ret = -EBUSY;
560 		goto out_unlock;
561 	}
562 
563 	pct = powercap_register_control_type(NULL, "dtpm", NULL);
564 	if (IS_ERR(pct)) {
565 		pr_err("Failed to register control type\n");
566 		ret = PTR_ERR(pct);
567 		goto out_pct;
568 	}
569 
570 	ret = -ENODEV;
571 	np = of_find_node_by_path("/");
572 	if (!np)
573 		goto out_err;
574 
575 	match = of_match_node(dtpm_match_table, np);
576 
577 	of_node_put(np);
578 
579 	if (!match)
580 		goto out_err;
581 
582 	hierarchy = match->data;
583 	if (!hierarchy) {
584 		ret = -EFAULT;
585 		goto out_err;
586 	}
587 
588 	ret = dtpm_for_each_child(hierarchy, NULL, NULL);
589 	if (ret)
590 		goto out_err;
591 
592 	for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
593 
594 		if (!dtpm_subsys[i]->init)
595 			continue;
596 
597 		ret = dtpm_subsys[i]->init();
598 		if (ret)
599 			pr_info("Failed to initialize '%s': %d",
600 				dtpm_subsys[i]->name, ret);
601 	}
602 
603 	mutex_unlock(&dtpm_lock);
604 
605 	return 0;
606 
607 out_err:
608 	powercap_unregister_control_type(pct);
609 out_pct:
610 	pct = NULL;
611 out_unlock:
612 	mutex_unlock(&dtpm_lock);
613 
614 	return ret;
615 }
616 EXPORT_SYMBOL_GPL(dtpm_create_hierarchy);
617 
618 static void __dtpm_destroy_hierarchy(struct dtpm *dtpm)
619 {
620 	struct dtpm *child, *aux;
621 
622 	list_for_each_entry_safe(child, aux, &dtpm->children, sibling)
623 		__dtpm_destroy_hierarchy(child);
624 
625 	/*
626 	 * At this point, we know all children were removed from the
627 	 * recursive call before
628 	 */
629 	dtpm_unregister(dtpm);
630 }
631 
632 void dtpm_destroy_hierarchy(void)
633 {
634 	int i;
635 
636 	mutex_lock(&dtpm_lock);
637 
638 	if (!pct)
639 		goto out_unlock;
640 
641 	__dtpm_destroy_hierarchy(root);
642 
643 
644 	for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
645 
646 		if (!dtpm_subsys[i]->exit)
647 			continue;
648 
649 		dtpm_subsys[i]->exit();
650 	}
651 
652 	powercap_unregister_control_type(pct);
653 
654 	pct = NULL;
655 
656 	root = NULL;
657 
658 out_unlock:
659 	mutex_unlock(&dtpm_lock);
660 }
661 EXPORT_SYMBOL_GPL(dtpm_destroy_hierarchy);
662