diff options
Diffstat (limited to 'tools/thermal')
| -rw-r--r-- | tools/thermal/tmon/Makefile | 47 | ||||
| -rw-r--r-- | tools/thermal/tmon/README | 50 | ||||
| -rw-r--r-- | tools/thermal/tmon/pid.c | 131 | ||||
| -rw-r--r-- | tools/thermal/tmon/sysfs.c | 596 | ||||
| -rw-r--r-- | tools/thermal/tmon/tmon.8 | 142 | ||||
| -rw-r--r-- | tools/thermal/tmon/tmon.c | 376 | ||||
| -rw-r--r-- | tools/thermal/tmon/tmon.h | 204 | ||||
| -rw-r--r-- | tools/thermal/tmon/tui.c | 638 | 
8 files changed, 2184 insertions, 0 deletions
diff --git a/tools/thermal/tmon/Makefile b/tools/thermal/tmon/Makefile new file mode 100644 index 00000000000..e775adcbd29 --- /dev/null +++ b/tools/thermal/tmon/Makefile @@ -0,0 +1,47 @@ +VERSION = 1.0 + +BINDIR=usr/bin +WARNFLAGS=-Wall -Wshadow -W -Wformat -Wimplicit-function-declaration -Wimplicit-int +CFLAGS= -O1 ${WARNFLAGS} -fstack-protector +CC=gcc + +CFLAGS+=-D VERSION=\"$(VERSION)\" +LDFLAGS+= +TARGET=tmon + +INSTALL_PROGRAM=install -m 755 -p +DEL_FILE=rm -f + +INSTALL_CONFIGFILE=install -m 644 -p +CONFIG_FILE= +CONFIG_PATH= + + +OBJS = tmon.o tui.o sysfs.o pid.o +OBJS += + +tmon: $(OBJS) Makefile tmon.h +	$(CC) ${CFLAGS} $(LDFLAGS) $(OBJS)  -o $(TARGET) -lm -lpanel -lncursesw -ltinfo -lpthread + +valgrind: tmon +	 sudo valgrind -v --track-origins=yes --tool=memcheck --leak-check=yes --show-reachable=yes --num-callers=20 --track-fds=yes ./$(TARGET)  1> /dev/null + +install: +	- mkdir -p $(INSTALL_ROOT)/$(BINDIR) +	- $(INSTALL_PROGRAM) "$(TARGET)" "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)" +	- mkdir -p $(INSTALL_ROOT)/$(CONFIG_PATH) +	- $(INSTALL_CONFIGFILE) "$(CONFIG_FILE)" "$(INSTALL_ROOT)/$(CONFIG_PATH)" + +uninstall: +	$(DEL_FILE) "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)" +	$(CONFIG_FILE) "$(CONFIG_PATH)" + + +clean: +	find . -name "*.o" | xargs $(DEL_FILE) +	rm -f $(TARGET) + +dist: +	git tag v$(VERSION) +	git archive --format=tar --prefix="$(TARGET)-$(VERSION)/" v$(VERSION) | \ +		gzip > $(TARGET)-$(VERSION).tar.gz diff --git a/tools/thermal/tmon/README b/tools/thermal/tmon/README new file mode 100644 index 00000000000..457949897a8 --- /dev/null +++ b/tools/thermal/tmon/README @@ -0,0 +1,50 @@ +TMON - A Monitoring and Testing Tool for Linux kernel thermal subsystem + +Why TMON? +========== +Increasingly, Linux is running on thermally constrained devices. The simple +thermal relationship between processor and fan has become past for modern +computers. + +As hardware vendors cope with the thermal constraints on their products, more +and more sensors are added, new cooling capabilities are introduced. The +complexity of the thermal relationship can grow exponentially among cooling +devices, zones, sensors, and trip points. They can also change dynamically. + +To expose such relationship to the userspace, Linux generic thermal layer +introduced sysfs entry at /sys/class/thermal with a matrix of symbolic +links, trip point bindings, and device instances. To traverse such +matrix by hand is not a trivial task. Testing is also difficult in that +thermal conditions are often exception cases that hard to reach in +normal operations. + +TMON is conceived as a tool to help visualize, tune, and test the +complex thermal subsystem. + +Files +===== +	tmon.c : main function for set up and configurations. +	tui.c : handles ncurses based user interface +	sysfs.c : access to the generic thermal sysfs +	pid.c : a proportional-integral-derivative (PID) controller +	that can be used for thermal relationship training. + +Requirements +============ +Depends on ncurses + +Build +========= +$ make +$ sudo ./tmon -h +Usage: tmon [OPTION...] +  -c, --control         cooling device in control +  -d, --daemon          run as daemon, no TUI +  -l, --log             log data to /var/tmp/tmon.log +  -h, --help            show this help message +  -t, --time-interval   set time interval for sampling +  -v, --version         show version +  -g, --debug           debug message in syslog + +1. For monitoring only: +$ sudo ./tmon diff --git a/tools/thermal/tmon/pid.c b/tools/thermal/tmon/pid.c new file mode 100644 index 00000000000..fd7e9e9d6f4 --- /dev/null +++ b/tools/thermal/tmon/pid.c @@ -0,0 +1,131 @@ +/* + * pid.c PID controller for testing cooling devices + * + * + * + * Copyright (C) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 or later as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * Author Name Jacob Pan <jacob.jun.pan@linux.intel.com> + * + */ + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <sys/types.h> +#include <dirent.h> +#include <libintl.h> +#include <ctype.h> +#include <assert.h> +#include <time.h> +#include <limits.h> +#include <math.h> +#include <sys/stat.h> +#include <syslog.h> + +#include "tmon.h" + +/************************************************************************** + * PID (Proportional-Integral-Derivative) controller is commonly used in + * linear control system, consider the the process. + * G(s) = U(s)/E(s) + * kp = proportional gain + * ki = integral gain + * kd = derivative gain + * Ts + * We use type C Alan Bradley equation which takes set point off the + * output dependency in P and D term. + * + *   y[k] = y[k-1] - kp*(x[k] - x[k-1]) + Ki*Ts*e[k] - Kd*(x[k] + *          - 2*x[k-1]+x[k-2])/Ts + * + * + ***********************************************************************/ +struct pid_params p_param; +/* cached data from previous loop */ +static double xk_1, xk_2; /* input temperature x[k-#] */ + +/* + * TODO: make PID parameters tuned automatically, + * 1. use CPU burn to produce open loop unit step response + * 2. calculate PID based on Ziegler-Nichols rule + * + * add a flag for tuning PID + */ +int init_thermal_controller(void) +{ +	int ret = 0; + +	/* init pid params */ +	p_param.ts = ticktime; +	/* TODO: get it from TUI tuning tab */ +	p_param.kp = .36; +	p_param.ki = 5.0; +	p_param.kd = 0.19; + +	p_param.t_target = target_temp_user; + +	return ret; +} + +void controller_reset(void) +{ +	/* TODO: relax control data when not over thermal limit */ +	syslog(LOG_DEBUG, "TC inactive, relax p-state\n"); +	p_param.y_k = 0.0; +	xk_1 = 0.0; +	xk_2 = 0.0; +	set_ctrl_state(0); +} + +/* To be called at time interval Ts. Type C PID controller. + *    y[k] = y[k-1] - kp*(x[k] - x[k-1]) + Ki*Ts*e[k] - Kd*(x[k] + *          - 2*x[k-1]+x[k-2])/Ts + * TODO: add low pass filter for D term + */ +#define GUARD_BAND (2) +void controller_handler(const double xk, double *yk) +{ +	double ek; +	double p_term, i_term, d_term; + +	ek = p_param.t_target - xk; /* error */ +	if (ek >= 3.0) { +		syslog(LOG_DEBUG, "PID: %3.1f Below set point %3.1f, stop\n", +			xk, p_param.t_target); +		controller_reset(); +		*yk = 0.0; +		return; +	} +	/* compute intermediate PID terms */ +	p_term = -p_param.kp * (xk - xk_1); +	i_term = p_param.kp * p_param.ki * p_param.ts * ek; +	d_term = -p_param.kp * p_param.kd * (xk - 2 * xk_1 + xk_2) / p_param.ts; +	/* compute output */ +	*yk += p_term + i_term + d_term; +	/* update sample data */ +	xk_1 = xk; +	xk_2 = xk_1; + +	/* clamp output adjustment range */ +	if (*yk < -LIMIT_HIGH) +		*yk = -LIMIT_HIGH; +	else if (*yk > -LIMIT_LOW) +		*yk = -LIMIT_LOW; + +	p_param.y_k = *yk; + +	set_ctrl_state(lround(fabs(p_param.y_k))); + +} diff --git a/tools/thermal/tmon/sysfs.c b/tools/thermal/tmon/sysfs.c new file mode 100644 index 00000000000..dfe454855cd --- /dev/null +++ b/tools/thermal/tmon/sysfs.c @@ -0,0 +1,596 @@ +/* + * sysfs.c sysfs ABI access functions for TMON program + * + * Copyright (C) 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 or later as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * Author: Jacob Pan <jacob.jun.pan@linux.intel.com> + * + */ +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <dirent.h> +#include <libintl.h> +#include <ctype.h> +#include <time.h> +#include <syslog.h> +#include <sys/time.h> +#include <errno.h> + +#include "tmon.h" + +struct tmon_platform_data ptdata; +const char *trip_type_name[] = { +	"critical", +	"hot", +	"passive", +	"active", +}; + +int sysfs_set_ulong(char *path, char *filename, unsigned long val) +{ +	FILE *fd; +	int ret = -1; +	char filepath[256]; + +	snprintf(filepath, 256, "%s/%s", path, filename); + +	fd = fopen(filepath, "w"); +	if (!fd) { +		syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath); +		return ret; +	} +	ret = fprintf(fd, "%lu", val); +	fclose(fd); + +	return 0; +} + +/* history of thermal data, used for control algo */ +#define NR_THERMAL_RECORDS 3 +struct thermal_data_record trec[NR_THERMAL_RECORDS]; +int cur_thermal_record; /* index to the trec array */ + +static int sysfs_get_ulong(char *path, char *filename, unsigned long *p_ulong) +{ +	FILE *fd; +	int ret = -1; +	char filepath[256]; + +	snprintf(filepath, 256, "%s/%s", path, filename); + +	fd = fopen(filepath, "r"); +	if (!fd) { +		syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath); +		return ret; +	} +	ret = fscanf(fd, "%lu", p_ulong); +	fclose(fd); + +	return 0; +} + +static int sysfs_get_string(char *path, char *filename, char *str) +{ +	FILE *fd; +	int ret = -1; +	char filepath[256]; + +	snprintf(filepath, 256, "%s/%s", path, filename); + +	fd = fopen(filepath, "r"); +	if (!fd) { +		syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath); +		return ret; +	} +	ret = fscanf(fd, "%256s", str); +	fclose(fd); + +	return ret; +} + +/* get states of the cooling device instance */ +static int probe_cdev(struct cdev_info *cdi, char *path) +{ +	sysfs_get_string(path, "type", cdi->type); +	sysfs_get_ulong(path, "max_state",  &cdi->max_state); +	sysfs_get_ulong(path, "cur_state", &cdi->cur_state); + +	syslog(LOG_INFO, "%s: %s: type %s, max %lu, curr %lu inst %d\n", +		__func__, path, +		cdi->type, cdi->max_state, cdi->cur_state, cdi->instance); + +	return 0; +} + +static int str_to_trip_type(char *name) +{ +	int i; + +	for (i = 0; i < NR_THERMAL_TRIP_TYPE; i++) { +		if (!strcmp(name, trip_type_name[i])) +			return i; +	} + +	return -ENOENT; +} + +/* scan and fill in trip point info for a thermal zone and trip point id */ +static int get_trip_point_data(char *tz_path, int tzid, int tpid) +{ +	char filename[256]; +	char temp_str[256]; +	int trip_type; + +	if (tpid >= MAX_NR_TRIP) +		return -EINVAL; +	/* check trip point type */ +	snprintf(filename, sizeof(filename), "trip_point_%d_type", tpid); +	sysfs_get_string(tz_path, filename, temp_str); +	trip_type = str_to_trip_type(temp_str); +	if (trip_type < 0) { +		syslog(LOG_ERR, "%s:%s no matching type\n", __func__, temp_str); +		return -ENOENT; +	} +	ptdata.tzi[tzid].tp[tpid].type = trip_type; +	syslog(LOG_INFO, "%s:tz:%d tp:%d:type:%s type id %d\n", __func__, tzid, +		tpid, temp_str, trip_type); + +	/* TODO: check attribute */ + +	return 0; +} + +/* return instance id for file format such as trip_point_4_temp */ +static int get_instance_id(char *name, int pos, int skip) +{ +	char *ch; +	int i = 0; + +	ch = strtok(name, "_"); +	while (ch != NULL) { +		++i; +		syslog(LOG_INFO, "%s:%s:%s:%d", __func__, name, ch, i); +		ch = strtok(NULL, "_"); +		if (pos == i) +			return atol(ch + skip); +	} + +	return -1; +} + +/* Find trip point info of a thermal zone */ +static int find_tzone_tp(char *tz_name, char *d_name, struct tz_info *tzi, +			int tz_id) +{ +	int tp_id; +	unsigned long temp_ulong; + +	if (strstr(d_name, "trip_point") && +		strstr(d_name, "temp")) { +		/* check if trip point temp is non-zero +		 * ignore 0/invalid trip points +		 */ +		sysfs_get_ulong(tz_name, d_name, &temp_ulong); +		if (temp_ulong < MAX_TEMP_KC) { +			tzi->nr_trip_pts++; +			/* found a valid trip point */ +			tp_id = get_instance_id(d_name, 2, 0); +			syslog(LOG_DEBUG, "tzone %s trip %d temp %lu tpnode %s", +				tz_name, tp_id, temp_ulong, d_name); +			if (tp_id < 0 || tp_id >= MAX_NR_TRIP) { +				syslog(LOG_ERR, "Failed to find TP inst %s\n", +					d_name); +				return -1; +			} +			get_trip_point_data(tz_name, tz_id, tp_id); +			tzi->tp[tp_id].temp = temp_ulong; +		} +	} + +	return 0; +} + +/* check cooling devices for binding info. */ +static int find_tzone_cdev(struct dirent *nl, char *tz_name, +			struct tz_info *tzi, int tz_id, int cid) +{ +	unsigned long trip_instance = 0; +	char cdev_name_linked[256]; +	char cdev_name[256]; +	char cdev_trip_name[256]; +	int cdev_id; + +	if (nl->d_type == DT_LNK) { +		syslog(LOG_DEBUG, "TZ%d: cdev: %s cid %d\n", tz_id, nl->d_name, +			cid); +		tzi->nr_cdev++; +		if (tzi->nr_cdev > ptdata.nr_cooling_dev) { +			syslog(LOG_ERR, "Err: Too many cdev? %d\n", +				tzi->nr_cdev); +			return -EINVAL; +		} +		/* find the link to real cooling device record binding */ +		snprintf(cdev_name, 256, "%s/%s", tz_name, nl->d_name); +		memset(cdev_name_linked, 0, sizeof(cdev_name_linked)); +		if (readlink(cdev_name, cdev_name_linked, +				sizeof(cdev_name_linked) - 1) != -1) { +			cdev_id = get_instance_id(cdev_name_linked, 1, +						sizeof("device") - 1); +			syslog(LOG_DEBUG, "cdev %s linked to %s : %d\n", +				cdev_name, cdev_name_linked, cdev_id); +			tzi->cdev_binding |= (1 << cdev_id); + +			/* find the trip point in which the cdev is binded to +			 * in this tzone +			 */ +			snprintf(cdev_trip_name, 256, "%s%s", nl->d_name, +				"_trip_point"); +			sysfs_get_ulong(tz_name, cdev_trip_name, +					&trip_instance); +			/* validate trip point range, e.g. trip could return -1 +			 * when passive is enabled +			 */ +			if (trip_instance > MAX_NR_TRIP) +				trip_instance = 0; +			tzi->trip_binding[cdev_id] |= 1 << trip_instance; +			syslog(LOG_DEBUG, "cdev %s -> trip:%lu: 0x%lx %d\n", +				cdev_name, trip_instance, +				tzi->trip_binding[cdev_id], +				cdev_id); + + +		} +		return 0; +	} + +	return -ENODEV; +} + + + +/***************************************************************************** + * Before calling scan_tzones, thermal sysfs must be probed to determine + * the number of thermal zones and cooling devices. + * We loop through each thermal zone and fill in tz_info struct, i.e. + * ptdata.tzi[] +root@jacob-chiefriver:~# tree -d /sys/class/thermal/thermal_zone0 +/sys/class/thermal/thermal_zone0 +|-- cdev0 -> ../cooling_device4 +|-- cdev1 -> ../cooling_device3 +|-- cdev10 -> ../cooling_device7 +|-- cdev11 -> ../cooling_device6 +|-- cdev12 -> ../cooling_device5 +|-- cdev2 -> ../cooling_device2 +|-- cdev3 -> ../cooling_device1 +|-- cdev4 -> ../cooling_device0 +|-- cdev5 -> ../cooling_device12 +|-- cdev6 -> ../cooling_device11 +|-- cdev7 -> ../cooling_device10 +|-- cdev8 -> ../cooling_device9 +|-- cdev9 -> ../cooling_device8 +|-- device -> ../../../LNXSYSTM:00/device:62/LNXTHERM:00 +|-- power +`-- subsystem -> ../../../../class/thermal +*****************************************************************************/ +static int scan_tzones(void) +{ +	DIR *dir; +	struct dirent **namelist; +	char tz_name[256]; +	int i, j, n, k = 0; + +	if (!ptdata.nr_tz_sensor) +		return -1; + +	for (i = 0; i <= ptdata.max_tz_instance; i++) { +		memset(tz_name, 0, sizeof(tz_name)); +		snprintf(tz_name, 256, "%s/%s%d", THERMAL_SYSFS, TZONE, i); + +		dir = opendir(tz_name); +		if (!dir) { +			syslog(LOG_INFO, "Thermal zone %s skipped\n", tz_name); +			continue; +		} +		/* keep track of valid tzones */ +		n = scandir(tz_name, &namelist, 0, alphasort); +		if (n < 0) +			syslog(LOG_ERR, "scandir failed in %s",  tz_name); +		else { +			sysfs_get_string(tz_name, "type", ptdata.tzi[k].type); +			ptdata.tzi[k].instance = i; +			/* detect trip points and cdev attached to this tzone */ +			j = 0; /* index for cdev */ +			ptdata.tzi[k].nr_cdev = 0; +			ptdata.tzi[k].nr_trip_pts = 0; +			while (n--) { +				char *temp_str; + +				if (find_tzone_tp(tz_name, namelist[n]->d_name, +							&ptdata.tzi[k], k)) +					break; +				temp_str = strstr(namelist[n]->d_name, "cdev"); +				if (!temp_str) { +					free(namelist[n]); +					continue; +				} +				if (!find_tzone_cdev(namelist[n], tz_name, +							&ptdata.tzi[k], i, j)) +					j++; /* increment cdev index */ +				free(namelist[n]); +			} +			free(namelist); +		} +		/*TODO: reverse trip points */ +		closedir(dir); +		syslog(LOG_INFO, "TZ %d has %d cdev\n",	i, +			ptdata.tzi[k].nr_cdev); +		k++; +	} + +	return 0; +} + +static int scan_cdevs(void) +{ +	DIR *dir; +	struct dirent **namelist; +	char cdev_name[256]; +	int i, n, k = 0; + +	if (!ptdata.nr_cooling_dev) { +		fprintf(stderr, "No cooling devices found\n"); +		return 0; +	} +	for (i = 0; i <= ptdata.max_cdev_instance; i++) { +		memset(cdev_name, 0, sizeof(cdev_name)); +		snprintf(cdev_name, 256, "%s/%s%d", THERMAL_SYSFS, CDEV, i); + +		dir = opendir(cdev_name); +		if (!dir) { +			syslog(LOG_INFO, "Cooling dev %s skipped\n", cdev_name); +			/* there is a gap in cooling device id, check again +			 * for the same index. +			 */ +			continue; +		} + +		n = scandir(cdev_name, &namelist, 0, alphasort); +		if (n < 0) +			syslog(LOG_ERR, "scandir failed in %s",  cdev_name); +		else { +			sysfs_get_string(cdev_name, "type", ptdata.cdi[k].type); +			ptdata.cdi[k].instance = i; +			if (strstr(ptdata.cdi[k].type, ctrl_cdev)) { +				ptdata.cdi[k].flag |= CDEV_FLAG_IN_CONTROL; +				syslog(LOG_DEBUG, "control cdev id %d\n", i); +			} +			while (n--) +				free(namelist[n]); +			free(namelist); +		} +		closedir(dir); +		k++; +	} +	return 0; +} + + +int probe_thermal_sysfs(void) +{ +	DIR *dir; +	struct dirent **namelist; +	int n; + +	dir = opendir(THERMAL_SYSFS); +	if (!dir) { +		fprintf(stderr, "\nNo thermal sysfs, exit\n"); +		return -1; +	} +	n = scandir(THERMAL_SYSFS, &namelist, 0, alphasort); +	if (n < 0) +		syslog(LOG_ERR, "scandir failed in thermal sysfs"); +	else { +		/* detect number of thermal zones and cooling devices */ +		while (n--) { +			int inst; + +			if (strstr(namelist[n]->d_name, CDEV)) { +				inst = get_instance_id(namelist[n]->d_name, 1, +						sizeof("device") - 1); +				/* keep track of the max cooling device since +				 * there may be gaps. +				 */ +				if (inst > ptdata.max_cdev_instance) +					ptdata.max_cdev_instance = inst; + +				syslog(LOG_DEBUG, "found cdev: %s %d %d\n", +					namelist[n]->d_name, +					ptdata.nr_cooling_dev, +					ptdata.max_cdev_instance); +				ptdata.nr_cooling_dev++; +			} else if (strstr(namelist[n]->d_name, TZONE)) { +				inst = get_instance_id(namelist[n]->d_name, 1, +						sizeof("zone") - 1); +				if (inst > ptdata.max_tz_instance) +					ptdata.max_tz_instance = inst; + +				syslog(LOG_DEBUG, "found tzone: %s %d %d\n", +					namelist[n]->d_name, +					ptdata.nr_tz_sensor, +					ptdata.max_tz_instance); +				ptdata.nr_tz_sensor++; +			} +			free(namelist[n]); +		} +		free(namelist); +	} +	syslog(LOG_INFO, "found %d tzone(s), %d cdev(s), target zone %d\n", +		ptdata.nr_tz_sensor, ptdata.nr_cooling_dev, +		target_thermal_zone); +	closedir(dir); + +	if (!ptdata.nr_tz_sensor) { +		fprintf(stderr, "\nNo thermal zones found, exit\n\n"); +		return -1; +	} + +	ptdata.tzi = calloc(sizeof(struct tz_info), ptdata.max_tz_instance+1); +	if (!ptdata.tzi) { +		fprintf(stderr, "Err: allocate tz_info\n"); +		return -1; +	} + +	/* we still show thermal zone information if there is no cdev */ +	if (ptdata.nr_cooling_dev) { +		ptdata.cdi = calloc(sizeof(struct cdev_info), +				ptdata.max_cdev_instance + 1); +		if (!ptdata.cdi) { +			free(ptdata.tzi); +			fprintf(stderr, "Err: allocate cdev_info\n"); +			return -1; +		} +	} + +	/* now probe tzones */ +	if (scan_tzones()) +		return -1; +	if (scan_cdevs()) +		return -1; +	return 0; +} + +/* convert sysfs zone instance to zone array index */ +int zone_instance_to_index(int zone_inst) +{ +	int i; + +	for (i = 0; i < ptdata.nr_tz_sensor; i++) +		if (ptdata.tzi[i].instance == zone_inst) +			return i; +	return -ENOENT; +} + +/* read temperature of all thermal zones */ +int update_thermal_data() +{ +	int i; +	char tz_name[256]; +	static unsigned long samples; + +	if (!ptdata.nr_tz_sensor) { +		syslog(LOG_ERR, "No thermal zones found!\n"); +		return -1; +	} + +	/* circular buffer for keeping historic data */ +	if (cur_thermal_record >= NR_THERMAL_RECORDS) +		cur_thermal_record = 0; +	gettimeofday(&trec[cur_thermal_record].tv, NULL); +	if (tmon_log) { +		fprintf(tmon_log, "%lu ", ++samples); +		fprintf(tmon_log, "%3.1f ", p_param.t_target); +	} +	for (i = 0; i < ptdata.nr_tz_sensor; i++) { +		memset(tz_name, 0, sizeof(tz_name)); +		snprintf(tz_name, 256, "%s/%s%d", THERMAL_SYSFS, TZONE, +			ptdata.tzi[i].instance); +		sysfs_get_ulong(tz_name, "temp", +				&trec[cur_thermal_record].temp[i]); +		if (tmon_log) +			fprintf(tmon_log, "%lu ", +				trec[cur_thermal_record].temp[i]/1000); +	} +	for (i = 0; i < ptdata.nr_cooling_dev; i++) { +		char cdev_name[256]; +		unsigned long val; + +		snprintf(cdev_name, 256, "%s/%s%d", THERMAL_SYSFS, CDEV, +			ptdata.cdi[i].instance); +		probe_cdev(&ptdata.cdi[i], cdev_name); +		val = ptdata.cdi[i].cur_state; +		if (val > 1000000) +			val = 0; +		if (tmon_log) +			fprintf(tmon_log, "%lu ", val); +	} + +	if (tmon_log) { +		fprintf(tmon_log, "\n"); +		fflush(tmon_log); +	} + +	return 0; +} + +void set_ctrl_state(unsigned long state) +{ +	char ctrl_cdev_path[256]; +	int i; +	unsigned long cdev_state; + +	if (no_control) +		return; +	/* set all ctrl cdev to the same state */ +	for (i = 0; i < ptdata.nr_cooling_dev; i++) { +		if (ptdata.cdi[i].flag & CDEV_FLAG_IN_CONTROL) { +			if (ptdata.cdi[i].max_state < 10) { +				strcpy(ctrl_cdev, "None."); +				return; +			} +			/* scale to percentage of max_state */ +			cdev_state = state * ptdata.cdi[i].max_state/100; +			syslog(LOG_DEBUG, +				"ctrl cdev %d set state %lu scaled to %lu\n", +				ptdata.cdi[i].instance, state, cdev_state); +			snprintf(ctrl_cdev_path, 256, "%s/%s%d", THERMAL_SYSFS, +				CDEV, ptdata.cdi[i].instance); +			syslog(LOG_DEBUG, "ctrl cdev path %s", ctrl_cdev_path); +			sysfs_set_ulong(ctrl_cdev_path, "cur_state", +					cdev_state); +		} +	} +} + +void get_ctrl_state(unsigned long *state) +{ +	char ctrl_cdev_path[256]; +	int ctrl_cdev_id = -1; +	int i; + +	/* TODO: take average of all ctrl types. also consider change based on +	 * uevent. Take the first reading for now. +	 */ +	for (i = 0; i < ptdata.nr_cooling_dev; i++) { +		if (ptdata.cdi[i].flag & CDEV_FLAG_IN_CONTROL) { +			ctrl_cdev_id = ptdata.cdi[i].instance; +			syslog(LOG_INFO, "ctrl cdev %d get state\n", +				ptdata.cdi[i].instance); +			break; +		} +	} +	if (ctrl_cdev_id == -1) { +		*state = 0; +		return; +	} +	snprintf(ctrl_cdev_path, 256, "%s/%s%d", THERMAL_SYSFS, +		CDEV, ctrl_cdev_id); +	sysfs_get_ulong(ctrl_cdev_path, "cur_state", state); +} + +void free_thermal_data(void) +{ +	free(ptdata.tzi); +	free(ptdata.cdi); +} diff --git a/tools/thermal/tmon/tmon.8 b/tools/thermal/tmon/tmon.8 new file mode 100644 index 00000000000..0be727cb989 --- /dev/null +++ b/tools/thermal/tmon/tmon.8 @@ -0,0 +1,142 @@ +.TH TMON 8 +.SH NAME +\fBtmon\fP - A monitoring and testing tool for Linux kernel thermal subsystem + +.SH SYNOPSIS +.ft B +.B tmon +.RB [ Options ] +.br +.SH DESCRIPTION +\fBtmon \fP can be used to visualize thermal relationship and +real-time thermal data; tune +and test cooling devices and sensors; collect thermal data for offline +analysis and plot. \fBtmon\fP must be run as root in order to control device +states via sysfs. +.PP +\fBFunctions\fP +.PP +.nf +1. Thermal relationships: +- show thermal zone information +- show cooling device information +- show trip point binding within each thermal zone +- show trip point and cooling device instance bindings +.PP +2. Real time data display +- show temperature of all thermal zones w.r.t. its trip points and types +- show states of all cooling devices +.PP +3. Thermal relationship learning and device tuning +- with a built-in Proportional Integral Derivative (\fBPID\fP) +controller, user can pair a cooling device to a thermal sensor for +testing the effectiveness and learn about the thermal distance between the two +- allow manual control of cooling device states and target temperature +.PP +4. Data logging in /var/tmp/tmon.log +- contains thermal configuration data, i.e. cooling device, thermal + zones, and trip points. Can be used for data  collection in remote + debugging. +- log real-time thermal data into space separated format that can be + directly consumed by plotting tools such as Rscript. + +.SS Options +.PP +The \fB-c --control\fP option sets a cooling device type to control temperature +of a thermal zone +.PP +The \fB-d --daemon\fP option runs \fBtmon \fP as daemon without user interface +.PP +The \fB-g --debug\fP option allow debug messages to be stored in syslog +.PP +The \fB-h --help\fP option shows help message +.PP +The \fB-l --log\fP option write data to /var/tmp/tmon.log +.PP +The \fB-t --time-interval\fP option sets the polling interval in seconds +.PP +The \fB-v --version\fP option shows the version of \fBtmon \fP +.PP +The \fB-z --zone\fP option sets the target therma zone instance to be controlled +.PP + +.SH FIELD DESCRIPTIONS +.nf +.PP +\fBP \fP passive cooling trip point type +\fBA \fP active cooling trip point type (fan) +\fBC \fP critical trip point type +\fBA \fP hot trip point type +\fBkp \fP proportional gain of \fBPID\fP controller +\fBki \fP integral gain of \fBPID\fP controller +\fBkd \fP derivative gain of \fBPID\fP controller + +.SH REQUIREMENT +Build depends on ncurses +.PP +Runtime depends on window size large enough to show the number of +devices found on the system. + +.PP + +.SH INTERACTIVE COMMANDS +.pp +.nf +\fBCtrl-C, q/Q\fP stops \fBtmon\fP +\fBTAB\fP shows tuning pop up panel, choose a letter to modify + +.SH EXAMPLES +Without any parameters, tmon is in monitoring only mode and refresh +screen every 1 second. +.PP +1. For monitoring only: +.nf +$ sudo ./tmon + +2. Use Processor cooling device to control thermal zone 0 at default 65C. +$ sudo ./tmon -c Processor -z 0 + +3. Use intel_powerclamp(idle injection) cooling device to control thermal zone 1 +$ sudo ./tmon -c intel_powerclamp -z 1 + +4. Turn on debug and collect data log at /var/tmp/tmon.log +$ sudo ./tmon -g -l + +For example, the log below shows PID controller was adjusting current states +for all cooling devices with "Processor" type such that thermal zone 0 +can stay below 65 dC. + +#---------- THERMAL DATA LOG STARTED ----------- +Samples TargetTemp acpitz0    acpitz1    Fan0 Fan1 Fan2 Fan3 Fan4 Fan5 +Fan6 Fan7 Fan8 Fan9 Processor10 Processor11 Processor12 Processor13 +LCD14 intel_powerclamp15 1 65.0 65 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 2 +65.0 66 65 0 0 0 0 0 0 0 0 0 0 4 4 4 4 6 0 3 65.0 60 54 0 0 0 0 0 0 0 0 +0 0 4 4 4 4 6 0 4 65.0 53 53 0 0 0 0 0 0 0 0 0 0 4 4 4 4 6 0 +5 65.0 52 52 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 +6 65.0 53 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 +7 65.0 68 70 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 +8 65.0 68 68 0 0 0 0 0 0 0 0 0 0 5 5 5 5 6 0 +9 65.0 68 68 0 0 0 0 0 0 0 0 0 0 6 6 6 6 6 0 +10 65.0 67 67 0 0 0 0 0 0 0 0 0 0 7 7 7 7 6 0 +11 65.0 67 67 0 0 0 0 0 0 0 0 0 0 8 8 8 8 6 0 +12 65.0 67 67 0 0 0 0 0 0 0 0 0 0 8 8 8 8 6 0 +13 65.0 67 67 0 0 0 0 0 0 0 0 0 0 9 9 9 9 6 0 +14 65.0 66 66 0 0 0 0 0 0 0 0 0 0 10 10 10 10 6 0 +15 65.0 66 67 0 0 0 0 0 0 0 0 0 0 10 10 10 10 6 0 +16 65.0 66 66 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0 +17 65.0 66 66 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0 +18 65.0 64 61 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0 +19 65.0 60 59 0 0 0 0 0 0 0 0 0 0 12 12 12 12 6 0 + +Data can be read directly into an array by an example R-script below: + +#!/usr/bin/Rscript +tdata <- read.table("/var/tmp/tmon.log", header=T, comment.char="#") +attach(tdata) +jpeg("tmon.jpg") +X11() +g_range <- range(0, intel_powerclamp15, TargetTemp, acpitz0) +plot( Samples, intel_powerclamp15, col="blue", ylim=g_range, axes=FALSE, ann=FALSE) +par(new=TRUE) +lines(TargetTemp, type="o", pch=22, lty=2, col="red") +dev.off() diff --git a/tools/thermal/tmon/tmon.c b/tools/thermal/tmon/tmon.c new file mode 100644 index 00000000000..09b7c321833 --- /dev/null +++ b/tools/thermal/tmon/tmon.c @@ -0,0 +1,376 @@ +/* + * tmon.c Thermal Monitor (TMON) main function and entry point + * + * Copyright (C) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 or later as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * Author: Jacob Pan <jacob.jun.pan@linux.intel.com> + * + */ + +#include <getopt.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <ncurses.h> +#include <ctype.h> +#include <time.h> +#include <signal.h> +#include <limits.h> +#include <sys/time.h> +#include <pthread.h> +#include <math.h> +#include <stdarg.h> +#include <syslog.h> + +#include "tmon.h" + +unsigned long ticktime = 1; /* seconds */ +unsigned long no_control = 1; /* monitoring only or use cooling device for +			       * temperature control. +			       */ +double time_elapsed = 0.0; +unsigned long target_temp_user = 65; /* can be select by tui later */ +int dialogue_on; +int tmon_exit; +static short	daemon_mode; +static int logging; /* for recording thermal data to a file */ +static int debug_on; +FILE *tmon_log; +/*cooling device used for the PID controller */ +char ctrl_cdev[CDEV_NAME_SIZE] = "None"; +int target_thermal_zone; /* user selected target zone instance */ +static void	start_daemon_mode(void); + +pthread_t event_tid; +pthread_mutex_t input_lock; +void usage() +{ +	printf("Usage: tmon [OPTION...]\n"); +	printf("  -c, --control         cooling device in control\n"); +	printf("  -d, --daemon          run as daemon, no TUI\n"); +	printf("  -g, --debug           debug message in syslog\n"); +	printf("  -h, --help            show this help message\n"); +	printf("  -l, --log             log data to /var/tmp/tmon.log\n"); +	printf("  -t, --time-interval   sampling time interval, > 1 sec.\n"); +	printf("  -v, --version         show version\n"); +	printf("  -z, --zone            target thermal zone id\n"); + +	exit(0); +} + +void version() +{ +	printf("TMON version %s\n", VERSION); +	exit(EXIT_SUCCESS); +} + +static void tmon_cleanup(void) +{ + +	syslog(LOG_INFO, "TMON exit cleanup\n"); +	fflush(stdout); +	refresh(); +	if (tmon_log) +		fclose(tmon_log); +	if (event_tid) { +		pthread_mutex_lock(&input_lock); +		pthread_cancel(event_tid); +		pthread_mutex_unlock(&input_lock); +		pthread_mutex_destroy(&input_lock); +	} +	closelog(); +	/* relax control knobs, undo throttling */ +	set_ctrl_state(0); + +	keypad(stdscr, FALSE); +	echo(); +	nocbreak(); +	close_windows(); +	endwin(); +	free_thermal_data(); + +	exit(1); +} + + +static void tmon_sig_handler(int sig) +{ +	syslog(LOG_INFO, "TMON caught signal %d\n", sig); +	refresh(); +	switch (sig) { +	case SIGTERM: +		printf("sigterm, exit and clean up\n"); +		fflush(stdout); +		break; +	case SIGKILL: +		printf("sigkill, exit and clean up\n"); +		fflush(stdout); +		break; +	case SIGINT: +		printf("ctrl-c, exit and clean up\n"); +		fflush(stdout); +		break; +	default: +		break; +	} +	tmon_exit = true; +} + + +static void start_syslog(void) +{ +	if (debug_on) +		setlogmask(LOG_UPTO(LOG_DEBUG)); +	else +		setlogmask(LOG_UPTO(LOG_ERR)); +	openlog("tmon.log", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL0); +	syslog(LOG_NOTICE, "TMON started by User %d", getuid()); +} + +static void prepare_logging(void) +{ +	int i; +	struct stat logstat; + +	if (!logging) +		return; +	/* open local data log file */ +	tmon_log = fopen(TMON_LOG_FILE, "w+"); +	if (!tmon_log) { +		syslog(LOG_ERR, "failed to open log file %s\n", TMON_LOG_FILE); +		return; +	} + +	if (lstat(TMON_LOG_FILE, &logstat) < 0) { +		syslog(LOG_ERR, "Unable to stat log file %s\n", TMON_LOG_FILE); +		fclose(tmon_log); +		tmon_log = NULL; +		return; +	} + +	/* The log file must be a regular file owned by us */ +	if (S_ISLNK(logstat.st_mode)) { +		syslog(LOG_ERR, "Log file is a symlink.  Will not log\n"); +		fclose(tmon_log); +		tmon_log = NULL; +		return; +	} + +	if (logstat.st_uid != getuid()) { +		syslog(LOG_ERR, "We don't own the log file.  Not logging\n"); +		fclose(tmon_log); +		tmon_log = NULL; +		return; +	} + + +	fprintf(tmon_log, "#----------- THERMAL SYSTEM CONFIG -------------\n"); +	for (i = 0; i < ptdata.nr_tz_sensor; i++) { +		char binding_str[33]; /* size of long + 1 */ +		int j; + +		memset(binding_str, 0, sizeof(binding_str)); +		for (j = 0; j < 32; j++) +			binding_str[j] = (ptdata.tzi[i].cdev_binding & 1<<j) ? +				'1' : '0'; + +		fprintf(tmon_log, "#thermal zone %s%02d cdevs binding: %32s\n", +			ptdata.tzi[i].type, +			ptdata.tzi[i].instance, +			binding_str); +		for (j = 0; j <	ptdata.tzi[i].nr_trip_pts; j++) { +			fprintf(tmon_log, "#\tTP%02d type:%s, temp:%lu\n", j, +				trip_type_name[ptdata.tzi[i].tp[j].type], +				ptdata.tzi[i].tp[j].temp); +		} + +	} + +	for (i = 0; i <	ptdata.nr_cooling_dev; i++) +		fprintf(tmon_log, "#cooling devices%02d: %s\n", +			i, ptdata.cdi[i].type); + +	fprintf(tmon_log, "#---------- THERMAL DATA LOG STARTED -----------\n"); +	fprintf(tmon_log, "Samples TargetTemp "); +	for (i = 0; i < ptdata.nr_tz_sensor; i++) { +		fprintf(tmon_log, "%s%d    ", ptdata.tzi[i].type, +			ptdata.tzi[i].instance); +	} +	for (i = 0; i <	ptdata.nr_cooling_dev; i++) +		fprintf(tmon_log, "%s%d ", ptdata.cdi[i].type, +			ptdata.cdi[i].instance); + +	fprintf(tmon_log, "\n"); +} + +static struct option opts[] = { +	{ "control", 1, NULL, 'c' }, +	{ "daemon", 0, NULL, 'd' }, +	{ "time-interval", 1, NULL, 't' }, +	{ "log", 0, NULL, 'l' }, +	{ "help", 0, NULL, 'h' }, +	{ "version", 0, NULL, 'v' }, +	{ "debug", 0, NULL, 'g' }, +	{ 0, 0, NULL, 0 } +}; + + +int main(int argc, char **argv) +{ +	int err = 0; +	int id2 = 0, c; +	double yk = 0.0; /* controller output */ +	int target_tz_index; + +	if (geteuid() != 0) { +		printf("TMON needs to be run as root\n"); +		exit(EXIT_FAILURE); +	} + +	while ((c = getopt_long(argc, argv, "c:dlht:vgz:", opts, &id2)) != -1) { +		switch (c) { +		case 'c': +			no_control = 0; +			strncpy(ctrl_cdev, optarg, CDEV_NAME_SIZE); +			break; +		case 'd': +			start_daemon_mode(); +			printf("Run TMON in daemon mode\n"); +			break; +		case 't': +			ticktime = strtod(optarg, NULL); +			if (ticktime < 1) +				ticktime = 1; +			break; +		case 'l': +			printf("Logging data to /var/tmp/tmon.log\n"); +			logging = 1; +			break; +		case 'h': +			usage(); +			break; +		case 'v': +			version(); +			break; +		case 'g': +			debug_on = 1; +			break; +		case 'z': +			target_thermal_zone = strtod(optarg, NULL); +			break; +		default: +			break; +		} +	} +	if (pthread_mutex_init(&input_lock, NULL) != 0) { +		fprintf(stderr, "\n mutex init failed, exit\n"); +		return 1; +	} +	start_syslog(); +	if (signal(SIGINT, tmon_sig_handler) == SIG_ERR) +		syslog(LOG_DEBUG, "Cannot handle SIGINT\n"); +	if (signal(SIGTERM, tmon_sig_handler) == SIG_ERR) +		syslog(LOG_DEBUG, "Cannot handle SIGINT\n"); + +	if (probe_thermal_sysfs()) { +		pthread_mutex_destroy(&input_lock); +		closelog(); +		return -1; +	} +	initialize_curses(); +	setup_windows(); +	signal(SIGWINCH, resize_handler); +	show_title_bar(); +	show_sensors_w(); +	show_cooling_device(); +	update_thermal_data(); +	show_data_w(); +	prepare_logging(); +	init_thermal_controller(); + +	nodelay(stdscr, TRUE); +	err = pthread_create(&event_tid, NULL, &handle_tui_events, NULL); +	if (err != 0) { +		printf("\ncan't create thread :[%s]", strerror(err)); +		tmon_cleanup(); +		exit(EXIT_FAILURE); +	} + +	/* validate range of user selected target zone, default to the first +	 * instance if out of range +	 */ +	target_tz_index = zone_instance_to_index(target_thermal_zone); +	if (target_tz_index < 0) { +		target_thermal_zone = ptdata.tzi[0].instance; +		syslog(LOG_ERR, "target zone is not found, default to %d\n", +			target_thermal_zone); +	} +	while (1) { +		sleep(ticktime); +		show_title_bar(); +		show_sensors_w(); +		update_thermal_data(); +		if (!dialogue_on) { +			show_data_w(); +			show_cooling_device(); +		} +		cur_thermal_record++; +		time_elapsed += ticktime; +		controller_handler(trec[0].temp[target_tz_index] / 1000, +				&yk); +		trec[0].pid_out_pct = yk; +		if (!dialogue_on) +			show_control_w(); +		if (tmon_exit) +			break; +	} +	tmon_cleanup(); +	return 0; +} + +static void start_daemon_mode() +{ +	daemon_mode = 1; +	/* fork */ +	pid_t	sid, pid = fork(); +	if (pid < 0) { +		exit(EXIT_FAILURE); +	} else if (pid > 0) +		/* kill parent */ +		exit(EXIT_SUCCESS); + +	/* disable TUI, it may not be necessary, but saves some resource */ +	disable_tui(); + +	/* change the file mode mask */ +	umask(S_IWGRP | S_IWOTH); + +	/* new SID for the daemon process */ +	sid = setsid(); +	if (sid < 0) +		exit(EXIT_FAILURE); + +	/* change working directory */ +	if ((chdir("/")) < 0) +		exit(EXIT_FAILURE); + + +	sleep(10); + +	close(STDIN_FILENO); +	close(STDOUT_FILENO); +	close(STDERR_FILENO); + +} diff --git a/tools/thermal/tmon/tmon.h b/tools/thermal/tmon/tmon.h new file mode 100644 index 00000000000..9e3c49c547a --- /dev/null +++ b/tools/thermal/tmon/tmon.h @@ -0,0 +1,204 @@ +/* + * tmon.h contains data structures and constants used by TMON + * + * Copyright (C) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 or later as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * Author Name Jacob Pan <jacob.jun.pan@linux.intel.com> + * + */ + +#ifndef TMON_H +#define TMON_H + +#define MAX_DISP_TEMP 125 +#define MAX_CTRL_TEMP 105 +#define MIN_CTRL_TEMP 40 +#define MAX_NR_TZONE 16 +#define MAX_NR_CDEV 32 +#define MAX_NR_TRIP 16 +#define MAX_NR_CDEV_TRIP 12 /* number of cooling devices that can bind +			     * to a thermal zone trip. +			     */ +#define MAX_TEMP_KC 140000 +/* starting char position to draw sensor data, such as tz names + * trip point list, etc. + */ +#define DATA_LEFT_ALIGN 10 +#define NR_LINES_TZDATA 1 +#define TMON_LOG_FILE "/var/tmp/tmon.log" + +extern unsigned long ticktime; +extern double time_elapsed; +extern unsigned long target_temp_user; +extern int dialogue_on; +extern char ctrl_cdev[]; +extern pthread_mutex_t input_lock; +extern int tmon_exit; +extern int target_thermal_zone; +/* use fixed size record to simplify data processing and transfer + * TBD: more info to be added, e.g. programmable trip point data. +*/ +struct thermal_data_record { +	struct timeval tv; +	unsigned long temp[MAX_NR_TZONE]; +	double pid_out_pct; +}; + +struct cdev_info { +	char type[64]; +	int instance; +	unsigned long max_state; +	unsigned long cur_state; +	unsigned long flag; +}; + +enum trip_type { +	THERMAL_TRIP_CRITICAL, +	THERMAL_TRIP_HOT, +	THERMAL_TRIP_PASSIVE, +	THERMAL_TRIP_ACTIVE, +	NR_THERMAL_TRIP_TYPE, +}; + +struct trip_point { +	enum trip_type type; +	unsigned long temp; +	unsigned long hysteresis; +	int attribute; /* programmability etc. */ +}; + +/* thermal zone configuration information, binding with cooling devices could + * change at runtime. + */ +struct tz_info { +	char type[256]; /* e.g. acpitz */ +	int instance; +	int passive; /* active zone has passive node to force passive mode */ +	int nr_cdev; /* number of cooling device binded */ +	int nr_trip_pts; +	struct trip_point tp[MAX_NR_TRIP]; +	unsigned long cdev_binding; /* bitmap for attached cdevs */ +	/* cdev bind trip points, allow one cdev bind to multiple trips */ +	unsigned long trip_binding[MAX_NR_CDEV]; +}; + +struct tmon_platform_data { +	int nr_tz_sensor; +	int nr_cooling_dev; +	/* keep track of instance ids since there might be gaps */ +	int max_tz_instance; +	int max_cdev_instance; +	struct tz_info *tzi; +	struct cdev_info *cdi; +}; + +struct control_ops { +	void (*set_ratio)(unsigned long ratio); +	unsigned long (*get_ratio)(unsigned long ratio); + +}; + +enum cdev_types { +	CDEV_TYPE_PROC, +	CDEV_TYPE_FAN, +	CDEV_TYPE_MEM, +	CDEV_TYPE_NR, +}; + +/* REVISIT: the idea is to group sensors if possible, e.g. on intel mid + * we have "skin0", "skin1", "sys", "msicdie" + * on DPTF enabled systems, we might have PCH, TSKN, TAMB, etc. + */ +enum tzone_types { +	TZONE_TYPE_ACPI, +	TZONE_TYPE_PCH, +	TZONE_TYPE_NR, +}; + +/* limit the output of PID controller adjustment */ +#define LIMIT_HIGH (95) +#define LIMIT_LOW  (2) + +struct pid_params { +	double kp;  /* Controller gain from Dialog Box */ +	double ki;  /* Time-constant for I action from Dialog Box */ +	double kd;  /* Time-constant for D action from Dialog Box */ +	double ts; +	double k_lpf; + +	double t_target; +	double y_k; +}; + +extern int init_thermal_controller(void); +extern void controller_handler(const double xk, double *yk); + +extern struct tmon_platform_data ptdata; +extern struct pid_params p_param; + +extern FILE *tmon_log; +extern int cur_thermal_record; /* index to the trec array */ +extern struct thermal_data_record trec[]; +extern const char *trip_type_name[]; +extern unsigned long no_control; + +extern void initialize_curses(void); +extern void show_controller_stats(char *line); +extern void show_title_bar(void); +extern void setup_windows(void); +extern void disable_tui(void); +extern void show_sensors_w(void); +extern void show_data_w(void); +extern void write_status_bar(int x, char *line); +extern void show_control_w(); + +extern void show_cooling_device(void); +extern void show_dialogue(void); +extern int update_thermal_data(void); + +extern int probe_thermal_sysfs(void); +extern void free_thermal_data(void); +extern	void resize_handler(int sig); +extern void set_ctrl_state(unsigned long state); +extern void get_ctrl_state(unsigned long *state); +extern void *handle_tui_events(void *arg); +extern int sysfs_set_ulong(char *path, char *filename, unsigned long val); +extern int zone_instance_to_index(int zone_inst); +extern void close_windows(void); + +#define PT_COLOR_DEFAULT    1 +#define PT_COLOR_HEADER_BAR 2 +#define PT_COLOR_ERROR      3 +#define PT_COLOR_RED        4 +#define PT_COLOR_YELLOW     5 +#define PT_COLOR_GREEN      6 +#define PT_COLOR_BRIGHT     7 +#define PT_COLOR_BLUE	    8 + +/* each thermal zone uses 12 chars, 8 for name, 2 for instance, 2 space + * also used to list trip points in forms of AAAC, which represents + * A: Active + * C: Critical + */ +#define TZONE_RECORD_SIZE 12 +#define TZ_LEFT_ALIGN 32 +#define CDEV_NAME_SIZE 20 +#define CDEV_FLAG_IN_CONTROL (1 << 0) + +/* dialogue box starts */ +#define DIAG_X 48 +#define DIAG_Y 8 +#define THERMAL_SYSFS "/sys/class/thermal" +#define CDEV "cooling_device" +#define TZONE "thermal_zone" +#define TDATA_LEFT 16 +#endif /* TMON_H */ diff --git a/tools/thermal/tmon/tui.c b/tools/thermal/tmon/tui.c new file mode 100644 index 00000000000..89f8ef0e15c --- /dev/null +++ b/tools/thermal/tmon/tui.c @@ -0,0 +1,638 @@ +/* + * tui.c ncurses text user interface for TMON program + * + * Copyright (C) 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 or later as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * Author: Jacob Pan <jacob.jun.pan@linux.intel.com> + * + */ + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <ncurses.h> +#include <time.h> +#include <syslog.h> +#include <panel.h> +#include <pthread.h> +#include <signal.h> + +#include "tmon.h" + +static PANEL *data_panel; +static PANEL *dialogue_panel; +static PANEL *top; + +static WINDOW *title_bar_window; +static WINDOW *tz_sensor_window; +static WINDOW *cooling_device_window; +static WINDOW *control_window; +static WINDOW *status_bar_window; +static WINDOW *thermal_data_window; +static WINDOW *dialogue_window; + +char status_bar_slots[10][40]; +static void draw_hbar(WINDOW *win, int y, int start, int len, +		unsigned long pattern, bool end); + +static int maxx, maxy; +static int maxwidth = 200; + +#define TITLE_BAR_HIGHT 1 +#define SENSOR_WIN_HIGHT 4 /* one row for tz name, one for trip points */ + + +/* daemon mode flag (set by startup parameter -d) */ +static int  tui_disabled; + +static void close_panel(PANEL *p) +{ +	if (p) { +		del_panel(p); +		p = NULL; +	} +} + +static void close_window(WINDOW *win) +{ +	if (win) { +		delwin(win); +		win = NULL; +	} +} + +void close_windows(void) +{ +	if (tui_disabled) +		return; +	/* must delete panels before their attached windows */ +	if (dialogue_window) +		close_panel(dialogue_panel); +	if (cooling_device_window) +		close_panel(data_panel); + +	close_window(title_bar_window); +	close_window(tz_sensor_window); +	close_window(status_bar_window); +	close_window(cooling_device_window); +	close_window(control_window); +	close_window(thermal_data_window); +	close_window(dialogue_window); + +} + +void write_status_bar(int x, char *line) +{ +	mvwprintw(status_bar_window, 0, x, "%s", line); +	wrefresh(status_bar_window); +} + +void setup_windows(void) +{ +	int y_begin = 1; + +	if (tui_disabled) +		return; + +	getmaxyx(stdscr, maxy, maxx); +	resizeterm(maxy, maxx); + +	title_bar_window = subwin(stdscr, TITLE_BAR_HIGHT, maxx, 0, 0); +	y_begin += TITLE_BAR_HIGHT; + +	tz_sensor_window = subwin(stdscr, SENSOR_WIN_HIGHT, maxx, y_begin, 0); +	y_begin += SENSOR_WIN_HIGHT; + +	cooling_device_window = subwin(stdscr, ptdata.nr_cooling_dev + 3, maxx, +				y_begin, 0); +	y_begin += ptdata.nr_cooling_dev + 3; /* 2 lines for border */ +	/* two lines to show borders, one line per tz show trip point position +	 * and value. +	 * dialogue window is a pop-up, when needed it lays on top of cdev win +	 */ + +	dialogue_window = subwin(stdscr, ptdata.nr_cooling_dev+5, maxx-50, +				DIAG_Y, DIAG_X); + +	thermal_data_window = subwin(stdscr, ptdata.nr_tz_sensor * +				NR_LINES_TZDATA + 3, maxx, y_begin, 0); +	y_begin += ptdata.nr_tz_sensor * NR_LINES_TZDATA + 3; +	control_window = subwin(stdscr, 4, maxx, y_begin, 0); + +	scrollok(cooling_device_window, TRUE); +	maxwidth = maxx - 18; +	status_bar_window = subwin(stdscr, 1, maxx, maxy-1, 0); + +	strcpy(status_bar_slots[0], " Ctrl-c - Quit "); +	strcpy(status_bar_slots[1], " TAB - Tuning "); +	wmove(status_bar_window, 1, 30); + +	/* prepare panels for dialogue, if panel already created then we must +	 * be doing resizing, so just replace windows with new ones, old ones +	 * should have been deleted by close_window +	 */ +	data_panel = new_panel(cooling_device_window); +	if (!data_panel) +		syslog(LOG_DEBUG, "No data panel\n"); +	else { +		if (dialogue_window) { +			dialogue_panel = new_panel(dialogue_window); +			if (!dialogue_panel) +				syslog(LOG_DEBUG, "No dialogue panel\n"); +			else { +				/* Set up the user pointer to the next panel*/ +				set_panel_userptr(data_panel, dialogue_panel); +				set_panel_userptr(dialogue_panel, data_panel); +				top = data_panel; +			} +		} else +			syslog(LOG_INFO, "no dialogue win, term too small\n"); +	} +	doupdate(); +	werase(stdscr); +	refresh(); +} + +void resize_handler(int sig) +{ +	/* start over when term gets resized, but first we clean up */ +	close_windows(); +	endwin(); +	refresh(); +	clear(); +	getmaxyx(stdscr, maxy, maxx);  /* get the new screen size */ +	setup_windows(); +	/* rate limit */ +	sleep(1); +	syslog(LOG_DEBUG, "SIG %d, term resized to %d x %d\n", +		sig, maxy, maxx); +	signal(SIGWINCH, resize_handler); +} + +const char cdev_title[] = " COOLING DEVICES "; +void show_cooling_device(void) +{ +	int i, j, x, y = 0; + +	if (tui_disabled || !cooling_device_window) +		return; + +	werase(cooling_device_window); +	wattron(cooling_device_window, A_BOLD); +	mvwprintw(cooling_device_window,  1, 1, +		"ID  Cooling Dev   Cur    Max   Thermal Zone Binding"); +	wattroff(cooling_device_window, A_BOLD); +	for (j = 0; j <	ptdata.nr_cooling_dev; j++) { +		/* draw cooling device list on the left in the order of +		 * cooling device instances. skip unused idr. +		 */ +		mvwprintw(cooling_device_window, j + 2, 1, +			"%02d %12.12s%6d %6d", +			ptdata.cdi[j].instance, +			ptdata.cdi[j].type, +			ptdata.cdi[j].cur_state, +			ptdata.cdi[j].max_state); +	} + +	/* show cdev binding, y is the global cooling device instance */ +	for (i = 0; i < ptdata.nr_tz_sensor; i++) { +		int tz_inst = ptdata.tzi[i].instance; +		for (j = 0; j < ptdata.nr_cooling_dev; j++) { +			int cdev_inst; +			y = j; +			x = tz_inst * TZONE_RECORD_SIZE + TZ_LEFT_ALIGN; + +			draw_hbar(cooling_device_window, y+2, x, +				TZONE_RECORD_SIZE-1, ACS_VLINE, false); + +			/* draw a column of spaces to separate thermal zones */ +			mvwprintw(cooling_device_window, y+2, x-1, " "); +			if (ptdata.tzi[i].cdev_binding) { +				cdev_inst = ptdata.cdi[j].instance; +				unsigned long trip_binding = +					ptdata.tzi[i].trip_binding[cdev_inst]; +				int k = 0; /* per zone trip point id that +					    * binded to this cdev, one to +					    * many possible based on the +					    * binding bitmask. +					    */ +				syslog(LOG_DEBUG, +					"bind tz%d cdev%d tp%lx %d cdev%lx\n", +					i, j, trip_binding, y, +					ptdata.tzi[i].cdev_binding); +				/* draw each trip binding for the cdev */ +				while (trip_binding >>= 1) { +					k++; +					if (!(trip_binding & 1)) +						continue; +					/* draw '*' to show binding */ +					mvwprintw(cooling_device_window, +						y + 2, +						x + ptdata.tzi[i].nr_trip_pts - +						k - 1, "*"); +				} +			} +		} +	} +	/* draw border after data so that border will not be messed up +	 * even there is not enough space for all the data to be shown +	 */ +	wborder(cooling_device_window, 0, 0, 0, 0, 0, 0, 0, 0); +	wattron(cooling_device_window, A_BOLD); +	mvwprintw(cooling_device_window, 0, maxx/2 - sizeof(cdev_title), +		cdev_title); +	wattroff(cooling_device_window, A_BOLD); + +	wrefresh(cooling_device_window); +} + +const char DIAG_TITLE[] = "[ TUNABLES ]"; +#define DIAG_DEV_ROWS  5 +void show_dialogue(void) +{ +	int j, x = 0, y = 0; +	WINDOW *w = dialogue_window; + +	if (tui_disabled || !w) +		return; + +	werase(w); +	box(w, 0, 0); +	mvwprintw(w, 0, maxx/4, DIAG_TITLE); +	/* list all the available tunables */ +	for (j = 0; j <= ptdata.nr_cooling_dev; j++) { +		y = j % DIAG_DEV_ROWS; +		if (y == 0 && j != 0) +			x += 20; +		if (j == ptdata.nr_cooling_dev) +			/* save last choice for target temp */ +			mvwprintw(w, y+1, x+1, "%C-%.12s", 'A'+j, "Set Temp"); +		else +			mvwprintw(w, y+1, x+1, "%C-%.10s-%2d", 'A'+j, +				ptdata.cdi[j].type, ptdata.cdi[j].instance); +	} +	wattron(w, A_BOLD); +	mvwprintw(w, DIAG_DEV_ROWS+1, 1, "Enter Choice [A-Z]?"); +	wattroff(w, A_BOLD); +	/* y size of dialogue win is nr cdev + 5, so print legend +	 * at the bottom line +	 */ +	mvwprintw(w, ptdata.nr_cooling_dev+3, 1, +		"Legend: A=Active, P=Passive, C=Critical"); + +	wrefresh(dialogue_window); +} + +void write_dialogue_win(char *buf, int y, int x) +{ +	WINDOW *w = dialogue_window; + +	mvwprintw(w, y, x, "%s", buf); +} + +const char control_title[] = " CONTROLS "; +void show_control_w(void) +{ +	unsigned long state; + +	get_ctrl_state(&state); + +	if (tui_disabled || !control_window) +		return; + +	werase(control_window); +	mvwprintw(control_window, 1, 1, +		"PID gain: kp=%2.2f ki=%2.2f kd=%2.2f Output %2.2f", +		p_param.kp, p_param.ki, p_param.kd, p_param.y_k); + +	mvwprintw(control_window, 2, 1, +		"Target Temp: %2.1fC, Zone: %d, Control Device: %.12s", +		p_param.t_target, target_thermal_zone, ctrl_cdev); + +	/* draw border last such that everything is within boundary */ +	wborder(control_window, 0, 0, 0, 0, 0, 0, 0, 0); +	wattron(control_window, A_BOLD); +	mvwprintw(control_window, 0, maxx/2 - sizeof(control_title), +		control_title); +	wattroff(control_window, A_BOLD); + +	wrefresh(control_window); +} + +void initialize_curses(void) +{ +	if (tui_disabled) +		return; + +	initscr(); +	start_color(); +	keypad(stdscr, TRUE);	/* enable keyboard mapping */ +	nonl();			/* tell curses not to do NL->CR/NL on output */ +	cbreak();		/* take input chars one at a time */ +	noecho();		/* dont echo input */ +	curs_set(0);		/* turn off cursor */ +	use_default_colors(); + +	init_pair(PT_COLOR_DEFAULT, COLOR_WHITE, COLOR_BLACK); +	init_pair(PT_COLOR_HEADER_BAR, COLOR_BLACK, COLOR_WHITE); +	init_pair(PT_COLOR_ERROR, COLOR_BLACK, COLOR_RED); +	init_pair(PT_COLOR_RED, COLOR_WHITE, COLOR_RED); +	init_pair(PT_COLOR_YELLOW, COLOR_WHITE, COLOR_YELLOW); +	init_pair(PT_COLOR_GREEN, COLOR_WHITE, COLOR_GREEN); +	init_pair(PT_COLOR_BLUE, COLOR_WHITE, COLOR_BLUE); +	init_pair(PT_COLOR_BRIGHT, COLOR_WHITE, COLOR_BLACK); + +} + +void show_title_bar(void) +{ +	int i; +	int x = 0; + +	if (tui_disabled || !title_bar_window) +		return; + +	wattrset(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR)); +	wbkgd(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR)); +	werase(title_bar_window); + +	mvwprintw(title_bar_window, 0, 0, +		"     TMON v%s", VERSION); + +	wrefresh(title_bar_window); + +	werase(status_bar_window); + +	for (i = 0; i < 10; i++) { +		if (strlen(status_bar_slots[i]) == 0) +			continue; +		wattron(status_bar_window, A_REVERSE); +		mvwprintw(status_bar_window, 0, x, "%s", status_bar_slots[i]); +		wattroff(status_bar_window, A_REVERSE); +		x += strlen(status_bar_slots[i]) + 1; +	} +	wrefresh(status_bar_window); +} + +static void handle_input_val(int ch) +{ +	char buf[32]; +	int val; +	char path[256]; +	WINDOW *w = dialogue_window; + +	echo(); +	keypad(w, TRUE); +	wgetnstr(w, buf, 31); +	val = atoi(buf); + +	if (ch == ptdata.nr_cooling_dev) { +		snprintf(buf, 31, "Invalid Temp %d! %d-%d", val, +			MIN_CTRL_TEMP, MAX_CTRL_TEMP); +		if (val < MIN_CTRL_TEMP || val > MAX_CTRL_TEMP) +			write_status_bar(40, buf); +		else { +			p_param.t_target = val; +			snprintf(buf, 31, "Set New Target Temp %d", val); +			write_status_bar(40, buf); +		} +	} else { +		snprintf(path, 256, "%s/%s%d", THERMAL_SYSFS, +			CDEV, ptdata.cdi[ch].instance); +		sysfs_set_ulong(path, "cur_state", val); +	} +	noecho(); +	dialogue_on = 0; +	show_data_w(); +	show_control_w(); + +	top = (PANEL *)panel_userptr(top); +	top_panel(top); +} + +static void handle_input_choice(int ch) +{ +	char buf[48]; +	int base = 0; +	int cdev_id = 0; + +	if ((ch >= 'A' && ch <= 'A' + ptdata.nr_cooling_dev) || +		(ch >= 'a' && ch <= 'a' + ptdata.nr_cooling_dev)) { +		base = (ch < 'a') ? 'A' : 'a'; +		cdev_id = ch - base; +		if (ptdata.nr_cooling_dev == cdev_id) +			snprintf(buf, sizeof(buf), "New Target Temp:"); +		else +			snprintf(buf, sizeof(buf), "New Value for %.10s-%2d: ", +				ptdata.cdi[cdev_id].type, +				ptdata.cdi[cdev_id].instance); +		write_dialogue_win(buf, DIAG_DEV_ROWS+2, 2); +		handle_input_val(cdev_id); +	} else { +		snprintf(buf, sizeof(buf), "Invalid selection %d", ch); +		write_dialogue_win(buf, 8, 2); +	} +} + +void *handle_tui_events(void *arg) +{ +	int ch; + +	keypad(cooling_device_window, TRUE); +	while ((ch = wgetch(cooling_device_window)) != EOF) { +		if (tmon_exit) +			break; +		/* when term size is too small, no dialogue panels are set. +		 * we need to filter out such cases. +		 */ +		if (!data_panel || !dialogue_panel || +			!cooling_device_window || +			!dialogue_window) { + +			continue; +		} +		pthread_mutex_lock(&input_lock); +		if (dialogue_on) { +			handle_input_choice(ch); +			/* top panel filter */ +			if (ch == 'q' || ch == 'Q') +				ch = 0; +		} +		switch (ch) { +		case KEY_LEFT: +			box(cooling_device_window, 10, 0); +			break; +		case 9: /* TAB */ +			top = (PANEL *)panel_userptr(top); +			top_panel(top); +			if (top == dialogue_panel) { +				dialogue_on = 1; +				show_dialogue(); +			} else { +				dialogue_on = 0; +				/* force refresh */ +				show_data_w(); +				show_control_w(); +			} +			break; +		case 'q': +		case 'Q': +			tmon_exit = 1; +			break; +		} +		update_panels(); +		doupdate(); +		pthread_mutex_unlock(&input_lock); +	} + +	if (arg) +		*(int *)arg = 0; /* make gcc happy */ + +	return NULL; +} + +/* draw a horizontal bar in given pattern */ +static void draw_hbar(WINDOW *win, int y, int start, int len, unsigned long ptn, +		bool end) +{ +	mvwaddch(win, y, start, ptn); +	whline(win, ptn, len); +	if (end) +		mvwaddch(win, y, MAX_DISP_TEMP+TDATA_LEFT, ']'); +} + +static char trip_type_to_char(int type) +{ +	switch (type) { +	case THERMAL_TRIP_CRITICAL: return 'C'; +	case THERMAL_TRIP_HOT: return 'H'; +	case THERMAL_TRIP_PASSIVE: return 'P'; +	case THERMAL_TRIP_ACTIVE: return 'A'; +	default: +		return '?'; +	} +} + +/* fill a string with trip point type and value in one line + * e.g.      P(56)    C(106) + * maintain the distance one degree per char + */ +static void draw_tp_line(int tz, int y) +{ +	int j; +	int x; + +	for (j = 0; j < ptdata.tzi[tz].nr_trip_pts; j++) { +		x = ptdata.tzi[tz].tp[j].temp / 1000; +		mvwprintw(thermal_data_window, y + 0, x + TDATA_LEFT, +			"%c%d", trip_type_to_char(ptdata.tzi[tz].tp[j].type), +			x); +		syslog(LOG_INFO, "%s:tz %d tp %d temp = %lu\n", __func__, +			tz, j, ptdata.tzi[tz].tp[j].temp); +	} +} + +const char data_win_title[] = " THERMAL DATA "; +void show_data_w(void) +{ +	int i; + + +	if (tui_disabled || !thermal_data_window) +		return; + +	werase(thermal_data_window); +	wattron(thermal_data_window, A_BOLD); +	mvwprintw(thermal_data_window, 0, maxx/2 - sizeof(data_win_title), +		data_win_title); +	wattroff(thermal_data_window, A_BOLD); +	/* draw a line as ruler */ +	for (i = 10; i < MAX_DISP_TEMP; i += 10) +		mvwprintw(thermal_data_window, 1, i+TDATA_LEFT, "%2d", i); + +	for (i = 0; i < ptdata.nr_tz_sensor; i++) { +		int temp = trec[cur_thermal_record].temp[i] / 1000; +		int y = 0; + +		y = i * NR_LINES_TZDATA + 2; +		/* y at tz temp data line */ +		mvwprintw(thermal_data_window, y, 1, "%6.6s%2d:[%3d][", +			ptdata.tzi[i].type, +			ptdata.tzi[i].instance, temp); +		draw_hbar(thermal_data_window, y, TDATA_LEFT, temp, ACS_RARROW, +			true); +		draw_tp_line(i, y); +	} +	wborder(thermal_data_window, 0, 0, 0, 0, 0, 0, 0, 0); +	wrefresh(thermal_data_window); +} + +const char tz_title[] = "THERMAL ZONES(SENSORS)"; + +void show_sensors_w(void) +{ +	int i, j; +	char buffer[512]; + +	if (tui_disabled || !tz_sensor_window) +		return; + +	werase(tz_sensor_window); + +	memset(buffer, 0, sizeof(buffer)); +	wattron(tz_sensor_window, A_BOLD); +	mvwprintw(tz_sensor_window, 1, 1, "Thermal Zones:"); +	wattroff(tz_sensor_window, A_BOLD); + +	mvwprintw(tz_sensor_window, 1, TZ_LEFT_ALIGN, "%s", buffer); +	/* fill trip points for each tzone */ +	wattron(tz_sensor_window, A_BOLD); +	mvwprintw(tz_sensor_window, 2, 1, "Trip Points:"); +	wattroff(tz_sensor_window, A_BOLD); + +	/* draw trip point from low to high for each tz */ +	for (i = 0; i < ptdata.nr_tz_sensor; i++) { +		int inst = ptdata.tzi[i].instance; + +		mvwprintw(tz_sensor_window, 1, +			TZ_LEFT_ALIGN+TZONE_RECORD_SIZE * inst, "%.9s%02d", +			ptdata.tzi[i].type, ptdata.tzi[i].instance); +		for (j = ptdata.tzi[i].nr_trip_pts - 1; j >= 0; j--) { +			/* loop through all trip points */ +			char type; +			int tp_pos; +			/* reverse the order here since trips are sorted +			 * in ascending order in terms of temperature. +			 */ +			tp_pos = ptdata.tzi[i].nr_trip_pts - j - 1; + +			type = trip_type_to_char(ptdata.tzi[i].tp[j].type); +			mvwaddch(tz_sensor_window, 2, +				inst * TZONE_RECORD_SIZE + TZ_LEFT_ALIGN + +				tp_pos,	type); +			syslog(LOG_DEBUG, "draw tz %d tp %d ch:%c\n", +				inst, j, type); +		} +	} +	wborder(tz_sensor_window, 0, 0, 0, 0, 0, 0, 0, 0); +	wattron(tz_sensor_window, A_BOLD); +	mvwprintw(tz_sensor_window, 0, maxx/2 - sizeof(tz_title), tz_title); +	wattroff(tz_sensor_window, A_BOLD); +	wrefresh(tz_sensor_window); +} + +void disable_tui(void) +{ +	tui_disabled = 1; +}  | 
