mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	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>
This commit is contained in:
		
							parent
							
								
									827a07525c
								
							
						
					
					
						commit
						445936f9e2
					
				
					 6 changed files with 256 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										229
									
								
								drivers/thermal/thermal_thresholds.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								drivers/thermal/thermal_thresholds.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								drivers/thermal/thermal_thresholds.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								drivers/thermal/thermal_thresholds.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -56,6 +56,9 @@ enum thermal_notify_event {
 | 
			
		|||
	THERMAL_TZ_UNBIND_CDEV, /* Cooling dev is unbind from the thermal zone */
 | 
			
		||||
	THERMAL_INSTANCE_WEIGHT_CHANGED, /* Thermal instance weight changed */
 | 
			
		||||
	THERMAL_TZ_RESUME, /* Thermal zone is resuming after system sleep */
 | 
			
		||||
	THERMAL_TZ_ADD_THRESHOLD, /* Threshold added */
 | 
			
		||||
	THERMAL_TZ_DEL_THRESHOLD, /* Threshold deleted */
 | 
			
		||||
	THERMAL_TZ_FLUSH_THRESHOLDS, /* All thresholds deleted */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,8 @@
 | 
			
		|||
#define _UAPI_LINUX_THERMAL_H
 | 
			
		||||
 | 
			
		||||
#define THERMAL_NAME_LENGTH	20
 | 
			
		||||
#define THERMAL_THRESHOLD_WAY_UP	BIT(0)
 | 
			
		||||
#define THERMAL_THRESHOLD_WAY_DOWN	BIT(1)
 | 
			
		||||
 | 
			
		||||
enum thermal_device_mode {
 | 
			
		||||
	THERMAL_DEVICE_DISABLED = 0,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue