mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/4] pmdomain: Preserve boot performance constraints until sync_state
@ 2026-10-13 12:48 Abel Vesa
  2026-10-13 12:48 ` [PATCH 1/4] pmdomain: core: Support a boot performance floor " Abel Vesa
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Abel Vesa @ 2026-10-13 12:48 UTC (permalink / raw)
  To: Ulf Hansson, Rafael J. Wysocki
  Cc: linux-pm, linux-kernel, linux-arm-msm, Abel Vesa

RPM and RPMh providers clamp performance requests until sync_state() to
accommodate hardware configured by firmware. This hides the boot floor
from genpd when aggregating performance requirements towards parents.

Move the floor into genpd, keeping consumer requests separate from the
effective vote. Apply it before runtime power-on and prepare the domain
hierarchy before system suspend. Release it at sync_state() and convert
RPM and RPMh to use the core mechanism.

Assisted-by: LLM
Signed-off-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
---
Abel Vesa (4):
      pmdomain: core: Support a boot performance floor until sync_state
      pmdomain: core: Prepare boot performance floors before system suspend
      pmdomain: qcom: rpmhpd: Let genpd enforce the boot performance floor
      pmdomain: qcom: rpmpd: Let genpd enforce the boot performance floor

 drivers/pmdomain/core.c        | 80 ++++++++++++++++++++++++++++++++++++++----
 drivers/pmdomain/qcom/rpmhpd.c | 17 +++++----
 drivers/pmdomain/qcom/rpmpd.c  | 12 +++----
 include/linux/pm_domain.h      |  6 ++++
 4 files changed, 93 insertions(+), 22 deletions(-)
---
base-commit: 4eb2586bd40db27d77cc5b724ca9386ed0c71d53
change-id: 20261010-genpd-boot-performance-floor-8afd51d6eab0

Best regards,
--  
Abel Vesa <abel.vesa@oss.qualcomm.com>


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/4] pmdomain: core: Support a boot performance floor until sync_state
  2026-10-13 12:48 [PATCH 0/4] pmdomain: Preserve boot performance constraints until sync_state Abel Vesa
@ 2026-10-13 12:48 ` Abel Vesa
  2026-10-13 12:48 ` [PATCH 2/4] pmdomain: core: Prepare boot performance floors before system suspend Abel Vesa
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Abel Vesa @ 2026-10-13 12:48 UTC (permalink / raw)
  To: Ulf Hansson, Rafael J. Wysocki
  Cc: linux-pm, linux-kernel, linux-arm-msm, Abel Vesa

Some providers retain conservative performance votes until their
sync_state() callbacks run to accommodate hardware configured by
firmware. Enforcing this policy inside the provider hides the floor
from genpd when aggregating performance requirements towards parents.

Add a provider-specified boot_performance_state for domains using
GENPD_FLAG_POWER_UNKNOWN. Clamp the effective domain vote without
changing individual consumer requests, and reaggregate those requests
when sync_state releases the floor. Zero imposes no additional floor.

Apply the floor before runtime power-on even without a preceding
performance request. Do not skip an unchanged consumer request if the
boot performance floor has yet to be applied.

Report sync-time performance errors and continue the normal power-off
attempt. Leave the provider sync callbacks and existing performance
error handling in place, without introducing retries or a new recovery
policy.

Assisted-by: LLM
Signed-off-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
---
 drivers/pmdomain/core.c   | 45 ++++++++++++++++++++++++++++++++++++++++-----
 include/linux/pm_domain.h |  6 ++++++
 2 files changed, 46 insertions(+), 5 deletions(-)

diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c
index 964e44c2f78f..6f2b406ce049 100644
--- a/drivers/pmdomain/core.c
+++ b/drivers/pmdomain/core.c
@@ -512,6 +512,10 @@ static int _genpd_set_performance_state(struct generic_pm_domain *genpd,
 	struct gpd_link *link = NULL;
 	int ret;
 
+	/* Keep consumer votes intact; clamp only the effective domain vote. */
+	if (genpd_is_power_unknown(genpd) && genpd->stay_on)
+		state = max(state, genpd->boot_performance_state);
+
 	if (state == genpd->performance_state)
 		return 0;
 
@@ -555,6 +559,12 @@ static int _genpd_set_performance_state(struct generic_pm_domain *genpd,
 	return ret;
 }
 
+static bool genpd_needs_boot_performance_state(struct generic_pm_domain *genpd)
+{
+	return genpd_is_power_unknown(genpd) && genpd->stay_on &&
+		genpd->performance_state < genpd->boot_performance_state;
+}
+
 static int genpd_set_performance_state(struct device *dev, unsigned int state)
 {
 	struct generic_pm_domain *genpd = dev_to_genpd(dev);
@@ -563,7 +573,7 @@ static int genpd_set_performance_state(struct device *dev, unsigned int state)
 	int ret;
 
 	prev_state = gpd_data->performance_state;
-	if (prev_state == state)
+	if (prev_state == state && !genpd_needs_boot_performance_state(genpd))
 		return 0;
 
 	gpd_data->performance_state = state;
@@ -1180,6 +1190,14 @@ static int genpd_power_on(struct generic_pm_domain *genpd, struct device *dev,
 		}
 	}
 
+	/* A consumer may enable a domain without first requesting an OPP. */
+	if (genpd_needs_boot_performance_state(genpd)) {
+		ret = _genpd_set_performance_state(genpd,
+						 genpd->performance_state, depth);
+		if (ret)
+			goto err;
+	}
+
 	ret = _genpd_power_on(genpd, true);
 	if (ret)
 		goto err;
@@ -3794,6 +3812,25 @@ int of_genpd_parse_idle_states(struct device_node *dn,
 }
 EXPORT_SYMBOL_GPL(of_genpd_parse_idle_states);
 
+/* Called with the domain lock held. */
+static void genpd_sync_boot_state(struct generic_pm_domain *genpd)
+{
+	unsigned int state;
+	int ret;
+
+	genpd->stay_on = false;
+	if (genpd_is_power_unknown(genpd) && genpd->boot_performance_state) {
+		/* Reaggregate real votes, excluding the boot performance floor. */
+		state = _genpd_reeval_performance_state(genpd, 0);
+		ret = _genpd_set_performance_state(genpd, state, 0);
+		if (ret)
+			pr_err("%s: Failed to sync boot performance state: %d\n",
+			       genpd->name, ret);
+	}
+
+	genpd_power_off(genpd, false, 0);
+}
+
 /**
  * of_genpd_sync_state() - A common sync_state function for genpd providers
  * @np: The device node the genpd provider is associated with.
@@ -3813,8 +3850,7 @@ void of_genpd_sync_state(struct device_node *np)
 	list_for_each_entry(genpd, &gpd_list, gpd_list_node) {
 		if (genpd->provider == of_fwnode_handle(np)) {
 			genpd_lock(genpd);
-			genpd->stay_on = false;
-			genpd_power_off(genpd, false, 0);
+			genpd_sync_boot_state(genpd);
 			genpd_unlock(genpd);
 		}
 	}
@@ -3841,8 +3877,7 @@ static void genpd_provider_sync_state(struct device *dev)
 
 	case GENPD_SYNC_STATE_SIMPLE:
 		genpd_lock(genpd);
-		genpd->stay_on = false;
-		genpd_power_off(genpd, false, 0);
+		genpd_sync_boot_state(genpd);
 		genpd_unlock(genpd);
 		break;
 
diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h
index 14e0e346c610..5927d9485054 100644
--- a/include/linux/pm_domain.h
+++ b/include/linux/pm_domain.h
@@ -129,6 +129,11 @@ struct dev_pm_domain_list {
  *				powered on during boot, genpd will prevent it
  *				from being powered off until the ->sync_state
  *				callback is invoked for it.
+ *				Providers may also set boot_performance_state
+ *				before pm_genpd_init() to specify a performance
+ *				floor until sync_state. This must be a valid
+ *				provider performance level, not a corner index.
+ *				Zero disables the boot performance floor.
  */
 #define GENPD_FLAG_PM_CLK	 (1U << 0)
 #define GENPD_FLAG_IRQ_SAFE	 (1U << 1)
@@ -221,6 +226,7 @@ struct generic_pm_domain {
 	unsigned int suspended_count;	/* System suspend device counter */
 	unsigned int prepared_count;	/* Suspend counter of prepared devices */
 	unsigned int performance_state;	/* Aggregated max performance state */
+	unsigned int boot_performance_state; /* Provider-specified boot floor */
 	cpumask_var_t cpus;		/* A cpumask of the attached CPUs */
 	bool synced_poweroff;		/* A consumer needs a synced poweroff */
 	bool stay_on;			/* Stay powered-on during boot. */

-- 
2.54.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 2/4] pmdomain: core: Prepare boot performance floors before system suspend
  2026-10-13 12:48 [PATCH 0/4] pmdomain: Preserve boot performance constraints until sync_state Abel Vesa
  2026-10-13 12:48 ` [PATCH 1/4] pmdomain: core: Support a boot performance floor " Abel Vesa
