/* $OpenBSD: ofw_thermal.c,v 1.8 2023/11/23 00:47:13 dlg Exp $ */ /* * Copyright (c) 2019 Mark Kettenis * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "kstat.h" #include #include #include #include #include #include #include #include #include #include #include LIST_HEAD(, thermal_sensor) thermal_sensors = LIST_HEAD_INITIALIZER(thermal_sensors); LIST_HEAD(, cooling_device) cooling_devices = LIST_HEAD_INITIALIZER(cooling_devices); struct taskq *tztq; struct trippoint { int tp_node; int32_t tp_temperature; uint32_t tp_hysteresis; int tp_type; uint32_t tp_phandle; }; #define THERMAL_NONE 0 #define THERMAL_ACTIVE 1 #define THERMAL_PASSIVE 2 #define THERMAL_HOT 3 #define THERMAL_CRITICAL 4 static const char *trip_types[] = { [THERMAL_NONE] = "none", [THERMAL_ACTIVE] = "active", [THERMAL_PASSIVE] = "passive", [THERMAL_HOT] = "hot", [THERMAL_CRITICAL] = "critical", }; struct cmap { uint32_t *cm_cdev; uint32_t *cm_cdevend; uint32_t cm_trip; }; struct cdev { uint32_t cd_phandle; int32_t cd_level; int cd_active; LIST_ENTRY(cdev) cd_list; }; struct thermal_zone { int tz_node; char tz_name[64]; struct task tz_poll_task; struct timeout tz_poll_to; uint32_t *tz_sensors; uint32_t tz_polling_delay; uint32_t tz_polling_delay_passive; LIST_ENTRY(thermal_zone) tz_list; struct trippoint *tz_trips; int tz_ntrips; struct trippoint *tz_tp; struct cmap *tz_cmaps; int tz_ncmaps; struct cmap *tz_cm; LIST_HEAD(, cdev) tz_cdevs; int32_t tz_temperature; struct rwlock tz_lock; struct kstat *tz_kstat; }; #if NKSTAT > 0 static void thermal_zone_kstat_attach(struct thermal_zone *); static void thermal_zone_kstat_update(struct thermal_zone *); #endif /* NKSTAT > 0 */ LIST_HEAD(, thermal_zone) thermal_zones = LIST_HEAD_INITIALIZER(thermal_zones); void thermal_sensor_register(struct thermal_sensor *ts) { ts->ts_cells = OF_getpropint(ts->ts_node, "#thermal-sensor-cells", 0); ts->ts_phandle = OF_getpropint(ts->ts_node, "phandle", 0); if (ts->ts_phandle == 0) return; LIST_INSERT_HEAD(&thermal_sensors, ts, ts_list); } void thermal_sensor_update(struct thermal_sensor *ts, uint32_t *cells) { struct thermal_zone *tz; LIST_FOREACH(tz, &thermal_zones, tz_list) { if (tz->tz_sensors[0] == ts->ts_phandle && memcmp(&tz->tz_sensors[1], cells, ts->ts_cells * sizeof(uint32_t)) == 0) task_add(tztq, &tz->tz_poll_task); } } void cooling_device_register(struct cooling_device *cd) { cd->cd_cells = OF_getpropint(cd->cd_node, "#cooling-cells", 0); cd->cd_phandle = OF_getpropint(cd->cd_node, "phandle", 0); if (cd->cd_phandle == 0) return; LIST_INSERT_HEAD(&cooling_devices, cd, cd_list); } int32_t thermal_get_temperature_cells(uint32_t *cells) { struct thermal_sensor *ts; uint32_t phandle = cells[0]; LIST_FOREACH(ts, &thermal_sensors, ts_list) { if (ts->ts_phandle == phandle) break; } if (ts && ts->ts_get_temperature) return ts->ts_get_temperature(ts->ts_cookie, &cells[1]); return THERMAL_SENSOR_MAX; } void thermal_zone_poll_timeout(void *arg) { struct thermal_zone *tz = arg; task_add(tztq, &tz->tz_poll_task); } uint32_t * cdev_next_cdev(uint32_t *cells) { uint32_t phandle = cells[0]; int node, ncells; node = OF_getnodebyphandle(phandle); if (node == 0) return NULL; ncells = OF_getpropint(node, "#cooling-cells", 2); return cells + ncells + 1; } uint32_t cdev_get_level(uint32_t *cells) { struct cooling_device *cd; uint32_t phandle = cells[0]; LIST_FOREACH(cd, &cooling_devices, cd_list) { if (cd->cd_phandle == phandle) break; } if (cd && cd->cd_get_level) return cd->cd_get_level(cd->cd_cookie, &cells[1]); return 0; } void cdev_set_level(uint32_t *cells, uint32_t level) { struct cooling_device *cd; uint32_t phandle = cells[0]; LIST_FOREACH(cd, &cooling_devices, cd_list) { if (cd->cd_phandle == phandle) break; } if (cd && cd->cd_set_level) cd->cd_set_level(cd->cd_cookie, &cells[1], level); } void cmap_deactivate(struct thermal_zone *tz, struct cmap *cm) { struct cdev *cd; uint32_t *cdev; if (cm == NULL) return; cdev = cm->cm_cdev; while (cdev && cdev < cm->cm_cdevend) { LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) { if (cd->cd_phandle == cdev[0]) break; } KASSERT(cd != NULL); cd->cd_active = 0; cdev = cdev_next_cdev(cdev); } } void cmap_activate(struct thermal_zone *tz, struct cmap *cm, int32_t delta) { struct cdev *cd; uint32_t *cdev; int32_t min, max; if (cm == NULL) return; cdev = cm->cm_cdev; while (cdev && cdev < cm->cm_cdevend) { LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) { if (cd->cd_phandle == cdev[0]) break; } KASSERT(cd != NULL); min = (cdev[1] == THERMAL_NO_LIMIT) ? 0 : cdev[1]; max = (cdev[2] == THERMAL_NO_LIMIT) ? INT32_MAX : cdev[2]; cd->cd_active = 1; cd->cd_level = cdev_get_level(cdev) + delta; cd->cd_level = MAX(cd->cd_level, min); cd->cd_level = MIN(cd->cd_level, max); cdev_set_level(cdev, cd->cd_level); cdev = cdev_next_cdev(cdev); } } void cmap_finish(struct thermal_zone *tz) { struct cdev *cd; LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) { if (cd->cd_active == 0 && cd->cd_level != 0) { cdev_set_level(&cd->cd_phandle, 0); cd->cd_level = 0; } } } void thermal_zone_poll(void *arg) { struct thermal_zone *tz = arg; struct trippoint *tp, *newtp; struct cmap *cm, *newcm; uint32_t polling_delay; int32_t temp, delta; int i; temp = thermal_get_temperature_cells(tz->tz_sensors); if (temp == THERMAL_SENSOR_MAX) goto out; newtp = NULL; tp = tz->tz_trips; for (i = 0; i < tz->tz_ntrips; i++) { if (temp < tp->tp_temperature && tp != tz->tz_tp) break; if (temp < tp->tp_temperature - tp->tp_hysteresis) break; newtp = tp++; } /* Short circuit if we didn't hit a trip point. */ if (newtp == NULL && tz->tz_tp == NULL) goto out; /* * If the current temperature is above the trip temperature: * - increase the cooling level if the temperature is rising * - do nothing if the temperature is falling * If the current temperature is below the trip temperature: * - do nothing if the temperature is rising * - decrease the cooling level if the temperature is falling */ delta = 0; if (newtp && tz->tz_temperature != THERMAL_SENSOR_MAX) { if (temp >= newtp->tp_temperature) { if (temp > tz->tz_temperature) delta = 1; } else { if (temp < tz->tz_temperature) delta = -1; } } newcm = NULL; cm = tz->tz_cmaps; for (i = 0; i < tz->tz_ncmaps; i++) { if (newtp && cm->cm_trip == newtp->tp_phandle) { newcm = cm; break; } cm++; } cmap_deactivate(tz, tz->tz_cm); cmap_activate(tz, newcm, delta); cmap_finish(tz); tz->tz_tp = newtp; tz->tz_cm = newcm; out: tz->tz_temperature = temp; #if NKSTAT > 0 thermal_zone_kstat_update(tz); #endif if (tz->tz_tp && tz->tz_tp->tp_type == THERMAL_PASSIVE) polling_delay = tz->tz_polling_delay_passive; else polling_delay = tz->tz_polling_delay; timeout_add_msec(&tz->tz_poll_to, polling_delay); } static int thermal_zone_triptype(const char *prop) { size_t i; for (i = 0; i < nitems(trip_types); i++) { const char *name = trip_types[i]; if (name == NULL) continue; if (strcmp(name, prop) == 0) return (i); } return (THERMAL_NONE); } void thermal_zone_init(int node) { struct thermal_zone *tz; struct trippoint *tp; struct cmap *cm; struct cdev *cd; int len, i; len = OF_getproplen(node, "thermal-sensors"); if (len <= 0) return; if (OF_getnodebyname(node, "trips") == 0) return; if (OF_getnodebyname(node, "cooling-maps") == 0) return; tz = malloc(sizeof(struct thermal_zone), M_DEVBUF, M_ZERO | M_WAITOK); tz->tz_node = node; rw_init(&tz->tz_lock, "tzlk"); OF_getprop(node, "name", &tz->tz_name, sizeof(tz->tz_name)); tz->tz_name[sizeof(tz->tz_name) - 1] = 0; tz->tz_sensors = malloc(len, M_DEVBUF, M_WAITOK); OF_getpropintarray(node, "thermal-sensors", tz->tz_sensors, len); tz->tz_polling_delay = OF_getpropint(node, "polling-delay", 0); tz->tz_polling_delay_passive = OF_getpropint(node, "polling-delay-passive", tz->tz_polling_delay); task_set(&tz->tz_poll_task, thermal_zone_poll, tz); timeout_set(&tz->tz_poll_to, thermal_zone_poll_timeout, tz); /* * Trip points for this thermal zone. */ node = OF_getnodebyname(tz->tz_node, "trips"); for (node = OF_child(node); node != 0; node = OF_peer(node)) tz->tz_ntrips++; tz->tz_trips = mallocarray(tz->tz_ntrips, sizeof(struct trippoint), M_DEVBUF, M_ZERO | M_WAITOK); node = OF_getnodebyname(tz->tz_node, "trips"); for (node = OF_child(node); node != 0; node = OF_peer(node)) { char type[32] = "none"; int32_t temp; temp = OF_getpropint(node, "temperature", THERMAL_SENSOR_MAX); /* Sorted insertion, since tree might not be */ for (i = 0; i < tz->tz_ntrips; i++) { /* No trip point should be 0 degC, take it */ if (tz->tz_trips[i].tp_temperature == 0) break; /* We should be bigger than the one before us */ if (tz->tz_trips[i].tp_temperature < temp) continue; /* Free current slot */ memmove(&tz->tz_trips[i + 1], &tz->tz_trips[i], (tz->tz_ntrips - (i + 1)) * sizeof(*tp)); break; } tp = &tz->tz_trips[i]; tp->tp_node = node; tp->tp_temperature = temp; tp->tp_hysteresis = OF_getpropint(node, "hysteresis", 0); OF_getprop(node, "type", type, sizeof(type)); tp->tp_type = thermal_zone_triptype(type); tp->tp_phandle = OF_getpropint(node, "phandle", 0); tp++; } /* * Cooling maps for this thermal zone. */ node = OF_getnodebyname(tz->tz_node, "cooling-maps"); for (node = OF_child(node); node != 0; node = OF_peer(node)) tz->tz_ncmaps++; tz->tz_cmaps = mallocarray(tz->tz_ncmaps, sizeof(struct cmap), M_DEVBUF, M_ZERO | M_WAITOK); cm = tz->tz_cmaps; node = OF_getnodebyname(tz->tz_node, "cooling-maps"); for (node = OF_child(node); node != 0; node = OF_peer(node)) { len = OF_getproplen(node, "cooling-device"); if (len <= 0) continue; cm->cm_cdev = malloc(len, M_DEVBUF, M_ZERO | M_WAITOK); OF_getpropintarray(node, "cooling-device", cm->cm_cdev, len); cm->cm_cdevend = cm->cm_cdev + len / sizeof(uint32_t); cm->cm_trip = OF_getpropint(node, "trip", 0); cm++; } /* * Create a list of all the possible cooling devices from the * cooling maps for this thermal zone, and initialize their * state. */ LIST_INIT(&tz->tz_cdevs); cm = tz->tz_cmaps; for (i = 0; i < tz->tz_ncmaps; i++) { uint32_t *cdev; cdev = cm->cm_cdev; while (cdev && cdev < cm->cm_cdevend) { LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) { if (cd->cd_phandle == cdev[0]) break; } if (cd == NULL) { cd = malloc(sizeof(struct cdev), M_DEVBUF, M_ZERO | M_WAITOK); cd->cd_phandle = cdev[0]; cd->cd_level = 0; cd->cd_active = 0; LIST_INSERT_HEAD(&tz->tz_cdevs, cd, cd_list); } cdev = cdev_next_cdev(cdev); } cm++; } /* Start polling if we are requested to do so. */ if (tz->tz_polling_delay > 0) timeout_add_msec(&tz->tz_poll_to, tz->tz_polling_delay); LIST_INSERT_HEAD(&thermal_zones, tz, tz_list); #if NKSTAT > 0 thermal_zone_kstat_attach(tz); #endif } void thermal_init(void) { int node = OF_finddevice("/thermal-zones"); if (node == -1) return; tztq = taskq_create("tztq", 1, IPL_SOFTCLOCK, 0); for (node = OF_child(node); node != 0; node = OF_peer(node)) thermal_zone_init(node); } #if NKSTAT > 0 static const char * thermal_zone_tripname(int type) { if (type >= nitems(trip_types)) return (NULL); return (trip_types[type]); } struct thermal_zone_kstats { struct kstat_kv tzk_name; /* istr could be short */ struct kstat_kv tzk_temp; struct kstat_kv tzk_tp; struct kstat_kv tzk_tp_type; struct kstat_kv tzk_cooling; }; static void thermal_zone_kstat_update(struct thermal_zone *tz) { struct kstat *ks = tz->tz_kstat; struct thermal_zone_kstats *tzk; if (ks == NULL) return; tzk = ks->ks_data; rw_enter_write(&tz->tz_lock); if (tz->tz_temperature == THERMAL_SENSOR_MAX) tzk->tzk_temp.kv_type = KSTAT_KV_T_NULL; else { tzk->tzk_temp.kv_type = KSTAT_KV_T_TEMP; kstat_kv_temp(&tzk->tzk_temp) = 273150000 + 1000 * tz->tz_temperature; } if (tz->tz_tp == NULL) { kstat_kv_u32(&tzk->tzk_tp) = 0; strlcpy(kstat_kv_istr(&tzk->tzk_tp_type), "none", sizeof(kstat_kv_istr(&tzk->tzk_tp_type))); } else { int triptype = tz->tz_tp->tp_type; const char *tripname = thermal_zone_tripname(triptype); kstat_kv_u32(&tzk->tzk_tp) = tz->tz_tp->tp_node; if (tripname == NULL) { snprintf(kstat_kv_istr(&tzk->tzk_tp_type), sizeof(kstat_kv_istr(&tzk->tzk_tp_type)), "%u", triptype); } else { strlcpy(kstat_kv_istr(&tzk->tzk_tp_type), tripname, sizeof(kstat_kv_istr(&tzk->tzk_tp_type))); } } kstat_kv_bool(&tzk->tzk_cooling) = (tz->tz_cm != NULL); getnanouptime(&ks->ks_updated); rw_exit_write(&tz->tz_lock); } static void thermal_zone_kstat_attach(struct thermal_zone *tz) { struct kstat *ks; struct thermal_zone_kstats *tzk; static unsigned int unit = 0; ks = kstat_create("dt", 0, "thermal-zone", unit++, KSTAT_T_KV, 0); if (ks == NULL) { printf("unable to create thermal-zone kstats for %s", tz->tz_name); return; } tzk = malloc(sizeof(*tzk), M_DEVBUF, M_WAITOK|M_ZERO); kstat_kv_init(&tzk->tzk_name, "name", KSTAT_KV_T_ISTR); strlcpy(kstat_kv_istr(&tzk->tzk_name), tz->tz_name, sizeof(kstat_kv_istr(&tzk->tzk_name))); kstat_kv_init(&tzk->tzk_temp, "temperature", KSTAT_KV_T_NULL); /* XXX dt node is not be the most useful info here. */ kstat_kv_init(&tzk->tzk_tp, "trip-point-node", KSTAT_KV_T_UINT32); kstat_kv_init(&tzk->tzk_tp_type, "trip-type", KSTAT_KV_T_ISTR); strlcpy(kstat_kv_istr(&tzk->tzk_tp_type), "unknown", sizeof(kstat_kv_istr(&tzk->tzk_tp_type))); kstat_kv_init(&tzk->tzk_cooling, "active-cooling", KSTAT_KV_T_BOOL); kstat_kv_bool(&tzk->tzk_cooling) = 0; ks->ks_softc = tz; ks->ks_data = tzk; ks->ks_datalen = sizeof(*tzk); ks->ks_read = kstat_read_nop; kstat_set_rlock(ks, &tz->tz_lock); tz->tz_kstat = ks; kstat_install(ks); } #endif /* NKSTAT > 0 */