1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright 2024, Intel Corporation
7 * Thermal zone tempalates handling for thermal core testing.
10 #define pr_fmt(fmt) "thermal-testing: " fmt
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>
18 #include "thermal_testing.h"
20 #define TT_MAX_FILE_NAME_LENGTH 16
23 * struct tt_thermal_zone - Testing thermal zone template
25 * Represents a template of a thermal zone that can be used for registering
26 * a test thermal zone with the thermal core.
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.
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;
50 unsigned int num_trips;
51 unsigned int refcount;
54 DEFINE_GUARD(tt_zone, struct tt_thermal_zone *, mutex_lock(&_T->lock), mutex_unlock(&_T->lock))
57 * struct tt_trip - Testing trip point template
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.
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.
67 struct list_head list_node;
68 struct thermal_trip trip;
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.
80 struct work_struct work;
81 struct tt_thermal_zone *tt_zone;
82 struct tt_trip *tt_trip;
85 static inline struct tt_work *tt_work_of_work(struct work_struct *work)
87 return container_of(work, struct tt_work, work);
90 static LIST_HEAD(tt_thermal_zones);
91 static DEFINE_IDA(tt_thermal_zones_ida);
92 static DEFINE_MUTEX(tt_thermal_zones_lock);
94 static int tt_int_get(void *data, u64 *val)
99 static int tt_int_set(void *data, u64 val)
101 if ((int)val < THERMAL_TEMP_INVALID)
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");
110 static int tt_zone_tz_temp_get(void *data, u64 *val)
112 struct tt_thermal_zone *tt_zone = data;
114 guard(tt_zone)(tt_zone);
119 *val = tt_zone->tz_temp;
123 static int tt_zone_tz_temp_set(void *data, u64 val)
125 struct tt_thermal_zone *tt_zone = data;
127 guard(tt_zone)(tt_zone);
132 WRITE_ONCE(tt_zone->tz_temp, val);
133 thermal_zone_device_update(tt_zone->tz, THERMAL_EVENT_TEMP_SAMPLE);
137 DEFINE_DEBUGFS_ATTRIBUTE_SIGNED(tt_zone_tz_temp_attr, tt_zone_tz_temp_get,
138 tt_zone_tz_temp_set, "%lld\n");
140 static void tt_zone_free_trips(struct tt_thermal_zone *tt_zone)
142 struct tt_trip *tt_trip, *aux;
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);
151 static void tt_zone_free(struct tt_thermal_zone *tt_zone)
153 tt_zone_free_trips(tt_zone);
154 ida_free(&tt_thermal_zones_ida, tt_zone->id);
155 ida_destroy(&tt_zone->ida);
159 static void tt_add_tz_work_fn(struct work_struct *work)
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];
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);
174 debugfs_create_file_unsafe("temp", 0600, tt_zone->d_tt_zone, tt_zone,
175 &tt_zone_tz_temp_attr);
177 debugfs_create_file_unsafe("init_temp", 0600, tt_zone->d_tt_zone,
178 &tt_zone->temp, &tt_int_attr);
180 guard(mutex)(&tt_thermal_zones_lock);
182 list_add_tail(&tt_zone->list_node, &tt_thermal_zones);
187 struct tt_thermal_zone *tt_zone __free(kfree);
188 struct tt_work *tt_work __free(kfree);
191 tt_zone = kzalloc(sizeof(*tt_zone), GFP_KERNEL);
195 tt_work = kzalloc(sizeof(*tt_work), GFP_KERNEL);
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;
204 ret = ida_alloc(&tt_thermal_zones_ida, GFP_KERNEL);
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));
217 static void tt_del_tz_work_fn(struct work_struct *work)
219 struct tt_work *tt_work = tt_work_of_work(work);
220 struct tt_thermal_zone *tt_zone = tt_work->tt_zone;
224 debugfs_remove(tt_zone->d_tt_zone);
225 tt_zone_free(tt_zone);
228 static void tt_zone_unregister_tz(struct tt_thermal_zone *tt_zone)
230 guard(tt_zone)(tt_zone);
233 thermal_zone_device_unregister(tt_zone->tz);
238 int tt_del_tz(const char *arg)
240 struct tt_work *tt_work __free(kfree);
241 struct tt_thermal_zone *tt_zone, *aux;
245 ret = sscanf(arg, "%d", &id);
249 tt_work = kzalloc(sizeof(*tt_work), GFP_KERNEL);
253 guard(mutex)(&tt_thermal_zones_lock);
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) {
261 list_del(&tt_zone->list_node);
271 tt_zone_unregister_tz(tt_zone);
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));
280 static struct tt_thermal_zone *tt_get_tt_zone(const char *arg)
282 struct tt_thermal_zone *tt_zone;
285 ret = sscanf(arg, "%d", &id);
287 return ERR_PTR(-EINVAL);
289 guard(mutex)(&tt_thermal_zones_lock);
292 list_for_each_entry(tt_zone, &tt_thermal_zones, list_node) {
293 if (tt_zone->id == id) {
306 static void tt_put_tt_zone(struct tt_thermal_zone *tt_zone)
308 guard(mutex)(&tt_thermal_zones_lock);
313 static void tt_zone_add_trip_work_fn(struct work_struct *work)
315 struct tt_work *tt_work = tt_work_of_work(work);
316 struct tt_thermal_zone *tt_zone = tt_work->tt_zone;
317 struct tt_trip *tt_trip = tt_work->tt_trip;
318 char d_name[TT_MAX_FILE_NAME_LENGTH];
322 snprintf(d_name, TT_MAX_FILE_NAME_LENGTH, "trip_%d_temp", tt_trip->id);
323 debugfs_create_file_unsafe(d_name, 0600, tt_zone->d_tt_zone,
324 &tt_trip->trip.temperature, &tt_int_attr);
326 snprintf(d_name, TT_MAX_FILE_NAME_LENGTH, "trip_%d_hyst", tt_trip->id);
327 debugfs_create_file_unsafe(d_name, 0600, tt_zone->d_tt_zone,
328 &tt_trip->trip.hysteresis, &tt_unsigned_int_attr);
330 tt_put_tt_zone(tt_zone);
333 int tt_zone_add_trip(const char *arg)
335 struct tt_work *tt_work __free(kfree);
336 struct tt_trip *tt_trip __free(kfree);
337 struct tt_thermal_zone *tt_zone;
340 tt_work = kzalloc(sizeof(*tt_work), GFP_KERNEL);
344 tt_trip = kzalloc(sizeof(*tt_trip), GFP_KERNEL);
348 tt_zone = tt_get_tt_zone(arg);
350 return PTR_ERR(tt_zone);
352 id = ida_alloc(&tt_zone->ida, GFP_KERNEL);
354 tt_put_tt_zone(tt_zone);
358 tt_trip->trip.type = THERMAL_TRIP_ACTIVE;
359 tt_trip->trip.temperature = THERMAL_TEMP_INVALID;
360 tt_trip->trip.flags = THERMAL_TRIP_FLAG_RW;
363 guard(tt_zone)(tt_zone);
365 list_add_tail(&tt_trip->list_node, &tt_zone->trips);
366 tt_zone->num_trips++;
368 INIT_WORK(&tt_work->work, tt_zone_add_trip_work_fn);
369 tt_work->tt_zone = tt_zone;
370 tt_work->tt_trip = no_free_ptr(tt_trip);
371 schedule_work(&(no_free_ptr(tt_work)->work));
376 static int tt_zone_get_temp(struct thermal_zone_device *tz, int *temp)
378 struct tt_thermal_zone *tt_zone = thermal_zone_device_priv(tz);
380 *temp = READ_ONCE(tt_zone->tz_temp);
382 if (*temp < THERMAL_TEMP_INVALID)
388 static struct thermal_zone_device_ops tt_zone_ops = {
389 .get_temp = tt_zone_get_temp,
392 static int tt_zone_register_tz(struct tt_thermal_zone *tt_zone)
394 struct thermal_trip *trips __free(kfree);
395 struct thermal_zone_device *tz;
396 struct tt_trip *tt_trip;
399 guard(tt_zone)(tt_zone);
404 trips = kcalloc(tt_zone->num_trips, sizeof(*trips), GFP_KERNEL);
409 list_for_each_entry(tt_trip, &tt_zone->trips, list_node)
410 trips[i++] = tt_trip->trip;
412 tt_zone->tz_temp = tt_zone->temp;
414 tz = thermal_zone_device_register_with_trips("test_tz", trips, i, tt_zone,
415 &tt_zone_ops, NULL, 0, 0);
421 thermal_zone_device_enable(tz);
426 int tt_zone_reg(const char *arg)
428 struct tt_thermal_zone *tt_zone;
431 tt_zone = tt_get_tt_zone(arg);
433 return PTR_ERR(tt_zone);
435 ret = tt_zone_register_tz(tt_zone);
437 tt_put_tt_zone(tt_zone);
442 int tt_zone_unreg(const char *arg)
444 struct tt_thermal_zone *tt_zone;
446 tt_zone = tt_get_tt_zone(arg);
448 return PTR_ERR(tt_zone);
450 tt_zone_unregister_tz(tt_zone);
452 tt_put_tt_zone(tt_zone);
457 void tt_zone_cleanup(void)
459 struct tt_thermal_zone *tt_zone, *aux;
461 list_for_each_entry_safe(tt_zone, aux, &tt_thermal_zones, list_node) {
462 tt_zone_unregister_tz(tt_zone);
464 list_del(&tt_zone->list_node);
466 tt_zone_free(tt_zone);