446 lines · 10.4 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Copyright 2024, Intel Corporation * * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com> * * Thermal zone tempalates handling for thermal core testing. */ #define pr_fmt(fmt) "thermal-testing: " fmt #include <linux/debugfs.h>#include <linux/idr.h>#include <linux/list.h>#include <linux/thermal.h> #include "thermal_testing.h" #define TT_MAX_FILE_NAME_LENGTH		16 /** * struct tt_thermal_zone - Testing thermal zone template * * Represents a template of a thermal zone that can be used for registering * a test thermal zone with the thermal core. * * @list_node: Node in the list of all testing thermal zone templates. * @trips: List of trip point templates for this thermal zone template. * @d_tt_zone: Directory in debugfs representing this template. * @tz: Test thermal zone based on this template, if present. * @lock: Mutex for synchronizing changes of this template. * @ida: IDA for trip point IDs. * @id: The ID of this template for the debugfs interface. * @temp: Temperature value. * @tz_temp: Current thermal zone temperature (after registration). * @num_trips: Number of trip points in the @trips list. * @refcount: Reference counter for usage and removal synchronization. */struct tt_thermal_zone {	struct list_head list_node;	struct list_head trips;	struct dentry *d_tt_zone;	struct thermal_zone_device *tz;	struct mutex lock;	struct ida ida;	int id;	int temp;	int tz_temp;	unsigned int num_trips;	unsigned int refcount;}; DEFINE_GUARD(tt_zone, struct tt_thermal_zone *, mutex_lock(&_T->lock), mutex_unlock(&_T->lock)) /** * struct tt_trip - Testing trip point template * * Represents a template of a trip point to be used for populating a trip point * during the registration of a thermal zone based on a given zone template. * * @list_node: Node in the list of all trip templates in the zone template. * @trip: Trip point data to use for thernal zone registration. * @id: The ID of this trip template for the debugfs interface. */struct tt_trip {	struct list_head list_node;	struct thermal_trip trip;	int id;}; /* * It is both questionable and potentially problematic from the sychnronization * perspective to attempt to manipulate debugfs from within a debugfs file * "write" operation, so auxiliary work items are used for that.  The majority * of zone-related command functions have a part that runs from a workqueue and * make changes in debugs, among other things. */struct tt_work {	struct work_struct work;	struct tt_thermal_zone *tt_zone;	struct tt_trip *tt_trip;}; static inline struct tt_work *tt_work_of_work(struct work_struct *work){	return container_of(work, struct tt_work, work);} static LIST_HEAD(tt_thermal_zones);static DEFINE_IDA(tt_thermal_zones_ida);static DEFINE_MUTEX(tt_thermal_zones_lock); static int tt_int_get(void *data, u64 *val){	*val = *(int *)data;	return 0;}static int tt_int_set(void *data, u64 val){	if ((int)val < THERMAL_TEMP_INVALID)		return -EINVAL; 	*(int *)data = val;	return 0;}DEFINE_DEBUGFS_ATTRIBUTE_SIGNED(tt_int_attr, tt_int_get, tt_int_set, "%lld\n");DEFINE_DEBUGFS_ATTRIBUTE(tt_unsigned_int_attr, tt_int_get, tt_int_set, "%llu\n"); static int tt_zone_tz_temp_get(void *data, u64 *val){	struct tt_thermal_zone *tt_zone = data; 	guard(tt_zone)(tt_zone); 	if (!tt_zone->tz)		return -EBUSY; 	*val = tt_zone->tz_temp; 	return 0;}static int tt_zone_tz_temp_set(void *data, u64 val){	struct tt_thermal_zone *tt_zone = data; 	guard(tt_zone)(tt_zone); 	if (!tt_zone->tz)		return -EBUSY; 	WRITE_ONCE(tt_zone->tz_temp, val);	thermal_zone_device_update(tt_zone->tz, THERMAL_EVENT_TEMP_SAMPLE); 	return 0;}DEFINE_DEBUGFS_ATTRIBUTE_SIGNED(tt_zone_tz_temp_attr, tt_zone_tz_temp_get,				tt_zone_tz_temp_set, "%lld\n"); static void tt_zone_free_trips(struct tt_thermal_zone *tt_zone){	struct tt_trip *tt_trip, *aux; 	list_for_each_entry_safe(tt_trip, aux, &tt_zone->trips, list_node) {		list_del(&tt_trip->list_node);		ida_free(&tt_zone->ida, tt_trip->id);		kfree(tt_trip);	}} static void tt_zone_free(struct tt_thermal_zone *tt_zone){	tt_zone_free_trips(tt_zone);	ida_free(&tt_thermal_zones_ida, tt_zone->id);	ida_destroy(&tt_zone->ida);	kfree(tt_zone);} static void tt_add_tz_work_fn(struct work_struct *work){	struct tt_work *tt_work = tt_work_of_work(work);	struct tt_thermal_zone *tt_zone = tt_work->tt_zone;	char f_name[TT_MAX_FILE_NAME_LENGTH]; 	kfree(tt_work); 	snprintf(f_name, TT_MAX_FILE_NAME_LENGTH, "tz%d", tt_zone->id);	tt_zone->d_tt_zone = debugfs_create_dir(f_name, d_testing);	if (IS_ERR(tt_zone->d_tt_zone)) {		tt_zone_free(tt_zone);		return;	} 	debugfs_create_file_unsafe("temp", 0600, tt_zone->d_tt_zone, tt_zone,				   &tt_zone_tz_temp_attr); 	debugfs_create_file_unsafe("init_temp", 0600, tt_zone->d_tt_zone,				   &tt_zone->temp, &tt_int_attr); 	guard(mutex)(&tt_thermal_zones_lock); 	list_add_tail(&tt_zone->list_node, &tt_thermal_zones);} int tt_add_tz(void){	int ret; 	struct tt_thermal_zone *tt_zone __free(kfree) = kzalloc_obj(*tt_zone);	if (!tt_zone)		return -ENOMEM; 	struct tt_work *tt_work __free(kfree) = kzalloc_obj(*tt_work);	if (!tt_work)		return -ENOMEM; 	INIT_LIST_HEAD(&tt_zone->trips);	mutex_init(&tt_zone->lock);	ida_init(&tt_zone->ida);	tt_zone->temp = THERMAL_TEMP_INVALID; 	ret = ida_alloc(&tt_thermal_zones_ida, GFP_KERNEL);	if (ret < 0)		return ret; 	tt_zone->id = ret; 	INIT_WORK(&tt_work->work, tt_add_tz_work_fn);	tt_work->tt_zone = no_free_ptr(tt_zone);	tt_queue_work(&(no_free_ptr(tt_work)->work)); 	return 0;} static void tt_del_tz_work_fn(struct work_struct *work){	struct tt_work *tt_work = tt_work_of_work(work);	struct tt_thermal_zone *tt_zone = tt_work->tt_zone; 	kfree(tt_work); 	debugfs_remove(tt_zone->d_tt_zone);	tt_zone_free(tt_zone);} static void tt_zone_unregister_tz(struct tt_thermal_zone *tt_zone){	guard(tt_zone)(tt_zone); 	if (tt_zone->tz) {		thermal_zone_device_unregister(tt_zone->tz);		tt_zone->tz = NULL;	}} int tt_del_tz(const char *arg){	struct tt_thermal_zone *tt_zone, *aux;	int ret;	int id; 	ret = kstrtoint(arg, 10, &id);	if (ret < 0)		return ret; 	struct tt_work *tt_work __free(kfree) = kzalloc_obj(*tt_work);	if (!tt_work)		return -ENOMEM; 	guard(mutex)(&tt_thermal_zones_lock); 	ret = -EINVAL;	list_for_each_entry_safe(tt_zone, aux, &tt_thermal_zones, list_node) {		if (tt_zone->id == id) {			if (tt_zone->refcount) {				ret = -EBUSY;			} else {				list_del(&tt_zone->list_node);				ret = 0;			}			break;		}	} 	if (ret)		return ret; 	tt_zone_unregister_tz(tt_zone); 	INIT_WORK(&tt_work->work, tt_del_tz_work_fn);	tt_work->tt_zone = tt_zone;	tt_queue_work(&(no_free_ptr(tt_work)->work)); 	return 0;} static struct tt_thermal_zone *tt_get_tt_zone(const char *arg){	struct tt_thermal_zone *tt_zone;	int ret, id; 	ret = kstrtoint(arg, 10, &id);	if (ret < 0)		return ERR_PTR(ret); 	guard(mutex)(&tt_thermal_zones_lock); 	list_for_each_entry(tt_zone, &tt_thermal_zones, list_node) {		if (tt_zone->id == id) {			tt_zone->refcount++;			return tt_zone;		}	} 	return ERR_PTR(-EINVAL);} static void tt_put_tt_zone(struct tt_thermal_zone *tt_zone){	guard(mutex)(&tt_thermal_zones_lock); 	tt_zone->refcount--;} DEFINE_FREE(put_tt_zone, struct tt_thermal_zone *,	    if (!IS_ERR_OR_NULL(_T)) tt_put_tt_zone(_T)) static void tt_zone_add_trip_work_fn(struct work_struct *work){	struct tt_work *tt_work = tt_work_of_work(work);	struct tt_thermal_zone *tt_zone = tt_work->tt_zone;	struct tt_trip *tt_trip = tt_work->tt_trip;	char d_name[TT_MAX_FILE_NAME_LENGTH]; 	kfree(tt_work); 	snprintf(d_name, TT_MAX_FILE_NAME_LENGTH, "trip_%d_temp", tt_trip->id);	debugfs_create_file_unsafe(d_name, 0600, tt_zone->d_tt_zone,				   &tt_trip->trip.temperature, &tt_int_attr); 	snprintf(d_name, TT_MAX_FILE_NAME_LENGTH, "trip_%d_hyst", tt_trip->id);	debugfs_create_file_unsafe(d_name, 0600, tt_zone->d_tt_zone,				   &tt_trip->trip.hysteresis, &tt_unsigned_int_attr); 	tt_put_tt_zone(tt_zone);} int tt_zone_add_trip(const char *arg){	int id; 	struct tt_work *tt_work __free(kfree) = kzalloc_obj(*tt_work);	if (!tt_work)		return -ENOMEM; 	struct tt_trip *tt_trip __free(kfree) = kzalloc_obj(*tt_trip);	if (!tt_trip)		return -ENOMEM; 	struct tt_thermal_zone *tt_zone __free(put_tt_zone) = tt_get_tt_zone(arg);	if (IS_ERR(tt_zone))		return PTR_ERR(tt_zone); 	id = ida_alloc(&tt_zone->ida, GFP_KERNEL);	if (id < 0)		return id; 	tt_trip->trip.type = THERMAL_TRIP_ACTIVE;	tt_trip->trip.temperature = THERMAL_TEMP_INVALID;	tt_trip->trip.flags = THERMAL_TRIP_FLAG_RW;	tt_trip->id = id; 	guard(tt_zone)(tt_zone); 	list_add_tail(&tt_trip->list_node, &tt_zone->trips);	tt_zone->num_trips++; 	INIT_WORK(&tt_work->work, tt_zone_add_trip_work_fn);	tt_work->tt_zone = no_free_ptr(tt_zone);	tt_work->tt_trip = no_free_ptr(tt_trip);	tt_queue_work(&(no_free_ptr(tt_work)->work)); 	return 0;} static int tt_zone_get_temp(struct thermal_zone_device *tz, int *temp){	struct tt_thermal_zone *tt_zone = thermal_zone_device_priv(tz); 	*temp = READ_ONCE(tt_zone->tz_temp); 	if (*temp < THERMAL_TEMP_INVALID)		return -ENODATA; 	return 0;} static const struct thermal_zone_device_ops tt_zone_ops = {	.get_temp = tt_zone_get_temp,}; static int tt_zone_register_tz(struct tt_thermal_zone *tt_zone){	struct thermal_zone_device *tz;	struct tt_trip *tt_trip;	int i; 	guard(tt_zone)(tt_zone); 	if (tt_zone->tz)		return -EINVAL; 	struct thermal_trip *trips __free(kfree) = kzalloc_objs(*trips,								tt_zone->num_trips);	if (!trips)		return -ENOMEM; 	i = 0;	list_for_each_entry(tt_trip, &tt_zone->trips, list_node)		trips[i++] = tt_trip->trip; 	tt_zone->tz_temp = tt_zone->temp; 	tz = thermal_zone_device_register_with_trips("test_tz", trips, i, tt_zone,						     &tt_zone_ops, NULL, 0, 0);	if (IS_ERR(tz))		return PTR_ERR(tz); 	tt_zone->tz = tz; 	thermal_zone_device_enable(tz); 	return 0;} int tt_zone_reg(const char *arg){	struct tt_thermal_zone *tt_zone __free(put_tt_zone) = tt_get_tt_zone(arg);	if (IS_ERR(tt_zone))		return PTR_ERR(tt_zone); 	return tt_zone_register_tz(tt_zone);} int tt_zone_unreg(const char *arg){	struct tt_thermal_zone *tt_zone __free(put_tt_zone) = tt_get_tt_zone(arg);	if (IS_ERR(tt_zone))		return PTR_ERR(tt_zone); 	tt_zone_unregister_tz(tt_zone); 	return 0;} void tt_zone_cleanup(void){	struct tt_thermal_zone *tt_zone, *aux; 	list_for_each_entry_safe(tt_zone, aux, &tt_thermal_zones, list_node) {		tt_zone_unregister_tz(tt_zone); 		list_del(&tt_zone->list_node); 		tt_zone_free(tt_zone);	}}