summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/Makefile15
-rw-r--r--tools/thermal/tmon/Makefile47
-rw-r--r--tools/thermal/tmon/README50
-rw-r--r--tools/thermal/tmon/pid.c131
-rw-r--r--tools/thermal/tmon/sysfs.c596
-rw-r--r--tools/thermal/tmon/tmon.8142
-rw-r--r--tools/thermal/tmon/tmon.c352
-rw-r--r--tools/thermal/tmon/tmon.h204
-rw-r--r--tools/thermal/tmon/tui.c638
9 files changed, 2173 insertions, 2 deletions
diff --git a/tools/Makefile b/tools/Makefile
index 41067f304215..a9b02008443c 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -15,6 +15,7 @@ help:
@echo ' net - misc networking tools'
@echo ' vm - misc vm tools'
@echo ' x86_energy_perf_policy - Intel energy policy tool'
+ @echo ' tmon - thermal monitoring and tuning tool'
@echo ''
@echo 'You can do:'
@echo ' $$ make -C tools/ <tool>_install'
@@ -50,6 +51,9 @@ selftests: FORCE
turbostat x86_energy_perf_policy: FORCE
$(call descend,power/x86/$@)
+tmon: FORCE
+ $(call descend,thermal/$@)
+
cpupower_install:
$(call descend,power/$(@:_install=),install)
@@ -62,9 +66,13 @@ selftests_install:
turbostat_install x86_energy_perf_policy_install:
$(call descend,power/x86/$(@:_install=),install)
+tmon_install:
+ $(call descend,thermal/$(@:_install=),install)
+
install: cgroup_install cpupower_install firewire_install lguest_install \
perf_install selftests_install turbostat_install usb_install \
- virtio_install vm_install net_install x86_energy_perf_policy_install
+ virtio_install vm_install net_install x86_energy_perf_policy_install \
+ tmon
cpupower_clean:
$(call descend,power/cpupower,clean)
@@ -84,8 +92,11 @@ selftests_clean:
turbostat_clean x86_energy_perf_policy_clean:
$(call descend,power/x86/$(@:_clean=),clean)
+tmon_clean:
+ $(call descend,thermal/tmon,clean)
+
clean: cgroup_clean cpupower_clean firewire_clean lguest_clean perf_clean \
selftests_clean turbostat_clean usb_clean virtio_clean \
- vm_clean net_clean x86_energy_perf_policy_clean
+ vm_clean net_clean x86_energy_perf_policy_clean tmon_clean
.PHONY: FORCE
diff --git a/tools/thermal/tmon/Makefile b/tools/thermal/tmon/Makefile
new file mode 100644
index 000000000000..447321104ec0
--- /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 -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 000000000000..457949897a8e
--- /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 000000000000..fd7e9e9d6f4a
--- /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 000000000000..dfe454855cd2
--- /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 000000000000..0be727cb9892
--- /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 000000000000..b30f531173e4
--- /dev/null
+++ b/tools/thermal/tmon/tmon.c
@@ -0,0 +1,352 @@
+/*
+ * 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-interv