File Index
Files and directories
Sign in
FILE

zone.c

File preview with syntax highlighting.

linux-6.19.14/drivers/thermal/testing/zone.c C
  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}
~/ Read-only file preview.