@ 2026-10-13 12:48 ` Abel Vesa
  2026-10-13 12:48 ` [PATCH 3/4] pmdomain: qcom: rpmhpd: Let genpd enforce the boot performance floor Abel Vesa
  2026-10-13 12:48 ` [PATCH 4/4] pmdomain: qcom: rpmpd: " Abel Vesa
  3 siblings, 0 replies; 5+ messages in thread
From: Abel Vesa @ 2026-10-13 12:48 UTC (permalink / raw)
  To: Ulf Hansson, Rafael J. Wysocki
  Cc: linux-pm, linux-kernel, linux-arm-msm, Abel Vesa

A system suspend can occur before a provider has received sync_state().
If a domain has not been used through runtime PM, its boot performance
floor may not have been applied when genpd_sync_power_on() enables it
during resume-noirq.

Prepare boot performance floors from genpd_prepare(), where performance
callbacks may sleep. Walk the parent hierarchy even when the device's
own domain has no boot floor: a parent without direct consumers would
otherwise miss preparation entirely. Take parent locks in the existing
child-to-parent order and propagate preparation failures to the PM core.

Also retain boot power protection in genpd_sync_power_off() for domains
with a boot performance floor. This prevents the system-sleep path from
withdrawing their power vote while boot protection remains active.

Keep performance programming out of resume-noirq. Domains with their
floor already satisfied do not need another performance update, and
no floor is imposed after sync_state() releases boot protection.

Assisted-by: LLM
Signed-off-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
---
 drivers/pmdomain/core.c | 35 ++++++++++++++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)

diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c
index 6f2b406ce049..4ff55175d899 100644
--- a/drivers/pmdomain/core.c
+++ b/drivers/pmdomain/core.c
@@ -1538,7 +1538,9 @@ static void genpd_sync_power_off(struct generic_pm_domain *genpd, bool use_lock,
 {
 	struct gpd_link *link;
 
-	if (!genpd_status_on_unlocked(genpd) || genpd_is_always_on(genpd))
+	if (!genpd_status_on_unlocked(genpd) || genpd_is_always_on(genpd) ||
+	    (genpd_is_power_unknown(genpd) && genpd->stay_on &&
+	     genpd->boot_performance_state))
 		return;
 
 	if (genpd->suspended_count != genpd->device_count
@@ -1623,6 +1625,31 @@ static void genpd_sync_power_on(struct generic_pm_domain *genpd, bool use_lock,
 	genpd->status = GENPD_STATE_ON;
 }
 
+/* Called with the domain lock held, before entering noirq. */
+static int genpd_prepare_boot_performance(struct generic_pm_domain *genpd,
+					 unsigned int depth)
+{
+	struct gpd_link *link;
+	int ret;
+
+	/* Parents may need a boot floor even when this domain does not. */
+	list_for_each_entry(link, &genpd->child_links, child_node) {
+		struct generic_pm_domain *parent = link->parent;
+
+		genpd_lock_nested(parent, depth + 1);
+		ret = genpd_prepare_boot_performance(parent, depth + 1);
+		genpd_unlock(parent);
+		if (ret)
+			return ret;
+	}
+
+	if (genpd_needs_boot_performance_state(genpd))
+		return _genpd_set_performance_state(genpd,
+						   genpd->performance_state, depth);
+
+	return 0;
+}
+
 /**
  * genpd_prepare - Start power transition of a device in a PM domain.
  * @dev: Device to start the transition of.
@@ -1644,6 +1671,12 @@ static int genpd_prepare(struct device *dev)
 		return -EINVAL;
 
 	genpd_lock(genpd);
+	/* Performance callbacks may sleep; prepare the hierarchy before noirq. */
+	ret = genpd_prepare_boot_performance(genpd, 0);
+	if (ret) {
+		genpd_unlock(genpd);
+		return ret;
+	}
 	genpd->prepared_count++;
 	genpd_unlock(genpd);
 

-- 
2.54.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 3/4] pmdomain: qcom: rpmhpd: Let genpd enforce the boot performance floor
  2026-10-13 12:48 [PATCH 0/4] pmdomain: Preserve boot performance constraints until sync_state Abel Vesa
  2026-10-13 12:48 ` [PATCH 1/4] pmdomain: core: Support a boot performance floor " Abel Vesa
  2026-10-13 12:48 ` [PATCH 2/4] pmdomain: core: Prepare boot performance floors before system suspend Abel Vesa
@ 2026-10-13 12:48 ` Abel Vesa
  2026-10-13 12:48 ` [PATCH 4/4] pmdomain: qcom: rpmpd: " Abel Vesa
  3 siblings, 0 replies; 5+ messages in thread
From: Abel Vesa @ 2026-10-13 12:48 UTC (permalink / raw)
  To: Ulf Hansson, Rafael J. Wysocki
  Cc: linux-pm, linux-kernel, linux-arm-msm, Abel Vesa

RPMh power domains are registered as powered off without knowing their
initial hardware state. Until the provider's sync_state() callback runs,
rpmhpd_aggregate_corner() replaces requests with the highest supported
corner, leaving genpd unaware of this boot performance constraint.

Set GENPD_FLAG_POWER_UNKNOWN and provide the highest mapped performance
level as boot_performance_state. Reject an empty mapping before indexing
its last entry. Let genpd enforce the floor and retain consumer-enabled
domains while boot protection is active, instead of clamping corners
inside the provider.

Retain state_synced for the hardware-specific sleep-vote policy. Include
the supplied corner in sleep votes for active-only domains until the
provider's sync_state() callback releases that protection. This also
applies when translating an enabled peer's vote. Keep the existing sync
callback to reconcile votes for domains left software-OFF by genpd.

Assisted-by: LLM
Signed-off-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
---
 drivers/pmdomain/qcom/rpmhpd.c | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/drivers/pmdomain/qcom/rpmhpd.c b/drivers/pmdomain/qcom/rpmhpd.c
index fa8fed5bd3e5..1488f31bcdda 100644
--- a/drivers/pmdomain/qcom/rpmhpd.c
+++ b/drivers/pmdomain/qcom/rpmhpd.c
@@ -40,7 +40,7 @@
  * @res_name:		Resource name used for cmd-db lookup
  * @addr:		Resource address as looped up using resource name from
  *			cmd-db
- * @state_synced:	Indicator that sync_state has been invoked for the rpmhpd resource
+ * @state_synced:	Whether boot protection of sleep votes has been released
  */
 struct rpmhpd {
 	struct device	*dev;
@@ -982,7 +982,7 @@ static void to_active_sleep(struct rpmhpd *pd, unsigned int corner,
 {
 	*active = corner;
 
-	if (pd->active_only)
+	if (pd->active_only && pd->state_synced)
 		*sleep = 0;
 	else
 		*sleep = *active;
@@ -1006,13 +1006,7 @@ static int rpmhpd_aggregate_corner(struct rpmhpd *pd, unsigned int corner)
 	unsigned int peer_active_corner = 0, peer_sleep_corner = 0;
 	unsigned int peer_enabled_corner;
 
-	if (pd->state_synced) {
-		to_active_sleep(pd, corner, &this_active_corner, &this_sleep_corner);
-	} else {
-		/* Clamp to highest corner if sync_state hasn't happened */
-		this_active_corner = pd->level_count - 1;
-		this_sleep_corner = pd->level_count - 1;
-	}
+	to_active_sleep(pd, corner, &this_active_corner, &this_sleep_corner);
 
 	if (peer && peer->enabled) {
 		peer_enabled_corner = max(peer->corner, peer->enable_corner);
@@ -1210,10 +1204,15 @@ static int rpmhpd_probe(struct platform_device *pdev)
 		ret = rpmhpd_update_level_mapping(rpmhpds[i]);
 		if (ret)
 			return ret;
+		if (!rpmhpds[i]->level_count)
+			return -EINVAL;
 
 		rpmhpds[i]->pd.power_off = rpmhpd_power_off;
 		rpmhpds[i]->pd.power_on = rpmhpd_power_on;
 		rpmhpds[i]->pd.set_performance_state = rpmhpd_set_performance_state;
+		rpmhpds[i]->pd.flags |= GENPD_FLAG_POWER_UNKNOWN;
+		rpmhpds[i]->pd.boot_performance_state =
+			rpmhpds[i]->level[rpmhpds[i]->level_count - 1];
 		pm_genpd_init(&rpmhpds[i]->pd, NULL, true);
 
 		data->domains[i] = &rpmhpds[i]->pd;

-- 
2.54.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 4/4] pmdomain: qcom: rpmpd: Let genpd enforce the boot performance floor
  2026-10-13 12:48 [PATCH 0/4] pmdomain: Preserve boot performance constraints until sync_state Abel Vesa
                   ` (2 preceding siblings ...)
  2026-10-13 12:48 ` [PATCH 3/4] pmdomain: qcom: rpmhpd: Let genpd enforce the boot performance floor Abel Vesa
@ 2026-10-13 12:48 ` Abel Vesa
  3 siblings, 0 replies; 5+ messages in thread
