drivers/thermal/gov_step_wise.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-)
When two or more thermal zones bind to a common cooling device and one zone
uses a non-zero instance->lower value, there is a bug where the instance
holds a stale mitigation vote even after its trip is cleared.
Problem scenario:
- thermal-zone1: Trip at 50°C, cooling-map with lower=0
- thermal-zone2: Trip at 55°C, cooling-map with lower=2
- Both zones share the same cooling device (e.g., CPU)
Issue flow:
1. Both trips trigger, zone1 requests state 5, zone2 also mitigates
2. Zone2 trip clears (temp < 53°C due to hysteresis)
3. When throttle=false and trend=THERMAL_TREND_DROPPING:
- Current code checks: if (cur_state <= instance->lower)
return THERMAL_NO_TARGET
- Since cur_state (5) > instance->lower (2),
it returns instance->lower (2)
- This is the BUG where it returns instance->lower even though
trip is cleared
4. Zone2's passive polling stops (tz->passive reaches 0) - no more updates
for zone2
5. Zone2's stale vote of 2 persists indefinitely
6. Even when zone1 wants to reduce cooling to state, the cooling device
cannot go below state 2 due to zone2's stale vote
When a trip is cleared (throttle == false), always return THERMAL_NO_TARGET
instead of instance->lower. Remove the unnecessary check comparing
cur_state with instance->lower. Since passive polling is already
deactivated when the trip is cleared, the instance should always be
deactivated regardless of its current cooling state. This ensures that
instances with non-zero lower bounds do not retain stale mitigation votes
after their trips are cleared.
Fixes: 042a3d80f118 ("thermal: core: Move passive polling management to the core")
Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
---
When two thermal zones bind to a common cooling device and one zone uses
a non-zero instance->lower value, there is a bug where the instance holds
a stale mitigation vote even after its trip is cleared.
Problem scenario:
- thermal-zone1: Trip at 50°C, cooling-map with lower=0
- thermal-zone2: Trip at 55°C, cooling-map with lower=2
- Both zones share the same cooling device (e.g., CPU)
Issue flow:
1. Both trips trigger, zone1 requests state 5, zone2 also mitigates
2. Zone2 trip clears (temp < 53°C due to hysteresis)
3. When throttle=false and trend=THERMAL_TREND_DROPPING:
- Current code checks: if (cur_state <= instance->lower)
return THERMAL_NO_TARGET
- Since cur_state (5) > instance->lower (2),
it returns instance->lower (2)
- This is the BUG where it returns instance->lower even though
trip is cleared
4. Zone2's passive polling stops (tz->passive reaches 0) - no more updates
for zone2
5. Zone2's stale vote of 2 persists indefinitely
6. Even when zone1 wants to reduce cooling to state, the cooling device
cannot go below state 2 due to zone2's stale vote
When a trip is cleared (throttle == false), always return THERMAL_NO_TARGET
instead of instance->lower. Remove the unnecessary check comparing
cur_state with instance->lower. Since passive polling is already
deactivated when the trip is cleared, the instance should always be
deactivated regardless of its current cooling state. This ensures that
instances with non-zero lower bounds do not retain stale mitigation votes
after their trips are cleared.
---
drivers/thermal/gov_step_wise.c | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/drivers/thermal/gov_step_wise.c b/drivers/thermal/gov_step_wise.c
index ea277c466d8d..4fa4377f0d42 100644
--- a/drivers/thermal/gov_step_wise.c
+++ b/drivers/thermal/gov_step_wise.c
@@ -65,14 +65,12 @@ static unsigned long get_target_state(struct thermal_instance *instance,
min(instance->lower + 1, instance->upper),
instance->upper);
} else if (trend == THERMAL_TREND_DROPPING) {
- if (cur_state <= instance->lower)
- return THERMAL_NO_TARGET;
-
/*
- * If 'throttle' is false, no mitigation is necessary, so
- * request the lower state for this instance.
+ * If 'throttle' is false, no mitigation is necessary and
+ * passive polling is already deactivated, so clear this
+ * instance state by returning THERMAL_NO_TARGET.
*/
- return instance->lower;
+ return THERMAL_NO_TARGET;
}
return instance->target;
---
base-commit: 1a1de54f7369cd2b5bac0f265910e60ad3a6b4c3
change-id: 20260922-step_wise_multi_zone_stale_vote_fix-e79368ead1eb
Best regards,
--
Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
On Tue, Sep 22, 2026 at 2:19 PM Manaf Meethalavalappu Pallikunhi
<manaf.pallikunhi@oss.qualcomm.com> wrote:
>
> When two or more thermal zones bind to a common cooling device and one zone
> uses a non-zero instance->lower value, there is a bug where the instance
> holds a stale mitigation vote even after its trip is cleared.
>
> Problem scenario:
> - thermal-zone1: Trip at 50°C, cooling-map with lower=0
> - thermal-zone2: Trip at 55°C, cooling-map with lower=2
> - Both zones share the same cooling device (e.g., CPU)
>
> Issue flow:
> 1. Both trips trigger, zone1 requests state 5, zone2 also mitigates
> 2. Zone2 trip clears (temp < 53°C due to hysteresis)
> 3. When throttle=false and trend=THERMAL_TREND_DROPPING:
> - Current code checks: if (cur_state <= instance->lower)
> return THERMAL_NO_TARGET
> - Since cur_state (5) > instance->lower (2),
> it returns instance->lower (2)
> - This is the BUG where it returns instance->lower even though
> trip is cleared
> 4. Zone2's passive polling stops (tz->passive reaches 0) - no more updates
> for zone2
> 5. Zone2's stale vote of 2 persists indefinitely
> 6. Even when zone1 wants to reduce cooling to state, the cooling device
> cannot go below state 2 due to zone2's stale vote
>
> When a trip is cleared (throttle == false), always return THERMAL_NO_TARGET
> instead of instance->lower. Remove the unnecessary check comparing
> cur_state with instance->lower. Since passive polling is already
> deactivated when the trip is cleared, the instance should always be
> deactivated regardless of its current cooling state. This ensures that
> instances with non-zero lower bounds do not retain stale mitigation votes
> after their trips are cleared.
>
> Fixes: 042a3d80f118 ("thermal: core: Move passive polling management to the core")
> Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
> ---
> When two thermal zones bind to a common cooling device and one zone uses
> a non-zero instance->lower value, there is a bug where the instance holds
> a stale mitigation vote even after its trip is cleared.
>
> Problem scenario:
> - thermal-zone1: Trip at 50°C, cooling-map with lower=0
> - thermal-zone2: Trip at 55°C, cooling-map with lower=2
> - Both zones share the same cooling device (e.g., CPU)
>
> Issue flow:
> 1. Both trips trigger, zone1 requests state 5, zone2 also mitigates
> 2. Zone2 trip clears (temp < 53°C due to hysteresis)
> 3. When throttle=false and trend=THERMAL_TREND_DROPPING:
> - Current code checks: if (cur_state <= instance->lower)
> return THERMAL_NO_TARGET
> - Since cur_state (5) > instance->lower (2),
> it returns instance->lower (2)
> - This is the BUG where it returns instance->lower even though
> trip is cleared
> 4. Zone2's passive polling stops (tz->passive reaches 0) - no more updates
> for zone2
> 5. Zone2's stale vote of 2 persists indefinitely
> 6. Even when zone1 wants to reduce cooling to state, the cooling device
> cannot go below state 2 due to zone2's stale vote
>
> When a trip is cleared (throttle == false), always return THERMAL_NO_TARGET
> instead of instance->lower. Remove the unnecessary check comparing
> cur_state with instance->lower. Since passive polling is already
> deactivated when the trip is cleared, the instance should always be
> deactivated regardless of its current cooling state. This ensures that
> instances with non-zero lower bounds do not retain stale mitigation votes
> after their trips are cleared.
> ---
> drivers/thermal/gov_step_wise.c | 10 ++++------
> 1 file changed, 4 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/thermal/gov_step_wise.c b/drivers/thermal/gov_step_wise.c
> index ea277c466d8d..4fa4377f0d42 100644
> --- a/drivers/thermal/gov_step_wise.c
> +++ b/drivers/thermal/gov_step_wise.c
> @@ -65,14 +65,12 @@ static unsigned long get_target_state(struct thermal_instance *instance,
> min(instance->lower + 1, instance->upper),
> instance->upper);
> } else if (trend == THERMAL_TREND_DROPPING) {
> - if (cur_state <= instance->lower)
> - return THERMAL_NO_TARGET;
> -
> /*
> - * If 'throttle' is false, no mitigation is necessary, so
> - * request the lower state for this instance.
> + * If 'throttle' is false, no mitigation is necessary and
> + * passive polling is already deactivated, so clear this
> + * instance state by returning THERMAL_NO_TARGET.
> */
> - return instance->lower;
> + return THERMAL_NO_TARGET;
> }
>
> return instance->target;
>
> ---
Applied as 7.3-rc material, thanks!
© 2016 - 2026 Red Hat, Inc.