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_threshold_is_crossed(struct user_threshold *threshold, int temperature, 73 int last_temperature, int direction, 74 int *low, int *high) 75 { 76 77 if (temperature >= threshold->temperature) { 78 if (threshold->temperature > *low && 79 THERMAL_THRESHOLD_WAY_DOWN & threshold->direction) 80 *low = threshold->temperature; 81 82 if (last_temperature < threshold->temperature && 83 threshold->direction & direction) 84 return true; 85 } else { 86 if (threshold->temperature < *high && THERMAL_THRESHOLD_WAY_UP 87 & threshold->direction) 88 *high = threshold->temperature; 89 90 if (last_temperature >= threshold->temperature && 91 threshold->direction & direction) 92 return true; 93 } 94 95 return false; 96 } 97 98 static bool thermal_thresholds_handle_raising(struct list_head *thresholds, int temperature, 99 int last_temperature, int *low, int *high) 100 { 101 struct user_threshold *t; 102 103 list_for_each_entry(t, thresholds, list_node) { 104 if (__thermal_threshold_is_crossed(t, temperature, last_temperature, 105 THERMAL_THRESHOLD_WAY_UP, low, high)) 106 return true; 107 } 108 109 return false; 110 } 111 112 static bool thermal_thresholds_handle_dropping(struct list_head *thresholds, int temperature, 113 int last_temperature, int *low, int *high) 114 { 115 struct user_threshold *t; 116 117 list_for_each_entry_reverse(t, thresholds, list_node) { 118 if (__thermal_threshold_is_crossed(t, temperature, last_temperature, 119 THERMAL_THRESHOLD_WAY_DOWN, low, high)) 120 return true; 121 } 122 123 return false; 124 } 125 126 void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high) 127 { 128 struct list_head *thresholds = &tz->user_thresholds; 129 130 int temperature = tz->temperature; 131 int last_temperature = tz->last_temperature; 132 133 lockdep_assert_held(&tz->lock); 134 135 /* 136 * We need a second update in order to detect a threshold being crossed 137 */ 138 if (last_temperature == THERMAL_TEMP_INVALID) 139 return; 140 141 /* 142 * The temperature is stable, so obviously we can not have 143 * crossed a threshold. 144 */ 145 if (last_temperature == temperature) 146 return; 147 148 /* 149 * Since last update the temperature: 150 * - increased : thresholds are crossed the way up 151 * - decreased : thresholds are crossed the way down 152 */ 153 if (temperature > last_temperature) { 154 if (thermal_thresholds_handle_raising(thresholds, temperature, 155 last_temperature, low, high)) 156 thermal_notify_threshold_up(tz); 157 } else { 158 if (thermal_thresholds_handle_dropping(thresholds, temperature, 159 last_temperature, low, high)) 160 thermal_notify_threshold_down(tz); 161 } 162 } 163 164 int thermal_thresholds_add(struct thermal_zone_device *tz, 165 int temperature, int direction) 166 { 167 struct list_head *thresholds = &tz->user_thresholds; 168 struct user_threshold *t; 169 170 lockdep_assert_held(&tz->lock); 171 172 t = __thermal_thresholds_find(thresholds, temperature); 173 if (t) { 174 if (t->direction == direction) 175 return -EEXIST; 176 177 t->direction |= direction; 178 } else { 179 180 t = kmalloc(sizeof(*t), GFP_KERNEL); 181 if (!t) 182 return -ENOMEM; 183 184 INIT_LIST_HEAD(&t->list_node); 185 t->temperature = temperature; 186 t->direction = direction; 187 list_add(&t->list_node, thresholds); 188 list_sort(NULL, thresholds, __thermal_thresholds_cmp); 189 } 190 191 thermal_notify_threshold_add(tz, temperature, direction); 192 193 __thermal_zone_device_update(tz, THERMAL_TZ_ADD_THRESHOLD); 194 195 return 0; 196 } 197 198 int thermal_thresholds_delete(struct thermal_zone_device *tz, 199 int temperature, int direction) 200 { 201 struct list_head *thresholds = &tz->user_thresholds; 202 struct user_threshold *t; 203 204 lockdep_assert_held(&tz->lock); 205 206 t = __thermal_thresholds_find(thresholds, temperature); 207 if (!t) 208 return -ENOENT; 209 210 if (t->direction == direction) { 211 list_del(&t->list_node); 212 kfree(t); 213 } else { 214 t->direction &= ~direction; 215 } 216 217 thermal_notify_threshold_delete(tz, temperature, direction); 218 219 __thermal_zone_device_update(tz, THERMAL_TZ_DEL_THRESHOLD); 220 221 return 0; 222 } 223 224 int thermal_thresholds_for_each(struct thermal_zone_device *tz, 225 int (*cb)(struct user_threshold *, void *arg), void *arg) 226 { 227 struct list_head *thresholds = &tz->user_thresholds; 228 struct user_threshold *entry; 229 int ret; 230 231 guard(thermal_zone)(tz); 232 233 list_for_each_entry(entry, thresholds, list_node) { 234 ret = cb(entry, arg); 235 if (ret) 236 return ret; 237 } 238 239 return 0; 240 } 241