xref: /linux/drivers/thermal/testing/zone.c (revision fb1a5dfe86d3af1e1c3ce168cf0d8d43897e0f77)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright 2024, Intel Corporation
4  *
5  * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
6  *
7  * Thermal zone tempalates handling for thermal core testing.
8  */
9 
10 #define pr_fmt(fmt) "thermal-testing: " fmt
11 
12 #include <linux/debugfs.h>
13 #include <linux/idr.h>
14 #include <linux/list.h>
15 #include <linux/thermal.h>
16 
17 #include "thermal_testing.h"
18 
19 #define TT_MAX_FILE_NAME_LENGTH		16
20 
21 /**
22  * struct tt_thermal_zone - Testing thermal zone template
23  *
24  * Represents a template of a thermal zone that can be used for registering
25  * a test thermal zone with the thermal core.
26  *
27  * @list_node: Node in the list of all testing thermal zone templates.
28  * @trips: List of trip point templates for this thermal zone template.
29  * @d_tt_zone: Directory in debugfs representing this template.
30  * @tz: Test thermal zone based on this template, if present.
31  * @lock: Mutex for synchronizing changes of this template.
32  * @ida: IDA for trip point IDs.
33  * @id: The ID of this template for the debugfs interface.
34  * @temp: Temperature value.
35  * @tz_temp: Current thermal zone temperature (after registration).
36  * @num_trips: Number of trip points in the @trips list.
37  * @refcount: Reference counter for usage and removal synchronization.
38  */
39 struct tt_thermal_zone {
40 	struct list_head list_node;
41 	struct list_head trips;
42 	struct dentry *d_tt_zone;
43 	struct thermal_zone_device *tz;
44 	struct mutex lock;
45 	struct ida ida;
46 	int id;
47 	int temp;
48 	int tz_temp;
49 	unsigned int num_trips;
50 	unsigned int refcount;
51 };
52 
53 DEFINE_GUARD(tt_zone, struct tt_thermal_zone *, mutex_lock(&_T->lock), mutex_unlock(&_T->lock))
54 
55 /**
56  * struct tt_trip - Testing trip point template
57  *
58  * Represents a template of a trip point to be used for populating a trip point
59  * during the registration of a thermal zone based on a given zone template.
60  *
61  * @list_node: Node in the list of all trip templates in the zone template.
62  * @trip: Trip point data to use for thernal zone registration.
63  * @id: The ID of this trip template for the debugfs interface.
64  */
65 struct tt_trip {
66 	struct list_head list_node;
67 	struct thermal_trip trip;
68 	int id;
69 };
70 
71 /*
72  * It is both questionable and potentially problematic from the sychnronization
73  * perspective to attempt to manipulate debugfs from within a debugfs file
74  * "write" operation, so auxiliary work items are used for that.  The majority
75  * of zone-related command functions have a part that runs from a workqueue and
76  * make changes in debugs, among other things.
77  */
78 struct tt_work {
79 	struct work_struct work;
80 	struct tt_thermal_zone *tt_zone;
81 	struct tt_trip *tt_trip;
82 };
83 
84 static inline struct tt_work *tt_work_of_work(struct work_struct *work)
85 {
86 	return container_of(work, struct tt_work, work);
87 }
88 
89 static LIST_HEAD(tt_thermal_zones);
90 static DEFINE_IDA(tt_thermal_zones_ida);
91 static DEFINE_MUTEX(tt_thermal_zones_lock);
92 
93 static int tt_int_get(void *data, u64 *val)
94 {
95 	*val = *(int *)data;
96 	return 0;
97 }
98 static int tt_int_set(void *data, u64 val)
99 {
100 	if ((int)val < THERMAL_TEMP_INVALID)
101 		return -EINVAL;
102 
103 	*(int *)data = val;
104 	return 0;
105 }
106 DEFINE_DEBUGFS_ATTRIBUTE_SIGNED(tt_int_attr, tt_int_get, tt_int_set, "%lld\n");
107 DEFINE_DEBUGFS_ATTRIBUTE(tt_unsigned_int_attr, tt_int_get, tt_int_set, "%llu\n");
108 
109 static int tt_zone_tz_temp_get(void *data, u64 *val)
110 {
111 	struct tt_thermal_zone *tt_zone = data;
112 
113 	guard(tt_zone)(tt_zone);
114 
115 	if (!tt_zone->tz)
116 		return -EBUSY;
117 
118 	*val = tt_zone->tz_temp;
119 
120 	return 0;
121 }
122 static int tt_zone_tz_temp_set(void *data, u64 val)
123 {
124 	struct tt_thermal_zone *tt_zone = data;
125 
126 	guard(tt_zone)(tt_zone);
127 
128 	if (!tt_zone->tz)
129 		return -EBUSY;
130 
131 	WRITE_ONCE(tt_zone->tz_temp, val);
132 	thermal_zone_device_update(tt_zone->tz, THERMAL_EVENT_TEMP_SAMPLE);
133 
134 	return 0;
135 }
136 DEFINE_DEBUGFS_ATTRIBUTE_SIGNED(tt_zone_tz_temp_attr, tt_zone_tz_temp_get,
137 				tt_zone_tz_temp_set, "%lld\n");
138 
139 static void tt_zone_free_trips(struct tt_thermal_zone *tt_zone)
140 {
141 	struct tt_trip *tt_trip, *aux;
142 
143 	list_for_each_entry_safe(tt_trip, aux, &tt_zone->trips, list_node) {
144 		list_del(&tt_trip->list_node);
145 		ida_free(&tt_zone->ida, tt_trip->id);
146 		kfree(tt_trip);
147 	}
148 }
149 
150 static void tt_zone_free(struct tt_thermal_zone *tt_zone)
151 {
152 	tt_zone_free_trips(tt_zone);
153 	ida_free(&tt_thermal_zones_ida, tt_zone->id);
154 	ida_destroy(&tt_zone->ida);
155 	kfree(tt_zone);
156 }
157 
158 static void tt_add_tz_work_fn(struct work_struct *work)
159 {
160 	struct tt_work *tt_work = tt_work_of_work(work);
161 	struct tt_thermal_zone *tt_zone = tt_work->tt_zone;
162 	char f_name[TT_MAX_FILE_NAME_LENGTH];
163 
164 	kfree(tt_work);
165 
166 	snprintf(f_name, TT_MAX_FILE_NAME_LENGTH, "tz%d", tt_zone->id);
167 	tt_zone->d_tt_zone = debugfs_create_dir(f_name, d_testing);
168 	if (IS_ERR(tt_zone->d_tt_zone)) {
169 		tt_zone_free(tt_zone);
170 		return;
171 	}
172 
173 	debugfs_create_file_unsafe("temp", 0600, tt_zone->d_tt_zone, tt_zone,
174 				   &tt_zone_tz_temp_attr);
175 
176 	debugfs_create_file_unsafe("init_temp", 0600, tt_zone->d_tt_zone,
177 				   &tt_zone->temp, &tt_int_attr);
178 
179 	guard(mutex)(&tt_thermal_zones_lock);
180 
181 	list_add_tail(&tt_zone->list_node, &tt_thermal_zones);
182 }
183 
184 int tt_add_tz(void)
185 {
186 	int ret;
187 
188 	struct tt_thermal_zone *tt_zone __free(kfree) = kzalloc_obj(*tt_zone);
189 	if (!tt_zone)
190 		return -ENOMEM;
191 
192 	struct tt_work *tt_work __free(kfree) = kzalloc_obj(*tt_work);
193 	if (!tt_work)
194 		return -ENOMEM;
195 
196 	INIT_LIST_HEAD(&tt_zone->trips);
197 	mutex_init(&tt_zone->lock);
198 	ida_init(&tt_zone->ida);
199 	tt_zone->temp = THERMAL_TEMP_INVALID;
200 
201 	ret = ida_alloc(&tt_thermal_zones_ida, GFP_KERNEL);
202 	if (ret < 0)
203 		return ret;
204 
205 	tt_zone->id = ret;
206 
207 	INIT_WORK(&tt_work->work, tt_add_tz_work_fn);
208 	tt_work->tt_zone = no_free_ptr(tt_zone);
209 	tt_queue_work(&(no_free_ptr(tt_work)->work));
210 
211 	return 0;
212 }
213 
214 static void tt_del_tz_work_fn(struct work_struct *work)
215 {
216 	struct tt_work *tt_work = tt_work_of_work(work);
217 	struct tt_thermal_zone *tt_zone = tt_work->tt_zone;
218 
219 	kfree(tt_work);
220 
221 	debugfs_remove(tt_zone->d_tt_zone);
222 	tt_zone_free(tt_zone);
223 }
224 
225 static void tt_zone_unregister_tz(struct tt_thermal_zone *tt_zone)
226 {
227 	guard(tt_zone)(tt_zone);
228 
229 	if (tt_zone->tz) {
230 		thermal_zone_device_unregister(tt_zone->tz);
231 		tt_zone->tz = NULL;
232 	}
233 }
234 
235 int tt_del_tz(const char *arg)
236 {
237 	struct tt_thermal_zone *tt_zone, *aux;
238 	int ret;
239 	int id;
240 
241 	ret = kstrtoint(arg, 10, &id);
242 	if (ret < 0)
243 		return ret;
244 
245 	struct tt_work *tt_work __free(kfree) = kzalloc_obj(*tt_work);
246 	if (!tt_work)
247 		return -ENOMEM;
248 
249 	guard(mutex)(&tt_thermal_zones_lock);
250 
251 	ret = -EINVAL;
252 	list_for_each_entry_safe(tt_zone, aux, &tt_thermal_zones, list_node) {
253 		if (tt_zone->id == id) {
254 			if (tt_zone->refcount) {
255 				ret = -EBUSY;
256 			} else {
257 				list_del(&tt_zone->list_node);
258 				ret = 0;
259 			}
260 			break;
261 		}
262 	}
263 
264 	if (ret)
265 		return ret;
266 
267 	tt_zone_unregister_tz(tt_zone);
268 
269 	INIT_WORK(&tt_work->work, tt_del_tz_work_fn);
270 	tt_work->tt_zone = tt_zone;
271 	tt_queue_work(&(no_free_ptr(tt_work)->work));
272 
273 	return 0;
274 }
275 
276 static struct tt_thermal_zone *tt_get_tt_zone(const char *arg)
277 {
278 	struct tt_thermal_zone *tt_zone;
279 	int ret, id;
280 
281 	ret = kstrtoint(arg, 10, &id);
282 	if (ret < 0)
283 		return ERR_PTR(ret);
284 
285 	guard(mutex)(&tt_thermal_zones_lock);
286 
287 	list_for_each_entry(tt_zone, &tt_thermal_zones, list_node) {
288 		if (tt_zone->id == id) {
289 			tt_zone->refcount++;
290 			return tt_zone;
291 		}
292 	}
293 
294 	return ERR_PTR(-EINVAL);
295 }
296 
297 static void tt_put_tt_zone(struct tt_thermal_zone *tt_zone)
298 {
299 	guard(mutex)(&tt_thermal_zones_lock);
300 
301 	tt_zone->refcount--;
302 }
303 
304 DEFINE_FREE(put_tt_zone, struct tt_thermal_zone *,
305 	    if (!IS_ERR_OR_NULL(_T)) tt_put_tt_zone(_T))
306 
307 static void tt_zone_add_trip_work_fn(struct work_struct *work)
308 {
309 	struct tt_work *tt_work = tt_work_of_work(work);
310 	struct tt_thermal_zone *tt_zone = tt_work->tt_zone;
311 	struct tt_trip *tt_trip = tt_work->tt_trip;
312 	char d_name[TT_MAX_FILE_NAME_LENGTH];
313 
314 	kfree(tt_work);
315 
316 	snprintf(d_name, TT_MAX_FILE_NAME_LENGTH, "trip_%d_temp", tt_trip->id);
317 	debugfs_create_file_unsafe(d_name, 0600, tt_zone->d_tt_zone,
318 				   &tt_trip->trip.temperature, &tt_int_attr);
319 
320 	snprintf(d_name, TT_MAX_FILE_NAME_LENGTH, "trip_%d_hyst", tt_trip->id);
321 	debugfs_create_file_unsafe(d_name, 0600, tt_zone->d_tt_zone,
322 				   &tt_trip->trip.hysteresis, &tt_unsigned_int_attr);
323 
324 	tt_put_tt_zone(tt_zone);
325 }
326 
327 int tt_zone_add_trip(const char *arg)
328 {
329 	int id;
330 
331 	struct tt_work *tt_work __free(kfree) = kzalloc_obj(*tt_work);
332 	if (!tt_work)
333 		return -ENOMEM;
334 
335 	struct tt_trip *tt_trip __free(kfree) = kzalloc_obj(*tt_trip);
336 	if (!tt_trip)
337 		return -ENOMEM;
338 
339 	struct tt_thermal_zone *tt_zone __free(put_tt_zone) = tt_get_tt_zone(arg);
340 	if (IS_ERR(tt_zone))
341 		return PTR_ERR(tt_zone);
342 
343 	id = ida_alloc(&tt_zone->ida, GFP_KERNEL);
344 	if (id < 0)
345 		return id;
346 
347 	tt_trip->trip.type = THERMAL_TRIP_ACTIVE;
348 	tt_trip->trip.temperature = THERMAL_TEMP_INVALID;
349 	tt_trip->trip.flags = THERMAL_TRIP_FLAG_RW;
350 	tt_trip->id = id;
351 
352 	guard(tt_zone)(tt_zone);
353 
354 	list_add_tail(&tt_trip->list_node, &tt_zone->trips);
355 	tt_zone->num_trips++;
356 
357 	INIT_WORK(&tt_work->work, tt_zone_add_trip_work_fn);
358 	tt_work->tt_zone = no_free_ptr(tt_zone);
359 	tt_work->tt_trip = no_free_ptr(tt_trip);
360 	tt_queue_work(&(no_free_ptr(tt_work)->work));
361 
362 	return 0;
363 }
364 
365 static int tt_zone_get_temp(struct thermal_zone_device *tz, int *temp)
366 {
367 	struct tt_thermal_zone *tt_zone = thermal_zone_device_priv(tz);
368 
369 	*temp = READ_ONCE(tt_zone->tz_temp);
370 
371 	if (*temp < THERMAL_TEMP_INVALID)
372 		return -ENODATA;
373 
374 	return 0;
375 }
376 
377 static const struct thermal_zone_device_ops tt_zone_ops = {
378 	.get_temp = tt_zone_get_temp,
379 };
380 
381 static int tt_zone_register_tz(struct tt_thermal_zone *tt_zone)
382 {
383 	struct thermal_zone_device *tz;
384 	struct tt_trip *tt_trip;
385 	int i;
386 
387 	guard(tt_zone)(tt_zone);
388 
389 	if (tt_zone->tz)
390 		return -EINVAL;
391 
392 	struct thermal_trip *trips __free(kfree) = kzalloc_objs(*trips,
393 								tt_zone->num_trips);
394 	if (!trips)
395 		return -ENOMEM;
396 
397 	i = 0;
398 	list_for_each_entry(tt_trip, &tt_zone->trips, list_node)
399 		trips[i++] = tt_trip->trip;
400 
401 	tt_zone->tz_temp = tt_zone->temp;
402 
403 	tz = thermal_zone_device_register_with_trips("test_tz", trips, i, tt_zone,
404 						     &tt_zone_ops, NULL, 0, 0);
405 	if (IS_ERR(tz))
406 		return PTR_ERR(tz);
407 
408 	tt_zone->tz = tz;
409 
410 	thermal_zone_device_enable(tz);
411 
412 	return 0;
413 }
414 
415 int tt_zone_reg(const char *arg)
416 {
417 	struct tt_thermal_zone *tt_zone __free(put_tt_zone) = tt_get_tt_zone(arg);
418 	if (IS_ERR(tt_zone))
419 		return PTR_ERR(tt_zone);
420 
421 	return tt_zone_register_tz(tt_zone);
422 }
423 
424 int tt_zone_unreg(const char *arg)
425 {
426 	struct tt_thermal_zone *tt_zone __free(put_tt_zone) = tt_get_tt_zone(arg);
427 	if (IS_ERR(tt_zone))
428 		return PTR_ERR(tt_zone);
429 
430 	tt_zone_unregister_tz(tt_zone);
431 
432 	return 0;
433 }
434 
435 void tt_zone_cleanup(void)
436 {
437 	struct tt_thermal_zone *tt_zone, *aux;
438 
439 	list_for_each_entry_safe(tt_zone, aux, &tt_thermal_zones, list_node) {
440 		tt_zone_unregister_tz(tt_zone);
441 
442 		list_del(&tt_zone->list_node);
443 
444 		tt_zone_free(tt_zone);
445 	}
446 }
447