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