| 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(list: &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: &entry->list_node); | 
|---|
| 30 | kfree(objp: 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, event: 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(list: &t->list_node); | 
|---|
| 189 | t->temperature = temperature; | 
|---|
| 190 | t->direction = direction; | 
|---|
| 191 | list_add(new: &t->list_node, head: thresholds); | 
|---|
| 192 | list_sort(NULL, head: thresholds, cmp: __thermal_thresholds_cmp); | 
|---|
| 193 | } | 
|---|
| 194 |  | 
|---|
| 195 | thermal_notify_threshold_add(tz, temperature, direction); | 
|---|
| 196 |  | 
|---|
| 197 | __thermal_zone_device_update(tz, event: 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(entry: &t->list_node); | 
|---|
| 216 | kfree(objp: t); | 
|---|
| 217 | } else { | 
|---|
| 218 | t->direction &= ~direction; | 
|---|
| 219 | } | 
|---|
| 220 |  | 
|---|
| 221 | thermal_notify_threshold_delete(tz, temperature, direction); | 
|---|
| 222 |  | 
|---|
| 223 | __thermal_zone_device_update(tz, event: 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)(T: 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 |  | 
|---|