forked from mirrors/linux
		
	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
	
	 Daniel Lezcano
						Daniel Lezcano