|  | // 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 dentry *d_testing; | 
|  |  | 
|  | #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: | 
|  | ret = tt_del_tz(arg); | 
|  | break; | 
|  |  | 
|  | case TT_CMD_TZADDTRIP: | 
|  | ret = tt_zone_add_trip(arg); | 
|  | break; | 
|  |  | 
|  | case TT_CMD_TZREG: | 
|  | ret = tt_zone_reg(arg); | 
|  | break; | 
|  |  | 
|  | case TT_CMD_TZUNREG: | 
|  | 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) | 
|  | { | 
|  | d_testing = debugfs_create_dir("thermal-testing", NULL); | 
|  | if (!IS_ERR(d_testing)) | 
|  | debugfs_create_file("command", 0200, d_testing, NULL, | 
|  | &tt_command_fops); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | module_init(thermal_testing_init); | 
|  |  | 
|  | static void __exit thermal_testing_exit(void) | 
|  | { | 
|  | debugfs_remove(d_testing); | 
|  | tt_zone_cleanup(); | 
|  | } | 
|  | module_exit(thermal_testing_exit); | 
|  |  | 
|  | MODULE_DESCRIPTION("Thermal core testing facility"); | 
|  | MODULE_LICENSE("GPL v2"); |