Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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 */
40struct 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
54DEFINE_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 */
66struct 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 */
79struct tt_work {
80 struct work_struct work;
81 struct tt_thermal_zone *tt_zone;
82 struct tt_trip *tt_trip;
83};
84
85static inline struct tt_work *tt_work_of_work(struct work_struct *work)
86{
87 return container_of(work, struct tt_work, work);
88}
89
90static LIST_HEAD(tt_thermal_zones);
91static DEFINE_IDA(tt_thermal_zones_ida);
92static DEFINE_MUTEX(tt_thermal_zones_lock);
93
94static int tt_int_get(void *data, u64 *val)
95{
96 *val = *(int *)data;
97 return 0;
98}
99static 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}
107DEFINE_DEBUGFS_ATTRIBUTE_SIGNED(tt_int_attr, tt_int_get, tt_int_set, "%lld\n");
108DEFINE_DEBUGFS_ATTRIBUTE(tt_unsigned_int_attr, tt_int_get, tt_int_set, "%llu\n");
109
110static 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}
123static 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}
137DEFINE_DEBUGFS_ATTRIBUTE_SIGNED(tt_zone_tz_temp_attr, tt_zone_tz_temp_get,
138 tt_zone_tz_temp_set, "%lld\n");
139
140static 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
151static 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
159static 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
185int tt_add_tz(void)
186{
187 int ret;
188
189 struct tt_thermal_zone *tt_zone __free(kfree) = kzalloc(sizeof(*tt_zone),
190 GFP_KERNEL);
191 if (!tt_zone)
192 return -ENOMEM;
193
194 struct tt_work *tt_work __free(kfree) = kzalloc(sizeof(*tt_work), GFP_KERNEL);
195 if (!tt_work)
196 return -ENOMEM;
197
198 INIT_LIST_HEAD(&tt_zone->trips);
199 mutex_init(&tt_zone->lock);
200 ida_init(&tt_zone->ida);
201 tt_zone->temp = THERMAL_TEMP_INVALID;
202
203 ret = ida_alloc(&tt_thermal_zones_ida, GFP_KERNEL);
204 if (ret < 0)
205 return ret;
206
207 tt_zone->id = ret;
208
209 INIT_WORK(&tt_work->work, tt_add_tz_work_fn);
210 tt_work->tt_zone = no_free_ptr(tt_zone);
211 schedule_work(&(no_free_ptr(tt_work)->work));
212
213 return 0;
214}
215
216static void tt_del_tz_work_fn(struct work_struct *work)
217{
218 struct tt_work *tt_work = tt_work_of_work(work);
219 struct tt_thermal_zone *tt_zone = tt_work->tt_zone;
220
221 kfree(tt_work);
222
223 debugfs_remove(tt_zone->d_tt_zone);
224 tt_zone_free(tt_zone);
225}
226
227static void tt_zone_unregister_tz(struct tt_thermal_zone *tt_zone)
228{
229 guard(tt_zone)(tt_zone);
230
231 if (tt_zone->tz) {
232 thermal_zone_device_unregister(tt_zone->tz);
233 tt_zone->tz = NULL;
234 }
235}
236
237int tt_del_tz(const char *arg)
238{
239 struct tt_thermal_zone *tt_zone, *aux;
240 int ret;
241 int id;
242
243 ret = sscanf(arg, "%d", &id);
244 if (ret != 1)
245 return -EINVAL;
246
247 struct tt_work *tt_work __free(kfree) = kzalloc(sizeof(*tt_work), GFP_KERNEL);
248 if (!tt_work)
249 return -ENOMEM;
250
251 guard(mutex)(&tt_thermal_zones_lock);
252
253 ret = -EINVAL;
254 list_for_each_entry_safe(tt_zone, aux, &tt_thermal_zones, list_node) {
255 if (tt_zone->id == id) {
256 if (tt_zone->refcount) {
257 ret = -EBUSY;
258 } else {
259 list_del(&tt_zone->list_node);
260 ret = 0;
261 }
262 break;
263 }
264 }
265
266 if (ret)
267 return ret;
268
269 tt_zone_unregister_tz(tt_zone);
270
271 INIT_WORK(&tt_work->work, tt_del_tz_work_fn);
272 tt_work->tt_zone = tt_zone;
273 schedule_work(&(no_free_ptr(tt_work)->work));
274
275 return 0;
276}
277
278static struct tt_thermal_zone *tt_get_tt_zone(const char *arg)
279{
280 struct tt_thermal_zone *tt_zone;
281 int ret, id;
282
283 ret = sscanf(arg, "%d", &id);
284 if (ret != 1)
285 return ERR_PTR(-EINVAL);
286
287 guard(mutex)(&tt_thermal_zones_lock);
288
289 list_for_each_entry(tt_zone, &tt_thermal_zones, list_node) {
290 if (tt_zone->id == id) {
291 tt_zone->refcount++;
292 return tt_zone;
293 }
294 }
295
296 return ERR_PTR(-EINVAL);
297}
298
299static void tt_put_tt_zone(struct tt_thermal_zone *tt_zone)
300{
301 guard(mutex)(&tt_thermal_zones_lock);
302
303 tt_zone->refcount--;
304}
305
306DEFINE_FREE(put_tt_zone, struct tt_thermal_zone *,
307 if (!IS_ERR_OR_NULL(_T)) tt_put_tt_zone(_T))
308
309static void tt_zone_add_trip_work_fn(struct work_struct *work)
310{
311 struct tt_work *tt_work = tt_work_of_work(work);
312 struct tt_thermal_zone *tt_zone = tt_work->tt_zone;
313 struct tt_trip *tt_trip = tt_work->tt_trip;
314 char d_name[TT_MAX_FILE_NAME_LENGTH];
315
316 kfree(tt_work);
317
318 snprintf(d_name, TT_MAX_FILE_NAME_LENGTH, "trip_%d_temp", tt_trip->id);
319 debugfs_create_file_unsafe(d_name, 0600, tt_zone->d_tt_zone,
320 &tt_trip->trip.temperature, &tt_int_attr);
321
322 snprintf(d_name, TT_MAX_FILE_NAME_LENGTH, "trip_%d_hyst", tt_trip->id);
323 debugfs_create_file_unsafe(d_name, 0600, tt_zone->d_tt_zone,
324 &tt_trip->trip.hysteresis, &tt_unsigned_int_attr);
325
326 tt_put_tt_zone(tt_zone);
327}
328
329int tt_zone_add_trip(const char *arg)
330{
331 int id;
332
333 struct tt_work *tt_work __free(kfree) = kzalloc(sizeof(*tt_work), GFP_KERNEL);
334 if (!tt_work)
335 return -ENOMEM;
336
337 struct tt_trip *tt_trip __free(kfree) = kzalloc(sizeof(*tt_trip), GFP_KERNEL);
338 if (!tt_trip)
339 return -ENOMEM;
340
341 struct tt_thermal_zone *tt_zone __free(put_tt_zone) = tt_get_tt_zone(arg);
342 if (IS_ERR(tt_zone))
343 return PTR_ERR(tt_zone);
344
345 id = ida_alloc(&tt_zone->ida, GFP_KERNEL);
346 if (id < 0)
347 return id;
348
349 tt_trip->trip.type = THERMAL_TRIP_ACTIVE;
350 tt_trip->trip.temperature = THERMAL_TEMP_INVALID;
351 tt_trip->trip.flags = THERMAL_TRIP_FLAG_RW;
352 tt_trip->id = id;
353
354 guard(tt_zone)(tt_zone);
355
356 list_add_tail(&tt_trip->list_node, &tt_zone->trips);
357 tt_zone->num_trips++;
358
359 INIT_WORK(&tt_work->work, tt_zone_add_trip_work_fn);
360 tt_work->tt_zone = no_free_ptr(tt_zone);
361 tt_work->tt_trip = no_free_ptr(tt_trip);
362 schedule_work(&(no_free_ptr(tt_work)->work));
363
364 return 0;
365}
366
367static int tt_zone_get_temp(struct thermal_zone_device *tz, int *temp)
368{
369 struct tt_thermal_zone *tt_zone = thermal_zone_device_priv(tz);
370
371 *temp = READ_ONCE(tt_zone->tz_temp);
372
373 if (*temp < THERMAL_TEMP_INVALID)
374 return -ENODATA;
375
376 return 0;
377}
378
379static const struct thermal_zone_device_ops tt_zone_ops = {
380 .get_temp = tt_zone_get_temp,
381};
382
383static int tt_zone_register_tz(struct tt_thermal_zone *tt_zone)
384{
385 struct thermal_zone_device *tz;
386 struct tt_trip *tt_trip;
387 int i;
388
389 guard(tt_zone)(tt_zone);
390
391 if (tt_zone->tz)
392 return -EINVAL;
393
394 struct thermal_trip *trips __free(kfree) = kcalloc(tt_zone->num_trips,
395 sizeof(*trips), GFP_KERNEL);
396 if (!trips)
397 return -ENOMEM;
398
399 i = 0;
400 list_for_each_entry(tt_trip, &tt_zone->trips, list_node)
401 trips[i++] = tt_trip->trip;
402
403 tt_zone->tz_temp = tt_zone->temp;
404
405 tz = thermal_zone_device_register_with_trips("test_tz", trips, i, tt_zone,
406 &tt_zone_ops, NULL, 0, 0);
407 if (IS_ERR(tz))
408 return PTR_ERR(tz);
409
410 tt_zone->tz = tz;
411
412 thermal_zone_device_enable(tz);
413
414 return 0;
415}
416
417int tt_zone_reg(const char *arg)
418{
419 struct tt_thermal_zone *tt_zone __free(put_tt_zone) = tt_get_tt_zone(arg);
420 if (IS_ERR(tt_zone))
421 return PTR_ERR(tt_zone);
422
423 return tt_zone_register_tz(tt_zone);
424}
425
426int tt_zone_unreg(const char *arg)
427{
428 struct tt_thermal_zone *tt_zone __free(put_tt_zone) = tt_get_tt_zone(arg);
429 if (IS_ERR(tt_zone))
430 return PTR_ERR(tt_zone);
431
432 tt_zone_unregister_tz(tt_zone);
433
434 return 0;
435}
436
437void tt_zone_cleanup(void)
438{
439 struct tt_thermal_zone *tt_zone, *aux;
440
441 list_for_each_entry_safe(tt_zone, aux, &tt_thermal_zones, list_node) {
442 tt_zone_unregister_tz(tt_zone);
443
444 list_del(&tt_zone->list_node);
445
446 tt_zone_free(tt_zone);
447 }
448}