DTPM represents the devices supporting the power limitation as a tree of nodes: the leaves are the devices themselves, like CPU performance domains or devfreq devices, and the intermediate nodes group their children. When a power limit is set on an intermediate node, __set_power_limit_uw() distributes it among the children proportionally to their weights, which __dtpm_rebalance_weight() computes from the maximum power of the nodes only. So when the limit is close to the minimum power of the node, the share of a child having a high minimum power compared to its maximum power can be lower than its own minimum power. For example, with a parent having two children, the first one with a power range of 80-1200 and the second one with a range of 400-3000, the parent's range is 480-4200. A limit of 500 gives a share of about 143 to the first child and 357 to the second one, below its minimum power of 400. As the second child can't go below its lowest performance state, the actual power of the children exceeds the parent's limit, while it could have been met by giving 400 to the second child and 100 to the first one. Moreover, such a share is lower than the power of the lowest performance state of the child, so set_pd_power_limit() in the CPU and devfreq backends reads out of the bounds of the energy model table. This out-of-bounds access is addressed in the backends by [1], which is still needed, as the share can also be too low when the minimum power of a child is stale, e.g. right after a CPU of its performance domain is brought online. The power limit of a node is never lower than the sum of the minimum power of its children, so a distribution where every child gets at least its minimum power always exists. When the share of a child is below its minimum power, give it its minimum power and take the difference from the siblings, proportionally to their headroom above their own minimum power. The distribution is unchanged when every share is at least the child's minimum power. Found by Linux Verification Center (linuxtesting.org) with SVACE. Link: https://lore.kernel.org/linux-pm/20260612062536.9147-1-elazarl@gmail.com/ [1] Fixes: a20d0ef97abf ("powercap/drivers/dtpm: Add API for dynamic thermal power management") Cc: stable@vger.kernel.org Signed-off-by: Mikhail Dmitrichenko --- Documentation/power/powercap/dtpm.rst | 5 ++ drivers/powercap/dtpm.c | 68 +++++++++++++++++++++------ 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/Documentation/power/powercap/dtpm.rst b/Documentation/power/powercap/dtpm.rst index a38dee3d815b..997664da7ac7 100644 --- a/Documentation/power/powercap/dtpm.rst +++ b/Documentation/power/powercap/dtpm.rst @@ -99,6 +99,11 @@ When a power limitation is applied to a node, then it is distributed along the c | `-- pd2 (w=486) --> power_limit = 1519mW +As the weights are based on the maximum power only, the share of a node +can be lower than its minimum power. In this case, the node gets its +minimum power and the difference is taken from its siblings, +proportionally to their headroom above their own minimum power. + Flat description ---------------- diff --git a/drivers/powercap/dtpm.c b/drivers/powercap/dtpm.c index b7a65e543f19..402b18005a33 100644 --- a/drivers/powercap/dtpm.c +++ b/drivers/powercap/dtpm.c @@ -195,6 +195,28 @@ static int get_power_limit_uw(struct powercap_zone *pcz, return 0; } +/* + * Compute the share of the power limit of a node given to one of its + * children, based on the weight of the child. + */ +static u64 __dtpm_child_share(struct dtpm *dtpm, struct dtpm *child, + u64 power_limit) +{ + /* + * Integer division rounding will inevitably lead to a different + * min or max value when set several times. In order to restore + * the initial value, we force the child's min or max power every + * time if the constraint is at the boundaries. + */ + if (power_limit == dtpm->power_max) + return child->power_max; + + if (power_limit == dtpm->power_min) + return child->power_min; + + return DIV_ROUND_CLOSEST_ULL(power_limit * child->weight, 1024); +} + /* * Set the power limit on the nodes, the power limit is distributed * given the weight of the children. @@ -226,26 +248,42 @@ static int __set_power_limit_uw(struct dtpm *dtpm, int cid, u64 power_limit) if (dtpm->ops) { dtpm->power_limit = dtpm->ops->set_power_uw(dtpm, power_limit); } else { + u64 deficit = 0, headroom = 0; + + /* + * The weights are based on the maximum power only, so the + * share of a child can be lower than its minimum power. In + * this case, the child gets its minimum power and the + * difference is taken from the siblings, proportionally to + * their headroom above their own minimum power. + */ + list_for_each_entry(child, &dtpm->children, sibling) { + power = __dtpm_child_share(dtpm, child, power_limit); + if (power < child->power_min) + deficit += child->power_min - power; + else + headroom += power - child->power_min; + } + + /* + * The power limit of a node is never lower than the sum of + * the minimum power of its children, so the headroom covers + * the deficit except for rounding errors. + */ + deficit = min(deficit, headroom); + dtpm->power_limit = 0; list_for_each_entry(child, &dtpm->children, sibling) { - /* - * Integer division rounding will inevitably - * lead to a different min or max value when - * set several times. In order to restore the - * initial value, we force the child's min or - * max power every time if the constraint is - * at the boundaries. - */ - if (power_limit == dtpm->power_max) { - power = child->power_max; - } else if (power_limit == dtpm->power_min) { + power = __dtpm_child_share(dtpm, child, power_limit); + + if (power < child->power_min) power = child->power_min; - } else { - power = DIV_ROUND_CLOSEST_ULL( - power_limit * child->weight, 1024); - } + else if (deficit) + power -= DIV64_U64_ROUND_UP(deficit * + (power - child->power_min), + headroom); pr_debug("Setting power limit for '%s': %llu uW\n", child->zone.name, power); -- 2.43.0