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