1 /* $OpenBSD: ofw_thermal.c,v 1.10 2024/07/01 14:13:43 kettenis Exp $ */
2 /*
3 * Copyright (c) 2019 Mark Kettenis
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include "kstat.h"
19
20 #include <sys/types.h>
21 #include <sys/systm.h>
22 #include <sys/malloc.h>
23 #include <sys/stdint.h>
24 #include <sys/task.h>
25 #include <sys/timeout.h>
26 #include <sys/sched.h>
27 #include <sys/kstat.h>
28
29 #include <machine/bus.h>
30
31 #include <dev/ofw/openfirm.h>
32 #include <dev/ofw/ofw_thermal.h>
33
34 LIST_HEAD(, thermal_sensor) thermal_sensors =
35 LIST_HEAD_INITIALIZER(thermal_sensors);
36
37 LIST_HEAD(, cooling_device) cooling_devices =
38 LIST_HEAD_INITIALIZER(cooling_devices);
39
40 struct taskq *tztq;
41
42 struct trippoint {
43 int tp_node;
44 int32_t tp_temperature;
45 uint32_t tp_hysteresis;
46 int tp_type;
47 uint32_t tp_phandle;
48 };
49
50 #define THERMAL_NONE 0
51 #define THERMAL_ACTIVE 1
52 #define THERMAL_PASSIVE 2
53 #define THERMAL_HOT 3
54 #define THERMAL_CRITICAL 4
55
56 static const char *trip_types[] = {
57 [THERMAL_NONE] = "none",
58 [THERMAL_ACTIVE] = "active",
59 [THERMAL_PASSIVE] = "passive",
60 [THERMAL_HOT] = "hot",
61 [THERMAL_CRITICAL] = "critical",
62 };
63
64 struct cmap {
65 uint32_t *cm_cdev;
66 uint32_t *cm_cdevend;
67 uint32_t cm_trip;
68 };
69
70 struct cdev {
71 uint32_t cd_phandle;
72 int32_t cd_level;
73 int cd_active;
74 LIST_ENTRY(cdev) cd_list;
75 };
76
77 struct thermal_zone {
78 int tz_node;
79 char tz_name[64];
80 struct task tz_poll_task;
81 struct timeout tz_poll_to;
82 uint32_t *tz_sensors;
83 uint32_t tz_polling_delay;
84 uint32_t tz_polling_delay_passive;
85 LIST_ENTRY(thermal_zone) tz_list;
86
87 struct trippoint *tz_trips;
88 int tz_ntrips;
89 struct trippoint *tz_tp;
90
91 struct cmap *tz_cmaps;
92 int tz_ncmaps;
93 struct cmap *tz_cm;
94
95 LIST_HEAD(, cdev) tz_cdevs;
96
97 int32_t tz_temperature;
98
99 struct rwlock tz_lock;
100 struct kstat *tz_kstat;
101 };
102
103 #if NKSTAT > 0
104 static void thermal_zone_kstat_attach(struct thermal_zone *);
105 static void thermal_zone_kstat_update(struct thermal_zone *);
106 #endif /* NKSTAT > 0 */
107
108 LIST_HEAD(, thermal_zone) thermal_zones =
109 LIST_HEAD_INITIALIZER(thermal_zones);
110
111 void
thermal_sensor_register(struct thermal_sensor * ts)112 thermal_sensor_register(struct thermal_sensor *ts)
113 {
114 ts->ts_cells = OF_getpropint(ts->ts_node, "#thermal-sensor-cells", 0);
115 ts->ts_phandle = OF_getpropint(ts->ts_node, "phandle", 0);
116 if (ts->ts_phandle == 0)
117 return;
118
119 LIST_INSERT_HEAD(&thermal_sensors, ts, ts_list);
120 }
121
122 void
thermal_sensor_update(struct thermal_sensor * ts,uint32_t * cells)123 thermal_sensor_update(struct thermal_sensor *ts, uint32_t *cells)
124 {
125 struct thermal_zone *tz;
126
127 LIST_FOREACH(tz, &thermal_zones, tz_list) {
128 if (tz->tz_sensors[0] == ts->ts_phandle &&
129 memcmp(&tz->tz_sensors[1], cells,
130 ts->ts_cells * sizeof(uint32_t)) == 0)
131 task_add(tztq, &tz->tz_poll_task);
132 }
133 }
134
135 void
cooling_device_register(struct cooling_device * cd)136 cooling_device_register(struct cooling_device *cd)
137 {
138 cd->cd_cells = OF_getpropint(cd->cd_node, "#cooling-cells", 0);
139 cd->cd_phandle = OF_getpropint(cd->cd_node, "phandle", 0);
140 if (cd->cd_phandle == 0)
141 return;
142
143 LIST_INSERT_HEAD(&cooling_devices, cd, cd_list);
144 }
145
146 int32_t
thermal_get_temperature_cells(uint32_t * cells)147 thermal_get_temperature_cells(uint32_t *cells)
148 {
149 struct thermal_sensor *ts;
150 uint32_t phandle = cells[0];
151
152 LIST_FOREACH(ts, &thermal_sensors, ts_list) {
153 if (ts->ts_phandle == phandle)
154 break;
155 }
156
157 if (ts && ts->ts_get_temperature)
158 return ts->ts_get_temperature(ts->ts_cookie, &cells[1]);
159
160 return THERMAL_SENSOR_MAX;
161 }
162
163 int
thermal_set_limit_cells(uint32_t * cells,uint32_t temp)164 thermal_set_limit_cells(uint32_t *cells, uint32_t temp)
165 {
166 struct thermal_sensor *ts;
167 uint32_t phandle = cells[0];
168
169 LIST_FOREACH(ts, &thermal_sensors, ts_list) {
170 if (ts->ts_phandle == phandle)
171 break;
172 }
173
174 if (ts && ts->ts_set_limit)
175 return ts->ts_set_limit(ts->ts_cookie, &cells[1], temp);
176
177 return ENXIO;
178 }
179
180 void
thermal_zone_poll_timeout(void * arg)181 thermal_zone_poll_timeout(void *arg)
182 {
183 struct thermal_zone *tz = arg;
184
185 task_add(tztq, &tz->tz_poll_task);
186 }
187
188 uint32_t *
cdev_next_cdev(uint32_t * cells)189 cdev_next_cdev(uint32_t *cells)
190 {
191 uint32_t phandle = cells[0];
192 int node, ncells;
193
194 node = OF_getnodebyphandle(phandle);
195 if (node == 0)
196 return NULL;
197
198 ncells = OF_getpropint(node, "#cooling-cells", 2);
199 return cells + ncells + 1;
200 }
201
202 uint32_t
cdev_get_level(uint32_t * cells)203 cdev_get_level(uint32_t *cells)
204 {
205 struct cooling_device *cd;
206 uint32_t phandle = cells[0];
207
208 LIST_FOREACH(cd, &cooling_devices, cd_list) {
209 if (cd->cd_phandle == phandle)
210 break;
211 }
212
213 if (cd && cd->cd_get_level)
214 return cd->cd_get_level(cd->cd_cookie, &cells[1]);
215
216 return 0;
217 }
218
219 void
cdev_set_level(uint32_t * cells,uint32_t level)220 cdev_set_level(uint32_t *cells, uint32_t level)
221 {
222 struct cooling_device *cd;
223 uint32_t phandle = cells[0];
224
225 LIST_FOREACH(cd, &cooling_devices, cd_list) {
226 if (cd->cd_phandle == phandle)
227 break;
228 }
229
230 if (cd && cd->cd_set_level)
231 cd->cd_set_level(cd->cd_cookie, &cells[1], level);
232 }
233
234
235 void
cmap_deactivate(struct thermal_zone * tz,struct cmap * cm)236 cmap_deactivate(struct thermal_zone *tz, struct cmap *cm)
237 {
238 struct cdev *cd;
239 uint32_t *cdev;
240
241 if (cm == NULL)
242 return;
243
244 cdev = cm->cm_cdev;
245 while (cdev && cdev < cm->cm_cdevend) {
246 LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) {
247 if (cd->cd_phandle == cdev[0])
248 break;
249 }
250 KASSERT(cd != NULL);
251 cd->cd_active = 0;
252 cdev = cdev_next_cdev(cdev);
253 }
254 }
255
256 void
cmap_activate(struct thermal_zone * tz,struct cmap * cm,int32_t delta)257 cmap_activate(struct thermal_zone *tz, struct cmap *cm, int32_t delta)
258 {
259 struct cdev *cd;
260 uint32_t *cdev;
261 int32_t min, max;
262
263 if (cm == NULL)
264 return;
265
266 cdev = cm->cm_cdev;
267 while (cdev && cdev < cm->cm_cdevend) {
268 LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) {
269 if (cd->cd_phandle == cdev[0])
270 break;
271 }
272 KASSERT(cd != NULL);
273
274 min = (cdev[1] == THERMAL_NO_LIMIT) ? 0 : cdev[1];
275 max = (cdev[2] == THERMAL_NO_LIMIT) ? INT32_MAX : cdev[2];
276
277 cd->cd_active = 1;
278 cd->cd_level = cdev_get_level(cdev) + delta;
279 cd->cd_level = MAX(cd->cd_level, min);
280 cd->cd_level = MIN(cd->cd_level, max);
281 cdev_set_level(cdev, cd->cd_level);
282 cdev = cdev_next_cdev(cdev);
283 }
284 }
285
286 void
cmap_finish(struct thermal_zone * tz)287 cmap_finish(struct thermal_zone *tz)
288 {
289 struct cdev *cd;
290
291 LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) {
292 if (cd->cd_active == 0 && cd->cd_level != 0) {
293 cdev_set_level(&cd->cd_phandle, 0);
294 cd->cd_level = 0;
295 }
296 }
297 }
298
299 void
thermal_zone_poll(void * arg)300 thermal_zone_poll(void *arg)
301 {
302 struct thermal_zone *tz = arg;
303 struct trippoint *tp, *newtp;
304 struct cmap *cm, *newcm;
305 uint32_t polling_delay;
306 int32_t temp, delta;
307 int i;
308
309 tp = tz->tz_trips;
310 temp = thermal_get_temperature_cells(tz->tz_sensors);
311 if (temp == THERMAL_SENSOR_MAX)
312 goto out;
313
314 newtp = NULL;
315 for (i = 0; i < tz->tz_ntrips; i++) {
316 if (temp < tp->tp_temperature && tp != tz->tz_tp)
317 break;
318 if (temp < tp->tp_temperature - tp->tp_hysteresis)
319 break;
320 newtp = tp++;
321 }
322
323 /* Short circuit if we didn't hit a trip point. */
324 if (newtp == NULL && tz->tz_tp == NULL)
325 goto out;
326
327 /*
328 * If the current temperature is above the trip temperature:
329 * - increase the cooling level if the temperature is rising
330 * - do nothing if the temperature is falling
331 * If the current temperature is below the trip temperature:
332 * - do nothing if the temperature is rising
333 * - decrease the cooling level if the temperature is falling
334 */
335 delta = 0;
336 if (newtp && tz->tz_temperature != THERMAL_SENSOR_MAX) {
337 if (temp >= newtp->tp_temperature) {
338 if (temp > tz->tz_temperature)
339 delta = 1;
340 } else {
341 if (temp < tz->tz_temperature)
342 delta = -1;
343 }
344 }
345
346 newcm = NULL;
347 cm = tz->tz_cmaps;
348 for (i = 0; i < tz->tz_ncmaps; i++) {
349 if (newtp && cm->cm_trip == newtp->tp_phandle) {
350 newcm = cm;
351 break;
352 }
353 cm++;
354 }
355
356 cmap_deactivate(tz, tz->tz_cm);
357 cmap_activate(tz, newcm, delta);
358 cmap_finish(tz);
359
360 tz->tz_tp = newtp;
361 tz->tz_cm = newcm;
362
363 out:
364 tz->tz_temperature = temp;
365 #if NKSTAT > 0
366 thermal_zone_kstat_update(tz);
367 #endif
368 if (tz->tz_tp && tz->tz_tp->tp_type == THERMAL_PASSIVE)
369 polling_delay = tz->tz_polling_delay_passive;
370 else
371 polling_delay = tz->tz_polling_delay;
372
373 if (polling_delay > 0)
374 timeout_add_msec(&tz->tz_poll_to, polling_delay);
375 else if (tp)
376 thermal_set_limit_cells(tz->tz_sensors, tp->tp_temperature);
377 }
378
379 static int
thermal_zone_triptype(const char * prop)380 thermal_zone_triptype(const char *prop)
381 {
382 size_t i;
383
384 for (i = 0; i < nitems(trip_types); i++) {
385 const char *name = trip_types[i];
386 if (name == NULL)
387 continue;
388
389 if (strcmp(name, prop) == 0)
390 return (i);
391 }
392
393 return (THERMAL_NONE);
394 }
395
396 void
thermal_zone_init(int node)397 thermal_zone_init(int node)
398 {
399 struct thermal_zone *tz;
400 struct trippoint *tp;
401 struct cmap *cm;
402 struct cdev *cd;
403 int len, i;
404
405 len = OF_getproplen(node, "thermal-sensors");
406 if (len <= 0)
407 return;
408
409 if (OF_getnodebyname(node, "trips") == 0)
410 return;
411 if (OF_getnodebyname(node, "cooling-maps") == 0)
412 return;
413
414 tz = malloc(sizeof(struct thermal_zone), M_DEVBUF, M_ZERO | M_WAITOK);
415 tz->tz_node = node;
416 rw_init(&tz->tz_lock, "tzlk");
417
418 OF_getprop(node, "name", &tz->tz_name, sizeof(tz->tz_name));
419 tz->tz_name[sizeof(tz->tz_name) - 1] = 0;
420 tz->tz_sensors = malloc(len, M_DEVBUF, M_WAITOK);
421 OF_getpropintarray(node, "thermal-sensors", tz->tz_sensors, len);
422 tz->tz_polling_delay = OF_getpropint(node, "polling-delay", 0);
423 tz->tz_polling_delay_passive =
424 OF_getpropint(node, "polling-delay-passive", tz->tz_polling_delay);
425
426 task_set(&tz->tz_poll_task, thermal_zone_poll, tz);
427 timeout_set(&tz->tz_poll_to, thermal_zone_poll_timeout, tz);
428
429 /*
430 * Trip points for this thermal zone.
431 */
432 node = OF_getnodebyname(tz->tz_node, "trips");
433 for (node = OF_child(node); node != 0; node = OF_peer(node))
434 tz->tz_ntrips++;
435
436 tz->tz_trips = mallocarray(tz->tz_ntrips, sizeof(struct trippoint),
437 M_DEVBUF, M_ZERO | M_WAITOK);
438
439 node = OF_getnodebyname(tz->tz_node, "trips");
440 for (node = OF_child(node); node != 0; node = OF_peer(node)) {
441 char type[32] = "none";
442 int32_t temp;
443
444 temp = OF_getpropint(node, "temperature", THERMAL_SENSOR_MAX);
445
446 /* Sorted insertion, since tree might not be */
447 for (i = 0; i < tz->tz_ntrips; i++) {
448 /* No trip point should be 0 degC, take it */
449 if (tz->tz_trips[i].tp_temperature == 0)
450 break;
451 /* We should be bigger than the one before us */
452 if (tz->tz_trips[i].tp_temperature < temp)
453 continue;
454 /* Free current slot */
455 memmove(&tz->tz_trips[i + 1], &tz->tz_trips[i],
456 (tz->tz_ntrips - (i + 1)) * sizeof(*tp));
457 break;
458 }
459 tp = &tz->tz_trips[i];
460 tp->tp_node = node;
461 tp->tp_temperature = temp;
462 tp->tp_hysteresis = OF_getpropint(node, "hysteresis", 0);
463 OF_getprop(node, "type", type, sizeof(type));
464 tp->tp_type = thermal_zone_triptype(type);
465 tp->tp_phandle = OF_getpropint(node, "phandle", 0);
466 tp++;
467 }
468
469 /*
470 * Cooling maps for this thermal zone.
471 */
472 node = OF_getnodebyname(tz->tz_node, "cooling-maps");
473 for (node = OF_child(node); node != 0; node = OF_peer(node))
474 tz->tz_ncmaps++;
475
476 tz->tz_cmaps = mallocarray(tz->tz_ncmaps, sizeof(struct cmap),
477 M_DEVBUF, M_ZERO | M_WAITOK);
478 cm = tz->tz_cmaps;
479
480 node = OF_getnodebyname(tz->tz_node, "cooling-maps");
481 for (node = OF_child(node); node != 0; node = OF_peer(node)) {
482 len = OF_getproplen(node, "cooling-device");
483 if (len <= 0)
484 continue;
485 cm->cm_cdev = malloc(len, M_DEVBUF, M_ZERO | M_WAITOK);
486 OF_getpropintarray(node, "cooling-device", cm->cm_cdev, len);
487 cm->cm_cdevend = cm->cm_cdev + len / sizeof(uint32_t);
488 cm->cm_trip = OF_getpropint(node, "trip", 0);
489 cm++;
490 }
491
492 /*
493 * Create a list of all the possible cooling devices from the
494 * cooling maps for this thermal zone, and initialize their
495 * state.
496 */
497 LIST_INIT(&tz->tz_cdevs);
498 cm = tz->tz_cmaps;
499 for (i = 0; i < tz->tz_ncmaps; i++) {
500 uint32_t *cdev;
501
502 cdev = cm->cm_cdev;
503 while (cdev && cdev < cm->cm_cdevend) {
504 LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) {
505 if (cd->cd_phandle == cdev[0])
506 break;
507 }
508 if (cd == NULL) {
509 cd = malloc(sizeof(struct cdev), M_DEVBUF,
510 M_ZERO | M_WAITOK);
511 cd->cd_phandle = cdev[0];
512 cd->cd_level = 0;
513 cd->cd_active = 0;
514 LIST_INSERT_HEAD(&tz->tz_cdevs, cd, cd_list);
515 }
516 cdev = cdev_next_cdev(cdev);
517 }
518 cm++;
519 }
520
521 LIST_INSERT_HEAD(&thermal_zones, tz, tz_list);
522
523 #if NKSTAT > 0
524 thermal_zone_kstat_attach(tz);
525 #endif
526
527 /* Poll once to get things going. */
528 thermal_zone_poll(tz);
529 }
530
531 void
thermal_init(void)532 thermal_init(void)
533 {
534 int node = OF_finddevice("/thermal-zones");
535
536 if (node == -1)
537 return;
538
539 tztq = taskq_create("tztq", 1, IPL_SOFTCLOCK, 0);
540
541 for (node = OF_child(node); node != 0; node = OF_peer(node))
542 thermal_zone_init(node);
543 }
544
545 #if NKSTAT > 0
546
547 static const char *
thermal_zone_tripname(int type)548 thermal_zone_tripname(int type)
549 {
550 if (type >= nitems(trip_types))
551 return (NULL);
552
553 return (trip_types[type]);
554 }
555
556 struct thermal_zone_kstats {
557 struct kstat_kv tzk_name; /* istr could be short */
558 struct kstat_kv tzk_temp;
559 struct kstat_kv tzk_tp;
560 struct kstat_kv tzk_tp_type;
561 struct kstat_kv tzk_cooling;
562 };
563
564 static void
thermal_zone_kstat_update(struct thermal_zone * tz)565 thermal_zone_kstat_update(struct thermal_zone *tz)
566 {
567 struct kstat *ks = tz->tz_kstat;
568 struct thermal_zone_kstats *tzk;
569
570 if (ks == NULL)
571 return;
572
573 tzk = ks->ks_data;
574
575 rw_enter_write(&tz->tz_lock);
576 if (tz->tz_temperature == THERMAL_SENSOR_MAX)
577 tzk->tzk_temp.kv_type = KSTAT_KV_T_NULL;
578 else {
579 tzk->tzk_temp.kv_type = KSTAT_KV_T_TEMP;
580 kstat_kv_temp(&tzk->tzk_temp) = 273150000 +
581 1000 * tz->tz_temperature;
582 }
583
584 if (tz->tz_tp == NULL) {
585 kstat_kv_u32(&tzk->tzk_tp) = 0;
586 strlcpy(kstat_kv_istr(&tzk->tzk_tp_type), "none",
587 sizeof(kstat_kv_istr(&tzk->tzk_tp_type)));
588 } else {
589 int triptype = tz->tz_tp->tp_type;
590 const char *tripname = thermal_zone_tripname(triptype);
591
592 kstat_kv_u32(&tzk->tzk_tp) = tz->tz_tp->tp_node;
593
594 if (tripname == NULL) {
595 snprintf(kstat_kv_istr(&tzk->tzk_tp_type),
596 sizeof(kstat_kv_istr(&tzk->tzk_tp_type)),
597 "%u", triptype);
598 } else {
599 strlcpy(kstat_kv_istr(&tzk->tzk_tp_type), tripname,
600 sizeof(kstat_kv_istr(&tzk->tzk_tp_type)));
601 }
602 }
603
604 kstat_kv_bool(&tzk->tzk_cooling) = (tz->tz_cm != NULL);
605
606 getnanouptime(&ks->ks_updated);
607 rw_exit_write(&tz->tz_lock);
608 }
609
610 static void
thermal_zone_kstat_attach(struct thermal_zone * tz)611 thermal_zone_kstat_attach(struct thermal_zone *tz)
612 {
613 struct kstat *ks;
614 struct thermal_zone_kstats *tzk;
615 static unsigned int unit = 0;
616
617 ks = kstat_create("dt", 0, "thermal-zone", unit++, KSTAT_T_KV, 0);
618 if (ks == NULL) {
619 printf("unable to create thermal-zone kstats for %s",
620 tz->tz_name);
621 return;
622 }
623
624 tzk = malloc(sizeof(*tzk), M_DEVBUF, M_WAITOK|M_ZERO);
625
626 kstat_kv_init(&tzk->tzk_name, "name", KSTAT_KV_T_ISTR);
627 strlcpy(kstat_kv_istr(&tzk->tzk_name), tz->tz_name,
628 sizeof(kstat_kv_istr(&tzk->tzk_name)));
629 kstat_kv_init(&tzk->tzk_temp, "temperature", KSTAT_KV_T_NULL);
630
631 /* XXX dt node is not be the most useful info here. */
632 kstat_kv_init(&tzk->tzk_tp, "trip-point-node", KSTAT_KV_T_UINT32);
633 kstat_kv_init(&tzk->tzk_tp_type, "trip-type", KSTAT_KV_T_ISTR);
634 strlcpy(kstat_kv_istr(&tzk->tzk_tp_type), "unknown",
635 sizeof(kstat_kv_istr(&tzk->tzk_tp_type)));
636
637 kstat_kv_init(&tzk->tzk_cooling, "active-cooling", KSTAT_KV_T_BOOL);
638 kstat_kv_bool(&tzk->tzk_cooling) = 0;
639
640 ks->ks_softc = tz;
641 ks->ks_data = tzk;
642 ks->ks_datalen = sizeof(*tzk);
643 ks->ks_read = kstat_read_nop;
644 kstat_set_rlock(ks, &tz->tz_lock);
645
646 tz->tz_kstat = ks;
647 kstat_install(ks);
648 }
649 #endif /* NKSTAT > 0 */
650