diff options
author | Daniel Lezcano <daniel.lezcano@linaro.org> | 2024-09-23 11:59:57 +0200 |
---|---|---|
committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2024-10-10 23:33:37 +0200 |
commit | 445936f9e258eca624c8239056bd8cd6e853b3fd (patch) | |
tree | 8613f6a964acd230ec6f7b75cad79ec6390be108 /drivers/thermal | |
parent | 827a07525c099f54d3b15110408824541ec66b3c (diff) | |
download | lwn-445936f9e258eca624c8239056bd8cd6e853b3fd.tar.gz lwn-445936f9e258eca624c8239056bd8cd6e853b3fd.zip |
thermal: core: Add user thresholds support
The user thresholds mechanism is a way to have the userspace to tell
the thermal framework to send a notification when a temperature limit
is crossed. There is no id, no hysteresis, just the temperature and
the direction of the limit crossing. That means we can be notified
when a threshold is crossed the way up only, or the way down only or
both ways. That allows to create hysteresis values if it is needed.
A threshold can be added, deleted or flushed. The latter means all
thresholds belonging to a thermal zone will be deleted.
When a threshold is added:
- if the same threshold (temperature and direction) exists, an error
is returned
- if a threshold is specified with the same temperature but a
different direction, the specified direction is added
- if there is no threshold with the same temperature then it is
created
When a threshold is deleted:
- if the same threshold (temperature and direction) exists, it is
deleted
- if a threshold is specified with the same temperature but a
different direction, the specified direction is removed
- if there is no threshold with the same temperature, then an error
is returned
When the threshold are flushed:
- All thresholds related to a thermal zone are deleted
When a threshold is crossed:
- the userspace does not need to know which threshold(s) have been
crossed, it will be notified with the current temperature and the
previous temperature
- if multiple thresholds have been crossed between two updates only
one notification will be send to the userspace, it is pointless to
send a notification per thresholds crossed as the userspace can
handle that easily when it has the temperature delta information
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Link: https://patch.msgid.link/20240923100005.2532430-2-daniel.lezcano@linaro.org
[ rjw: Subject edit, use BIT(0) and BIT(1) in symbol definitions ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Diffstat (limited to 'drivers/thermal')
-rw-r--r-- | drivers/thermal/Makefile | 1 | ||||
-rw-r--r-- | drivers/thermal/thermal_core.h | 2 | ||||
-rw-r--r-- | drivers/thermal/thermal_thresholds.c | 229 | ||||
-rw-r--r-- | drivers/thermal/thermal_thresholds.h | 19 |
4 files changed, 251 insertions, 0 deletions
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 41c4d56beb40..1e1559bb971e 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -6,6 +6,7 @@ CFLAGS_thermal_core.o := -I$(src) obj-$(CONFIG_THERMAL) += thermal_sys.o thermal_sys-y += thermal_core.o thermal_sysfs.o thermal_sys-y += thermal_trip.o thermal_helpers.o +thermal_sys-y += thermal_thresholds.o # netlink interface to manage the thermal framework thermal_sys-$(CONFIG_THERMAL_NETLINK) += thermal_netlink.o diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index a64d39b1c86b..1ea91d59498b 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -13,6 +13,7 @@ #include <linux/thermal.h> #include "thermal_netlink.h" +#include "thermal_thresholds.h" #include "thermal_debugfs.h" struct thermal_attr { @@ -139,6 +140,7 @@ struct thermal_zone_device { #ifdef CONFIG_THERMAL_DEBUGFS struct thermal_debugfs *debugfs; #endif + struct list_head user_thresholds; struct thermal_trip_desc trips[] __counted_by(num_trips); }; diff --git a/drivers/thermal/thermal_thresholds.c b/drivers/thermal/thermal_thresholds.c new file mode 100644 index 000000000000..f33b6d5474d8 --- /dev/null +++ b/drivers/thermal/thermal_thresholds.c @@ -0,0 +1,229 @@ +// 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; +} + +void thermal_thresholds_flush(struct thermal_zone_device *tz) +{ + struct list_head *thresholds = &tz->user_thresholds; + struct user_threshold *entry, *tmp; + + lockdep_assert_held(&tz->lock); + + list_for_each_entry_safe(entry, tmp, thresholds, list_node) { + list_del(&entry->list_node); + kfree(entry); + } + + __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_threshold_is_crossed(struct user_threshold *threshold, int temperature, + int last_temperature, int direction, + int *low, int *high) +{ + + if (temperature >= threshold->temperature) { + if (threshold->temperature > *low && + THERMAL_THRESHOLD_WAY_DOWN & threshold->direction) + *low = threshold->temperature; + + if (last_temperature < threshold->temperature && + threshold->direction & direction) + return true; + } else { + if (threshold->temperature < *high && THERMAL_THRESHOLD_WAY_UP + & threshold->direction) + *high = threshold->temperature; + + if (last_temperature >= threshold->temperature && + threshold->direction & direction) + return true; + } + + return false; +} + +static bool thermal_thresholds_handle_raising(struct list_head *thresholds, int temperature, + int last_temperature, int *low, int *high) +{ + struct user_threshold *t; + + list_for_each_entry(t, thresholds, list_node) { + if (__thermal_threshold_is_crossed(t, temperature, last_temperature, + THERMAL_THRESHOLD_WAY_UP, low, high)) + return true; + } + + return false; +} + +static bool thermal_thresholds_handle_dropping(struct list_head *thresholds, int temperature, + int last_temperature, int *low, int *high) +{ + struct user_threshold *t; + + list_for_each_entry_reverse(t, thresholds, list_node) { + if (__thermal_threshold_is_crossed(t, temperature, last_temperature, + THERMAL_THRESHOLD_WAY_DOWN, low, high)) + return true; + } + + return false; +} + +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; + bool notify; + + lockdep_assert_held(&tz->lock); + + /* + * 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) + notify = thermal_thresholds_handle_raising(thresholds, temperature, + last_temperature, low, high); + else + notify = thermal_thresholds_handle_dropping(thresholds, temperature, + last_temperature, low, high); + + if (notify) + pr_debug("A threshold has been crossed the way %s, with a temperature=%d, last_temperature=%d\n", + temperature > last_temperature ? "up" : "down", temperature, last_temperature); +} + +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_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_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; + + lockdep_assert_held(&tz->lock); + + list_for_each_entry(entry, thresholds, list_node) { + ret = cb(entry, arg); + if (ret) + return ret; + } + + return 0; +} diff --git a/drivers/thermal/thermal_thresholds.h b/drivers/thermal/thermal_thresholds.h new file mode 100644 index 000000000000..232f4e8089af --- /dev/null +++ b/drivers/thermal/thermal_thresholds.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __THERMAL_THRESHOLDS_H__ +#define __THERMAL_THRESHOLDS_H__ + +struct user_threshold { + struct list_head list_node; + int temperature; + int direction; +}; + +int thermal_thresholds_init(struct thermal_zone_device *tz); +void thermal_thresholds_exit(struct thermal_zone_device *tz); +void thermal_thresholds_flush(struct thermal_zone_device *tz); +void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high); +int thermal_thresholds_add(struct thermal_zone_device *tz, int temperature, int direction); +int thermal_thresholds_delete(struct thermal_zone_device *tz, int temperature, int direction); +int thermal_thresholds_for_each(struct thermal_zone_device *tz, + int (*cb)(struct user_threshold *, void *arg), void *arg); +#endif |