brintos

brintos / linux-shallow public Read only

0
0
Text · 10.6 KiB · c6d8c66 Raw
469 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright 2024, Intel Corporation4 *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: " fmt11 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		1621 22/**23 * struct tt_thermal_zone - Testing thermal zone template24 *25 * Represents a template of a thermal zone that can be used for registering26 * 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 template58 *59 * Represents a template of a trip point to be used for populating a trip point60 * 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 sychnronization74 * perspective to attempt to manipulate debugfs from within a debugfs file75 * "write" operation, so auxiliary work items are used for that.  The majority76 * of zone-related command functions have a part that runs from a workqueue and77 * 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);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);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	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 306static 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 313static 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 333int 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 376static 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 388static struct thermal_zone_device_ops tt_zone_ops = {389	.get_temp = tt_zone_get_temp,390};391 392static 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 426int 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 442int 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 457void 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}469