1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright 2024, Intel Corporation 4 * 5 * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com> 6 * 7 * Thermal subsystem testing facility. 8 * 9 * This facility allows the thermal core functionality to be exercised in a 10 * controlled way in order to verify its behavior. 11 * 12 * It resides in the "thermal-testing" directory under the debugfs root and 13 * starts with a single file called "command" which can be written a string 14 * representing a thermal testing facility command. 15 * 16 * The currently supported commands are listed in the tt_commands enum below. 17 * 18 * The "addtz" command causes a new test thermal zone template to be created, 19 * for example: 20 * 21 * # echo addtz > /sys/kernel/debug/thermal-testing/command 22 * 23 * That template will be represented as a subdirectory in the "thermal-testing" 24 * directory, for example 25 * 26 * # ls /sys/kernel/debug/thermal-testing/ 27 * command tz0 28 * 29 * The thermal zone template can be populated with trip points with the help of 30 * the "tzaddtrip" command, for example: 31 * 32 * # echo tzaddtrip:0 > /sys/kernel/debug/thermal-testing/command 33 * 34 * which causes a trip point template to be added to the test thermal zone 35 * template 0 (represented by the tz0 subdirectory in "thermal-testing"). 36 * 37 * # ls /sys/kernel/debug/thermal-testing/tz0 38 * init_temp temp trip_0_temp trip_0_hyst 39 * 40 * The temperature of a trip point template is initially THERMAL_TEMP_INVALID 41 * and its hysteresis is initially 0. They can be adjusted by writing to the 42 * "trip_x_temp" and "trip_x_hyst" files correspoinding to that trip point 43 * template, respectively. 44 * 45 * The initial temperature of a thermal zone based on a template can be set by 46 * writing to the "init_temp" file in its directory under "thermal-testing", for 47 * example: 48 * 49 * echo 50000 > /sys/kernel/debug/thermal-testing/tz0/init_temp 50 * 51 * When ready, "tzreg" command can be used for registering and enabling a 52 * thermal zone based on a given template with the thermal core, for example 53 * 54 * # echo tzreg:0 > /sys/kernel/debug/thermal-testing/command 55 * 56 * In this case, test thermal zone template 0 is used for registering a new 57 * thermal zone and the set of trip point templates associated with it is used 58 * for populating the new thermal zone's trip points table. The type of the new 59 * thermal zone is "test_tz". 60 * 61 * The temperature and hysteresis of all of the trip points in that new thermal 62 * zone are adjustable via sysfs, so they can be updated at any time. 63 * 64 * The current temperature of the new thermal zone can be set by writing to the 65 * "temp" file in the corresponding thermal zone template's directory under 66 * "thermal-testing", for example 67 * 68 * echo 10000 > /sys/kernel/debug/thermal-testing/tz0/temp 69 * 70 * which will also trigger a temperature update for this zone in the thermal 71 * core, including checking its trip points, sending notifications to user space 72 * if any of them have been crossed and so on. 73 * 74 * When it is not needed any more, a test thermal zone template can be deleted 75 * with the help of the "deltz" command, for example 76 * 77 * # echo deltz:0 > /sys/kernel/debug/thermal-testing/command 78 * 79 * which will also unregister the thermal zone based on it, if present. 80 */ 81 82 #define pr_fmt(fmt) "thermal-testing: " fmt 83 84 #include <linux/debugfs.h> 85 #include <linux/module.h> 86 87 #include "thermal_testing.h" 88 89 struct workqueue_struct *tt_wq __ro_after_init; 90 91 struct dentry *d_testing __ro_after_init; 92 static struct dentry *d_command __ro_after_init; 93 94 #define TT_COMMAND_SIZE 16 95 96 enum tt_commands { 97 TT_CMD_ADDTZ, 98 TT_CMD_DELTZ, 99 TT_CMD_TZADDTRIP, 100 TT_CMD_TZREG, 101 TT_CMD_TZUNREG, 102 }; 103 104 static const char *tt_command_strings[] = { 105 [TT_CMD_ADDTZ] = "addtz", 106 [TT_CMD_DELTZ] = "deltz", 107 [TT_CMD_TZADDTRIP] = "tzaddtrip", 108 [TT_CMD_TZREG] = "tzreg", 109 [TT_CMD_TZUNREG] = "tzunreg", 110 }; 111 112 static int tt_command_exec(int index, const char *arg) 113 { 114 int ret; 115 116 switch (index) { 117 case TT_CMD_ADDTZ: 118 ret = tt_add_tz(); 119 break; 120 121 case TT_CMD_DELTZ: 122 if (!arg || !*arg) 123 return -EINVAL; 124 125 ret = tt_del_tz(arg); 126 break; 127 128 case TT_CMD_TZADDTRIP: 129 if (!arg || !*arg) 130 return -EINVAL; 131 132 ret = tt_zone_add_trip(arg); 133 break; 134 135 case TT_CMD_TZREG: 136 if (!arg || !*arg) 137 return -EINVAL; 138 139 ret = tt_zone_reg(arg); 140 break; 141 142 case TT_CMD_TZUNREG: 143 if (!arg || !*arg) 144 return -EINVAL; 145 146 ret = tt_zone_unreg(arg); 147 break; 148 149 default: 150 ret = -EINVAL; 151 break; 152 } 153 154 return ret; 155 } 156 157 static ssize_t tt_command_process(char *s) 158 { 159 char *arg; 160 int i; 161 162 strim(s); 163 164 arg = strchr(s, ':'); 165 if (arg) { 166 *arg = '\0'; 167 arg++; 168 } 169 170 for (i = 0; i < ARRAY_SIZE(tt_command_strings); i++) { 171 if (!strcmp(s, tt_command_strings[i])) 172 return tt_command_exec(i, arg); 173 } 174 175 return -EINVAL; 176 } 177 178 static ssize_t tt_command_write(struct file *file, const char __user *user_buf, 179 size_t count, loff_t *ppos) 180 { 181 char buf[TT_COMMAND_SIZE]; 182 ssize_t ret; 183 184 if (*ppos) 185 return -EINVAL; 186 187 if (count > TT_COMMAND_SIZE - 1) 188 return -E2BIG; 189 190 if (copy_from_user(buf, user_buf, count)) 191 return -EFAULT; 192 buf[count] = '\0'; 193 194 ret = tt_command_process(buf); 195 if (ret) 196 return ret; 197 198 return count; 199 } 200 201 static const struct file_operations tt_command_fops = { 202 .write = tt_command_write, 203 .open = simple_open, 204 .llseek = default_llseek, 205 }; 206 207 static int __init thermal_testing_init(void) 208 { 209 int error; 210 211 tt_wq = alloc_workqueue("thermal_testing", WQ_UNBOUND, 0); 212 if (!tt_wq) 213 return -ENOMEM; 214 215 d_testing = debugfs_create_dir("thermal-testing", NULL); 216 if (IS_ERR(d_testing)) { 217 error = PTR_ERR(d_testing); 218 goto destroy_wq; 219 } 220 221 d_command = debugfs_create_file("command", 0200, d_testing, NULL, &tt_command_fops); 222 if (IS_ERR(d_command)) { 223 error = PTR_ERR(d_command); 224 goto remove_d_testing; 225 } 226 227 return 0; 228 229 remove_d_testing: 230 debugfs_remove(d_testing); 231 destroy_wq: 232 destroy_workqueue(tt_wq); 233 return error; 234 } 235 module_init(thermal_testing_init); 236 237 static void __exit thermal_testing_exit(void) 238 { 239 /* First, prevent new commands from being entered. */ 240 debugfs_remove(d_command); 241 /* Flush commands in progress (if any). */ 242 flush_workqueue(tt_wq); 243 destroy_workqueue(tt_wq); 244 /* Remove the directory structure and clean up. */ 245 debugfs_remove(d_testing); 246 tt_zone_cleanup(); 247 } 248 module_exit(thermal_testing_exit); 249 250 MODULE_DESCRIPTION("Thermal core testing facility"); 251 MODULE_LICENSE("GPL v2"); 252