1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright 2024, Intel Corporation
5 * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
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
) = NULL
;
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
) = NULL
;
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
);
291 list_for_each_entry(tt_zone
, &tt_thermal_zones
, list_node
) {
292 if (tt_zone
->id
== id
) {
298 return ERR_PTR(-EINVAL
);
301 static void tt_put_tt_zone(struct tt_thermal_zone
*tt_zone
)
303 guard(mutex
)(&tt_thermal_zones_lock
);
308 DEFINE_FREE(put_tt_zone
, struct tt_thermal_zone
*,
309 if (!IS_ERR_OR_NULL(_T
)) tt_put_tt_zone(_T
))
311 static void tt_zone_add_trip_work_fn(struct work_struct
*work
)
313 struct tt_work
*tt_work
= tt_work_of_work(work
);
314 struct tt_thermal_zone
*tt_zone
= tt_work
->tt_zone
;
315 struct tt_trip
*tt_trip
= tt_work
->tt_trip
;
316 char d_name
[TT_MAX_FILE_NAME_LENGTH
];
320 snprintf(d_name
, TT_MAX_FILE_NAME_LENGTH
, "trip_%d_temp", tt_trip
->id
);
321 debugfs_create_file_unsafe(d_name
, 0600, tt_zone
->d_tt_zone
,
322 &tt_trip
->trip
.temperature
, &tt_int_attr
);
324 snprintf(d_name
, TT_MAX_FILE_NAME_LENGTH
, "trip_%d_hyst", tt_trip
->id
);
325 debugfs_create_file_unsafe(d_name
, 0600, tt_zone
->d_tt_zone
,
326 &tt_trip
->trip
.hysteresis
, &tt_unsigned_int_attr
);
328 tt_put_tt_zone(tt_zone
);
331 int tt_zone_add_trip(const char *arg
)
333 struct tt_thermal_zone
*tt_zone
__free(put_tt_zone
) = NULL
;
334 struct tt_trip
*tt_trip
__free(kfree
) = NULL
;
335 struct tt_work
*tt_work
__free(kfree
);
338 tt_work
= kzalloc(sizeof(*tt_work
), GFP_KERNEL
);
342 tt_trip
= kzalloc(sizeof(*tt_trip
), GFP_KERNEL
);
346 tt_zone
= tt_get_tt_zone(arg
);
348 return PTR_ERR(tt_zone
);
350 id
= ida_alloc(&tt_zone
->ida
, GFP_KERNEL
);
354 tt_trip
->trip
.type
= THERMAL_TRIP_ACTIVE
;
355 tt_trip
->trip
.temperature
= THERMAL_TEMP_INVALID
;
356 tt_trip
->trip
.flags
= THERMAL_TRIP_FLAG_RW
;
359 guard(tt_zone
)(tt_zone
);
361 list_add_tail(&tt_trip
->list_node
, &tt_zone
->trips
);
362 tt_zone
->num_trips
++;
364 INIT_WORK(&tt_work
->work
, tt_zone_add_trip_work_fn
);
365 tt_work
->tt_zone
= no_free_ptr(tt_zone
);
366 tt_work
->tt_trip
= no_free_ptr(tt_trip
);
367 schedule_work(&(no_free_ptr(tt_work
)->work
));
372 static int tt_zone_get_temp(struct thermal_zone_device
*tz
, int *temp
)
374 struct tt_thermal_zone
*tt_zone
= thermal_zone_device_priv(tz
);
376 *temp
= READ_ONCE(tt_zone
->tz_temp
);
378 if (*temp
< THERMAL_TEMP_INVALID
)
384 static struct thermal_zone_device_ops tt_zone_ops
= {
385 .get_temp
= tt_zone_get_temp
,
388 static int tt_zone_register_tz(struct tt_thermal_zone
*tt_zone
)
390 struct thermal_trip
*trips
__free(kfree
) = NULL
;
391 struct thermal_zone_device
*tz
;
392 struct tt_trip
*tt_trip
;
395 guard(tt_zone
)(tt_zone
);
400 trips
= kcalloc(tt_zone
->num_trips
, sizeof(*trips
), GFP_KERNEL
);
405 list_for_each_entry(tt_trip
, &tt_zone
->trips
, list_node
)
406 trips
[i
++] = tt_trip
->trip
;
408 tt_zone
->tz_temp
= tt_zone
->temp
;
410 tz
= thermal_zone_device_register_with_trips("test_tz", trips
, i
, tt_zone
,
411 &tt_zone_ops
, NULL
, 0, 0);
417 thermal_zone_device_enable(tz
);
422 int tt_zone_reg(const char *arg
)
424 struct tt_thermal_zone
*tt_zone
__free(put_tt_zone
);
426 tt_zone
= tt_get_tt_zone(arg
);
428 return PTR_ERR(tt_zone
);
430 return tt_zone_register_tz(tt_zone
);
433 int tt_zone_unreg(const char *arg
)
435 struct tt_thermal_zone
*tt_zone
__free(put_tt_zone
);
437 tt_zone
= tt_get_tt_zone(arg
);
439 return PTR_ERR(tt_zone
);
441 tt_zone_unregister_tz(tt_zone
);
446 void tt_zone_cleanup(void)
448 struct tt_thermal_zone
*tt_zone
, *aux
;
450 list_for_each_entry_safe(tt_zone
, aux
, &tt_thermal_zones
, list_node
) {
451 tt_zone_unregister_tz(tt_zone
);
453 list_del(&tt_zone
->list_node
);
455 tt_zone_free(tt_zone
);