From: Abel Vesa @ 2026-10-13 12:48 UTC (permalink / raw)
  To: Ulf Hansson, Rafael J. Wysocki
  Cc: linux-pm, linux-kernel, linux-arm-msm, Abel Vesa

RPMPD clamps corner votes to max_state until its sync_state() callback
runs, without exposing this boot constraint to genpd. The separate enable
vote is not covered by the clamp: rpmpd_power_off() can withdraw it before
the provider's sync_state() callback has run.

Set GENPD_FLAG_POWER_UNKNOWN and use max_state as boot_performance_state.
Let genpd enforce the performance floor and retain consumer-enabled
domains while boot protection is active. Remove the local corner clamp
so genpd accounts for the floor in parent-domain performance aggregation.

Keep state_synced to include the supplied corner in sleep votes for
active-only domains until the provider's sync_state() callback runs,
including when translating an enabled peer's vote. Retain the existing
callback to reconcile corner votes for domains left software-OFF in
genpd.

Assisted-by: LLM
Signed-off-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
---
 drivers/pmdomain/qcom/rpmpd.c | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/drivers/pmdomain/qcom/rpmpd.c b/drivers/pmdomain/qcom/rpmpd.c
index 5f55fc791131..302a5f3a021a 100644
--- a/drivers/pmdomain/qcom/rpmpd.c
+++ b/drivers/pmdomain/qcom/rpmpd.c
@@ -992,7 +992,7 @@ static void to_active_sleep(struct rpmpd *pd, unsigned int corner,
 {
 	*active = corner;
 
-	if (pd->active_only)
+	if (pd->active_only && pd->state_synced)
 		*sleep = 0;
 	else
 		*sleep = *active;
@@ -1006,11 +1006,7 @@ static int rpmpd_aggregate_corner(struct rpmpd *pd)
 	unsigned int this_active_corner = 0, this_sleep_corner = 0;
 	unsigned int peer_active_corner = 0, peer_sleep_corner = 0;
 
-	/* Clamp to the highest corner/level if sync_state isn't done yet */
-	if (!pd->state_synced)
-		this_active_corner = this_sleep_corner = pd->max_state;
-	else
-		to_active_sleep(pd, pd->corner, &this_active_corner, &this_sleep_corner);
+	to_active_sleep(pd, pd->corner, &this_active_corner, &this_sleep_corner);
 
 	if (peer && peer->enabled)
 		to_active_sleep(peer, peer->corner, &peer_active_corner,
@@ -1122,10 +1118,12 @@ static int rpmpd_probe(struct platform_device *pdev)
 		}
 
 		rpmpds[i]->max_state = desc->max_state;
+		rpmpds[i]->pd.boot_performance_state = desc->max_state;
 		rpmpds[i]->pd.power_off = rpmpd_power_off;
 		rpmpds[i]->pd.power_on = rpmpd_power_on;
 		rpmpds[i]->pd.set_performance_state = rpmpd_set_performance;
-		rpmpds[i]->pd.flags = GENPD_FLAG_ACTIVE_WAKEUP;
+		rpmpds[i]->pd.flags = GENPD_FLAG_ACTIVE_WAKEUP |
+				      GENPD_FLAG_POWER_UNKNOWN;
 		pm_genpd_init(&rpmpds[i]->pd, NULL, true);
 
 		data->domains[i] = &rpmpds[i]->pd;

-- 
2.54.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-10-13 12:49 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-13 12:48 [PATCH 0/4] pmdomain: Preserve boot performance constraints until sync_state Abel Vesa
2026-10-13 12:48 ` [PATCH 1/4] pmdomain: core: Support a boot performance floor " Abel Vesa
2026-10-13 12:48 ` [PATCH 2/4] pmdomain: core: Prepare boot performance floors before system suspend Abel Vesa
2026-10-13 12:48 ` [PATCH 3/4] pmdomain: qcom: rpmhpd: Let genpd enforce the boot performance floor Abel Vesa
2026-10-13 12:48 ` [PATCH 4/4] pmdomain: qcom: rpmpd: " Abel Vesa

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®