1 // SPDX-License-Identifier: GPL-2.0-only
3 * Copyright 2020 Linaro Limited
5 * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
7 * The powercap based Dynamic Thermal Power Management framework
8 * provides to the userspace a consistent API to set the power limit
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.
18 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
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>
27 #define DTPM_POWER_LIMIT_FLAG 0
29 static const char *constraint_name
[] = {
33 static DEFINE_MUTEX(dtpm_lock
);
34 static struct powercap_control_type
*pct
;
35 static struct dtpm
*root
;
37 static int get_time_window_us(struct powercap_zone
*pcz
, int cid
, u64
*window
)
42 static int set_time_window_us(struct powercap_zone
*pcz
, int cid
, u64 window
)
47 static int get_max_power_range_uw(struct powercap_zone
*pcz
, u64
*max_power_uw
)
49 struct dtpm
*dtpm
= to_dtpm(pcz
);
51 mutex_lock(&dtpm_lock
);
52 *max_power_uw
= dtpm
->power_max
- dtpm
->power_min
;
53 mutex_unlock(&dtpm_lock
);
58 static int __get_power_uw(struct dtpm
*dtpm
, u64
*power_uw
)
65 *power_uw
= dtpm
->ops
->get_power_uw(dtpm
);
71 list_for_each_entry(child
, &dtpm
->children
, sibling
) {
72 ret
= __get_power_uw(child
, &power
);
81 static int get_power_uw(struct powercap_zone
*pcz
, u64
*power_uw
)
83 struct dtpm
*dtpm
= to_dtpm(pcz
);
86 mutex_lock(&dtpm_lock
);
87 ret
= __get_power_uw(dtpm
, power_uw
);
88 mutex_unlock(&dtpm_lock
);
93 static void __dtpm_rebalance_weight(struct dtpm
*dtpm
)
97 list_for_each_entry(child
, &dtpm
->children
, sibling
) {
99 pr_debug("Setting weight '%d' for '%s'\n",
100 child
->weight
, child
->zone
.name
);
102 child
->weight
= DIV64_U64_ROUND_CLOSEST(
103 child
->power_max
* 1024, dtpm
->power_max
);
105 __dtpm_rebalance_weight(child
);
109 static void __dtpm_sub_power(struct dtpm
*dtpm
)
111 struct dtpm
*parent
= dtpm
->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
;
120 __dtpm_rebalance_weight(root
);
123 static void __dtpm_add_power(struct dtpm
*dtpm
)
125 struct dtpm
*parent
= dtpm
->parent
;
128 parent
->power_min
+= dtpm
->power_min
;
129 parent
->power_max
+= dtpm
->power_max
;
130 parent
->power_limit
+= dtpm
->power_limit
;
131 parent
= parent
->parent
;
134 __dtpm_rebalance_weight(root
);
138 * dtpm_update_power - Update the power on the dtpm
139 * @dtpm: a pointer to a dtpm structure to update
140 * @power_min: a u64 representing the new power_min value
141 * @power_max: a u64 representing the new power_max value
143 * Function to update the power values of the dtpm node specified in
144 * parameter. These new values will be propagated to the tree.
146 * Return: zero on success, -EINVAL if the values are inconsistent
148 int dtpm_update_power(struct dtpm
*dtpm
, u64 power_min
, u64 power_max
)
152 mutex_lock(&dtpm_lock
);
154 if (power_min
== dtpm
->power_min
&& power_max
== dtpm
->power_max
)
157 if (power_max
< power_min
) {
162 __dtpm_sub_power(dtpm
);
164 dtpm
->power_min
= power_min
;
165 dtpm
->power_max
= power_max
;
166 if (!test_bit(DTPM_POWER_LIMIT_FLAG
, &dtpm
->flags
))
167 dtpm
->power_limit
= power_max
;
169 __dtpm_add_power(dtpm
);
172 mutex_unlock(&dtpm_lock
);
178 * dtpm_release_zone - Cleanup when the node is released
179 * @pcz: a pointer to a powercap_zone structure
181 * Do some housecleaning and update the weight on the tree. The
182 * release will be denied if the node has children. This function must
183 * be called by the specific release callback of the different
186 * Return: 0 on success, -EBUSY if there are children
188 int dtpm_release_zone(struct powercap_zone
*pcz
)
190 struct dtpm
*dtpm
= to_dtpm(pcz
);
191 struct dtpm
*parent
= dtpm
->parent
;
193 mutex_lock(&dtpm_lock
);
195 if (!list_empty(&dtpm
->children
)) {
196 mutex_unlock(&dtpm_lock
);
201 list_del(&dtpm
->sibling
);
203 __dtpm_sub_power(dtpm
);
205 mutex_unlock(&dtpm_lock
);
208 dtpm
->ops
->release(dtpm
);
218 static int __get_power_limit_uw(struct dtpm
*dtpm
, int cid
, u64
*power_limit
)
220 *power_limit
= dtpm
->power_limit
;
224 static int get_power_limit_uw(struct powercap_zone
*pcz
,
225 int cid
, u64
*power_limit
)
227 struct dtpm
*dtpm
= to_dtpm(pcz
);
230 mutex_lock(&dtpm_lock
);
231 ret
= __get_power_limit_uw(dtpm
, cid
, power_limit
);
232 mutex_unlock(&dtpm_lock
);
238 * Set the power limit on the nodes, the power limit is distributed
239 * given the weight of the children.
241 * The dtpm node lock must be held when calling this function.
243 static int __set_power_limit_uw(struct dtpm
*dtpm
, int cid
, u64 power_limit
)
250 * A max power limitation means we remove the power limit,
251 * otherwise we set a constraint and flag the dtpm node.
253 if (power_limit
== dtpm
->power_max
) {
254 clear_bit(DTPM_POWER_LIMIT_FLAG
, &dtpm
->flags
);
256 set_bit(DTPM_POWER_LIMIT_FLAG
, &dtpm
->flags
);
259 pr_debug("Setting power limit for '%s': %llu uW\n",
260 dtpm
->zone
.name
, power_limit
);
263 * Only leaves of the dtpm tree has ops to get/set the power
266 dtpm
->power_limit
= dtpm
->ops
->set_power_uw(dtpm
, power_limit
);
268 dtpm
->power_limit
= 0;
270 list_for_each_entry(child
, &dtpm
->children
, sibling
) {
273 * Integer division rounding will inevitably
274 * lead to a different min or max value when
275 * set several times. In order to restore the
276 * initial value, we force the child's min or
277 * max power every time if the constraint is
280 if (power_limit
== dtpm
->power_max
) {
281 power
= child
->power_max
;
282 } else if (power_limit
== dtpm
->power_min
) {
283 power
= child
->power_min
;
285 power
= DIV_ROUND_CLOSEST_ULL(
286 power_limit
* child
->weight
, 1024);
289 pr_debug("Setting power limit for '%s': %llu uW\n",
290 child
->zone
.name
, power
);
292 ret
= __set_power_limit_uw(child
, cid
, power
);
294 ret
= __get_power_limit_uw(child
, cid
, &power
);
299 dtpm
->power_limit
+= power
;
306 static int set_power_limit_uw(struct powercap_zone
*pcz
,
307 int cid
, u64 power_limit
)
309 struct dtpm
*dtpm
= to_dtpm(pcz
);
312 mutex_lock(&dtpm_lock
);
315 * Don't allow values outside of the power range previously
316 * set when initializing the power numbers.
318 power_limit
= clamp_val(power_limit
, dtpm
->power_min
, dtpm
->power_max
);
320 ret
= __set_power_limit_uw(dtpm
, cid
, power_limit
);
322 pr_debug("%s: power limit: %llu uW, power max: %llu uW\n",
323 dtpm
->zone
.name
, dtpm
->power_limit
, dtpm
->power_max
);
325 mutex_unlock(&dtpm_lock
);
330 static const char *get_constraint_name(struct powercap_zone
*pcz
, int cid
)
332 return constraint_name
[cid
];
335 static int get_max_power_uw(struct powercap_zone
*pcz
, int id
, u64
*max_power
)
337 struct dtpm
*dtpm
= to_dtpm(pcz
);
339 mutex_lock(&dtpm_lock
);
340 *max_power
= dtpm
->power_max
;
341 mutex_unlock(&dtpm_lock
);
346 static struct powercap_zone_constraint_ops constraint_ops
= {
347 .set_power_limit_uw
= set_power_limit_uw
,
348 .get_power_limit_uw
= get_power_limit_uw
,
349 .set_time_window_us
= set_time_window_us
,
350 .get_time_window_us
= get_time_window_us
,
351 .get_max_power_uw
= get_max_power_uw
,
352 .get_name
= get_constraint_name
,
355 static struct powercap_zone_ops zone_ops
= {
356 .get_max_power_range_uw
= get_max_power_range_uw
,
357 .get_power_uw
= get_power_uw
,
358 .release
= dtpm_release_zone
,
362 * dtpm_alloc - Allocate and initialize a dtpm struct
363 * @name: a string specifying the name of the node
365 * Return: a struct dtpm pointer, NULL in case of error
367 struct dtpm
*dtpm_alloc(struct dtpm_ops
*ops
)
371 dtpm
= kzalloc(sizeof(*dtpm
), GFP_KERNEL
);
373 INIT_LIST_HEAD(&dtpm
->children
);
374 INIT_LIST_HEAD(&dtpm
->sibling
);
383 * dtpm_unregister - Unregister a dtpm node from the hierarchy tree
384 * @dtpm: a pointer to a dtpm structure corresponding to the node to be removed
386 * Call the underlying powercap unregister function. That will call
387 * the release callback of the powercap zone.
389 void dtpm_unregister(struct dtpm
*dtpm
)
391 powercap_unregister_zone(pct
, &dtpm
->zone
);
393 pr_info("Unregistered dtpm node '%s'\n", dtpm
->zone
.name
);
397 * dtpm_register - Register a dtpm node in the hierarchy tree
398 * @name: a string specifying the name of the node
399 * @dtpm: a pointer to a dtpm structure corresponding to the new node
400 * @parent: a pointer to a dtpm structure corresponding to the parent node
402 * Create a dtpm node in the tree. If no parent is specified, the node
403 * is the root node of the hierarchy. If the root node already exists,
404 * then the registration will fail. The powercap controller must be
405 * initialized before calling this function.
407 * The dtpm structure must be initialized with the power numbers
408 * before calling this function.
410 * Return: zero on success, a negative value in case of error:
411 * -EAGAIN: the function is called before the framework is initialized.
412 * -EBUSY: the root node is already inserted
413 * -EINVAL: * there is no root node yet and @parent is specified
414 * * no all ops are defined
415 * * parent have ops which are reserved for leaves
416 * Other negative values are reported back from the powercap framework
418 int dtpm_register(const char *name
, struct dtpm
*dtpm
, struct dtpm
*parent
)
420 struct powercap_zone
*pcz
;
431 if (parent
&& parent
->ops
)
437 if (dtpm
->ops
&& !(dtpm
->ops
->set_power_uw
&&
438 dtpm
->ops
->get_power_uw
&&
442 pcz
= powercap_register_zone(&dtpm
->zone
, pct
, name
,
443 parent
? &parent
->zone
: NULL
,
444 &zone_ops
, MAX_DTPM_CONSTRAINTS
,
449 mutex_lock(&dtpm_lock
);
452 list_add_tail(&dtpm
->sibling
, &parent
->children
);
453 dtpm
->parent
= parent
;
458 __dtpm_add_power(dtpm
);
460 pr_info("Registered dtpm node '%s' / %llu-%llu uW, \n",
461 dtpm
->zone
.name
, dtpm
->power_min
, dtpm
->power_max
);
463 mutex_unlock(&dtpm_lock
);
468 static int __init
dtpm_init(void)
470 struct dtpm_descr
**dtpm_descr
;
472 pct
= powercap_register_control_type(NULL
, "dtpm", NULL
);
474 pr_err("Failed to register control type\n");
478 for_each_dtpm_table(dtpm_descr
)
479 (*dtpm_descr
)->init(*dtpm_descr
);
483 late_initcall(dtpm_init
);