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