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