Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
#
# Thermal core testing facility.
obj-$(CONFIG_THERMAL_CORE_TESTING) += thermal-testing.o
thermal-testing-y := command.o zone.o
@@ -0,0 +1,251 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2024, Intel Corporation
*
* Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
*
* Thermal subsystem testing facility.
*
* This facility allows the thermal core functionality to be exercised in a
* controlled way in order to verify its behavior.
*
* It resides in the "thermal-testing" directory under the debugfs root and
* starts with a single file called "command" which can be written a string
* representing a thermal testing facility command.
*
* The currently supported commands are listed in the tt_commands enum below.
*
* The "addtz" command causes a new test thermal zone template to be created,
* for example:
*
* # echo addtz > /sys/kernel/debug/thermal-testing/command
*
* That template will be represented as a subdirectory in the "thermal-testing"
* directory, for example
*
* # ls /sys/kernel/debug/thermal-testing/
* command tz0
*
* The thermal zone template can be populated with trip points with the help of
* the "tzaddtrip" command, for example:
*
* # echo tzaddtrip:0 > /sys/kernel/debug/thermal-testing/command
*
* which causes a trip point template to be added to the test thermal zone
* template 0 (represented by the tz0 subdirectory in "thermal-testing").
*
* # ls /sys/kernel/debug/thermal-testing/tz0
* init_temp temp trip_0_temp trip_0_hyst
*
* The temperature of a trip point template is initially THERMAL_TEMP_INVALID
* and its hysteresis is initially 0. They can be adjusted by writing to the
* "trip_x_temp" and "trip_x_hyst" files correspoinding to that trip point
* template, respectively.
*
* The initial temperature of a thermal zone based on a template can be set by
* writing to the "init_temp" file in its directory under "thermal-testing", for
* example:
*
* echo 50000 > /sys/kernel/debug/thermal-testing/tz0/init_temp
*
* When ready, "tzreg" command can be used for registering and enabling a
* thermal zone based on a given template with the thermal core, for example
*
* # echo tzreg:0 > /sys/kernel/debug/thermal-testing/command
*
* In this case, test thermal zone template 0 is used for registering a new
* thermal zone and the set of trip point templates associated with it is used
* for populating the new thermal zone's trip points table. The type of the new
* thermal zone is "test_tz".
*
* The temperature and hysteresis of all of the trip points in that new thermal
* zone are adjustable via sysfs, so they can be updated at any time.
*
* The current temperature of the new thermal zone can be set by writing to the
* "temp" file in the corresponding thermal zone template's directory under
* "thermal-testing", for example
*
* echo 10000 > /sys/kernel/debug/thermal-testing/tz0/temp
*
* which will also trigger a temperature update for this zone in the thermal
* core, including checking its trip points, sending notifications to user space
* if any of them have been crossed and so on.
*
* When it is not needed any more, a test thermal zone template can be deleted
* with the help of the "deltz" command, for example
*
* # echo deltz:0 > /sys/kernel/debug/thermal-testing/command
*
* which will also unregister the thermal zone based on it, if present.
*/
#define pr_fmt(fmt) "thermal-testing: " fmt
#include <linux/debugfs.h>
#include <linux/module.h>
#include "thermal_testing.h"
struct workqueue_struct *tt_wq __ro_after_init;
struct dentry *d_testing __ro_after_init;
static struct dentry *d_command __ro_after_init;
#define TT_COMMAND_SIZE 16
enum tt_commands {
TT_CMD_ADDTZ,
TT_CMD_DELTZ,
TT_CMD_TZADDTRIP,
TT_CMD_TZREG,
TT_CMD_TZUNREG,
};
static const char *tt_command_strings[] = {
[TT_CMD_ADDTZ] = "addtz",
[TT_CMD_DELTZ] = "deltz",
[TT_CMD_TZADDTRIP] = "tzaddtrip",
[TT_CMD_TZREG] = "tzreg",
[TT_CMD_TZUNREG] = "tzunreg",
};
static int tt_command_exec(int index, const char *arg)
{
int ret;
switch (index) {
case TT_CMD_ADDTZ:
ret = tt_add_tz();
break;
case TT_CMD_DELTZ:
if (!arg || !*arg)
return -EINVAL;
ret = tt_del_tz(arg);
break;
case TT_CMD_TZADDTRIP:
if (!arg || !*arg)
return -EINVAL;
ret = tt_zone_add_trip(arg);
break;
case TT_CMD_TZREG:
if (!arg || !*arg)
return -EINVAL;
ret = tt_zone_reg(arg);
break;
case TT_CMD_TZUNREG:
if (!arg || !*arg)
return -EINVAL;
ret = tt_zone_unreg(arg);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static ssize_t tt_command_process(char *s)
{
char *arg;
int i;
strim(s);
arg = strchr(s, ':');
if (arg) {
*arg = '\0';
arg++;
}
for (i = 0; i < ARRAY_SIZE(tt_command_strings); i++) {
if (!strcmp(s, tt_command_strings[i]))
return tt_command_exec(i, arg);
}
return -EINVAL;
}
static ssize_t tt_command_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
char buf[TT_COMMAND_SIZE];
ssize_t ret;
if (*ppos)
return -EINVAL;
if (count > TT_COMMAND_SIZE - 1)
return -E2BIG;
if (copy_from_user(buf, user_buf, count))
return -EFAULT;
buf[count] = '\0';
ret = tt_command_process(buf);
if (ret)
return ret;
return count;
}
static const struct file_operations tt_command_fops = {
.write = tt_command_write,
.open = simple_open,
.llseek = default_llseek,
};
static int __init thermal_testing_init(void)
{
int error;
tt_wq = alloc_workqueue("thermal_testing", WQ_UNBOUND, 0);
if (!tt_wq)
return -ENOMEM;
d_testing = debugfs_create_dir("thermal-testing", NULL);
if (IS_ERR(d_testing)) {
error = PTR_ERR(d_testing);
goto destroy_wq;
}
d_command = debugfs_create_file("command", 0200, d_testing, NULL, &tt_command_fops);
if (IS_ERR(d_command)) {
error = PTR_ERR(d_command);
goto remove_d_testing;
}
return 0;
remove_d_testing:
debugfs_remove(d_testing);
destroy_wq:
destroy_workqueue(tt_wq);
return error;
}
module_init(thermal_testing_init);
static void __exit thermal_testing_exit(void)
{
/* First, prevent new commands from being entered. */
debugfs_remove(d_command);
/* Flush commands in progress (if any). */
flush_workqueue(tt_wq);
destroy_workqueue(tt_wq);
/* Remove the directory structure and clean up. */
debugfs_remove(d_testing);
tt_zone_cleanup();
}
module_exit(thermal_testing_exit);
MODULE_DESCRIPTION("Thermal core testing facility");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/workqueue.h>
extern struct workqueue_struct *tt_wq;
static inline void tt_queue_work(struct work_struct *work)
{
queue_work(tt_wq, work);
}
extern struct dentry *d_testing;
int tt_add_tz(void);
int tt_del_tz(const char *arg);
int tt_zone_add_trip(const char *arg);
int tt_zone_reg(const char *arg);
int tt_zone_unreg(const char *arg);
void tt_zone_cleanup(void);
+446
View File
@@ -0,0 +1,446 @@
// 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);
}
}