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 34LIST_HEAD(, thermal_sensor) thermal_sensors = 35 LIST_HEAD_INITIALIZER(thermal_sensors); 36 37LIST_HEAD(, cooling_device) cooling_devices = 38 LIST_HEAD_INITIALIZER(cooling_devices); 39 40struct taskq *tztq; 41 42struct 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 56static 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 64struct cmap { 65 uint32_t *cm_cdev; 66 uint32_t *cm_cdevend; 67 uint32_t cm_trip; 68}; 69 70struct cdev { 71 uint32_t cd_phandle; 72 int32_t cd_level; 73 int cd_active; 74 LIST_ENTRY(cdev) cd_list; 75}; 76 77struct 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 104static void thermal_zone_kstat_attach(struct thermal_zone *); 105static void thermal_zone_kstat_update(struct thermal_zone *); 106#endif /* NKSTAT > 0 */ 107 108LIST_HEAD(, thermal_zone) thermal_zones = 109 LIST_HEAD_INITIALIZER(thermal_zones); 110 111void 112thermal_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 122void 123thermal_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 135void 136cooling_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 146int32_t 147thermal_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 163int 164thermal_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 180void 181thermal_zone_poll_timeout(void *arg) 182{ 183 struct thermal_zone *tz = arg; 184 185 task_add(tztq, &tz->tz_poll_task); 186} 187 188uint32_t * 189cdev_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 202uint32_t 203cdev_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 219void 220cdev_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 235void 236cmap_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 256void 257cmap_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 286void 287cmap_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 299void 300thermal_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 363out: 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 379static int 380thermal_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 396void 397thermal_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 531void 532thermal_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 547static const char * 548thermal_zone_tripname(int type) 549{ 550 if (type >= nitems(trip_types)) 551 return (NULL); 552 553 return (trip_types[type]); 554} 555 556struct 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 564static void 565thermal_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 610static void 611thermal_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