Linux Audio

Check our new training course

Linux BSP development engineering services

Need help to port Linux and bootloaders to your hardware?
Loading...
Note: File does not exist in v3.1.
  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	struct tt_thermal_zone *tt_zone __free(kfree);
188	struct tt_work *tt_work __free(kfree) = NULL;
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
217static 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
228static 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
238int tt_del_tz(const char *arg)
239{
240	struct tt_work *tt_work __free(kfree) = NULL;
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
280static 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
301static 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
308DEFINE_FREE(put_tt_zone, struct tt_thermal_zone *,
309	    if (!IS_ERR_OR_NULL(_T)) tt_put_tt_zone(_T))
310
311static void tt_zone_add_trip_work_fn(struct work_struct *work)
312{
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];
317
318	kfree(tt_work);
319
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);
323
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);
327
328	tt_put_tt_zone(tt_zone);
329}
330
331int tt_zone_add_trip(const char *arg)
332{
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);
336	int id;
337
338	tt_work = kzalloc(sizeof(*tt_work), GFP_KERNEL);
339	if (!tt_work)
340		return -ENOMEM;
341
342	tt_trip = kzalloc(sizeof(*tt_trip), GFP_KERNEL);
343	if (!tt_trip)
344		return -ENOMEM;
345
346	tt_zone = tt_get_tt_zone(arg);
347	if (IS_ERR(tt_zone))
348		return PTR_ERR(tt_zone);
349
350	id = ida_alloc(&tt_zone->ida, GFP_KERNEL);
351	if (id < 0)
352		return id;
353
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;
357	tt_trip->id = id;
358
359	guard(tt_zone)(tt_zone);
360
361	list_add_tail(&tt_trip->list_node, &tt_zone->trips);
362	tt_zone->num_trips++;
363
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));
368
369	return 0;
370}
371
372static int tt_zone_get_temp(struct thermal_zone_device *tz, int *temp)
373{
374	struct tt_thermal_zone *tt_zone = thermal_zone_device_priv(tz);
375
376	*temp = READ_ONCE(tt_zone->tz_temp);
377
378	if (*temp < THERMAL_TEMP_INVALID)
379		return -ENODATA;
380
381	return 0;
382}
383
384static struct thermal_zone_device_ops tt_zone_ops = {
385	.get_temp = tt_zone_get_temp,
386};
387
388static int tt_zone_register_tz(struct tt_thermal_zone *tt_zone)
389{
390	struct thermal_trip *trips __free(kfree) = NULL;
391	struct thermal_zone_device *tz;
392	struct tt_trip *tt_trip;
393	int i;
394
395	guard(tt_zone)(tt_zone);
396
397	if (tt_zone->tz)
398		return -EINVAL;
399
400	trips = kcalloc(tt_zone->num_trips, sizeof(*trips), GFP_KERNEL);
401	if (!trips)
402		return -ENOMEM;
403
404	i = 0;
405	list_for_each_entry(tt_trip, &tt_zone->trips, list_node)
406		trips[i++] = tt_trip->trip;
407
408	tt_zone->tz_temp = tt_zone->temp;
409
410	tz = thermal_zone_device_register_with_trips("test_tz", trips, i, tt_zone,
411						     &tt_zone_ops, NULL, 0, 0);
412	if (IS_ERR(tz))
413		return PTR_ERR(tz);
414
415	tt_zone->tz = tz;
416
417	thermal_zone_device_enable(tz);
418
419	return 0;
420}
421
422int tt_zone_reg(const char *arg)
423{
424	struct tt_thermal_zone *tt_zone __free(put_tt_zone);
425
426	tt_zone = tt_get_tt_zone(arg);
427	if (IS_ERR(tt_zone))
428		return PTR_ERR(tt_zone);
429
430	return tt_zone_register_tz(tt_zone);
431}
432
433int tt_zone_unreg(const char *arg)
434{
435	struct tt_thermal_zone *tt_zone __free(put_tt_zone);
436
437	tt_zone = tt_get_tt_zone(arg);
438	if (IS_ERR(tt_zone))
439		return PTR_ERR(tt_zone);
440
441	tt_zone_unregister_tz(tt_zone);
442
443	return 0;
444}
445
446void tt_zone_cleanup(void)
447{
448	struct tt_thermal_zone *tt_zone, *aux;
449
450	list_for_each_entry_safe(tt_zone, aux, &tt_thermal_zones, list_node) {
451		tt_zone_unregister_tz(tt_zone);
452
453		list_del(&tt_zone->list_node);
454
455		tt_zone_free(tt_zone);
456	}
457}