zone.c 11 KB

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