src/sys/dev/ofw/ofw_thermal.c

650 lines
15 KiB
C
Raw Normal View History

2024-06-28 01:31:32 +00:00
/* $OpenBSD: ofw_thermal.c,v 1.9 2024/06/27 09:37:07 kettenis 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.
*/
2023-11-24 03:38:59 +00:00
#include "kstat.h"
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/stdint.h>
#include <sys/task.h>
#include <sys/timeout.h>
2023-11-24 03:38:59 +00:00
#include <sys/sched.h>
#include <sys/kstat.h>
#include <machine/bus.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_thermal.h>
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 {
2023-11-24 03:38:59 +00:00
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
2023-11-24 03:38:59 +00:00
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;
2023-11-24 03:38:59 +00:00
struct rwlock tz_lock;
struct kstat *tz_kstat;
};
2023-11-24 03:38:59 +00:00
#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;
}
2024-06-28 01:31:32 +00:00
int
thermal_set_limit_cells(uint32_t *cells, uint32_t temp)
{
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_set_limit)
return ts->ts_set_limit(ts->ts_cookie, &cells[1], temp);
return ENXIO;
}
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;
2023-11-24 03:38:59 +00:00
#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;
2024-06-28 01:31:32 +00:00
if (polling_delay > 0)
timeout_add_msec(&tz->tz_poll_to, polling_delay);
else
thermal_set_limit_cells(tz->tz_sensors, tp->tp_temperature);
}
2023-11-24 03:38:59 +00:00
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;
2023-11-24 03:38:59 +00:00
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];
2023-11-24 03:38:59 +00:00
tp->tp_node = node;
tp->tp_temperature = temp;
tp->tp_hysteresis = OF_getpropint(node, "hysteresis", 0);
OF_getprop(node, "type", type, sizeof(type));
2023-11-24 03:38:59 +00:00
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++;
}
LIST_INSERT_HEAD(&thermal_zones, tz, tz_list);
2023-11-24 03:38:59 +00:00
#if NKSTAT > 0
thermal_zone_kstat_attach(tz);
#endif
2024-06-28 01:31:32 +00:00
/* Poll once to get things going. */
thermal_zone_poll(tz);
}
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);
}
2023-11-24 03:38:59 +00:00
#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 */