* [PATCH 0/2] cpuidle/pmdomain: Correctly track state of excluded CPU clusters
@ 2026-08-11 5:50 Maulik Shah
2026-08-11 5:50 ` [PATCH 1/2] cpuidle: psci: Start CPU-cluster pmdomain OFF when OSI is used Maulik Shah
2026-08-11 5:50 ` [PATCH 2/2] pmdomain: core: Initialize state_idx to deepest state for OFF domains Maulik Shah
0 siblings, 2 replies; 5+ messages in thread
From: Maulik Shah @ 2026-08-11 5:50 UTC (permalink / raw)
To: Ulf Hansson, Rafael J. Wysocki, Daniel Lezcano, Christian Loehle,
Sudeep Holla, Rafael J. Wysocki, Dmitry Osipenko
Cc: linux-pm, linux-arm-kernel, linux-kernel, linux-arm-msm, Maulik Shah
When CPUs in a cluster are excluded at boot (e.g. via maxcpus=N), their
CPU-cluster genpd is never really powered off, and its reported
idle-state index never reflects the deepest state either. Both issues
independently prevent the parent domain from ever powering off, even
though the excluded clusters never ran any CPU.
Patch 1 addresses the domain status: psci_pd_init() always calls
pm_genpd_init() with is_off=false, so a CPU-cluster genpd is marked ON
at creation regardless of whether any CPU in it has actually powered it
on. For clusters whose CPUs never come online, nothing ever transitions
the domain to OFF afterwards, so it is stuck reporting ON forever. This
patch starts the domain OFF at creation when OSI is in use, since
dt_idle_attach_cpu() and the cpuhp callbacks already power it on
correctly for any CPU that is or becomes online.
Patch 2 addresses the idle-state index for domains that start OFF: even
after patch 1, a domain that starts OFF without ever going through an
actual power-off sequence is left with state_idx=0 (its kzalloc'd
value), i.e. reported as parked at its shallowest idle state instead of
its deepest one. genpd_power_off()/genpd_sync_power_off() gate parent
power-off on every child being at its deepest state index, so these
domains still block the parent from powering off, even though they are
genuinely off. This patch sets state_idx to the deepest state whenever
a domain is initialised OFF, addressing the root cause for every
is_off=true caller of pm_genpd_init(), not just PSCI CPU clusters.
Together, these two patches let the parent domain power off normally
on systems such as hamoa when booted with maxcpus=N, allowing the
deepest system-level low-power mode (SS3) to be entered.
The two patches address independent gaps and have no hard dependency
on each other; either can be merged on its own or in either order.
Signed-off-by: Maulik Shah <maulik.shah@oss.qualcomm.com>
---
Maulik Shah (2):
cpuidle: psci: Start CPU-cluster pmdomain OFF when OSI is used
pmdomain: core: Initialize state_idx to deepest state for OFF domains
drivers/cpuidle/cpuidle-psci-domain.c | 14 +++++++++++++-
drivers/pmdomain/core.c | 2 ++
2 files changed, 15 insertions(+), 1 deletion(-)
---
base-commit: 6b8c8af514d739d0335f5579b585e02babe8a727
change-id: 20260811-domain_off_ss3-ce65171c1416
Best regards,
--
Maulik Shah <maulik.shah@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 1/2] cpuidle: psci: Start CPU-cluster pmdomain OFF when OSI is used
2026-08-11 5:50 [PATCH 0/2] cpuidle/pmdomain: Correctly track state of excluded CPU clusters Maulik Shah
@ 2026-08-11 5:50 ` Maulik Shah
2026-08-12 15:34 ` Ulf Hansson
2026-08-11 5:50 ` [PATCH 2/2] pmdomain: core: Initialize state_idx to deepest state for OFF domains Maulik Shah
1 sibling, 1 reply; 5+ messages in thread
From: Maulik Shah @ 2026-08-11 5:50 UTC (permalink / raw)
To: Ulf Hansson, Rafael J. Wysocki, Daniel Lezcano, Christian Loehle,
Sudeep Holla, Rafael J. Wysocki, Dmitry Osipenko
Cc: linux-pm, linux-arm-kernel, linux-kernel, linux-arm-msm, Maulik Shah
psci_pd_init() always calls pm_genpd_init() with is_off=false, so a
CPU-cluster pmdomain is marked ON at creation regardless of whether any
CPU has actually powered it on yet. The pmdomain's own status tracking
(and the power-on notifier chain fired from _genpd_power_on()) is
therefore wrong from the outset for OSI mode.
A concrete example: hamoa has 3 CPU clusters, each with 4 CPUs, under a
single parent system-level pmdomain. Passing "maxcpus=4" on the kernel
command line limits boot to the first 4 CPUs, so only cluster0 is ever
actually powered on. Without this fix, cluster1 and cluster2 are still
marked ON at creation regardless, since is_off is always false. Because
none of their CPUs ever come online, neither dt_idle_attach_cpu() nor
psci_idle_cpuhp_up() ever fires for them, so nothing subsequently
drives those two domains to OFF - they stay marked ON for as long as
the system runs. When cluster0's CPUs go idle and its pmdomain is
powered off, the parent system-level pmdomain still sees cluster1 and
cluster2 as ON and therefore never selects a system-level idle state,
even though no CPU in those two clusters ever executed.
Starting the domain OFF is safe under OSI:
- For CPUs already online by the time this driver probes,
dt_idle_attach_cpu() explicitly checks cpu_online() and calls
pm_runtime_get_sync() on the CPU's attach device, which resolves to
this same pmdomain and drives a real power-on immediately. This runs
from psci_cpuidle_probe(), a device_initcall, strictly after
psci_idle_init_domains()'s core_initcall has already created and
marked off every pmdomain, so there is no window where an online
CPU's domain is left off with nothing left to turn it on.
- For CPUs that come online later, psci_idle_cpuhp_up() (registered
via cpuhp_setup_state_nocalls(), which never invokes the callback
for already-online CPUs at registration time) powers the domain on
through the normal cpuhp AP_ONLINE path.
Gate this on use_osi rather than applying it unconditionally: in
non-OSI mode psci_pd_init() sets GENPD_FLAG_ALWAYS_ON, which
independently blocks genpd_power_off() regardless of is_off, so the
domain can never really be off there and marking it is_off=true would
be misleading noise with no effect.
Fixes: a65a397f2451 ("cpuidle: psci: Add support for PM domains by using genpd")
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Maulik Shah <maulik.shah@oss.qualcomm.com>
---
drivers/cpuidle/cpuidle-psci-domain.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/drivers/cpuidle/cpuidle-psci-domain.c b/drivers/cpuidle/cpuidle-psci-domain.c
index b9e4ad7d43a3..fab9de648b17 100644
--- a/drivers/cpuidle/cpuidle-psci-domain.c
+++ b/drivers/cpuidle/cpuidle-psci-domain.c
@@ -78,7 +78,19 @@ static int psci_pd_init(struct device_node *np, bool use_osi)
/* Use governor for CPU PM domains if it has some states to manage. */
pd_gov = pd->states ? &pm_domain_cpu_gov : NULL;
- ret = pm_genpd_init(pd, pd_gov, false);
+ /*
+ * Start the domain in the OFF state when OSI is in use, so that
+ * genpd's own status tracking (and its power-on notifier chain)
+ * reflects reality from the outset instead of reporting ON before
+ * any CPU in the domain has actually requested it. This is safe:
+ * dt_idle_attach_cpu() runtime-resumes the domain for every CPU
+ * that is already online by the time this driver probes, and the
+ * PSCI cpuidle cpuhp callbacks power it on/off for CPUs that come
+ * online/offline afterwards. When OSI isn't in use, GENPD_FLAG_ALWAYS_ON
+ * above keeps the domain powered regardless of is_off, so this has
+ * no effect there.
+ */
+ ret = pm_genpd_init(pd, pd_gov, use_osi);
if (ret)
goto free_pd_prov;
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 2/2] pmdomain: core: Initialize state_idx to deepest state for OFF domains
2026-08-11 5:50 [PATCH 0/2] cpuidle/pmdomain: Correctly track state of excluded CPU clusters Maulik Shah
2026-08-11 5:50 ` [PATCH 1/2] cpuidle: psci: Start CPU-cluster pmdomain OFF when OSI is used Maulik Shah
@ 2026-08-11 5:50 ` Maulik Shah
2026-08-12 9:33 ` Ulf Hansson
1 sibling, 1 reply; 5+ messages in thread
From: Maulik Shah @ 2026-08-11 5:50 UTC (permalink / raw)
To: Ulf Hansson, Rafael J. Wysocki, Daniel Lezcano, Christian Loehle,
Sudeep Holla, Rafael J. Wysocki, Dmitry Osipenko
Cc: linux-pm, linux-arm-kernel, linux-kernel, linux-arm-msm, Maulik Shah
pm_genpd_init() sets genpd->status from is_off but never touches state_idx,
which stays at its kzalloc'd value of 0 (dt_idle_pd_alloc()) regardless of
is_off. A domain that starts OFF without ever having gone through an actual
power-off sequence is therefore left looking like it's parked at its
shallowest idle state (state_idx=0) instead of its deepest one, even though
it is fully powered down.
This is visible on hamoa, where cluster0/1/2 each expose two idle states,
CL4 (state_idx=0) and CL5 (state_idx=1, the deepest). When CPU-cluster
genpds are initialised directly into GENPD_STATE_OFF under OSI mode
("cpuidle: psci: Start CPU-cluster pmdomain OFF when OSI is used"),
clusters whose CPUs are excluded at boot (e.g. maxcpus=4, leaving clusters
1 and 2 untouched) never go through a power-off path, so their state_idx
remains 0 instead of the deepest 1. debugfs reports this as "off-0",
meaning the domain is off but parked at idle-state 0 (CL4) rather than
"off-1", idle-state 1 (CL5):
genpd_power_off() and genpd_sync_power_off() both gate parent power-off on
every child being at its deepest state index:
if (child->state_idx < child->state_count - 1)
return;
so the parent's check treats these fully-off children as "not yet at
deepest state" and refuses to power off, blocking the deepest system-level
low-power mode (SS3) from ever being entered.
Without this change:
$ cat .../power-domain-cpu-cluster1/current_state
off-0
$ cat /sys/kernel/debug/qcom_stats/apss
Count: 0
Last Entered At: 0
Last Exited At: 0
Accumulated Duration: 0
Fix this at the source: when a domain is initialised OFF, set its state_idx
to its deepest state (state_count - 1) instead of leaving it at 0. This
makes genpd's reported state consistent with reality for every is_off=true
caller of pm_genpd_init(), not just the PSCI CPU-cluster case, and requires
no special-casing in the parent power-off checks.
With this change, the domain correctly reports its deepest state and SS3
is entered normally:
$ cat .../power-domain-cpu-cluster1/current_state
off-1
$ cat /sys/kernel/debug/qcom_stats/apss
Count: 218
Last Entered At: 726792712
Last Exited At: 726950687
Accumulated Duration: 199888773
Fixes: e7d90cfac551 ("PM: domains: Prevent power off for parent unless child is in deepest state")
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Maulik Shah <maulik.shah@oss.qualcomm.com>
---
drivers/pmdomain/core.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c
index 842c4169e290..5d96d9eabc1f 100644
--- a/drivers/pmdomain/core.c
+++ b/drivers/pmdomain/core.c
@@ -2409,6 +2409,8 @@ int pm_genpd_init(struct generic_pm_domain *genpd,
INIT_WORK(&genpd->power_off_work, genpd_power_off_work_fn);
atomic_set(&genpd->sd_count, 0);
genpd->status = is_off ? GENPD_STATE_OFF : GENPD_STATE_ON;
+ if (is_off && genpd->state_count)
+ genpd->state_idx = genpd->state_count - 1;
genpd_set_stay_on(genpd, is_off);
genpd->sync_state = GENPD_SYNC_STATE_OFF;
genpd->device_count = 0;
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 2/2] pmdomain: core: Initialize state_idx to deepest state for OFF domains
2026-08-11 5:50 ` [PATCH 2/2] pmdomain: core: Initialize state_idx to deepest state for OFF domains Maulik Shah
@ 2026-08-12 9:33 ` Ulf Hansson
0 siblings, 0 replies; 5+ messages in thread
From: Ulf Hansson @ 2026-08-12 9:33 UTC (permalink / raw)
To: Maulik Shah
Cc: Ulf Hansson, Rafael J. Wysocki, Daniel Lezcano, Christian Loehle,
Sudeep Holla, Rafael J. Wysocki, Dmitry Osipenko, linux-pm,
linux-arm-kernel, linux-kernel, linux-arm-msm
On Tue, Aug 11, 2026 at 7:50 AM Maulik Shah
<maulik.shah@oss.qualcomm.com> wrote:
>
> pm_genpd_init() sets genpd->status from is_off but never touches state_idx,
> which stays at its kzalloc'd value of 0 (dt_idle_pd_alloc()) regardless of
> is_off. A domain that starts OFF without ever having gone through an actual
> power-off sequence is therefore left looking like it's parked at its
> shallowest idle state (state_idx=0) instead of its deepest one, even though
> it is fully powered down.
>
> This is visible on hamoa, where cluster0/1/2 each expose two idle states,
> CL4 (state_idx=0) and CL5 (state_idx=1, the deepest). When CPU-cluster
> genpds are initialised directly into GENPD_STATE_OFF under OSI mode
> ("cpuidle: psci: Start CPU-cluster pmdomain OFF when OSI is used"),
> clusters whose CPUs are excluded at boot (e.g. maxcpus=4, leaving clusters
> 1 and 2 untouched) never go through a power-off path, so their state_idx
> remains 0 instead of the deepest 1. debugfs reports this as "off-0",
> meaning the domain is off but parked at idle-state 0 (CL4) rather than
> "off-1", idle-state 1 (CL5):
>
> genpd_power_off() and genpd_sync_power_off() both gate parent power-off on
> every child being at its deepest state index:
>
> if (child->state_idx < child->state_count - 1)
> return;
>
> so the parent's check treats these fully-off children as "not yet at
> deepest state" and refuses to power off, blocking the deepest system-level
> low-power mode (SS3) from ever being entered.
>
> Without this change:
>
> $ cat .../power-domain-cpu-cluster1/current_state
> off-0
>
> $ cat /sys/kernel/debug/qcom_stats/apss
> Count: 0
> Last Entered At: 0
> Last Exited At: 0
> Accumulated Duration: 0
>
> Fix this at the source: when a domain is initialised OFF, set its state_idx
> to its deepest state (state_count - 1) instead of leaving it at 0. This
> makes genpd's reported state consistent with reality for every is_off=true
> caller of pm_genpd_init(), not just the PSCI CPU-cluster case, and requires
> no special-casing in the parent power-off checks.
>
> With this change, the domain correctly reports its deepest state and SS3
> is entered normally:
>
> $ cat .../power-domain-cpu-cluster1/current_state
> off-1
>
> $ cat /sys/kernel/debug/qcom_stats/apss
> Count: 218
> Last Entered At: 726792712
> Last Exited At: 726950687
> Accumulated Duration: 199888773
>
> Fixes: e7d90cfac551 ("PM: domains: Prevent power off for parent unless child is in deepest state")
> Assisted-by: Claude:claude-sonnet-5
> Signed-off-by: Maulik Shah <maulik.shah@oss.qualcomm.com>
> ---
> drivers/pmdomain/core.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c
> index 842c4169e290..5d96d9eabc1f 100644
> --- a/drivers/pmdomain/core.c
> +++ b/drivers/pmdomain/core.c
> @@ -2409,6 +2409,8 @@ int pm_genpd_init(struct generic_pm_domain *genpd,
> INIT_WORK(&genpd->power_off_work, genpd_power_off_work_fn);
> atomic_set(&genpd->sd_count, 0);
> genpd->status = is_off ? GENPD_STATE_OFF : GENPD_STATE_ON;
> + if (is_off && genpd->state_count)
> + genpd->state_idx = genpd->state_count - 1;
Even if this is probably the most sane default value, I would prefer
to keep the flexibility.
That said, please make this change on the genpd provider driver
instead of in the core.
> genpd_set_stay_on(genpd, is_off);
> genpd->sync_state = GENPD_SYNC_STATE_OFF;
> genpd->device_count = 0;
>
> --
> 2.43.0
>
Kind regards
Uffe
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 1/2] cpuidle: psci: Start CPU-cluster pmdomain OFF when OSI is used
2026-08-11 5:50 ` [PATCH 1/2] cpuidle: psci: Start CPU-cluster pmdomain OFF when OSI is used Maulik Shah
@ 2026-08-12 15:34 ` Ulf Hansson
0 siblings, 0 replies; 5+ messages in thread
From: Ulf Hansson @ 2026-08-12 15:34 UTC (permalink / raw)
To: Maulik Shah
Cc: Ulf Hansson, Rafael J. Wysocki, Daniel Lezcano, Christian Loehle,
Sudeep Holla, Rafael J. Wysocki, Dmitry Osipenko, linux-pm,
linux-arm-kernel, linux-kernel, linux-arm-msm
On Tue, Aug 11, 2026 at 7:50 AM Maulik Shah
<maulik.shah@oss.qualcomm.com> wrote:
>
> psci_pd_init() always calls pm_genpd_init() with is_off=false, so a
> CPU-cluster pmdomain is marked ON at creation regardless of whether any
> CPU has actually powered it on yet. The pmdomain's own status tracking
> (and the power-on notifier chain fired from _genpd_power_on()) is
> therefore wrong from the outset for OSI mode.
>
> A concrete example: hamoa has 3 CPU clusters, each with 4 CPUs, under a
> single parent system-level pmdomain. Passing "maxcpus=4" on the kernel
> command line limits boot to the first 4 CPUs, so only cluster0 is ever
> actually powered on. Without this fix, cluster1 and cluster2 are still
> marked ON at creation regardless, since is_off is always false. Because
> none of their CPUs ever come online, neither dt_idle_attach_cpu() nor
> psci_idle_cpuhp_up() ever fires for them, so nothing subsequently
Why isn't dt_idle_attach_cpu() called for them?
If we pass "maxcpus=4" on the kernel command line, the
cpu_present_mask should still contain all CPUs which cpuidle-psci uses
when attaching CPU devices to their respective CPU PM domain. Or maybe
I am wrong here?
> drives those two domains to OFF - they stay marked ON for as long as
> the system runs. When cluster0's CPUs go idle and its pmdomain is
> powered off, the parent system-level pmdomain still sees cluster1 and
> cluster2 as ON and therefore never selects a system-level idle state,
> even though no CPU in those two clusters ever executed.
Doesn't genpd_provider_sync_state() get called for these domains,
allowing them to be turned off?
>
> Starting the domain OFF is safe under OSI:
>
> - For CPUs already online by the time this driver probes,
> dt_idle_attach_cpu() explicitly checks cpu_online() and calls
> pm_runtime_get_sync() on the CPU's attach device, which resolves to
> this same pmdomain and drives a real power-on immediately. This runs
> from psci_cpuidle_probe(), a device_initcall, strictly after
> psci_idle_init_domains()'s core_initcall has already created and
> marked off every pmdomain, so there is no window where an online
> CPU's domain is left off with nothing left to turn it on.
>
> - For CPUs that come online later, psci_idle_cpuhp_up() (registered
> via cpuhp_setup_state_nocalls(), which never invokes the callback
> for already-online CPUs at registration time) powers the domain on
> through the normal cpuhp AP_ONLINE path.
>
> Gate this on use_osi rather than applying it unconditionally: in
> non-OSI mode psci_pd_init() sets GENPD_FLAG_ALWAYS_ON, which
> independently blocks genpd_power_off() regardless of is_off, so the
> domain can never really be off there and marking it is_off=true would
> be misleading noise with no effect.
>
> Fixes: a65a397f2451 ("cpuidle: psci: Add support for PM domains by using genpd")
> Assisted-by: Claude:claude-sonnet-5
> Signed-off-by: Maulik Shah <maulik.shah@oss.qualcomm.com>
Kind regards
Uffe
> ---
> drivers/cpuidle/cpuidle-psci-domain.c | 14 +++++++++++++-
> 1 file changed, 13 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/cpuidle/cpuidle-psci-domain.c b/drivers/cpuidle/cpuidle-psci-domain.c
> index b9e4ad7d43a3..fab9de648b17 100644
> --- a/drivers/cpuidle/cpuidle-psci-domain.c
> +++ b/drivers/cpuidle/cpuidle-psci-domain.c
> @@ -78,7 +78,19 @@ static int psci_pd_init(struct device_node *np, bool use_osi)
> /* Use governor for CPU PM domains if it has some states to manage. */
> pd_gov = pd->states ? &pm_domain_cpu_gov : NULL;
>
> - ret = pm_genpd_init(pd, pd_gov, false);
> + /*
> + * Start the domain in the OFF state when OSI is in use, so that
> + * genpd's own status tracking (and its power-on notifier chain)
> + * reflects reality from the outset instead of reporting ON before
> + * any CPU in the domain has actually requested it. This is safe:
> + * dt_idle_attach_cpu() runtime-resumes the domain for every CPU
> + * that is already online by the time this driver probes, and the
> + * PSCI cpuidle cpuhp callbacks power it on/off for CPUs that come
> + * online/offline afterwards. When OSI isn't in use, GENPD_FLAG_ALWAYS_ON
> + * above keeps the domain powered regardless of is_off, so this has
> + * no effect there.
> + */
> + ret = pm_genpd_init(pd, pd_gov, use_osi);
> if (ret)
> goto free_pd_prov;
>
>
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-12 15:34 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-11 5:50 [PATCH 0/2] cpuidle/pmdomain: Correctly track state of excluded CPU clusters Maulik Shah
2026-08-11 5:50 ` [PATCH 1/2] cpuidle: psci: Start CPU-cluster pmdomain OFF when OSI is used Maulik Shah
2026-08-12 15:34 ` Ulf Hansson
2026-08-11 5:50 ` [PATCH 2/2] pmdomain: core: Initialize state_idx to deepest state for OFF domains Maulik Shah
2026-08-12 9:33 ` Ulf Hansson
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®