| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- // 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 <linux/workqueue.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);
- schedule_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 = sscanf(arg, "%d", &id);
- if (ret != 1)
- return -EINVAL;
- 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;
- schedule_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 = sscanf(arg, "%d", &id);
- if (ret != 1)
- return ERR_PTR(-EINVAL);
- 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);
- schedule_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);
- }
- }
|