[PATCH] thermal: gov_step_wise: Fix stale mitigation vote with non-zero lower bounds

Manaf Meethalavalappu Pallikunhi posted 1 patch 2 days, 2 hours ago
drivers/thermal/gov_step_wise.c | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
[PATCH] thermal: gov_step_wise: Fix stale mitigation vote with non-zero lower bounds
Posted by Manaf Meethalavalappu Pallikunhi 2 days, 2 hours ago
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>

Re: [PATCH] thermal: gov_step_wise: Fix stale mitigation vote with non-zero lower bounds
Posted by Rafael J. Wysocki (Intel) 1 day, 19 hours ago
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!