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 zone tempalates handling for thermal core testing. 8 */ 9 10 #define pr_fmt(fmt) "thermal-testing: " fmt 11 12 #include <linux/debugfs.h> 13 #include <linux/idr.h> 14 #include <linux/list.h> 15 #include <linux/thermal.h> 16 #include <linux/workqueue.h> 17 18 #include "thermal_testing.h" 19 20 #define TT_MAX_FILE_NAME_LENGTH 16 21 22 /** 23 * struct tt_thermal_zone - Testing thermal zone template 24 * 25 * Represents a template of a thermal zone that can be used for registering 26 * a test thermal zone with the thermal core. 27 * 28 * @list_node: Node in the list of all testing thermal zone templates. 29 * @trips: List of trip point templates for this thermal zone template. 30 * @d_tt_zone: Directory in debugfs representing this template. 31 * @tz: Test thermal zone based on this template, if present. 32 * @lock: Mutex for synchronizing changes of this template. 33 * @ida: IDA for trip point IDs. 34 * @id: The ID of this template for the debugfs interface. 35 * @temp: Temperature value. 36 * @tz_temp: Current thermal zone temperature (after registration). 37 * @num_trips: Number of trip points in the @trips list. 38 * @refcount: Reference counter for usage and removal synchronization. 39 */ 40 struct tt_thermal_zone { 41 struct list_head list_node; 42 struct list_head trips; 43 struct dentry *d_tt_zone; 44 struct thermal_zone_device *tz; 45 struct mutex lock; 46 struct ida ida; 47 int id; 48 int temp; 49 int tz_temp; 50 unsigned int num_trips; 51 unsigned int refcount; 52 }; 53 54 DEFINE_GUARD(tt_zone, struct tt_thermal_zone *, mutex_lock(&_T->lock), mutex_unlock(&_T->lock)) 55 56 /** 57 * struct tt_trip - Testing trip point template 58 * 59 * Represents a template of a trip point to be used for populating a trip point 60 * during the registration of a thermal zone based on a given zone template. 61 * 62 * @list_node: Node in the list of all trip templates in the zone template. 63 * @trip: Trip point data to use for thernal zone registration. 64 * @id: The ID of this trip template for the debugfs interface. 65 */ 66 struct tt_trip { 67 struct list_head list_node; 68 struct thermal_trip trip; 69 int id; 70 }; 71 72 /* 73 * It is both questionable and potentially problematic from the sychnronization 74 * perspective to attempt to manipulate debugfs from within a debugfs file 75 * "write" operation, so auxiliary work items are used for that. The majority 76 * of zone-related command functions have a part that runs from a workqueue and 77 * make changes in debugs, among other things. 78 */ 79 struct tt_work { 80 struct work_struct work; 81 struct tt_thermal_zone *tt_zone; 82 struct tt_trip *tt_trip; 83 }; 84 85 static inline struct tt_work *tt_work_of_work(struct work_struct *work) 86 { 87 return container_of(work, struct tt_work, work); 88 } 89 90 static LIST_HEAD(tt_thermal_zones); 91 static DEFINE_IDA(tt_thermal_zones_ida); 92 static DEFINE_MUTEX(tt_thermal_zones_lock); 93 94 static int tt_int_get(void *data, u64 *val) 95 { 96 *val = *(int *)data; 97 return 0; 98 } 99 static int tt_int_set(void *data, u64 val) 100 { 101 if ((int)val < THERMAL_TEMP_INVALID) 102 return -EINVAL; 103 104 *(int *)data = val; 105 return 0; 106 } 107 DEFINE_DEBUGFS_ATTRIBUTE_SIGNED(tt_int_attr, tt_int_get, tt_int_set, "%lld\n"); 108 DEFINE_DEBUGFS_ATTRIBUTE(tt_unsigned_int_attr, tt_int_get, tt_int_set, "%llu\n"); 109 110 static int tt_zone_tz_temp_get(void *data, u64 *val) 111 { 112 struct tt_thermal_zone *tt_zone = data; 113 114 guard(tt_zone)(tt_zone); 115 116 if (!tt_zone->tz) 117 return -EBUSY; 118 119 *val = tt_zone->tz_temp; 120 121 return 0; 122 } 123 static int tt_zone_tz_temp_set(void *data, u64 val) 124 { 125 struct tt_thermal_zone *tt_zone = data; 126 127 guard(tt_zone)(tt_zone); 128 129 if (!tt_zone->tz) 130 return -EBUSY; 131 132 WRITE_ONCE(tt_zone->tz_temp, val); 133 thermal_zone_device_update(tt_zone->tz, THERMAL_EVENT_TEMP_SAMPLE); 134 135 return 0; 136 } 137 DEFINE_DEBUGFS_ATTRIBUTE_SIGNED(tt_zone_tz_temp_attr, tt_zone_tz_temp_get, 138 tt_zone_tz_temp_set, "%lld\n"); 139 140 static void tt_zone_free_trips(struct tt_thermal_zone *tt_zone) 141 { 142 struct tt_trip *tt_trip, *aux; 143 144 list_for_each_entry_safe(tt_trip, aux, &tt_zone->trips, list_node) { 145 list_del(&tt_trip->list_node); 146 ida_free(&tt_zone->ida, tt_trip->id); 147 kfree(tt_trip); 148 } 149 } 150 151 static void tt_zone_free(struct tt_thermal_zone *tt_zone) 152 { 153 tt_zone_free_trips(tt_zone); 154 ida_free(&tt_thermal_zones_ida, tt_zone->id); 155 ida_destroy(&tt_zone->ida); 156 kfree(tt_zone); 157 } 158 159 static void tt_add_tz_work_fn(struct work_struct *work) 160 { 161 struct tt_work *tt_work = tt_work_of_work(work); 162 struct tt_thermal_zone *tt_zone = tt_work->tt_zone; 163 char f_name[TT_MAX_FILE_NAME_LENGTH]; 164 165 kfree(tt_work); 166 167 snprintf(f_name, TT_MAX_FILE_NAME_LENGTH, "tz%d", tt_zone->id); 168 tt_zone->d_tt_zone = debugfs_create_dir(f_name, d_testing); 169 if (IS_ERR(tt_zone->d_tt_zone)) { 170 tt_zone_free(tt_zone); 171 return; 172 } 173 174 debugfs_create_file_unsafe("temp", 0600, tt_zone->d_tt_zone, tt_zone, 175 &tt_zone_tz_temp_attr); 176 177 debugfs_create_file_unsafe("init_temp", 0600, tt_zone->d_tt_zone, 178 &tt_zone->temp, &tt_int_attr); 179 180 guard(mutex)(&tt_thermal_zones_lock); 181 182 list_add_tail(&tt_zone->list_node, &tt_thermal_zones); 183 } 184 185 int tt_add_tz(void) 186 { 187 struct tt_thermal_zone *tt_zone __free(kfree); 188 struct tt_work *tt_work __free(kfree); 189 int ret; 190 191 tt_zone = kzalloc(sizeof(*tt_zone), GFP_KERNEL); 192 if (!tt_zone) 193 return -ENOMEM; 194 195 tt_work = kzalloc(sizeof(*tt_work), GFP_KERNEL); 196 if (!tt_work) 197 return -ENOMEM; 198 199 INIT_LIST_HEAD(&tt_zone->trips); 200 mutex_init(&tt_zone->lock); 201 ida_init(&tt_zone->ida); 202 tt_zone->temp = THERMAL_TEMP_INVALID; 203 204 ret = ida_alloc(&tt_thermal_zones_ida, GFP_KERNEL); 205 if (ret < 0) 206 return ret; 207 208 tt_zone->id = ret; 209 210 INIT_WORK(&tt_work->work, tt_add_tz_work_fn); 211 tt_work->tt_zone = no_free_ptr(tt_zone); 212 schedule_work(&(no_free_ptr(tt_work)->work)); 213 214 return 0; 215 } 216 217 static void tt_del_tz_work_fn(struct work_struct *work) 218 { 219 struct tt_work *tt_work = tt_work_of_work(work); 220 struct tt_thermal_zone *tt_zone = tt_work->tt_zone; 221 222 kfree(tt_work); 223 224 debugfs_remove(tt_zone->d_tt_zone); 225 tt_zone_free(tt_zone); 226 } 227 228 static void tt_zone_unregister_tz(struct tt_thermal_zone *tt_zone) 229 { 230 guard(tt_zone)(tt_zone); 231 232 if (tt_zone->tz) { 233 thermal_zone_device_unregister(tt_zone->tz); 234 tt_zone->tz = NULL; 235 } 236 } 237 238 int tt_del_tz(const char *arg) 239 { 240 struct tt_work *tt_work __free(kfree); 241 struct tt_thermal_zone *tt_zone, *aux; 242 int ret; 243 int id; 244 245 ret = sscanf(arg, "%d", &id); 246 if (ret != 1) 247 return -EINVAL; 248 249 tt_work = kzalloc(sizeof(*tt_work), GFP_KERNEL); 250 if (!tt_work) 251 return -ENOMEM; 252 253 guard(mutex)(&tt_thermal_zones_lock); 254 255 ret = -EINVAL; 256 list_for_each_entry_safe(tt_zone, aux, &tt_thermal_zones, list_node) { 257 if (tt_zone->id == id) { 258 if (tt_zone->refcount) { 259 ret = -EBUSY; 260 } else { 261 list_del(&tt_zone->list_node); 262 ret = 0; 263 } 264 break; 265 } 266 } 267 268 if (ret) 269 return ret; 270 271 tt_zone_unregister_tz(tt_zone); 272 273 INIT_WORK(&tt_work->work, tt_del_tz_work_fn); 274 tt_work->tt_zone = tt_zone; 275 schedule_work(&(no_free_ptr(tt_work)->work)); 276 277 return 0; 278 } 279 280 static struct tt_thermal_zone *tt_get_tt_zone(const char *arg) 281 { 282 struct tt_thermal_zone *tt_zone; 283 int ret, id; 284 285 ret = sscanf(arg, "%d", &id); 286 if (ret != 1) 287 return ERR_PTR(-EINVAL); 288 289 guard(mutex)(&tt_thermal_zones_lock); 290 291 ret = -EINVAL; 292 list_for_each_entry(tt_zone, &tt_thermal_zones, list_node) { 293 if (tt_zone->id == id) { 294 tt_zone->refcount++; 295 ret = 0; 296 break; 297 } 298 } 299 300 if (ret) 301 return ERR_PTR(ret); 302 303 return tt_zone; 304 } 305 306 static void tt_put_tt_zone(struct tt_thermal_zone *tt_zone) 307 { 308 guard(mutex)(&tt_thermal_zones_lock); 309 310 tt_zone->refcount--; 311 } 312 313 static void tt_zone_add_trip_work_fn(struct work_struct *work) 314 { 315 struct tt_work *tt_work = tt_work_of_work(work); 316 struct tt_thermal_zone *tt_zone = tt_work->tt_zone; 317 struct tt_trip *tt_trip = tt_work->tt_trip; 318 char d_name[TT_MAX_FILE_NAME_LENGTH]; 319 320 kfree(tt_work); 321 322 snprintf(d_name, TT_MAX_FILE_NAME_LENGTH, "trip_%d_temp", tt_trip->id); 323 debugfs_create_file_unsafe(d_name, 0600, tt_zone->d_tt_zone, 324 &tt_trip->trip.temperature, &tt_int_attr); 325 326 snprintf(d_name, TT_MAX_FILE_NAME_LENGTH, "trip_%d_hyst", tt_trip->id); 327 debugfs_create_file_unsafe(d_name, 0600, tt_zone->d_tt_zone, 328 &tt_trip->trip.hysteresis, &tt_unsigned_int_attr); 329 330 tt_put_tt_zone(tt_zone); 331 } 332 333 int tt_zone_add_trip(const char *arg) 334 { 335 struct tt_work *tt_work __free(kfree); 336 struct tt_trip *tt_trip __free(kfree); 337 struct tt_thermal_zone *tt_zone; 338 int id; 339 340 tt_work = kzalloc(sizeof(*tt_work), GFP_KERNEL); 341 if (!tt_work) 342 return -ENOMEM; 343 344 tt_trip = kzalloc(sizeof(*tt_trip), GFP_KERNEL); 345 if (!tt_trip) 346 return -ENOMEM; 347 348 tt_zone = tt_get_tt_zone(arg); 349 if (IS_ERR(tt_zone)) 350 return PTR_ERR(tt_zone); 351 352 id = ida_alloc(&tt_zone->ida, GFP_KERNEL); 353 if (id < 0) { 354 tt_put_tt_zone(tt_zone); 355 return id; 356 } 357 358 tt_trip->trip.type = THERMAL_TRIP_ACTIVE; 359 tt_trip->trip.temperature = THERMAL_TEMP_INVALID; 360 tt_trip->trip.flags = THERMAL_TRIP_FLAG_RW; 361 tt_trip->id = id; 362 363 guard(tt_zone)(tt_zone); 364 365 list_add_tail(&tt_trip->list_node, &tt_zone->trips); 366 tt_zone->num_trips++; 367 368 INIT_WORK(&tt_work->work, tt_zone_add_trip_work_fn); 369 tt_work->tt_zone = tt_zone; 370 tt_work->tt_trip = no_free_ptr(tt_trip); 371 schedule_work(&(no_free_ptr(tt_work)->work)); 372 373 return 0; 374 } 375 376 static int tt_zone_get_temp(struct thermal_zone_device *tz, int *temp) 377 { 378 struct tt_thermal_zone *tt_zone = thermal_zone_device_priv(tz); 379 380 *temp = READ_ONCE(tt_zone->tz_temp); 381 382 if (*temp < THERMAL_TEMP_INVALID) 383 return -ENODATA; 384 385 return 0; 386 } 387 388 static struct thermal_zone_device_ops tt_zone_ops = { 389 .get_temp = tt_zone_get_temp, 390 }; 391 392 static int tt_zone_register_tz(struct tt_thermal_zone *tt_zone) 393 { 394 struct thermal_trip *trips __free(kfree); 395 struct thermal_zone_device *tz; 396 struct tt_trip *tt_trip; 397 int i; 398 399 guard(tt_zone)(tt_zone); 400 401 if (tt_zone->tz) 402 return -EINVAL; 403 404 trips = kcalloc(tt_zone->num_trips, sizeof(*trips), GFP_KERNEL); 405 if (!trips) 406 return -ENOMEM; 407 408 i = 0; 409 list_for_each_entry(tt_trip, &tt_zone->trips, list_node) 410 trips[i++] = tt_trip->trip; 411 412 tt_zone->tz_temp = tt_zone->temp; 413 414 tz = thermal_zone_device_register_with_trips("test_tz", trips, i, tt_zone, 415 &tt_zone_ops, NULL, 0, 0); 416 if (IS_ERR(tz)) 417 return PTR_ERR(tz); 418 419 tt_zone->tz = tz; 420 421 thermal_zone_device_enable(tz); 422 423 return 0; 424 } 425 426 int tt_zone_reg(const char *arg) 427 { 428 struct tt_thermal_zone *tt_zone; 429 int ret; 430 431 tt_zone = tt_get_tt_zone(arg); 432 if (IS_ERR(tt_zone)) 433 return PTR_ERR(tt_zone); 434 435 ret = tt_zone_register_tz(tt_zone); 436 437 tt_put_tt_zone(tt_zone); 438 439 return ret; 440 } 441 442 int tt_zone_unreg(const char *arg) 443 { 444 struct tt_thermal_zone *tt_zone; 445 446 tt_zone = tt_get_tt_zone(arg); 447 if (IS_ERR(tt_zone)) 448 return PTR_ERR(tt_zone); 449 450 tt_zone_unregister_tz(tt_zone); 451 452 tt_put_tt_zone(tt_zone); 453 454 return 0; 455 } 456 457 void tt_zone_cleanup(void) 458 { 459 struct tt_thermal_zone *tt_zone, *aux; 460 461 list_for_each_entry_safe(tt_zone, aux, &tt_thermal_zones, list_node) { 462 tt_zone_unregister_tz(tt_zone); 463 464 list_del(&tt_zone->list_node); 465 466 tt_zone_free(tt_zone); 467 } 468 } 469