From 4617721c502b2ddaa4e324e86da4997edf738fa5 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Wed, 2 Sep 2026 09:55:01 -0400 Subject: ftrace: Synchronize the initialization of ftrace_ops There's some internal state that ftrace_ops needs to have set, but since it can be declared outside of the ftrace.c code, it calls ftrace_ops_init() on the ops in every global function. The issue is that if two tasks call it on the same ops at the same time it is possible to have the initialization of one corrupt the initialization of the other call. Create a ops_mutex to use to synchronize every initialization of the ftrace_ops. The mutex is taken within checking the ftrace_ops flag that states it was initializied but the flag is checked again after the mutex has been taken. Checking first outside the mutex allows it to shortcut having to take the mutex. But then the check needs to be done again after the mute is taken in case of races. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock") Reported-by: sashiko-bot@kernel.org Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/ Signed-off-by: Steven Rostedt --- drivers/thermal/testing/zone.c | 446 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 446 insertions(+) create mode 100644 drivers/thermal/testing/zone.c (limited to 'drivers/thermal/testing/zone.c') diff --git a/drivers/thermal/testing/zone.c b/drivers/thermal/testing/zone.c new file mode 100644 index 000000000..6db26276d --- /dev/null +++ b/drivers/thermal/testing/zone.c @@ -0,0 +1,446 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2024, Intel Corporation + * + * Author: Rafael J. Wysocki + * + * Thermal zone tempalates handling for thermal core testing. + */ + +#define pr_fmt(fmt) "thermal-testing: " fmt + +#include +#include +#include +#include + +#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); + } +} -- cgit v1.3.1