Skip to content

Commit 445936f

Browse files
dlezcanorafaeljw
authored andcommitted
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 <[email protected]> Link: https://patch.msgid.link/[email protected] [ rjw: Subject edit, use BIT(0) and BIT(1) in symbol definitions ] Signed-off-by: Rafael J. Wysocki <[email protected]>
1 parent 827a075 commit 445936f

File tree

6 files changed

+256
-0
lines changed

6 files changed

+256
-0
lines changed

drivers/thermal/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CFLAGS_thermal_core.o := -I$(src)
66
obj-$(CONFIG_THERMAL) += thermal_sys.o
77
thermal_sys-y += thermal_core.o thermal_sysfs.o
88
thermal_sys-y += thermal_trip.o thermal_helpers.o
9+
thermal_sys-y += thermal_thresholds.o
910

1011
# netlink interface to manage the thermal framework
1112
thermal_sys-$(CONFIG_THERMAL_NETLINK) += thermal_netlink.o

drivers/thermal/thermal_core.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <linux/thermal.h>
1414

1515
#include "thermal_netlink.h"
16+
#include "thermal_thresholds.h"
1617
#include "thermal_debugfs.h"
1718

1819
struct thermal_attr {
@@ -139,6 +140,7 @@ struct thermal_zone_device {
139140
#ifdef CONFIG_THERMAL_DEBUGFS
140141
struct thermal_debugfs *debugfs;
141142
#endif
143+
struct list_head user_thresholds;
142144
struct thermal_trip_desc trips[] __counted_by(num_trips);
143145
};
144146

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Copyright 2024 Linaro Limited
4+
*
5+
* Author: Daniel Lezcano <[email protected]>
6+
*
7+
* Thermal thresholds
8+
*/
9+
#include <linux/list.h>
10+
#include <linux/list_sort.h>
11+
#include <linux/slab.h>
12+
13+
#include "thermal_core.h"
14+
#include "thermal_thresholds.h"
15+
16+
int thermal_thresholds_init(struct thermal_zone_device *tz)
17+
{
18+
INIT_LIST_HEAD(&tz->user_thresholds);
19+
20+
return 0;
21+
}
22+
23+
void thermal_thresholds_flush(struct thermal_zone_device *tz)
24+
{
25+
struct list_head *thresholds = &tz->user_thresholds;
26+
struct user_threshold *entry, *tmp;
27+
28+
lockdep_assert_held(&tz->lock);
29+
30+
list_for_each_entry_safe(entry, tmp, thresholds, list_node) {
31+
list_del(&entry->list_node);
32+
kfree(entry);
33+
}
34+
35+
__thermal_zone_device_update(tz, THERMAL_TZ_FLUSH_THRESHOLDS);
36+
}
37+
38+
void thermal_thresholds_exit(struct thermal_zone_device *tz)
39+
{
40+
thermal_thresholds_flush(tz);
41+
}
42+
43+
static int __thermal_thresholds_cmp(void *data,
44+
const struct list_head *l1,
45+
const struct list_head *l2)
46+
{
47+
struct user_threshold *t1 = container_of(l1, struct user_threshold, list_node);
48+
struct user_threshold *t2 = container_of(l2, struct user_threshold, list_node);
49+
50+
return t1->temperature - t2->temperature;
51+
}
52+
53+
static struct user_threshold *__thermal_thresholds_find(const struct list_head *thresholds,
54+
int temperature)
55+
{
56+
struct user_threshold *t;
57+
58+
list_for_each_entry(t, thresholds, list_node)
59+
if (t->temperature == temperature)
60+
return t;
61+
62+
return NULL;
63+
}
64+
65+
static bool __thermal_threshold_is_crossed(struct user_threshold *threshold, int temperature,
66+
int last_temperature, int direction,
67+
int *low, int *high)
68+
{
69+
70+
if (temperature >= threshold->temperature) {
71+
if (threshold->temperature > *low &&
72+
THERMAL_THRESHOLD_WAY_DOWN & threshold->direction)
73+
*low = threshold->temperature;
74+
75+
if (last_temperature < threshold->temperature &&
76+
threshold->direction & direction)
77+
return true;
78+
} else {
79+
if (threshold->temperature < *high && THERMAL_THRESHOLD_WAY_UP
80+
& threshold->direction)
81+
*high = threshold->temperature;
82+
83+
if (last_temperature >= threshold->temperature &&
84+
threshold->direction & direction)
85+
return true;
86+
}
87+
88+
return false;
89+
}
90+
91+
static bool thermal_thresholds_handle_raising(struct list_head *thresholds, int temperature,
92+
int last_temperature, int *low, int *high)
93+
{
94+
struct user_threshold *t;
95+
96+
list_for_each_entry(t, thresholds, list_node) {
97+
if (__thermal_threshold_is_crossed(t, temperature, last_temperature,
98+
THERMAL_THRESHOLD_WAY_UP, low, high))
99+
return true;
100+
}
101+
102+
return false;
103+
}
104+
105+
static bool thermal_thresholds_handle_dropping(struct list_head *thresholds, int temperature,
106+
int last_temperature, int *low, int *high)
107+
{
108+
struct user_threshold *t;
109+
110+
list_for_each_entry_reverse(t, thresholds, list_node) {
111+
if (__thermal_threshold_is_crossed(t, temperature, last_temperature,
112+
THERMAL_THRESHOLD_WAY_DOWN, low, high))
113+
return true;
114+
}
115+
116+
return false;
117+
}
118+
119+
void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high)
120+
{
121+
struct list_head *thresholds = &tz->user_thresholds;
122+
123+
int temperature = tz->temperature;
124+
int last_temperature = tz->last_temperature;
125+
bool notify;
126+
127+
lockdep_assert_held(&tz->lock);
128+
129+
/*
130+
* We need a second update in order to detect a threshold being crossed
131+
*/
132+
if (last_temperature == THERMAL_TEMP_INVALID)
133+
return;
134+
135+
/*
136+
* The temperature is stable, so obviously we can not have
137+
* crossed a threshold.
138+
*/
139+
if (last_temperature == temperature)
140+
return;
141+
142+
/*
143+
* Since last update the temperature:
144+
* - increased : thresholds are crossed the way up
145+
* - decreased : thresholds are crossed the way down
146+
*/
147+
if (temperature > last_temperature)
148+
notify = thermal_thresholds_handle_raising(thresholds, temperature,
149+
last_temperature, low, high);
150+
else
151+
notify = thermal_thresholds_handle_dropping(thresholds, temperature,
152+
last_temperature, low, high);
153+
154+
if (notify)
155+
pr_debug("A threshold has been crossed the way %s, with a temperature=%d, last_temperature=%d\n",
156+
temperature > last_temperature ? "up" : "down", temperature, last_temperature);
157+
}
158+
159+
int thermal_thresholds_add(struct thermal_zone_device *tz, int temperature, int direction)
160+
{
161+
struct list_head *thresholds = &tz->user_thresholds;
162+
struct user_threshold *t;
163+
164+
lockdep_assert_held(&tz->lock);
165+
166+
t = __thermal_thresholds_find(thresholds, temperature);
167+
if (t) {
168+
if (t->direction == direction)
169+
return -EEXIST;
170+
171+
t->direction |= direction;
172+
} else {
173+
174+
t = kmalloc(sizeof(*t), GFP_KERNEL);
175+
if (!t)
176+
return -ENOMEM;
177+
178+
INIT_LIST_HEAD(&t->list_node);
179+
t->temperature = temperature;
180+
t->direction = direction;
181+
list_add(&t->list_node, thresholds);
182+
list_sort(NULL, thresholds, __thermal_thresholds_cmp);
183+
}
184+
185+
__thermal_zone_device_update(tz, THERMAL_TZ_ADD_THRESHOLD);
186+
187+
return 0;
188+
}
189+
190+
int thermal_thresholds_delete(struct thermal_zone_device *tz, int temperature, int direction)
191+
{
192+
struct list_head *thresholds = &tz->user_thresholds;
193+
struct user_threshold *t;
194+
195+
lockdep_assert_held(&tz->lock);
196+
197+
t = __thermal_thresholds_find(thresholds, temperature);
198+
if (!t)
199+
return -ENOENT;
200+
201+
if (t->direction == direction) {
202+
list_del(&t->list_node);
203+
kfree(t);
204+
} else {
205+
t->direction &= ~direction;
206+
}
207+
208+
__thermal_zone_device_update(tz, THERMAL_TZ_DEL_THRESHOLD);
209+
210+
return 0;
211+
}
212+
213+
int thermal_thresholds_for_each(struct thermal_zone_device *tz,
214+
int (*cb)(struct user_threshold *, void *arg), void *arg)
215+
{
216+
struct list_head *thresholds = &tz->user_thresholds;
217+
struct user_threshold *entry;
218+
int ret;
219+
220+
lockdep_assert_held(&tz->lock);
221+
222+
list_for_each_entry(entry, thresholds, list_node) {
223+
ret = cb(entry, arg);
224+
if (ret)
225+
return ret;
226+
}
227+
228+
return 0;
229+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
#ifndef __THERMAL_THRESHOLDS_H__
3+
#define __THERMAL_THRESHOLDS_H__
4+
5+
struct user_threshold {
6+
struct list_head list_node;
7+
int temperature;
8+
int direction;
9+
};
10+
11+
int thermal_thresholds_init(struct thermal_zone_device *tz);
12+
void thermal_thresholds_exit(struct thermal_zone_device *tz);
13+
void thermal_thresholds_flush(struct thermal_zone_device *tz);
14+
void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high);
15+
int thermal_thresholds_add(struct thermal_zone_device *tz, int temperature, int direction);
16+
int thermal_thresholds_delete(struct thermal_zone_device *tz, int temperature, int direction);
17+
int thermal_thresholds_for_each(struct thermal_zone_device *tz,
18+
int (*cb)(struct user_threshold *, void *arg), void *arg);
19+
#endif

include/linux/thermal.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ enum thermal_notify_event {
5656
THERMAL_TZ_UNBIND_CDEV, /* Cooling dev is unbind from the thermal zone */
5757
THERMAL_INSTANCE_WEIGHT_CHANGED, /* Thermal instance weight changed */
5858
THERMAL_TZ_RESUME, /* Thermal zone is resuming after system sleep */
59+
THERMAL_TZ_ADD_THRESHOLD, /* Threshold added */
60+
THERMAL_TZ_DEL_THRESHOLD, /* Threshold deleted */
61+
THERMAL_TZ_FLUSH_THRESHOLDS, /* All thresholds deleted */
5962
};
6063

6164
/**

include/uapi/linux/thermal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#define _UAPI_LINUX_THERMAL_H
44

55
#define THERMAL_NAME_LENGTH 20
6+
#define THERMAL_THRESHOLD_WAY_UP BIT(0)
7+
#define THERMAL_THRESHOLD_WAY_DOWN BIT(1)
68

79
enum thermal_device_mode {
810
THERMAL_DEVICE_DISABLED = 0,

0 commit comments

Comments
 (0)