// 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");
// 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");