mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-09 06:33:34 +00:00
b684682698
Consider a thermal zone with one passive trip point, a cooling device with 3 states (0, 1, 2) bound to it, passive polling enabled (nonzero passive_delay_jiffies) and no regular polling (polling_delay_jiffies equal to 0) that is managed by the Step-Wise governor. Suppose that the initial state of the cooling device is 0 and the zone temperature is below the trip point to start with. When the trip point is crossed, tz->passive is incremented by the thermal core and the governor's .manage() callback is invoked. It sets 'throttle' to 'true' for the trip in question and get_target_state() returns 1 for the instance corresponding to the cooling device (say that 'upper' and 'lower' are set to 2 and 0 for it, respectively), so its state changes to 1. Passive polling is still active for the zone, so next time the temperature is updated, the governor's .manage() callback will be invoked again. If the temperature is still rising, it will change the state of the cooling device to 2. Now suppose that next time the zone temperature is updated, it falls below the trip point, so tz->passive is decremented for the zone (say it becomes 0 then) and the governor's .manage() callbacks runs. It finds that the temperature trend for the zone is 'falling' and 'throttle' will be set to 'false' for the trip in question, so the cooling device's state will be changed to 1. However, because tz->polling is 0 for the zone, the governor's .manage() callback may not be invoked again for a long time and the cooling device's state will not be reset back to 0. This can happen because commit042a3d80f1
("thermal: core: Move passive polling management to the core") removed passive polling management from the Step-Wise governor. Before that change, thermal_zone_trip_update() would bump up tz->passive when changing the target state for a thermal instance from "no target" to a specific value and it would drop tz->passive when changing it back to "no target" which would cause passive polling to be active for the zone until the governor has reset the states of all cooling devices. In particular, in the example above tz->passive would be incremented when changing the state of the cooling device from 0 to 1 and then it would be still nonzero when the state of the cooling device was changed from 2 to 1. To prevent this problem from occurring, restore the passive polling management in the Step-Wise governor by partially reverting the commit in question and update the comment in the restored code to explain its role more clearly. Fixes:042a3d80f1
("thermal: core: Move passive polling management to the core") Closes: https://lore.kernel.org/linux-pm/ZmVfcEOxmjUHZTSX@hovoldconsulting.com Reported-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Johan Hovold <johan+linaro@kernel.org> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
155 lines
4.6 KiB
C
155 lines
4.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* step_wise.c - A step-by-step Thermal throttling governor
|
|
*
|
|
* Copyright (C) 2012 Intel Corp
|
|
* Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com>
|
|
*
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
*
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
*/
|
|
|
|
#include <linux/thermal.h>
|
|
#include <linux/minmax.h>
|
|
#include "thermal_trace.h"
|
|
|
|
#include "thermal_core.h"
|
|
|
|
/*
|
|
* If the temperature is higher than a trip point,
|
|
* a. if the trend is THERMAL_TREND_RAISING, use higher cooling
|
|
* state for this trip point
|
|
* b. if the trend is THERMAL_TREND_DROPPING, do nothing
|
|
* If the temperature is lower than a trip point,
|
|
* a. if the trend is THERMAL_TREND_RAISING, do nothing
|
|
* b. if the trend is THERMAL_TREND_DROPPING, use lower cooling
|
|
* state for this trip point, if the cooling state already
|
|
* equals lower limit, deactivate the thermal instance
|
|
*/
|
|
static unsigned long get_target_state(struct thermal_instance *instance,
|
|
enum thermal_trend trend, bool throttle)
|
|
{
|
|
struct thermal_cooling_device *cdev = instance->cdev;
|
|
unsigned long cur_state;
|
|
|
|
/*
|
|
* We keep this instance the way it is by default.
|
|
* Otherwise, we use the current state of the
|
|
* cdev in use to determine the next_target.
|
|
*/
|
|
cdev->ops->get_cur_state(cdev, &cur_state);
|
|
dev_dbg(&cdev->device, "cur_state=%ld\n", cur_state);
|
|
|
|
if (!instance->initialized) {
|
|
if (throttle)
|
|
return clamp(cur_state + 1, instance->lower, instance->upper);
|
|
|
|
return THERMAL_NO_TARGET;
|
|
}
|
|
|
|
if (throttle) {
|
|
if (trend == THERMAL_TREND_RAISING)
|
|
return clamp(cur_state + 1, instance->lower, instance->upper);
|
|
} else if (trend == THERMAL_TREND_DROPPING) {
|
|
if (cur_state <= instance->lower)
|
|
return THERMAL_NO_TARGET;
|
|
|
|
return clamp(cur_state - 1, instance->lower, instance->upper);
|
|
}
|
|
|
|
return instance->target;
|
|
}
|
|
|
|
static void thermal_zone_trip_update(struct thermal_zone_device *tz,
|
|
const struct thermal_trip *trip,
|
|
int trip_threshold)
|
|
{
|
|
enum thermal_trend trend = get_tz_trend(tz, trip);
|
|
int trip_id = thermal_zone_trip_id(tz, trip);
|
|
struct thermal_instance *instance;
|
|
bool throttle = false;
|
|
|
|
if (tz->temperature >= trip_threshold) {
|
|
throttle = true;
|
|
trace_thermal_zone_trip(tz, trip_id, trip->type);
|
|
}
|
|
|
|
dev_dbg(&tz->device, "Trip%d[type=%d,temp=%d]:trend=%d,throttle=%d\n",
|
|
trip_id, trip->type, trip_threshold, trend, throttle);
|
|
|
|
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
|
|
int old_target;
|
|
|
|
if (instance->trip != trip)
|
|
continue;
|
|
|
|
old_target = instance->target;
|
|
instance->target = get_target_state(instance, trend, throttle);
|
|
|
|
dev_dbg(&instance->cdev->device, "old_target=%d, target=%ld\n",
|
|
old_target, instance->target);
|
|
|
|
if (instance->initialized && old_target == instance->target)
|
|
continue;
|
|
|
|
if (trip->type == THERMAL_TRIP_PASSIVE) {
|
|
/*
|
|
* If the target state for this thermal instance
|
|
* changes from THERMAL_NO_TARGET to something else,
|
|
* ensure that the zone temperature will be updated
|
|
* (assuming enabled passive cooling) until it becomes
|
|
* THERMAL_NO_TARGET again, or the cooling device may
|
|
* not be reset to its initial state.
|
|
*/
|
|
if (old_target == THERMAL_NO_TARGET &&
|
|
instance->target != THERMAL_NO_TARGET)
|
|
tz->passive++;
|
|
else if (old_target != THERMAL_NO_TARGET &&
|
|
instance->target == THERMAL_NO_TARGET)
|
|
tz->passive--;
|
|
}
|
|
|
|
instance->initialized = true;
|
|
|
|
mutex_lock(&instance->cdev->lock);
|
|
instance->cdev->updated = false; /* cdev needs update */
|
|
mutex_unlock(&instance->cdev->lock);
|
|
}
|
|
}
|
|
|
|
static void step_wise_manage(struct thermal_zone_device *tz)
|
|
{
|
|
const struct thermal_trip_desc *td;
|
|
struct thermal_instance *instance;
|
|
|
|
lockdep_assert_held(&tz->lock);
|
|
|
|
/*
|
|
* Throttling Logic: Use the trend of the thermal zone to throttle.
|
|
* If the thermal zone is 'heating up', throttle all of the cooling
|
|
* devices associated with each trip point by one step. If the zone
|
|
* is 'cooling down', it brings back the performance of the devices
|
|
* by one step.
|
|
*/
|
|
for_each_trip_desc(tz, td) {
|
|
const struct thermal_trip *trip = &td->trip;
|
|
|
|
if (trip->temperature == THERMAL_TEMP_INVALID ||
|
|
trip->type == THERMAL_TRIP_CRITICAL ||
|
|
trip->type == THERMAL_TRIP_HOT)
|
|
continue;
|
|
|
|
thermal_zone_trip_update(tz, trip, td->threshold);
|
|
}
|
|
|
|
list_for_each_entry(instance, &tz->thermal_instances, tz_node)
|
|
thermal_cdev_update(instance->cdev);
|
|
}
|
|
|
|
static struct thermal_governor thermal_gov_step_wise = {
|
|
.name = "step_wise",
|
|
.manage = step_wise_manage,
|
|
};
|
|
THERMAL_GOVERNOR_DECLARE(thermal_gov_step_wise);
|