zone.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  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. int ret;
  156. struct tt_thermal_zone *tt_zone __free(kfree) = kzalloc_obj(*tt_zone);
  157. if (!tt_zone)
  158. return -ENOMEM;
  159. struct tt_work *tt_work __free(kfree) = kzalloc_obj(*tt_work);
  160. if (!tt_work)
  161. return -ENOMEM;
  162. INIT_LIST_HEAD(&tt_zone->trips);
  163. mutex_init(&tt_zone->lock);
  164. ida_init(&tt_zone->ida);
  165. tt_zone->temp = THERMAL_TEMP_INVALID;
  166. ret = ida_alloc(&tt_thermal_zones_ida, GFP_KERNEL);
  167. if (ret < 0)
  168. return ret;
  169. tt_zone->id = ret;
  170. INIT_WORK(&tt_work->work, tt_add_tz_work_fn);
  171. tt_work->tt_zone = no_free_ptr(tt_zone);
  172. schedule_work(&(no_free_ptr(tt_work)->work));
  173. return 0;
  174. }
  175. static void tt_del_tz_work_fn(struct work_struct *work)
  176. {
  177. struct tt_work *tt_work = tt_work_of_work(work);
  178. struct tt_thermal_zone *tt_zone = tt_work->tt_zone;
  179. kfree(tt_work);
  180. debugfs_remove(tt_zone->d_tt_zone);
  181. tt_zone_free(tt_zone);
  182. }
  183. static void tt_zone_unregister_tz(struct tt_thermal_zone *tt_zone)
  184. {
  185. guard(tt_zone)(tt_zone);
  186. if (tt_zone->tz) {
  187. thermal_zone_device_unregister(tt_zone->tz);
  188. tt_zone->tz = NULL;
  189. }
  190. }
  191. int tt_del_tz(const char *arg)
  192. {
  193. struct tt_thermal_zone *tt_zone, *aux;
  194. int ret;
  195. int id;
  196. ret = sscanf(arg, "%d", &id);
  197. if (ret != 1)
  198. return -EINVAL;
  199. struct tt_work *tt_work __free(kfree) = kzalloc_obj(*tt_work);
  200. if (!tt_work)
  201. return -ENOMEM;
  202. guard(mutex)(&tt_thermal_zones_lock);
  203. ret = -EINVAL;
  204. list_for_each_entry_safe(tt_zone, aux, &tt_thermal_zones, list_node) {
  205. if (tt_zone->id == id) {
  206. if (tt_zone->refcount) {
  207. ret = -EBUSY;
  208. } else {
  209. list_del(&tt_zone->list_node);
  210. ret = 0;
  211. }
  212. break;
  213. }
  214. }
  215. if (ret)
  216. return ret;
  217. tt_zone_unregister_tz(tt_zone);
  218. INIT_WORK(&tt_work->work, tt_del_tz_work_fn);
  219. tt_work->tt_zone = tt_zone;
  220. schedule_work(&(no_free_ptr(tt_work)->work));
  221. return 0;
  222. }
  223. static struct tt_thermal_zone *tt_get_tt_zone(const char *arg)
  224. {
  225. struct tt_thermal_zone *tt_zone;
  226. int ret, id;
  227. ret = sscanf(arg, "%d", &id);
  228. if (ret != 1)
  229. return ERR_PTR(-EINVAL);
  230. guard(mutex)(&tt_thermal_zones_lock);
  231. list_for_each_entry(tt_zone, &tt_thermal_zones, list_node) {
  232. if (tt_zone->id == id) {
  233. tt_zone->refcount++;
  234. return tt_zone;
  235. }
  236. }
  237. return ERR_PTR(-EINVAL);
  238. }
  239. static void tt_put_tt_zone(struct tt_thermal_zone *tt_zone)
  240. {
  241. guard(mutex)(&tt_thermal_zones_lock);
  242. tt_zone->refcount--;
  243. }
  244. DEFINE_FREE(put_tt_zone, struct tt_thermal_zone *,
  245. if (!IS_ERR_OR_NULL(_T)) tt_put_tt_zone(_T))
  246. static void tt_zone_add_trip_work_fn(struct work_struct *work)
  247. {
  248. struct tt_work *tt_work = tt_work_of_work(work);
  249. struct tt_thermal_zone *tt_zone = tt_work->tt_zone;
  250. struct tt_trip *tt_trip = tt_work->tt_trip;
  251. char d_name[TT_MAX_FILE_NAME_LENGTH];
  252. kfree(tt_work);
  253. snprintf(d_name, TT_MAX_FILE_NAME_LENGTH, "trip_%d_temp", tt_trip->id);
  254. debugfs_create_file_unsafe(d_name, 0600, tt_zone->d_tt_zone,
  255. &tt_trip->trip.temperature, &tt_int_attr);
  256. snprintf(d_name, TT_MAX_FILE_NAME_LENGTH, "trip_%d_hyst", tt_trip->id);
  257. debugfs_create_file_unsafe(d_name, 0600, tt_zone->d_tt_zone,
  258. &tt_trip->trip.hysteresis, &tt_unsigned_int_attr);
  259. tt_put_tt_zone(tt_zone);
  260. }
  261. int tt_zone_add_trip(const char *arg)
  262. {
  263. int id;
  264. struct tt_work *tt_work __free(kfree) = kzalloc_obj(*tt_work);
  265. if (!tt_work)
  266. return -ENOMEM;
  267. struct tt_trip *tt_trip __free(kfree) = kzalloc_obj(*tt_trip);
  268. if (!tt_trip)
  269. return -ENOMEM;
  270. struct tt_thermal_zone *tt_zone __free(put_tt_zone) = tt_get_tt_zone(arg);
  271. if (IS_ERR(tt_zone))
  272. return PTR_ERR(tt_zone);
  273. id = ida_alloc(&tt_zone->ida, GFP_KERNEL);
  274. if (id < 0)
  275. return id;
  276. tt_trip->trip.type = THERMAL_TRIP_ACTIVE;
  277. tt_trip->trip.temperature = THERMAL_TEMP_INVALID;
  278. tt_trip->trip.flags = THERMAL_TRIP_FLAG_RW;
  279. tt_trip->id = id;
  280. guard(tt_zone)(tt_zone);
  281. list_add_tail(&tt_trip->list_node, &tt_zone->trips);
  282. tt_zone->num_trips++;
  283. INIT_WORK(&tt_work->work, tt_zone_add_trip_work_fn);
  284. tt_work->tt_zone = no_free_ptr(tt_zone);
  285. tt_work->tt_trip = no_free_ptr(tt_trip);
  286. schedule_work(&(no_free_ptr(tt_work)->work));
  287. return 0;
  288. }
  289. static int tt_zone_get_temp(struct thermal_zone_device *tz, int *temp)
  290. {
  291. struct tt_thermal_zone *tt_zone = thermal_zone_device_priv(tz);
  292. *temp = READ_ONCE(tt_zone->tz_temp);
  293. if (*temp < THERMAL_TEMP_INVALID)
  294. return -ENODATA;
  295. return 0;
  296. }
  297. static const struct thermal_zone_device_ops tt_zone_ops = {
  298. .get_temp = tt_zone_get_temp,
  299. };
  300. static int tt_zone_register_tz(struct tt_thermal_zone *tt_zone)
  301. {
  302. struct thermal_zone_device *tz;
  303. struct tt_trip *tt_trip;
  304. int i;
  305. guard(tt_zone)(tt_zone);
  306. if (tt_zone->tz)
  307. return -EINVAL;
  308. struct thermal_trip *trips __free(kfree) = kzalloc_objs(*trips,
  309. tt_zone->num_trips);
  310. if (!trips)
  311. return -ENOMEM;
  312. i = 0;
  313. list_for_each_entry(tt_trip, &tt_zone->trips, list_node)
  314. trips[i++] = tt_trip->trip;
  315. tt_zone->tz_temp = tt_zone->temp;
  316. tz = thermal_zone_device_register_with_trips("test_tz", trips, i, tt_zone,
  317. &tt_zone_ops, NULL, 0, 0);
  318. if (IS_ERR(tz))
  319. return PTR_ERR(tz);
  320. tt_zone->tz = tz;
  321. thermal_zone_device_enable(tz);
  322. return 0;
  323. }
  324. int tt_zone_reg(const char *arg)
  325. {
  326. struct tt_thermal_zone *tt_zone __free(put_tt_zone) = tt_get_tt_zone(arg);
  327. if (IS_ERR(tt_zone))
  328. return PTR_ERR(tt_zone);
  329. return tt_zone_register_tz(tt_zone);
  330. }
  331. int tt_zone_unreg(const char *arg)
  332. {
  333. struct tt_thermal_zone *tt_zone __free(put_tt_zone) = tt_get_tt_zone(arg);
  334. if (IS_ERR(tt_zone))
  335. return PTR_ERR(tt_zone);
  336. tt_zone_unregister_tz(tt_zone);
  337. return 0;
  338. }
  339. void tt_zone_cleanup(void)
  340. {
  341. struct tt_thermal_zone *tt_zone, *aux;
  342. list_for_each_entry_safe(tt_zone, aux, &tt_thermal_zones, list_node) {
  343. tt_zone_unregister_tz(tt_zone);
  344. list_del(&tt_zone->list_node);
  345. tt_zone_free(tt_zone);
  346. }
  347. }