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 13 #include "thermal_core.h" 14 #include "thermal_thresholds.h" 15 16 int thermal_thresholds_init(struct thermal_zone_device *tz) 17 { 18 INIT_LIST_HEAD(&tz->user_thresholds); 19 20 return 0; 21 } 22 23 static void __thermal_thresholds_flush(struct thermal_zone_device *tz) 24 { 25 struct list_head *thresholds = &tz->user_thresholds; 26 struct user_threshold *entry, *tmp; 27 28 list_for_each_entry_safe(entry, tmp, thresholds, list_node) { 29 list_del(&entry->list_node); 30 kfree(entry); 31 } 32 } 33 34 void thermal_thresholds_flush(struct thermal_zone_device *tz) 35 { 36 lockdep_assert_held(&tz->lock); 37 38 __thermal_thresholds_flush(tz); 39 40 thermal_notify_threshold_flush(tz); 41 42 __thermal_zone_device_update(tz, THERMAL_TZ_FLUSH_THRESHOLDS); 43 } 44 45 void thermal_thresholds_exit(struct thermal_zone_device *tz) 46 { 47 __thermal_thresholds_flush(tz); 48 } 49 50 static int __thermal_thresholds_cmp(void *data, 51 const struct list_head *l1, 52 const struct list_head *l2) 53 { 54 struct user_threshold *t1 = container_of(l1, struct user_threshold, list_node); 55 struct user_threshold *t2 = container_of(l2, struct user_threshold, list_node); 56 57 return t1->temperature - t2->temperature; 58 } 59 60 static struct user_threshold *__thermal_thresholds_find(const struct list_head *thresholds, 61 int temperature) 62 { 63 struct user_threshold *t; 64 65 list_for_each_entry(t, thresholds, list_node) 66 if (t->temperature == temperature) 67 return t; 68 69 return NULL; 70 } 71 72 static bool thermal_thresholds_handle_raising(struct list_head *thresholds, int temperature, 73 int last_temperature) 74 { 75 struct user_threshold *t; 76 77 list_for_each_entry(t, thresholds, list_node) { 78 79 if (!(t->direction & THERMAL_THRESHOLD_WAY_UP)) 80 continue; 81 82 if (temperature >= t->temperature && 83 last_temperature < t->temperature) 84 return true; 85 } 86 87 return false; 88 } 89 90 static bool thermal_thresholds_handle_dropping(struct list_head *thresholds, int temperature, 91 int last_temperature) 92 { 93 struct user_threshold *t; 94 95 list_for_each_entry_reverse(t, thresholds, list_node) { 96 97 if (!(t->direction & THERMAL_THRESHOLD_WAY_DOWN)) 98 continue; 99 100 if (temperature <= t->temperature && 101 last_temperature > t->temperature) 102 return true; 103 } 104 105 return false; 106 } 107 108 static void thermal_threshold_find_boundaries(struct list_head *thresholds, int temperature, 109 int *low, int *high) 110 { 111 struct user_threshold *t; 112 113 list_for_each_entry(t, thresholds, list_node) { 114 if (temperature < t->temperature && 115 (t->direction & THERMAL_THRESHOLD_WAY_UP) && 116 *high > t->temperature) 117 *high = t->temperature; 118 } 119 120 list_for_each_entry_reverse(t, thresholds, list_node) { 121 if (temperature > t->temperature && 122 (t->direction & THERMAL_THRESHOLD_WAY_DOWN) && 123 *low < t->temperature) 124 *low = t->temperature; 125 } 126 } 127 128 void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high) 129 { 130 struct list_head *thresholds = &tz->user_thresholds; 131 132 int temperature = tz->temperature; 133 int last_temperature = tz->last_temperature; 134 135 lockdep_assert_held(&tz->lock); 136 137 thermal_threshold_find_boundaries(thresholds, temperature, low, high); 138 139 /* 140 * We need a second update in order to detect a threshold being crossed 141 */ 142 if (last_temperature == THERMAL_TEMP_INVALID) 143 return; 144 145 /* 146 * The temperature is stable, so obviously we can not have 147 * crossed a threshold. 148 */ 149 if (last_temperature == temperature) 150 return; 151 152 /* 153 * Since last update the temperature: 154 * - increased : thresholds are crossed the way up 155 * - decreased : thresholds are crossed the way down 156 */ 157 if (temperature > last_temperature) { 158 if (thermal_thresholds_handle_raising(thresholds, 159 temperature, last_temperature)) 160 thermal_notify_threshold_up(tz); 161 } else { 162 if (thermal_thresholds_handle_dropping(thresholds, 163 temperature, last_temperature)) 164 thermal_notify_threshold_down(tz); 165 } 166 } 167 168 int thermal_thresholds_add(struct thermal_zone_device *tz, 169 int temperature, int direction) 170 { 171 struct list_head *thresholds = &tz->user_thresholds; 172 struct user_threshold *t; 173 174 lockdep_assert_held(&tz->lock); 175 176 t = __thermal_thresholds_find(thresholds, temperature); 177 if (t) { 178 if (t->direction == direction) 179 return -EEXIST; 180 181 t->direction |= direction; 182 } else { 183 184 t = kmalloc(sizeof(*t), GFP_KERNEL); 185 if (!t) 186 return -ENOMEM; 187 188 INIT_LIST_HEAD(&t->list_node); 189 t->temperature = temperature; 190 t->direction = direction; 191 list_add(&t->list_node, thresholds); 192 list_sort(NULL, thresholds, __thermal_thresholds_cmp); 193 } 194 195 thermal_notify_threshold_add(tz, temperature, direction); 196 197 __thermal_zone_device_update(tz, THERMAL_TZ_ADD_THRESHOLD); 198 199 return 0; 200 } 201 202 int thermal_thresholds_delete(struct thermal_zone_device *tz, 203 int temperature, int direction) 204 { 205 struct list_head *thresholds = &tz->user_thresholds; 206 struct user_threshold *t; 207 208 lockdep_assert_held(&tz->lock); 209 210 t = __thermal_thresholds_find(thresholds, temperature); 211 if (!t) 212 return -ENOENT; 213 214 if (t->direction == direction) { 215 list_del(&t->list_node); 216 kfree(t); 217 } else { 218 t->direction &= ~direction; 219 } 220 221 thermal_notify_threshold_delete(tz, temperature, direction); 222 223 __thermal_zone_device_update(tz, THERMAL_TZ_DEL_THRESHOLD); 224 225 return 0; 226 } 227 228 int thermal_thresholds_for_each(struct thermal_zone_device *tz, 229 int (*cb)(struct user_threshold *, void *arg), void *arg) 230 { 231 struct list_head *thresholds = &tz->user_thresholds; 232 struct user_threshold *entry; 233 int ret; 234 235 guard(thermal_zone)(tz); 236 237 list_for_each_entry(entry, thresholds, list_node) { 238 ret = cb(entry, arg); 239 if (ret) 240 return ret; 241 } 242 243 return 0; 244 } 245