]> Git Repo - linux.git/blob - drivers/thermal/testing/zone.c
crypto: akcipher - Drop sign/verify operations
[linux.git] / drivers / thermal / testing / zone.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright 2024, Intel Corporation
4  *
5  * Author: Rafael J. Wysocki <[email protected]>
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         ret = -EINVAL;
292         list_for_each_entry(tt_zone, &tt_thermal_zones, list_node) {
293                 if (tt_zone->id == id) {
294                         tt_zone->refcount++;
295                         ret = 0;
296                         break;
297                 }
298         }
299
300         if (ret)
301                 return ERR_PTR(ret);
302
303         return tt_zone;
304 }
305
306 static void tt_put_tt_zone(struct tt_thermal_zone *tt_zone)
307 {
308         guard(mutex)(&tt_thermal_zones_lock);
309
310         tt_zone->refcount--;
311 }
312
313 static void tt_zone_add_trip_work_fn(struct work_struct *work)
314 {
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];
319
320         kfree(tt_work);
321
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);
325
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);
329
330         tt_put_tt_zone(tt_zone);
331 }
332
333 int tt_zone_add_trip(const char *arg)
334 {
335         struct tt_work *tt_work __free(kfree);
336         struct tt_trip *tt_trip __free(kfree);
337         struct tt_thermal_zone *tt_zone;
338         int id;
339
340         tt_work = kzalloc(sizeof(*tt_work), GFP_KERNEL);
341         if (!tt_work)
342                 return -ENOMEM;
343
344         tt_trip = kzalloc(sizeof(*tt_trip), GFP_KERNEL);
345         if (!tt_trip)
346                 return -ENOMEM;
347
348         tt_zone = tt_get_tt_zone(arg);
349         if (IS_ERR(tt_zone))
350                 return PTR_ERR(tt_zone);
351
352         id = ida_alloc(&tt_zone->ida, GFP_KERNEL);
353         if (id < 0) {
354                 tt_put_tt_zone(tt_zone);
355                 return id;
356         }
357
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;
361         tt_trip->id = id;
362
363         guard(tt_zone)(tt_zone);
364
365         list_add_tail(&tt_trip->list_node, &tt_zone->trips);
366         tt_zone->num_trips++;
367
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));
372
373         return 0;
374 }
375
376 static int tt_zone_get_temp(struct thermal_zone_device *tz, int *temp)
377 {
378         struct tt_thermal_zone *tt_zone = thermal_zone_device_priv(tz);
379
380         *temp = READ_ONCE(tt_zone->tz_temp);
381
382         if (*temp < THERMAL_TEMP_INVALID)
383                 return -ENODATA;
384
385         return 0;
386 }
387
388 static struct thermal_zone_device_ops tt_zone_ops = {
389         .get_temp = tt_zone_get_temp,
390 };
391
392 static int tt_zone_register_tz(struct tt_thermal_zone *tt_zone)
393 {
394         struct thermal_trip *trips __free(kfree);
395         struct thermal_zone_device *tz;
396         struct tt_trip *tt_trip;
397         int i;
398
399         guard(tt_zone)(tt_zone);
400
401         if (tt_zone->tz)
402                 return -EINVAL;
403
404         trips = kcalloc(tt_zone->num_trips, sizeof(*trips), GFP_KERNEL);
405         if (!trips)
406                 return -ENOMEM;
407
408         i = 0;
409         list_for_each_entry(tt_trip, &tt_zone->trips, list_node)
410                 trips[i++] = tt_trip->trip;
411
412         tt_zone->tz_temp = tt_zone->temp;
413
414         tz = thermal_zone_device_register_with_trips("test_tz", trips, i, tt_zone,
415                                                      &tt_zone_ops, NULL, 0, 0);
416         if (IS_ERR(tz))
417                 return PTR_ERR(tz);
418
419         tt_zone->tz = tz;
420
421         thermal_zone_device_enable(tz);
422
423         return 0;
424 }
425
426 int tt_zone_reg(const char *arg)
427 {
428         struct tt_thermal_zone *tt_zone;
429         int ret;
430
431         tt_zone = tt_get_tt_zone(arg);
432         if (IS_ERR(tt_zone))
433                 return PTR_ERR(tt_zone);
434
435         ret = tt_zone_register_tz(tt_zone);
436
437         tt_put_tt_zone(tt_zone);
438
439         return ret;
440 }
441
442 int tt_zone_unreg(const char *arg)
443 {
444         struct tt_thermal_zone *tt_zone;
445
446         tt_zone = tt_get_tt_zone(arg);
447         if (IS_ERR(tt_zone))
448                 return PTR_ERR(tt_zone);
449
450         tt_zone_unregister_tz(tt_zone);
451
452         tt_put_tt_zone(tt_zone);
453
454         return 0;
455 }
456
457 void tt_zone_cleanup(void)
458 {
459         struct tt_thermal_zone *tt_zone, *aux;
460
461         list_for_each_entry_safe(tt_zone, aux, &tt_thermal_zones, list_node) {
462                 tt_zone_unregister_tz(tt_zone);
463
464                 list_del(&tt_zone->list_node);
465
466                 tt_zone_free(tt_zone);
467         }
468 }
This page took 0.056315 seconds and 4 git commands to generate.