xref: /linux/drivers/thermal/thermal_thresholds.c (revision 1623bc27a85a93e82194c8d077eccc464efa67db)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright 2024 Linaro Limited
4  *
5  * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
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 static 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 	list_for_each_entry_safe(entry, tmp, thresholds, list_node) {
29 		list_del(&entry->list_node);
30 		kfree(entry);
31 	}
32 }
33 
34 void thermal_thresholds_flush(struct thermal_zone_device *tz)
35 {
36 	lockdep_assert_held(&tz->lock);
37 
38 	__thermal_thresholds_flush(tz);
39 
40 	thermal_notify_threshold_flush(tz);
41 
42 	__thermal_zone_device_update(tz, THERMAL_TZ_FLUSH_THRESHOLDS);
43 }
44 
45 void thermal_thresholds_exit(struct thermal_zone_device *tz)
46 {
47 	__thermal_thresholds_flush(tz);
48 }
49 
50 static int __thermal_thresholds_cmp(void *data,
51 				    const struct list_head *l1,
52 				    const struct list_head *l2)
53 {
54 	struct user_threshold *t1 = container_of(l1, struct user_threshold, list_node);
55 	struct user_threshold *t2 = container_of(l2, struct user_threshold, list_node);
56 
57 	return t1->temperature - t2->temperature;
58 }
59 
60 static struct user_threshold *__thermal_thresholds_find(const struct list_head *thresholds,
61 							int temperature)
62 {
63 	struct user_threshold *t;
64 
65 	list_for_each_entry(t, thresholds, list_node)
66 		if (t->temperature == temperature)
67 			return t;
68 
69 	return NULL;
70 }
71 
72 static bool thermal_thresholds_handle_raising(struct list_head *thresholds, int temperature,
73 					      int last_temperature)
74 {
75 	struct user_threshold *t;
76 
77 	list_for_each_entry(t, thresholds, list_node) {
78 
79 		if (!(t->direction & THERMAL_THRESHOLD_WAY_UP))
80 		    continue;
81 
82 		if (temperature >= t->temperature &&
83 		    last_temperature < t->temperature)
84 			return true;
85 	}
86 
87 	return false;
88 }
89 
90 static bool thermal_thresholds_handle_dropping(struct list_head *thresholds, int temperature,
91 					       int last_temperature)
92 {
93 	struct user_threshold *t;
94 
95 	list_for_each_entry_reverse(t, thresholds, list_node) {
96 
97 		if (!(t->direction & THERMAL_THRESHOLD_WAY_DOWN))
98 		    continue;
99 
100 		if (temperature <= t->temperature &&
101 		    last_temperature > t->temperature)
102 			return true;
103 	}
104 
105 	return false;
106 }
107 
108 static void thermal_threshold_find_boundaries(struct list_head *thresholds, int temperature,
109 					      int *low, int *high)
110 {
111 	struct user_threshold *t;
112 
113 	list_for_each_entry(t, thresholds, list_node) {
114 		if (temperature < t->temperature &&
115 		    (t->direction & THERMAL_THRESHOLD_WAY_UP) &&
116 		    *high > t->temperature)
117 			*high = t->temperature;
118 	}
119 
120 	list_for_each_entry_reverse(t, thresholds, list_node) {
121 		if (temperature > t->temperature &&
122 		    (t->direction & THERMAL_THRESHOLD_WAY_DOWN) &&
123 		    *low < t->temperature)
124 			*low = t->temperature;
125 	}
126 }
127 
128 void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high)
129 {
130 	struct list_head *thresholds = &tz->user_thresholds;
131 
132 	int temperature = tz->temperature;
133 	int last_temperature = tz->last_temperature;
134 
135 	lockdep_assert_held(&tz->lock);
136 
137 	thermal_threshold_find_boundaries(thresholds, temperature, low, high);
138 
139 	/*
140 	 * We need a second update in order to detect a threshold being crossed
141 	 */
142 	if (last_temperature == THERMAL_TEMP_INVALID)
143 		return;
144 
145 	/*
146 	 * The temperature is stable, so obviously we can not have
147 	 * crossed a threshold.
148 	 */
149 	if (last_temperature == temperature)
150 		return;
151 
152 	/*
153 	 * Since last update the temperature:
154 	 * - increased : thresholds are crossed the way up
155 	 * - decreased : thresholds are crossed the way down
156 	 */
157 	if (temperature > last_temperature) {
158 		if (thermal_thresholds_handle_raising(thresholds,
159 						      temperature, last_temperature))
160 			thermal_notify_threshold_up(tz);
161 	} else {
162 		if (thermal_thresholds_handle_dropping(thresholds,
163 						       temperature, last_temperature))
164 			thermal_notify_threshold_down(tz);
165 	}
166 }
167 
168 int thermal_thresholds_add(struct thermal_zone_device *tz,
169 			   int temperature, int direction)
170 {
171 	struct list_head *thresholds = &tz->user_thresholds;
172 	struct user_threshold *t;
173 
174 	lockdep_assert_held(&tz->lock);
175 
176 	t = __thermal_thresholds_find(thresholds, temperature);
177 	if (t) {
178 		if (t->direction == direction)
179 			return -EEXIST;
180 
181 		t->direction |= direction;
182 	} else {
183 
184 		t = kmalloc(sizeof(*t), GFP_KERNEL);
185 		if (!t)
186 			return -ENOMEM;
187 
188 		INIT_LIST_HEAD(&t->list_node);
189 		t->temperature = temperature;
190 		t->direction = direction;
191 		list_add(&t->list_node, thresholds);
192 		list_sort(NULL, thresholds, __thermal_thresholds_cmp);
193 	}
194 
195 	thermal_notify_threshold_add(tz, temperature, direction);
196 
197 	__thermal_zone_device_update(tz, THERMAL_TZ_ADD_THRESHOLD);
198 
199 	return 0;
200 }
201 
202 int thermal_thresholds_delete(struct thermal_zone_device *tz,
203 			      int temperature, int direction)
204 {
205 	struct list_head *thresholds = &tz->user_thresholds;
206 	struct user_threshold *t;
207 
208 	lockdep_assert_held(&tz->lock);
209 
210 	t = __thermal_thresholds_find(thresholds, temperature);
211 	if (!t)
212 		return -ENOENT;
213 
214 	if (t->direction == direction) {
215 		list_del(&t->list_node);
216 		kfree(t);
217 	} else {
218 		t->direction &= ~direction;
219 	}
220 
221 	thermal_notify_threshold_delete(tz, temperature, direction);
222 
223 	__thermal_zone_device_update(tz, THERMAL_TZ_DEL_THRESHOLD);
224 
225 	return 0;
226 }
227 
228 int thermal_thresholds_for_each(struct thermal_zone_device *tz,
229 				int (*cb)(struct user_threshold *, void *arg), void *arg)
230 {
231 	struct list_head *thresholds = &tz->user_thresholds;
232 	struct user_threshold *entry;
233 	int ret;
234 
235 	guard(thermal_zone)(tz);
236 
237 	list_for_each_entry(entry, thresholds, list_node) {
238 		ret = cb(entry, arg);
239 		if (ret)
240 			return ret;
241 	}
242 
243 	return 0;
244 }
245