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

From: Manaf Meethalavalappu Pallikunhi

Date: Tue Sep 22 2026 - 08:26:59 EST


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@xxxxxxxxxxxxxxxx>
---
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@xxxxxxxxxxxxxxxx>