pmdomain: core: Scale down parent/child performance states in reverse order

Power domains might have parent domains assigned that are automatically
managed by the PM domain core. In particular, parent domains are
automatically powered on/off and setting performance states on child
domains are propagated to parent domains (e.g. using an OPP table from the
device tree).

Currently the parent performance state is always adjusted before the
performance state of the child domain, which is a problem for some cases
when scaling down the performance state. More exactly, it may lead to that
the parent domain could run in a lower performance state, than what is
required by the child domain.

To fix the behaviour, let's differentiate between scaling up/down and
adjust the order of operations:

 - When scaling up, parent domains should be adjusted before the child
   domain. In case of an error, the rollback happens in reverse order.

 - When scaling down, parent domains should be adjusted after the child
   domain, in reverse order, just as if we would rollback scaling up.
   In case of an error, the rollback happens in normal order (just as
   if we would normally scale up).

Signed-off-by: Stephan Gerhold <stephan@gerhold.net>
Link: https://lore.kernel.org/r/20240103-genpd-perf-order-v2-1-eeecfc55624b@gerhold.net
Tested-by: Ulf Hansson <ulf.hansson@linaro.org>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
This commit is contained in:
Stephan Gerhold 2024-01-03 22:10:05 +01:00 committed by Ulf Hansson
parent b9401b65fb
commit 2b391c4ca7

View File

@ -310,73 +310,103 @@ static int genpd_xlate_performance_state(struct generic_pm_domain *genpd,
pstate);
}
static int _genpd_set_performance_state(struct generic_pm_domain *genpd,
unsigned int state, int depth);
static void _genpd_rollback_parent_state(struct gpd_link *link, int depth)
{
struct generic_pm_domain *parent = link->parent;
int parent_state;
genpd_lock_nested(parent, depth + 1);
parent_state = link->prev_performance_state;
link->performance_state = parent_state;
parent_state = _genpd_reeval_performance_state(parent, parent_state);
if (_genpd_set_performance_state(parent, parent_state, depth + 1)) {
pr_err("%s: Failed to roll back to %d performance state\n",
parent->name, parent_state);
}
genpd_unlock(parent);
}
static int _genpd_set_parent_state(struct generic_pm_domain *genpd,
struct gpd_link *link,
unsigned int state, int depth)
{
struct generic_pm_domain *parent = link->parent;
int parent_state, ret;
/* Find parent's performance state */
ret = genpd_xlate_performance_state(genpd, parent, state);
if (unlikely(ret < 0))
return ret;
parent_state = ret;
genpd_lock_nested(parent, depth + 1);
link->prev_performance_state = link->performance_state;
link->performance_state = parent_state;
parent_state = _genpd_reeval_performance_state(parent, parent_state);
ret = _genpd_set_performance_state(parent, parent_state, depth + 1);
if (ret)
link->performance_state = link->prev_performance_state;
genpd_unlock(parent);
return ret;
}
static int _genpd_set_performance_state(struct generic_pm_domain *genpd,
unsigned int state, int depth)
{
struct generic_pm_domain *parent;
struct gpd_link *link;
int parent_state, ret;
struct gpd_link *link = NULL;
int ret;
if (state == genpd->performance_state)
return 0;
/* Propagate to parents of genpd */
list_for_each_entry(link, &genpd->child_links, child_node) {
parent = link->parent;
/* Find parent's performance state */
ret = genpd_xlate_performance_state(genpd, parent, state);
if (unlikely(ret < 0))
goto err;
parent_state = ret;
genpd_lock_nested(parent, depth + 1);
link->prev_performance_state = link->performance_state;
link->performance_state = parent_state;
parent_state = _genpd_reeval_performance_state(parent,
parent_state);
ret = _genpd_set_performance_state(parent, parent_state, depth + 1);
if (ret)
link->performance_state = link->prev_performance_state;
genpd_unlock(parent);
if (ret)
goto err;
/* When scaling up, propagate to parents first in normal order */
if (state > genpd->performance_state) {
list_for_each_entry(link, &genpd->child_links, child_node) {
ret = _genpd_set_parent_state(genpd, link, state, depth);
if (ret)
goto rollback_parents_up;
}
}
if (genpd->set_performance_state) {
ret = genpd->set_performance_state(genpd, state);
if (ret)
goto err;
if (ret) {
if (link)
goto rollback_parents_up;
return ret;
}
}
/* When scaling down, propagate to parents last in reverse order */
if (state < genpd->performance_state) {
list_for_each_entry_reverse(link, &genpd->child_links, child_node) {
ret = _genpd_set_parent_state(genpd, link, state, depth);
if (ret)
goto rollback_parents_down;
}
}
genpd->performance_state = state;
return 0;
err:
/* Encountered an error, lets rollback */
list_for_each_entry_continue_reverse(link, &genpd->child_links,
child_node) {
parent = link->parent;
genpd_lock_nested(parent, depth + 1);
parent_state = link->prev_performance_state;
link->performance_state = parent_state;
parent_state = _genpd_reeval_performance_state(parent,
parent_state);
if (_genpd_set_performance_state(parent, parent_state, depth + 1)) {
pr_err("%s: Failed to roll back to %d performance state\n",
parent->name, parent_state);
}
genpd_unlock(parent);
}
rollback_parents_up:
list_for_each_entry_continue_reverse(link, &genpd->child_links, child_node)
_genpd_rollback_parent_state(link, depth);
return ret;
rollback_parents_down:
list_for_each_entry_continue(link, &genpd->child_links, child_node)
_genpd_rollback_parent_state(link, depth);
return ret;
}