thermal_thresholds.c 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. // SPDX-License-Identifier: GPL-2.0
  2. /*
  3. * Copyright 2024 Linaro Limited
  4. *
  5. * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
  6. *
  7. * Thermal thresholds
  8. */
  9. #include <linux/list.h>
  10. #include <linux/list_sort.h>
  11. #include <linux/slab.h>
  12. #include "thermal_core.h"
  13. #include "thermal_thresholds.h"
  14. int thermal_thresholds_init(struct thermal_zone_device *tz)
  15. {
  16. INIT_LIST_HEAD(&tz->user_thresholds);
  17. return 0;
  18. }
  19. static void __thermal_thresholds_flush(struct thermal_zone_device *tz)
  20. {
  21. struct list_head *thresholds = &tz->user_thresholds;
  22. struct user_threshold *entry, *tmp;
  23. list_for_each_entry_safe(entry, tmp, thresholds, list_node) {
  24. list_del(&entry->list_node);
  25. kfree(entry);
  26. }
  27. }
  28. void thermal_thresholds_flush(struct thermal_zone_device *tz)
  29. {
  30. lockdep_assert_held(&tz->lock);
  31. __thermal_thresholds_flush(tz);
  32. thermal_notify_threshold_flush(tz);
  33. __thermal_zone_device_update(tz, THERMAL_TZ_FLUSH_THRESHOLDS);
  34. }
  35. void thermal_thresholds_exit(struct thermal_zone_device *tz)
  36. {
  37. __thermal_thresholds_flush(tz);
  38. }
  39. static int __thermal_thresholds_cmp(void *data,
  40. const struct list_head *l1,
  41. const struct list_head *l2)
  42. {
  43. struct user_threshold *t1 = container_of(l1, struct user_threshold, list_node);
  44. struct user_threshold *t2 = container_of(l2, struct user_threshold, list_node);
  45. return t1->temperature - t2->temperature;
  46. }
  47. static struct user_threshold *__thermal_thresholds_find(const struct list_head *thresholds,
  48. int temperature)
  49. {
  50. struct user_threshold *t;
  51. list_for_each_entry(t, thresholds, list_node)
  52. if (t->temperature == temperature)
  53. return t;
  54. return NULL;
  55. }
  56. static bool thermal_thresholds_handle_raising(struct list_head *thresholds, int temperature,
  57. int last_temperature)
  58. {
  59. struct user_threshold *t;
  60. list_for_each_entry(t, thresholds, list_node) {
  61. if (!(t->direction & THERMAL_THRESHOLD_WAY_UP))
  62. continue;
  63. if (temperature >= t->temperature &&
  64. last_temperature < t->temperature)
  65. return true;
  66. }
  67. return false;
  68. }
  69. static bool thermal_thresholds_handle_dropping(struct list_head *thresholds, int temperature,
  70. int last_temperature)
  71. {
  72. struct user_threshold *t;
  73. list_for_each_entry_reverse(t, thresholds, list_node) {
  74. if (!(t->direction & THERMAL_THRESHOLD_WAY_DOWN))
  75. continue;
  76. if (temperature <= t->temperature &&
  77. last_temperature > t->temperature)
  78. return true;
  79. }
  80. return false;
  81. }
  82. static void thermal_threshold_find_boundaries(struct list_head *thresholds, int temperature,
  83. int *low, int *high)
  84. {
  85. struct user_threshold *t;
  86. list_for_each_entry(t, thresholds, list_node) {
  87. if (temperature < t->temperature &&
  88. (t->direction & THERMAL_THRESHOLD_WAY_UP) &&
  89. *high > t->temperature)
  90. *high = t->temperature;
  91. }
  92. list_for_each_entry_reverse(t, thresholds, list_node) {
  93. if (temperature > t->temperature &&
  94. (t->direction & THERMAL_THRESHOLD_WAY_DOWN) &&
  95. *low < t->temperature)
  96. *low = t->temperature;
  97. }
  98. }
  99. void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high)
  100. {
  101. struct list_head *thresholds = &tz->user_thresholds;
  102. int temperature = tz->temperature;
  103. int last_temperature = tz->last_temperature;
  104. lockdep_assert_held(&tz->lock);
  105. thermal_threshold_find_boundaries(thresholds, temperature, low, high);
  106. /*
  107. * We need a second update in order to detect a threshold being crossed
  108. */
  109. if (last_temperature == THERMAL_TEMP_INVALID)
  110. return;
  111. /*
  112. * The temperature is stable, so obviously we can not have
  113. * crossed a threshold.
  114. */
  115. if (last_temperature == temperature)
  116. return;
  117. /*
  118. * Since last update the temperature:
  119. * - increased : thresholds are crossed the way up
  120. * - decreased : thresholds are crossed the way down
  121. */
  122. if (temperature > last_temperature) {
  123. if (thermal_thresholds_handle_raising(thresholds,
  124. temperature, last_temperature))
  125. thermal_notify_threshold_up(tz);
  126. } else {
  127. if (thermal_thresholds_handle_dropping(thresholds,
  128. temperature, last_temperature))
  129. thermal_notify_threshold_down(tz);
  130. }
  131. }
  132. int thermal_thresholds_add(struct thermal_zone_device *tz,
  133. int temperature, int direction)
  134. {
  135. struct list_head *thresholds = &tz->user_thresholds;
  136. struct user_threshold *t;
  137. lockdep_assert_held(&tz->lock);
  138. t = __thermal_thresholds_find(thresholds, temperature);
  139. if (t) {
  140. if (t->direction == direction)
  141. return -EEXIST;
  142. t->direction |= direction;
  143. } else {
  144. t = kmalloc_obj(*t);
  145. if (!t)
  146. return -ENOMEM;
  147. INIT_LIST_HEAD(&t->list_node);
  148. t->temperature = temperature;
  149. t->direction = direction;
  150. list_add(&t->list_node, thresholds);
  151. list_sort(NULL, thresholds, __thermal_thresholds_cmp);
  152. }
  153. thermal_notify_threshold_add(tz, temperature, direction);
  154. __thermal_zone_device_update(tz, THERMAL_TZ_ADD_THRESHOLD);
  155. return 0;
  156. }
  157. int thermal_thresholds_delete(struct thermal_zone_device *tz,
  158. int temperature, int direction)
  159. {
  160. struct list_head *thresholds = &tz->user_thresholds;
  161. struct user_threshold *t;
  162. lockdep_assert_held(&tz->lock);
  163. t = __thermal_thresholds_find(thresholds, temperature);
  164. if (!t)
  165. return -ENOENT;
  166. if (t->direction == direction) {
  167. list_del(&t->list_node);
  168. kfree(t);
  169. } else {
  170. t->direction &= ~direction;
  171. }
  172. thermal_notify_threshold_delete(tz, temperature, direction);
  173. __thermal_zone_device_update(tz, THERMAL_TZ_DEL_THRESHOLD);
  174. return 0;
  175. }
  176. int thermal_thresholds_for_each(struct thermal_zone_device *tz,
  177. int (*cb)(struct user_threshold *, void *arg), void *arg)
  178. {
  179. struct list_head *thresholds = &tz->user_thresholds;
  180. struct user_threshold *entry;
  181. int ret;
  182. guard(thermal_zone)(tz);
  183. list_for_each_entry(entry, thresholds, list_node) {
  184. ret = cb(entry, arg);
  185. if (ret)
  186. return ret;
  187. }
  188. return 0;
  189. }