linux-next/drivers/thermal/thermal_thresholds.c
Daniel Lezcano 4feaedf7d2 thermal/thresholds: Fix boundaries and detection routine
The current implementation does not work if the thermal zone is
interrupt driven only.

The boundaries are not correctly checked and computed as it happens
only when the temperature is increasing or decreasing.

The problem arises because the routine to detect when we cross a
threshold is correlated with the computation of the boundaries. We
assume we have to recompute the boundaries when a threshold is crossed
but actually we should do that even if the it is not the case.

Mixing the boundaries computation and the threshold detection for the
sake of optimizing the routine is much more complex as it appears
intuitively and prone to errors.

This fix separates the boundaries computation and the threshold
crossing detection into different routines. The result is a code much
more simple to understand, thus easier to maintain.

The drawback is we browse the thresholds list several time but we can
consider that as neglictible because that happens when the temperature
is updated. There are certainly some aeras to improve in the
temperature update routine but it would be not adequate as this change
aims to fix the thresholds for v6.13.

Fixes: 445936f9e2 ("thermal: core: Add user thresholds support")
Tested-by: Daniel Lezcano <daniel.lezcano@linaro.org> # rock5b, Lenovo x13s
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Link: https://patch.msgid.link/20241216212644.1145122-1-daniel.lezcano@linaro.org
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2024-12-18 14:51:31 +01:00

245 lines
5.6 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2024 Linaro Limited
*
* Author: Daniel Lezcano <daniel.lezcano@linaro.org>
*
* Thermal thresholds
*/
#include <linux/list.h>
#include <linux/list_sort.h>
#include <linux/slab.h>
#include "thermal_core.h"
#include "thermal_thresholds.h"
int thermal_thresholds_init(struct thermal_zone_device *tz)
{
INIT_LIST_HEAD(&tz->user_thresholds);
return 0;
}
static void __thermal_thresholds_flush(struct thermal_zone_device *tz)
{
struct list_head *thresholds = &tz->user_thresholds;
struct user_threshold *entry, *tmp;
list_for_each_entry_safe(entry, tmp, thresholds, list_node) {
list_del(&entry->list_node);
kfree(entry);
}
}
void thermal_thresholds_flush(struct thermal_zone_device *tz)
{
lockdep_assert_held(&tz->lock);
__thermal_thresholds_flush(tz);
thermal_notify_threshold_flush(tz);
__thermal_zone_device_update(tz, THERMAL_TZ_FLUSH_THRESHOLDS);
}
void thermal_thresholds_exit(struct thermal_zone_device *tz)
{
__thermal_thresholds_flush(tz);
}
static int __thermal_thresholds_cmp(void *data,
const struct list_head *l1,
const struct list_head *l2)
{
struct user_threshold *t1 = container_of(l1, struct user_threshold, list_node);
struct user_threshold *t2 = container_of(l2, struct user_threshold, list_node);
return t1->temperature - t2->temperature;
}
static struct user_threshold *__thermal_thresholds_find(const struct list_head *thresholds,
int temperature)
{
struct user_threshold *t;
list_for_each_entry(t, thresholds, list_node)
if (t->temperature == temperature)
return t;
return NULL;
}
static bool thermal_thresholds_handle_raising(struct list_head *thresholds, int temperature,
int last_temperature)
{
struct user_threshold *t;
list_for_each_entry(t, thresholds, list_node) {
if (!(t->direction & THERMAL_THRESHOLD_WAY_UP))
continue;
if (temperature >= t->temperature &&
last_temperature < t->temperature)
return true;
}
return false;
}
static bool thermal_thresholds_handle_dropping(struct list_head *thresholds, int temperature,
int last_temperature)
{
struct user_threshold *t;
list_for_each_entry_reverse(t, thresholds, list_node) {
if (!(t->direction & THERMAL_THRESHOLD_WAY_DOWN))
continue;
if (temperature <= t->temperature &&
last_temperature > t->temperature)
return true;
}
return false;
}
static void thermal_threshold_find_boundaries(struct list_head *thresholds, int temperature,
int *low, int *high)
{
struct user_threshold *t;
list_for_each_entry(t, thresholds, list_node) {
if (temperature < t->temperature &&
(t->direction & THERMAL_THRESHOLD_WAY_UP) &&
*high > t->temperature)
*high = t->temperature;
}
list_for_each_entry_reverse(t, thresholds, list_node) {
if (temperature > t->temperature &&
(t->direction & THERMAL_THRESHOLD_WAY_DOWN) &&
*low < t->temperature)
*low = t->temperature;
}
}
void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high)
{
struct list_head *thresholds = &tz->user_thresholds;
int temperature = tz->temperature;
int last_temperature = tz->last_temperature;
lockdep_assert_held(&tz->lock);
thermal_threshold_find_boundaries(thresholds, temperature, low, high);
/*
* We need a second update in order to detect a threshold being crossed
*/
if (last_temperature == THERMAL_TEMP_INVALID)
return;
/*
* The temperature is stable, so obviously we can not have
* crossed a threshold.
*/
if (last_temperature == temperature)
return;
/*
* Since last update the temperature:
* - increased : thresholds are crossed the way up
* - decreased : thresholds are crossed the way down
*/
if (temperature > last_temperature) {
if (thermal_thresholds_handle_raising(thresholds,
temperature, last_temperature))
thermal_notify_threshold_up(tz);
} else {
if (thermal_thresholds_handle_dropping(thresholds,
temperature, last_temperature))
thermal_notify_threshold_down(tz);
}
}
int thermal_thresholds_add(struct thermal_zone_device *tz,
int temperature, int direction)
{
struct list_head *thresholds = &tz->user_thresholds;
struct user_threshold *t;
lockdep_assert_held(&tz->lock);
t = __thermal_thresholds_find(thresholds, temperature);
if (t) {
if (t->direction == direction)
return -EEXIST;
t->direction |= direction;
} else {
t = kmalloc(sizeof(*t), GFP_KERNEL);
if (!t)
return -ENOMEM;
INIT_LIST_HEAD(&t->list_node);
t->temperature = temperature;
t->direction = direction;
list_add(&t->list_node, thresholds);
list_sort(NULL, thresholds, __thermal_thresholds_cmp);
}
thermal_notify_threshold_add(tz, temperature, direction);
__thermal_zone_device_update(tz, THERMAL_TZ_ADD_THRESHOLD);
return 0;
}
int thermal_thresholds_delete(struct thermal_zone_device *tz,
int temperature, int direction)
{
struct list_head *thresholds = &tz->user_thresholds;
struct user_threshold *t;
lockdep_assert_held(&tz->lock);
t = __thermal_thresholds_find(thresholds, temperature);
if (!t)
return -ENOENT;
if (t->direction == direction) {
list_del(&t->list_node);
kfree(t);
} else {
t->direction &= ~direction;
}
thermal_notify_threshold_delete(tz, temperature, direction);
__thermal_zone_device_update(tz, THERMAL_TZ_DEL_THRESHOLD);
return 0;
}
int thermal_thresholds_for_each(struct thermal_zone_device *tz,
int (*cb)(struct user_threshold *, void *arg), void *arg)
{
struct list_head *thresholds = &tz->user_thresholds;
struct user_threshold *entry;
int ret;
guard(thermal_zone)(tz);
list_for_each_entry(entry, thresholds, list_node) {
ret = cb(entry, arg);
if (ret)
return ret;
}
return 0;
}