aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2013-11-14 00:42:31 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2013-11-14 00:42:31 -0500
commit549608eadb31eac5d579ed70a21ac722bdf72861 (patch)
tree0ecc4b03ed9feca47b7b5beb2f84d27f0c6f9d8d /tools
parent2f466d33f5f60542d3d82c0477de5863b22c94b9 (diff)
parent86e0a0bdf81c2dfa2a5a258dbb52f49c40ebc197 (diff)
Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux
Pull thermal management updates from Zhang Rui: "This time we only have a few changes as there are no soc thermal changes from Eduardo. The only big change is the introduction of TMON, a tool to help visualize, tune, and test the thermal subsystem. The rest is mostly cleanups and fixes all over. Specifics: - introduce TMON, a tool base on thermal sysfs I/F. It can be used to visualize, tune and test the thermal subsystem. - fix a zone/cooling device binding problem, when both thermal zone bind parameters and .bind() callback are available" * 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux: tools/thermal: Introduce tmon, a tool for thermal subsystem thermal: Fix binding problem when there is thermal zone params thermal: cpu_cooling: fix return value check in cpufreq_cooling_register() Thermal: Check for validity before doing kfree thermal/intel_powerclamp: Add newer CPU models Thermal: Tidy up error handling in powerclamp_init thermal: Kconfig: cosmetic fixes ACPI/thermal : Remove zone disabled warning typo in drivers/thermal/Kconfig: lpatform instead of platform
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:
15 @echo ' net - misc networking tools' 15 @echo ' net - misc networking tools'
16 @echo ' vm - misc vm tools' 16 @echo ' vm - misc vm tools'
17 @echo ' x86_energy_perf_policy - Intel energy policy tool' 17 @echo ' x86_energy_perf_policy - Intel energy policy tool'
18 @echo ' tmon - thermal monitoring and tuning tool'
18 @echo '' 19 @echo ''
19 @echo 'You can do:' 20 @echo 'You can do:'
20 @echo ' $$ make -C tools/ <tool>_install' 21 @echo ' $$ make -C tools/ <tool>_install'
@@ -50,6 +51,9 @@ selftests: FORCE
50turbostat x86_energy_perf_policy: FORCE 51turbostat x86_energy_perf_policy: FORCE
51 $(call descend,power/x86/$@) 52 $(call descend,power/x86/$@)
52 53
54tmon: FORCE
55 $(call descend,thermal/$@)
56
53cpupower_install: 57cpupower_install:
54 $(call descend,power/$(@:_install=),install) 58 $(call descend,power/$(@:_install=),install)
55 59
@@ -62,9 +66,13 @@ selftests_install:
62turbostat_install x86_energy_perf_policy_install: 66turbostat_install x86_energy_perf_policy_install:
63 $(call descend,power/x86/$(@:_install=),install) 67 $(call descend,power/x86/$(@:_install=),install)
64 68
69tmon_install:
70 $(call descend,thermal/$(@:_install=),install)
71
65install: cgroup_install cpupower_install firewire_install lguest_install \ 72install: cgroup_install cpupower_install firewire_install lguest_install \
66 perf_install selftests_install turbostat_install usb_install \ 73 perf_install selftests_install turbostat_install usb_install \
67 virtio_install vm_install net_install x86_energy_perf_policy_install 74 virtio_install vm_install net_install x86_energy_perf_policy_install \
75 tmon
68 76
69cpupower_clean: 77cpupower_clean:
70 $(call descend,power/cpupower,clean) 78 $(call descend,power/cpupower,clean)
@@ -84,8 +92,11 @@ selftests_clean:
84turbostat_clean x86_energy_perf_policy_clean: 92turbostat_clean x86_energy_perf_policy_clean:
85 $(call descend,power/x86/$(@:_clean=),clean) 93 $(call descend,power/x86/$(@:_clean=),clean)
86 94
95tmon_clean:
96 $(call descend,thermal/tmon,clean)
97
87clean: cgroup_clean cpupower_clean firewire_clean lguest_clean perf_clean \ 98clean: cgroup_clean cpupower_clean firewire_clean lguest_clean perf_clean \
88 selftests_clean turbostat_clean usb_clean virtio_clean \ 99 selftests_clean turbostat_clean usb_clean virtio_clean \
89 vm_clean net_clean x86_energy_perf_policy_clean 100 vm_clean net_clean x86_energy_perf_policy_clean tmon_clean
90 101
91.PHONY: FORCE 102.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 @@
1VERSION = 1.0
2
3BINDIR=usr/bin
4WARNFLAGS=-Wall -Wshadow -W -Wformat -Wimplicit-function-declaration -Wimplicit-int
5CFLAGS= -O1 ${WARNFLAGS} -fstack-protector
6CC=gcc
7
8CFLAGS+=-D VERSION=\"$(VERSION)\"
9LDFLAGS+=
10TARGET=tmon
11
12INSTALL_PROGRAM=install -m 755 -p
13DEL_FILE=rm -f
14
15INSTALL_CONFIGFILE=install -m 644 -p
16CONFIG_FILE=
17CONFIG_PATH=
18
19
20OBJS = tmon.o tui.o sysfs.o pid.o
21OBJS +=
22
23tmon: $(OBJS) Makefile tmon.h
24 $(CC) ${CFLAGS} $(LDFLAGS) $(OBJS) -o $(TARGET) -lm -lpanel -lncursesw -lpthread
25
26valgrind: tmon
27 sudo valgrind -v --track-origins=yes --tool=memcheck --leak-check=yes --show-reachable=yes --num-callers=20 --track-fds=yes ./$(TARGET) 1> /dev/null
28
29install:
30 - mkdir -p $(INSTALL_ROOT)/$(BINDIR)
31 - $(INSTALL_PROGRAM) "$(TARGET)" "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)"
32 - mkdir -p $(INSTALL_ROOT)/$(CONFIG_PATH)
33 - $(INSTALL_CONFIGFILE) "$(CONFIG_FILE)" "$(INSTALL_ROOT)/$(CONFIG_PATH)"
34
35uninstall:
36 $(DEL_FILE) "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)"
37 $(CONFIG_FILE) "$(CONFIG_PATH)"
38
39
40clean:
41 find . -name "*.o" | xargs $(DEL_FILE)
42 rm -f $(TARGET)
43
44dist:
45 git tag v$(VERSION)
46 git archive --format=tar --prefix="$(TARGET)-$(VERSION)/" v$(VERSION) | \
47 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 @@
1TMON - A Monitoring and Testing Tool for Linux kernel thermal subsystem
2
3Why TMON?
4==========
5Increasingly, Linux is running on thermally constrained devices. The simple
6thermal relationship between processor and fan has become past for modern
7computers.
8
9As hardware vendors cope with the thermal constraints on their products, more
10and more sensors are added, new cooling capabilities are introduced. The
11complexity of the thermal relationship can grow exponentially among cooling
12devices, zones, sensors, and trip points. They can also change dynamically.
13
14To expose such relationship to the userspace, Linux generic thermal layer
15introduced sysfs entry at /sys/class/thermal with a matrix of symbolic
16links, trip point bindings, and device instances. To traverse such
17matrix by hand is not a trivial task. Testing is also difficult in that
18thermal conditions are often exception cases that hard to reach in
19normal operations.
20
21TMON is conceived as a tool to help visualize, tune, and test the
22complex thermal subsystem.
23
24Files
25=====
26 tmon.c : main function for set up and configurations.
27 tui.c : handles ncurses based user interface
28 sysfs.c : access to the generic thermal sysfs
29 pid.c : a proportional-integral-derivative (PID) controller
30 that can be used for thermal relationship training.
31
32Requirements
33============
34Depends on ncurses
35
36Build
37=========
38$ make
39$ sudo ./tmon -h
40Usage: tmon [OPTION...]
41 -c, --control cooling device in control
42 -d, --daemon run as daemon, no TUI
43 -l, --log log data to /var/tmp/tmon.log
44 -h, --help show this help message
45 -t, --time-interval set time interval for sampling
46 -v, --version show version
47 -g, --debug debug message in syslog
48
491. For monitoring only:
50$ 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 @@
1/*
2 * pid.c PID controller for testing cooling devices
3 *
4 *
5 *
6 * Copyright (C) 2012 Intel Corporation. All rights reserved.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License version
10 * 2 or later as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * Author Name Jacob Pan <jacob.jun.pan@linux.intel.com>
18 *
19 */
20
21#include <unistd.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <stdint.h>
26#include <sys/types.h>
27#include <dirent.h>
28#include <libintl.h>
29#include <ctype.h>
30#include <assert.h>
31#include <time.h>
32#include <limits.h>
33#include <math.h>
34#include <sys/stat.h>
35#include <syslog.h>
36
37#include "tmon.h"
38
39/**************************************************************************
40 * PID (Proportional-Integral-Derivative) controller is commonly used in
41 * linear control system, consider the the process.
42 * G(s) = U(s)/E(s)
43 * kp = proportional gain
44 * ki = integral gain
45 * kd = derivative gain
46 * Ts
47 * We use type C Alan Bradley equation which takes set point off the
48 * output dependency in P and D term.
49 *
50 * y[k] = y[k-1] - kp*(x[k] - x[k-1]) + Ki*Ts*e[k] - Kd*(x[k]
51 * - 2*x[k-1]+x[k-2])/Ts
52 *
53 *
54 ***********************************************************************/
55struct pid_params p_param;
56/* cached data from previous loop */
57static double xk_1, xk_2; /* input temperature x[k-#] */
58
59/*
60 * TODO: make PID parameters tuned automatically,
61 * 1. use CPU burn to produce open loop unit step response
62 * 2. calculate PID based on Ziegler-Nichols rule
63 *
64 * add a flag for tuning PID
65 */
66int init_thermal_controller(void)
67{
68 int ret = 0;
69
70 /* init pid params */
71 p_param.ts = ticktime;
72 /* TODO: get it from TUI tuning tab */
73 p_param.kp = .36;
74 p_param.ki = 5.0;
75 p_param.kd = 0.19;
76
77 p_param.t_target = target_temp_user;
78
79 return ret;
80}
81
82void controller_reset(void)
83{
84 /* TODO: relax control data when not over thermal limit */
85 syslog(LOG_DEBUG, "TC inactive, relax p-state\n");
86 p_param.y_k = 0.0;
87 xk_1 = 0.0;
88 xk_2 = 0.0;
89 set_ctrl_state(0);
90}
91
92/* To be called at time interval Ts. Type C PID controller.
93 * y[k] = y[k-1] - kp*(x[k] - x[k-1]) + Ki*Ts*e[k] - Kd*(x[k]
94 * - 2*x[k-1]+x[k-2])/Ts
95 * TODO: add low pass filter for D term
96 */
97#define GUARD_BAND (2)
98void controller_handler(const double xk, double *yk)
99{
100 double ek;
101 double p_term, i_term, d_term;
102
103 ek = p_param.t_target - xk; /* error */
104 if (ek >= 3.0) {
105 syslog(LOG_DEBUG, "PID: %3.1f Below set point %3.1f, stop\n",
106 xk, p_param.t_target);
107 controller_reset();
108 *yk = 0.0;
109 return;
110 }
111 /* compute intermediate PID terms */
112 p_term = -p_param.kp * (xk - xk_1);
113 i_term = p_param.kp * p_param.ki * p_param.ts * ek;
114 d_term = -p_param.kp * p_param.kd * (xk - 2 * xk_1 + xk_2) / p_param.ts;
115 /* compute output */
116 *yk += p_term + i_term + d_term;
117 /* update sample data */
118 xk_1 = xk;
119 xk_2 = xk_1;
120
121 /* clamp output adjustment range */
122 if (*yk < -LIMIT_HIGH)
123 *yk = -LIMIT_HIGH;
124 else if (*yk > -LIMIT_LOW)
125 *yk = -LIMIT_LOW;
126
127 p_param.y_k = *yk;
128
129 set_ctrl_state(lround(fabs(p_param.y_k)));
130
131}
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 @@
1/*
2 * sysfs.c sysfs ABI access functions for TMON program
3 *
4 * Copyright (C) 2013 Intel Corporation. All rights reserved.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License version
8 * 2 or later as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * Author: Jacob Pan <jacob.jun.pan@linux.intel.com>
16 *
17 */
18#include <unistd.h>
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22#include <stdint.h>
23#include <dirent.h>
24#include <libintl.h>
25#include <ctype.h>
26#include <time.h>
27#include <syslog.h>
28#include <sys/time.h>
29#include <errno.h>
30
31#include "tmon.h"
32
33struct tmon_platform_data ptdata;
34const char *trip_type_name[] = {
35 "critical",
36 "hot",
37 "passive",
38 "active",
39};
40
41int sysfs_set_ulong(char *path, char *filename, unsigned long val)
42{
43 FILE *fd;
44 int ret = -1;
45 char filepath[256];
46
47 snprintf(filepath, 256, "%s/%s", path, filename);
48
49 fd = fopen(filepath, "w");
50 if (!fd) {
51 syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath);
52 return ret;
53 }
54 ret = fprintf(fd, "%lu", val);
55 fclose(fd);
56
57 return 0;
58}
59
60/* history of thermal data, used for control algo */
61#define NR_THERMAL_RECORDS 3
62struct thermal_data_record trec[NR_THERMAL_RECORDS];
63int cur_thermal_record; /* index to the trec array */
64
65static int sysfs_get_ulong(char *path, char *filename, unsigned long *p_ulong)
66{
67 FILE *fd;
68 int ret = -1;
69 char filepath[256];
70
71 snprintf(filepath, 256, "%s/%s", path, filename);
72
73 fd = fopen(filepath, "r");
74 if (!fd) {
75 syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath);
76 return ret;
77 }
78 ret = fscanf(fd, "%lu", p_ulong);
79 fclose(fd);
80
81 return 0;
82}
83
84static int sysfs_get_string(char *path, char *filename, char *str)
85{
86 FILE *fd;
87 int ret = -1;
88 char filepath[256];
89
90 snprintf(filepath, 256, "%s/%s", path, filename);
91
92 fd = fopen(filepath, "r");
93 if (!fd) {
94 syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath);
95 return ret;
96 }
97 ret = fscanf(fd, "%256s", str);
98 fclose(fd);
99
100 return ret;
101}
102
103/* get states of the cooling device instance */
104static int probe_cdev(struct cdev_info *cdi, char *path)
105{
106 sysfs_get_string(path, "type", cdi->type);
107 sysfs_get_ulong(path, "max_state", &cdi->max_state);
108 sysfs_get_ulong(path, "cur_state", &cdi->cur_state);
109
110 syslog(LOG_INFO, "%s: %s: type %s, max %lu, curr %lu inst %d\n",
111 __func__, path,
112 cdi->type, cdi->max_state, cdi->cur_state, cdi->instance);
113
114 return 0;
115}
116
117static int str_to_trip_type(char *name)
118{
119 int i;
120
121 for (i = 0; i < NR_THERMAL_TRIP_TYPE; i++) {
122 if (!strcmp(name, trip_type_name[i]))
123 return i;
124 }
125
126 return -ENOENT;
127}
128
129/* scan and fill in trip point info for a thermal zone and trip point id */
130static int get_trip_point_data(char *tz_path, int tzid, int tpid)
131{
132 char filename[256];
133 char temp_str[256];
134 int trip_type;
135
136 if (tpid >= MAX_NR_TRIP)
137 return -EINVAL;
138 /* check trip point type */
139 snprintf(filename, sizeof(filename), "trip_point_%d_type", tpid);
140 sysfs_get_string(tz_path, filename, temp_str);
141 trip_type = str_to_trip_type(temp_str);
142 if (trip_type < 0) {
143 syslog(LOG_ERR, "%s:%s no matching type\n", __func__, temp_str);
144 return -ENOENT;
145 }
146 ptdata.tzi[tzid].tp[tpid].type = trip_type;
147 syslog(LOG_INFO, "%s:tz:%d tp:%d:type:%s type id %d\n", __func__, tzid,
148 tpid, temp_str, trip_type);
149
150 /* TODO: check attribute */
151
152 return 0;
153}
154
155/* return instance id for file format such as trip_point_4_temp */
156static int get_instance_id(char *name, int pos, int skip)
157{
158 char *ch;
159 int i = 0;
160
161 ch = strtok(name, "_");
162 while (ch != NULL) {
163 ++i;
164 syslog(LOG_INFO, "%s:%s:%s:%d", __func__, name, ch, i);
165 ch = strtok(NULL, "_");
166 if (pos == i)
167 return atol(ch + skip);
168 }
169
170 return -1;
171}
172
173/* Find trip point info of a thermal zone */
174static int find_tzone_tp(char *tz_name, char *d_name, struct tz_info *tzi,
175 int tz_id)
176{
177 int tp_id;
178 unsigned long temp_ulong;
179
180 if (strstr(d_name, "trip_point") &&
181 strstr(d_name, "temp")) {
182 /* check if trip point temp is non-zero
183 * ignore 0/invalid trip points
184 */
185 sysfs_get_ulong(tz_name, d_name, &temp_ulong);
186 if (temp_ulong < MAX_TEMP_KC) {
187 tzi->nr_trip_pts++;
188 /* found a valid trip point */
189 tp_id = get_instance_id(d_name, 2, 0);
190 syslog(LOG_DEBUG, "tzone %s trip %d temp %lu tpnode %s",
191 tz_name, tp_id, temp_ulong, d_name);
192 if (tp_id < 0 || tp_id >= MAX_NR_TRIP) {
193 syslog(LOG_ERR, "Failed to find TP inst %s\n",
194 d_name);
195 return -1;
196 }
197 get_trip_point_data(tz_name, tz_id, tp_id);
198 tzi->tp[tp_id].temp = temp_ulong;
199 }
200 }
201
202 return 0;
203}
204
205/* check cooling devices for binding info. */
206static int find_tzone_cdev(struct dirent *nl, char *tz_name,
207 struct tz_info *tzi, int tz_id, int cid)
208{
209 unsigned long trip_instance = 0;
210 char cdev_name_linked[256];
211 char cdev_name[256];
212 char cdev_trip_name[256];
213 int cdev_id;
214
215 if (nl->d_type == DT_LNK) {
216 syslog(LOG_DEBUG, "TZ%d: cdev: %s cid %d\n", tz_id, nl->d_name,
217 cid);
218 tzi->nr_cdev++;
219 if (tzi->nr_cdev > ptdata.nr_cooling_dev) {
220 syslog(LOG_ERR, "Err: Too many cdev? %d\n",
221 tzi->nr_cdev);
222 return -EINVAL;
223 }
224 /* find the link to real cooling device record binding */
225 snprintf(cdev_name, 256, "%s/%s", tz_name, nl->d_name);
226 memset(cdev_name_linked, 0, sizeof(cdev_name_linked));
227 if (readlink(cdev_name, cdev_name_linked,
228 sizeof(cdev_name_linked) - 1) != -1) {
229 cdev_id = get_instance_id(cdev_name_linked, 1,
230 sizeof("device") - 1);
231 syslog(LOG_DEBUG, "cdev %s linked to %s : %d\n",
232 cdev_name, cdev_name_linked, cdev_id);
233 tzi->cdev_binding |= (1 << cdev_id);
234
235 /* find the trip point in which the cdev is binded to
236 * in this tzone
237 */
238 snprintf(cdev_trip_name, 256, "%s%s", nl->d_name,
239 "_trip_point");
240 sysfs_get_ulong(tz_name, cdev_trip_name,
241 &trip_instance);
242 /* validate trip point range, e.g. trip could return -1
243 * when passive is enabled
244 */
245 if (trip_instance > MAX_NR_TRIP)
246 trip_instance = 0;
247 tzi->trip_binding[cdev_id] |= 1 << trip_instance;
248 syslog(LOG_DEBUG, "cdev %s -> trip:%lu: 0x%lx %d\n",
249 cdev_name, trip_instance,
250 tzi->trip_binding[cdev_id],
251 cdev_id);
252
253
254 }
255 return 0;
256 }
257
258 return -ENODEV;
259}
260
261
262
263/*****************************************************************************
264 * Before calling scan_tzones, thermal sysfs must be probed to determine
265 * the number of thermal zones and cooling devices.
266 * We loop through each thermal zone and fill in tz_info struct, i.e.
267 * ptdata.tzi[]
268root@jacob-chiefriver:~# tree -d /sys/class/thermal/thermal_zone0
269/sys/class/thermal/thermal_zone0
270|-- cdev0 -> ../cooling_device4
271|-- cdev1 -> ../cooling_device3
272|-- cdev10 -> ../cooling_device7
273|-- cdev11 -> ../cooling_device6
274|-- cdev12 -> ../cooling_device5
275|-- cdev2 -> ../cooling_device2
276|-- cdev3 -> ../cooling_device1
277|-- cdev4 -> ../cooling_device0
278|-- cdev5 -> ../cooling_device12
279|-- cdev6 -> ../cooling_device11
280|-- cdev7 -> ../cooling_device10
281|-- cdev8 -> ../cooling_device9
282|-- cdev9 -> ../cooling_device8
283|-- device -> ../../../LNXSYSTM:00/device:62/LNXTHERM:00
284|-- power
285`-- subsystem -> ../../../../class/thermal
286*****************************************************************************/
287static int scan_tzones(void)
288{
289 DIR *dir;
290 struct dirent **namelist;
291 char tz_name[256];
292 int i, j, n, k = 0;
293
294 if (!ptdata.nr_tz_sensor)
295 return -1;
296
297 for (i = 0; i <= ptdata.max_tz_instance; i++) {
298 memset(tz_name, 0, sizeof(tz_name));
299 snprintf(tz_name, 256, "%s/%s%d", THERMAL_SYSFS, TZONE, i);
300
301 dir = opendir(tz_name);
302 if (!dir) {
303 syslog(LOG_INFO, "Thermal zone %s skipped\n", tz_name);
304 continue;
305 }
306 /* keep track of valid tzones */
307 n = scandir(tz_name, &namelist, 0, alphasort);
308 if (n < 0)
309 syslog(LOG_ERR, "scandir failed in %s", tz_name);
310 else {
311 sysfs_get_string(tz_name, "type", ptdata.tzi[k].type);
312 ptdata.tzi[k].instance = i;
313 /* detect trip points and cdev attached to this tzone */
314 j = 0; /* index for cdev */
315 ptdata.tzi[k].nr_cdev = 0;
316 ptdata.tzi[k].nr_trip_pts = 0;
317 while (n--) {
318 char *temp_str;
319
320 if (find_tzone_tp(tz_name, namelist[n]->d_name,
321 &ptdata.tzi[k], k))
322 break;
323 temp_str = strstr(namelist[n]->d_name, "cdev");
324 if (!temp_str) {
325 free(namelist[n]);
326 continue;
327 }
328 if (!find_tzone_cdev(namelist[n], tz_name,
329 &ptdata.tzi[k], i, j))
330 j++; /* increment cdev index */
331 free(namelist[n]);
332 }
333 free(namelist);
334 }
335 /*TODO: reverse trip points */
336 closedir(dir);
337 syslog(LOG_INFO, "TZ %d has %d cdev\n", i,
338 ptdata.tzi[k].nr_cdev);
339 k++;
340 }
341
342 return 0;
343}
344
345static int scan_cdevs(void)
346{
347 DIR *dir;
348 struct dirent **namelist;
349 char cdev_name[256];
350 int i, n, k = 0;
351
352 if (!ptdata.nr_cooling_dev) {
353 fprintf(stderr, "No cooling devices found\n");
354 return 0;
355 }
356 for (i = 0; i <= ptdata.max_cdev_instance; i++) {
357 memset(cdev_name, 0, sizeof(cdev_name));
358 snprintf(cdev_name, 256, "%s/%s%d", THERMAL_SYSFS, CDEV, i);
359
360 dir = opendir(cdev_name);
361 if (!dir) {
362 syslog(LOG_INFO, "Cooling dev %s skipped\n", cdev_name);
363 /* there is a gap in cooling device id, check again
364 * for the same index.
365 */
366 continue;
367 }
368
369 n = scandir(cdev_name, &namelist, 0, alphasort);
370 if (n < 0)
371 syslog(LOG_ERR, "scandir failed in %s", cdev_name);
372 else {
373 sysfs_get_string(cdev_name, "type", ptdata.cdi[k].type);
374 ptdata.cdi[k].instance = i;
375 if (strstr(ptdata.cdi[k].type, ctrl_cdev)) {
376 ptdata.cdi[k].flag |= CDEV_FLAG_IN_CONTROL;
377 syslog(LOG_DEBUG, "control cdev id %d\n", i);
378 }
379 while (n--)
380 free(namelist[n]);
381 free(namelist);
382 }
383 closedir(dir);
384 k++;
385 }
386 return 0;
387}
388
389
390int probe_thermal_sysfs(void)
391{
392 DIR *dir;
393 struct dirent **namelist;
394 int n;
395
396 dir = opendir(THERMAL_SYSFS);
397 if (!dir) {
398 fprintf(stderr, "\nNo thermal sysfs, exit\n");
399 return -1;
400 }
401 n = scandir(THERMAL_SYSFS, &namelist, 0, alphasort);
402 if (n < 0)
403 syslog(LOG_ERR, "scandir failed in thermal sysfs");
404 else {
405 /* detect number of thermal zones and cooling devices */
406 while (n--) {
407 int inst;
408
409 if (strstr(namelist[n]->d_name, CDEV)) {
410 inst = get_instance_id(namelist[n]->d_name, 1,
411 sizeof("device") - 1);
412 /* keep track of the max cooling device since
413 * there may be gaps.
414 */
415 if (inst > ptdata.max_cdev_instance)
416 ptdata.max_cdev_instance = inst;
417
418 syslog(LOG_DEBUG, "found cdev: %s %d %d\n",
419 namelist[n]->d_name,
420 ptdata.nr_cooling_dev,
421 ptdata.max_cdev_instance);
422 ptdata.nr_cooling_dev++;
423 } else if (strstr(namelist[n]->d_name, TZONE)) {
424 inst = get_instance_id(namelist[n]->d_name, 1,
425 sizeof("zone") - 1);
426 if (inst > ptdata.max_tz_instance)
427 ptdata.max_tz_instance = inst;
428
429 syslog(LOG_DEBUG, "found tzone: %s %d %d\n",
430 namelist[n]->d_name,
431 ptdata.nr_tz_sensor,
432 ptdata.max_tz_instance);
433 ptdata.nr_tz_sensor++;
434 }
435 free(namelist[n]);
436 }
437 free(namelist);
438 }
439 syslog(LOG_INFO, "found %d tzone(s), %d cdev(s), target zone %d\n",
440 ptdata.nr_tz_sensor, ptdata.nr_cooling_dev,
441 target_thermal_zone);
442 closedir(dir);
443
444 if (!ptdata.nr_tz_sensor) {
445 fprintf(stderr, "\nNo thermal zones found, exit\n\n");
446 return -1;
447 }
448
449 ptdata.tzi = calloc(sizeof(struct tz_info), ptdata.max_tz_instance+1);
450 if (!ptdata.tzi) {
451 fprintf(stderr, "Err: allocate tz_info\n");
452 return -1;
453 }
454
455 /* we still show thermal zone information if there is no cdev */
456 if (ptdata.nr_cooling_dev) {
457 ptdata.cdi = calloc(sizeof(struct cdev_info),
458 ptdata.max_cdev_instance + 1);
459 if (!ptdata.cdi) {
460 free(ptdata.tzi);
461 fprintf(stderr, "Err: allocate cdev_info\n");
462 return -1;
463 }
464 }
465
466 /* now probe tzones */
467 if (scan_tzones())
468 return -1;
469 if (scan_cdevs())
470 return -1;
471 return 0;
472}
473
474/* convert sysfs zone instance to zone array index */
475int zone_instance_to_index(int zone_inst)
476{
477 int i;
478
479 for (i = 0; i < ptdata.nr_tz_sensor; i++)
480 if (ptdata.tzi[i].instance == zone_inst)
481 return i;
482 return -ENOENT;
483}
484
485/* read temperature of all thermal zones */
486int update_thermal_data()
487{
488 int i;
489 char tz_name[256];
490 static unsigned long samples;
491
492 if (!ptdata.nr_tz_sensor) {
493 syslog(LOG_ERR, "No thermal zones found!\n");
494 return -1;
495 }
496
497 /* circular buffer for keeping historic data */
498 if (cur_thermal_record >= NR_THERMAL_RECORDS)
499 cur_thermal_record = 0;
500 gettimeofday(&trec[cur_thermal_record].tv, NULL);
501 if (tmon_log) {
502 fprintf(tmon_log, "%lu ", ++samples);
503 fprintf(tmon_log, "%3.1f ", p_param.t_target);
504 }
505 for (i = 0; i < ptdata.nr_tz_sensor; i++) {
506 memset(tz_name, 0, sizeof(tz_name));
507 snprintf(tz_name, 256, "%s/%s%d", THERMAL_SYSFS, TZONE,
508 ptdata.tzi[i].instance);
509 sysfs_get_ulong(tz_name, "temp",
510 &trec[cur_thermal_record].temp[i]);
511 if (tmon_log)
512 fprintf(tmon_log, "%lu ",
513 trec[cur_thermal_record].temp[i]/1000);
514 }
515 for (i = 0; i < ptdata.nr_cooling_dev; i++) {
516 char cdev_name[256];
517 unsigned long val;
518
519 snprintf(cdev_name, 256, "%s/%s%d", THERMAL_SYSFS, CDEV,
520 ptdata.cdi[i].instance);
521 probe_cdev(&ptdata.cdi[i], cdev_name);
522 val = ptdata.cdi[i].cur_state;
523 if (val > 1000000)
524 val = 0;
525 if (tmon_log)
526 fprintf(tmon_log, "%lu ", val);
527 }
528
529 if (tmon_log) {
530 fprintf(tmon_log, "\n");
531 fflush(tmon_log);
532 }
533
534 return 0;
535}
536
537void set_ctrl_state(unsigned long state)
538{
539 char ctrl_cdev_path[256];
540 int i;
541 unsigned long cdev_state;
542
543 if (no_control)
544 return;
545 /* set all ctrl cdev to the same state */
546 for (i = 0; i < ptdata.nr_cooling_dev; i++) {
547 if (ptdata.cdi[i].flag & CDEV_FLAG_IN_CONTROL) {
548 if (ptdata.cdi[i].max_state < 10) {
549 strcpy(ctrl_cdev, "None.");
550 return;
551 }
552 /* scale to percentage of max_state */
553 cdev_state = state * ptdata.cdi[i].max_state/100;
554 syslog(LOG_DEBUG,
555 "ctrl cdev %d set state %lu scaled to %lu\n",
556 ptdata.cdi[i].instance, state, cdev_state);
557 snprintf(ctrl_cdev_path, 256, "%s/%s%d", THERMAL_SYSFS,
558 CDEV, ptdata.cdi[i].instance);
559 syslog(LOG_DEBUG, "ctrl cdev path %s", ctrl_cdev_path);
560 sysfs_set_ulong(ctrl_cdev_path, "cur_state",
561 cdev_state);
562 }
563 }
564}
565
566void get_ctrl_state(unsigned long *state)
567{
568 char ctrl_cdev_path[256];
569 int ctrl_cdev_id = -1;
570 int i;
571
572 /* TODO: take average of all ctrl types. also consider change based on
573 * uevent. Take the first reading for now.
574 */
575 for (i = 0; i < ptdata.nr_cooling_dev; i++) {
576 if (ptdata.cdi[i].flag & CDEV_FLAG_IN_CONTROL) {
577 ctrl_cdev_id = ptdata.cdi[i].instance;
578 syslog(LOG_INFO, "ctrl cdev %d get state\n",
579 ptdata.cdi[i].instance);
580 break;
581 }
582 }
583 if (ctrl_cdev_id == -1) {
584 *state = 0;
585 return;
586 }
587 snprintf(ctrl_cdev_path, 256, "%s/%s%d", THERMAL_SYSFS,
588 CDEV, ctrl_cdev_id);
589 sysfs_get_ulong(ctrl_cdev_path, "cur_state", state);
590}
591
592void free_thermal_data(void)
593{
594 free(ptdata.tzi);
595 free(ptdata.cdi);
596}
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 @@
1.TH TMON 8
2.SH NAME
3\fBtmon\fP - A monitoring and testing tool for Linux kernel thermal subsystem
4
5.SH SYNOPSIS
6.ft B
7.B tmon
8.RB [ Options ]
9.br
10.SH DESCRIPTION
11\fBtmon \fP can be used to visualize thermal relationship and
12real-time thermal data; tune
13and test cooling devices and sensors; collect thermal data for offline
14analysis and plot. \fBtmon\fP must be run as root in order to control device
15states via sysfs.
16.PP
17\fBFunctions\fP
18.PP
19.nf
201. Thermal relationships:
21- show thermal zone information
22- show cooling device information
23- show trip point binding within each thermal zone
24- show trip point and cooling device instance bindings
25.PP
262. Real time data display
27- show temperature of all thermal zones w.r.t. its trip points and types
28- show states of all cooling devices
29.PP
303. Thermal relationship learning and device tuning
31- with a built-in Proportional Integral Derivative (\fBPID\fP)
32controller, user can pair a cooling device to a thermal sensor for
33testing the effectiveness and learn about the thermal distance between the two
34- allow manual control of cooling device states and target temperature
35.PP
364. Data logging in /var/tmp/tmon.log
37- contains thermal configuration data, i.e. cooling device, thermal
38 zones, and trip points. Can be used for data collection in remote
39 debugging.
40- log real-time thermal data into space separated format that can be
41 directly consumed by plotting tools such as Rscript.
42
43.SS Options
44.PP
45The \fB-c --control\fP option sets a cooling device type to control temperature
46of a thermal zone
47.PP
48The \fB-d --daemon\fP option runs \fBtmon \fP as daemon without user interface
49.PP
50The \fB-g --debug\fP option allow debug messages to be stored in syslog
51.PP
52The \fB-h --help\fP option shows help message
53.PP
54The \fB-l --log\fP option write data to /var/tmp/tmon.log
55.PP
56The \fB-t --time-interval\fP option sets the polling interval in seconds
57.PP
58The \fB-v --version\fP option shows the version of \fBtmon \fP
59.PP
60The \fB-z --zone\fP option sets the target therma zone instance to be controlled
61.PP
62
63.SH FIELD DESCRIPTIONS
64.nf
65.PP
66\fBP \fP passive cooling trip point type
67\fBA \fP active cooling trip point type (fan)
68\fBC \fP critical trip point type
69\fBA \fP hot trip point type
70\fBkp \fP proportional gain of \fBPID\fP controller
71\fBki \fP integral gain of \fBPID\fP controller
72\fBkd \fP derivative gain of \fBPID\fP controller
73
74.SH REQUIREMENT
75Build depends on ncurses
76.PP
77Runtime depends on window size large enough to show the number of
78devices found on the system.
79
80.PP
81
82.SH INTERACTIVE COMMANDS
83.pp
84.nf
85\fBCtrl-C, q/Q\fP stops \fBtmon\fP
86\fBTAB\fP shows tuning pop up panel, choose a letter to modify
87
88.SH EXAMPLES
89Without any parameters, tmon is in monitoring only mode and refresh
90screen every 1 second.
91.PP
921. For monitoring only:
93.nf
94$ sudo ./tmon
95
962. Use Processor cooling device to control thermal zone 0 at default 65C.
97$ sudo ./tmon -c Processor -z 0
98
993. Use intel_powerclamp(idle injection) cooling device to control thermal zone 1
100$ sudo ./tmon -c intel_powerclamp -z 1
101
1024. Turn on debug and collect data log at /var/tmp/tmon.log
103$ sudo ./tmon -g -l
104
105For example, the log below shows PID controller was adjusting current states
106for all cooling devices with "Processor" type such that thermal zone 0
107can stay below 65 dC.
108
109#---------- THERMAL DATA LOG STARTED -----------
110Samples TargetTemp acpitz0 acpitz1 Fan0 Fan1 Fan2 Fan3 Fan4 Fan5
111Fan6 Fan7 Fan8 Fan9 Processor10 Processor11 Processor12 Processor13
112LCD14 intel_powerclamp15 1 65.0 65 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 2
11365.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
1140 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
1155 65.0 52 52 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0
1166 65.0 53 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0
1177 65.0 68 70 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0
1188 65.0 68 68 0 0 0 0 0 0 0 0 0 0 5 5 5 5 6 0
1199 65.0 68 68 0 0 0 0 0 0 0 0 0 0 6 6 6 6 6 0
12010 65.0 67 67 0 0 0 0 0 0 0 0 0 0 7 7 7 7 6 0
12111 65.0 67 67 0 0 0 0 0 0 0 0 0 0 8 8 8 8 6 0
12212 65.0 67 67 0 0 0 0 0 0 0 0 0 0 8 8 8 8 6 0
12313 65.0 67 67 0 0 0 0 0 0 0 0 0 0 9 9 9 9 6 0
12414 65.0 66 66 0 0 0 0 0 0 0 0 0 0 10 10 10 10 6 0
12515 65.0 66 67 0 0 0 0 0 0 0 0 0 0 10 10 10 10 6 0
12616 65.0 66 66 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0
12717 65.0 66 66 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0
12818 65.0 64 61 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0
12919 65.0 60 59 0 0 0 0 0 0 0 0 0 0 12 12 12 12 6 0
130
131Data can be read directly into an array by an example R-script below:
132
133#!/usr/bin/Rscript
134tdata <- read.table("/var/tmp/tmon.log", header=T, comment.char="#")
135attach(tdata)
136jpeg("tmon.jpg")
137X11()
138g_range <- range(0, intel_powerclamp15, TargetTemp, acpitz0)
139plot( Samples, intel_powerclamp15, col="blue", ylim=g_range, axes=FALSE, ann=FALSE)
140par(new=TRUE)
141lines(TargetTemp, type="o", pch=22, lty=2, col="red")
142dev.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 @@
1/*
2 * tmon.c Thermal Monitor (TMON) main function and entry point
3 *
4 * Copyright (C) 2012 Intel Corporation. All rights reserved.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License version
8 * 2 or later as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * Author: Jacob Pan <jacob.jun.pan@linux.intel.com>
16 *
17 */
18
19#include <getopt.h>
20#include <unistd.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <sys/types.h>
25#include <sys/stat.h>
26#include <ncurses.h>
27#include <ctype.h>
28#include <time.h>
29#include <signal.h>
30#include <limits.h>
31#include <sys/time.h>
32#include <pthread.h>
33#include <math.h>
34#include <stdarg.h>
35#include <syslog.h>
36
37#include "tmon.h"
38
39unsigned long ticktime = 1; /* seconds */
40unsigned long no_control = 1; /* monitoring only or use cooling device for
41 * temperature control.
42 */
43double time_elapsed = 0.0;
44unsigned long target_temp_user = 65; /* can be select by tui later */
45int dialogue_on;
46int tmon_exit;
47static short daemon_mode;
48static int logging; /* for recording thermal data to a file */
49static int debug_on;
50FILE *tmon_log;
51/*cooling device used for the PID controller */
52char ctrl_cdev[CDEV_NAME_SIZE] = "None";
53int target_thermal_zone; /* user selected target zone instance */
54static void start_daemon_mode(void);
55
56pthread_t event_tid;
57pthread_mutex_t input_lock;
58void usage()
59{
60 printf("Usage: tmon [OPTION...]\n");
61 printf(" -c, --control cooling device in control\n");
62 printf(" -d, --daemon run as daemon, no TUI\n");
63 printf(" -g, --debug debug message in syslog\n");
64 printf(" -h, --help show this help message\n");
65 printf(" -l, --log log data to /var/tmp/tmon.log\n");
66 printf(" -t, --time-interval sampling time interval, > 1 sec.\n");
67 printf(" -v, --version show version\n");
68 printf(" -z, --zone target thermal zone id\n");
69
70 exit(0);
71}
72
73void version()
74{
75 printf("TMON version %s\n", VERSION);
76 exit(EXIT_SUCCESS);
77}
78
79static void tmon_cleanup(void)
80{
81
82 syslog(LOG_INFO, "TMON exit cleanup\n");
83 fflush(stdout);
84 refresh();
85 if (tmon_log)
86 fclose(tmon_log);
87 if (event_tid) {
88 pthread_mutex_lock(&input_lock);
89 pthread_cancel(event_tid);
90 pthread_mutex_unlock(&input_lock);
91 pthread_mutex_destroy(&input_lock);
92 }
93 closelog();
94 /* relax control knobs, undo throttling */
95 set_ctrl_state(0);
96
97 keypad(stdscr, FALSE);
98 echo();
99 nocbreak();
100 close_windows();
101 endwin();
102 free_thermal_data();
103
104 exit(1);
105}
106
107
108static void tmon_sig_handler(int sig)
109{
110 syslog(LOG_INFO, "TMON caught signal %d\n", sig);
111 refresh();
112 switch (sig) {
113 case SIGTERM:
114 printf("sigterm, exit and clean up\n");
115 fflush(stdout);
116 break;
117 case SIGKILL:
118 printf("sigkill, exit and clean up\n");
119 fflush(stdout);
120 break;
121 case SIGINT:
122 printf("ctrl-c, exit and clean up\n");
123 fflush(stdout);
124 break;
125 default:
126 break;
127 }
128 tmon_exit = true;
129}
130
131
132static void start_syslog(void)
133{
134 if (debug_on)
135 setlogmask(LOG_UPTO(LOG_DEBUG));
136 else
137 setlogmask(LOG_UPTO(LOG_ERR));
138 openlog("tmon.log", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL0);
139 syslog(LOG_NOTICE, "TMON started by User %d", getuid());
140}
141
142static void prepare_logging(void)
143{
144 int i;
145
146 if (!logging)
147 return;
148 /* open local data log file */
149 tmon_log = fopen(TMON_LOG_FILE, "w+");
150 if (!tmon_log) {
151 syslog(LOG_ERR, "failed to open log file %s\n", TMON_LOG_FILE);
152 return;
153 }
154
155 fprintf(tmon_log, "#----------- THERMAL SYSTEM CONFIG -------------\n");
156 for (i = 0; i < ptdata.nr_tz_sensor; i++) {
157 char binding_str[33]; /* size of long + 1 */
158 int j;
159
160 memset(binding_str, 0, sizeof(binding_str));
161 for (j = 0; j < 32; j++)
162 binding_str[j] = (ptdata.tzi[i].cdev_binding & 1<<j) ?
163 '1' : '0';
164
165 fprintf(tmon_log, "#thermal zone %s%02d cdevs binding: %32s\n",
166 ptdata.tzi[i].type,
167 ptdata.tzi[i].instance,
168 binding_str);
169 for (j = 0; j < ptdata.tzi[i].nr_trip_pts; j++) {
170 fprintf(tmon_log, "#\tTP%02d type:%s, temp:%lu\n", j,
171 trip_type_name[ptdata.tzi[i].tp[j].type],
172 ptdata.tzi[i].tp[j].temp);
173 }
174
175 }
176
177 for (i = 0; i < ptdata.nr_cooling_dev; i++)
178 fprintf(tmon_log, "#cooling devices%02d: %s\n",
179 i, ptdata.cdi[i].type);
180
181 fprintf(tmon_log, "#---------- THERMAL DATA LOG STARTED -----------\n");
182 fprintf(tmon_log, "Samples TargetTemp ");
183 for (i = 0; i < ptdata.nr_tz_sensor; i++) {
184 fprintf(tmon_log, "%s%d ", ptdata.tzi[i].type,
185 ptdata.tzi[i].instance);
186 }
187 for (i = 0; i < ptdata.nr_cooling_dev; i++)
188 fprintf(tmon_log, "%s%d ", ptdata.cdi[i].type,
189 ptdata.cdi[i].instance);
190
191 fprintf(tmon_log, "\n");
192}
193
194static struct option opts[] = {
195 { "control", 1, NULL, 'c' },
196 { "daemon", 0, NULL, 'd' },
197 { "time-interval", 1, NULL, 't' },
198 { "log", 0, NULL, 'l' },
199 { "help", 0, NULL, 'h' },
200 { "version", 0, NULL, 'v' },
201 { "debug", 0, NULL, 'g' },
202 { 0, 0, NULL, 0 }
203};
204
205
206int main(int argc, char **argv)
207{
208 int err = 0;
209 int id2 = 0, c;
210 double yk = 0.0; /* controller output */
211 int target_tz_index;
212
213 if (geteuid() != 0) {
214 printf("TMON needs to be run as root\n");
215 exit(EXIT_FAILURE);
216 }
217
218 while ((c = getopt_long(argc, argv, "c:dlht:vgz:", opts, &id2)) != -1) {
219 switch (c) {
220 case 'c':
221 no_control = 0;
222 strncpy(ctrl_cdev, optarg, CDEV_NAME_SIZE);
223 break;
224 case 'd':
225 start_daemon_mode();
226 printf("Run TMON in daemon mode\n");
227 break;
228 case 't':
229 ticktime = strtod(optarg, NULL);
230 if (ticktime < 1)
231 ticktime = 1;
232 break;
233 case 'l':
234 printf("Logging data to /var/tmp/tmon.log\n");
235 logging = 1;
236 break;
237 case 'h':
238 usage();
239 break;
240 case 'v':
241 version();
242 break;
243 case 'g':
244 debug_on = 1;
245 break;
246 case 'z':
247 target_thermal_zone = strtod(optarg, NULL);
248 break;
249 default:
250 break;
251 }
252 }
253 if (pthread_mutex_init(&input_lock, NULL) != 0) {
254 fprintf(stderr, "\n mutex init failed, exit\n");
255 return 1;
256 }
257 start_syslog();
258 if (signal(SIGINT, tmon_sig_handler) == SIG_ERR)
259 syslog(LOG_DEBUG, "Cannot handle SIGINT\n");
260 if (signal(SIGTERM, tmon_sig_handler) == SIG_ERR)
261 syslog(LOG_DEBUG, "Cannot handle SIGINT\n");
262
263 if (probe_thermal_sysfs()) {
264 pthread_mutex_destroy(&input_lock);
265 closelog();
266 return -1;
267 }
268 initialize_curses();
269 setup_windows();
270 signal(SIGWINCH, resize_handler);
271 show_title_bar();
272 show_sensors_w();
273 show_cooling_device();
274 update_thermal_data();
275 show_data_w();
276 prepare_logging();
277 init_thermal_controller();
278
279 nodelay(stdscr, TRUE);
280 err = pthread_create(&event_tid, NULL, &handle_tui_events, NULL);
281 if (err != 0) {
282 printf("\ncan't create thread :[%s]", strerror(err));
283 tmon_cleanup();
284 exit(EXIT_FAILURE);
285 }
286
287 /* validate range of user selected target zone, default to the first
288 * instance if out of range
289 */
290 target_tz_index = zone_instance_to_index(target_thermal_zone);
291 if (target_tz_index < 0) {
292 target_thermal_zone = ptdata.tzi[0].instance;
293 syslog(LOG_ERR, "target zone is not found, default to %d\n",
294 target_thermal_zone);
295 }
296 while (1) {
297 sleep(ticktime);
298 show_title_bar();
299 show_sensors_w();
300 update_thermal_data();
301 if (!dialogue_on) {
302 show_data_w();
303 show_cooling_device();
304 }
305 cur_thermal_record++;
306 time_elapsed += ticktime;
307 controller_handler(trec[0].temp[target_tz_index] / 1000,
308 &yk);
309 trec[0].pid_out_pct = yk;
310 if (!dialogue_on)
311 show_control_w();
312 if (tmon_exit)
313 break;
314 }
315 tmon_cleanup();
316 return 0;
317}
318
319static void start_daemon_mode()
320{
321 daemon_mode = 1;
322 /* fork */
323 pid_t sid, pid = fork();
324 if (pid < 0) {
325 exit(EXIT_FAILURE);
326 } else if (pid > 0)
327 /* kill parent */
328 exit(EXIT_SUCCESS);
329
330 /* disable TUI, it may not be necessary, but saves some resource */
331 disable_tui();
332
333 /* change the file mode mask */
334 umask(0);
335
336 /* new SID for the daemon process */
337 sid = setsid();
338 if (sid < 0)
339 exit(EXIT_FAILURE);
340
341 /* change working directory */
342 if ((chdir("/")) < 0)
343 exit(EXIT_FAILURE);
344
345
346 sleep(10);
347
348 close(STDIN_FILENO);
349 close(STDOUT_FILENO);
350 close(STDERR_FILENO);
351
352}
diff --git a/tools/thermal/tmon/tmon.h b/tools/thermal/tmon/tmon.h
new file mode 100644
index 000000000000..9e3c49c547ac
--- /dev/null
+++ b/tools/thermal/tmon/tmon.h
@@ -0,0 +1,204 @@
1/*
2 * tmon.h contains data structures and constants used by TMON
3 *
4 * Copyright (C) 2012 Intel Corporation. All rights reserved.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License version
8 * 2 or later as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * Author Name Jacob Pan <jacob.jun.pan@linux.intel.com>
16 *
17 */
18
19#ifndef TMON_H
20#define TMON_H
21
22#define MAX_DISP_TEMP 125
23#define MAX_CTRL_TEMP 105
24#define MIN_CTRL_TEMP 40
25#define MAX_NR_TZONE 16
26#define MAX_NR_CDEV 32
27#define MAX_NR_TRIP 16
28#define MAX_NR_CDEV_TRIP 12 /* number of cooling devices that can bind
29 * to a thermal zone trip.
30 */
31#define MAX_TEMP_KC 140000
32/* starting char position to draw sensor data, such as tz names
33 * trip point list, etc.
34 */
35#define DATA_LEFT_ALIGN 10
36#define NR_LINES_TZDATA 1
37#define TMON_LOG_FILE "/var/tmp/tmon.log"
38
39extern unsigned long ticktime;
40extern double time_elapsed;
41extern unsigned long target_temp_user;
42extern int dialogue_on;
43extern char ctrl_cdev[];
44extern pthread_mutex_t input_lock;
45extern int tmon_exit;
46extern int target_thermal_zone;
47/* use fixed size record to simplify data processing and transfer
48 * TBD: more info to be added, e.g. programmable trip point data.
49*/
50struct thermal_data_record {
51 struct timeval tv;
52 unsigned long temp[MAX_NR_TZONE];
53 double pid_out_pct;
54};
55
56struct cdev_info {
57 char type[64];
58 int instance;
59 unsigned long max_state;
60 unsigned long cur_state;
61 unsigned long flag;
62};
63
64enum trip_type {
65 THERMAL_TRIP_CRITICAL,
66 THERMAL_TRIP_HOT,
67 THERMAL_TRIP_PASSIVE,
68 THERMAL_TRIP_ACTIVE,
69 NR_THERMAL_TRIP_TYPE,
70};
71
72struct trip_point {
73 enum trip_type type;
74 unsigned long temp;
75 unsigned long hysteresis;
76 int attribute; /* programmability etc. */
77};
78
79/* thermal zone configuration information, binding with cooling devices could
80 * change at runtime.
81 */
82struct tz_info {
83 char type[256]; /* e.g. acpitz */
84 int instance;
85 int passive; /* active zone has passive node to force passive mode */
86 int nr_cdev; /* number of cooling device binded */
87 int nr_trip_pts;
88 struct trip_point tp[MAX_NR_TRIP];
89 unsigned long cdev_binding; /* bitmap for attached cdevs */
90 /* cdev bind trip points, allow one cdev bind to multiple trips */
91 unsigned long trip_binding[MAX_NR_CDEV];
92};
93
94struct tmon_platform_data {
95 int nr_tz_sensor;
96 int nr_cooling_dev;
97 /* keep track of instance ids since there might be gaps */
98 int max_tz_instance;
99 int max_cdev_instance;
100 struct tz_info *tzi;
101 struct cdev_info *cdi;
102};
103
104struct control_ops {
105 void (*set_ratio)(unsigned long ratio);
106 unsigned long (*get_ratio)(unsigned long ratio);
107
108};
109
110enum cdev_types {
111 CDEV_TYPE_PROC,
112 CDEV_TYPE_FAN,
113 CDEV_TYPE_MEM,
114 CDEV_TYPE_NR,
115};
116
117/* REVISIT: the idea is to group sensors if possible, e.g. on intel mid
118 * we have "skin0", "skin1", "sys", "msicdie"
119 * on DPTF enabled systems, we might have PCH, TSKN, TAMB, etc.
120 */
121enum tzone_types {
122 TZONE_TYPE_ACPI,
123 TZONE_TYPE_PCH,
124 TZONE_TYPE_NR,
125};
126
127/* limit the output of PID controller adjustment */
128#define LIMIT_HIGH (95)
129#define LIMIT_LOW (2)
130
131struct pid_params {
132 double kp; /* Controller gain from Dialog Box */
133 double ki; /* Time-constant for I action from Dialog Box */
134 double kd; /* Time-constant for D action from Dialog Box */
135 double ts;
136 double k_lpf;
137
138 double t_target;
139 double y_k;
140};
141
142extern int init_thermal_controller(void);
143extern void controller_handler(const double xk, double *yk);
144
145extern struct tmon_platform_data ptdata;
146extern struct pid_params p_param;
147
148extern FILE *tmon_log;
149extern int cur_thermal_record; /* index to the trec array */
150extern struct thermal_data_record trec[];
151extern const char *trip_type_name[];
152extern unsigned long no_control;
153
154extern void initialize_curses(void);
155extern void show_controller_stats(char *line);
156extern void show_title_bar(void);
157extern void setup_windows(void);
158extern void disable_tui(void);
159extern void show_sensors_w(void);
160extern void show_data_w(void);
161extern void write_status_bar(int x, char *line);
162extern void show_control_w();
163
164extern void show_cooling_device(void);
165extern void show_dialogue(void);
166extern int update_thermal_data(void);
167
168extern int probe_thermal_sysfs(void);
169extern void free_thermal_data(void);
170extern void resize_handler(int sig);
171extern void set_ctrl_state(unsigned long state);
172extern void get_ctrl_state(unsigned long *state);
173extern void *handle_tui_events(void *arg);
174extern int sysfs_set_ulong(char *path, char *filename, unsigned long val);
175extern int zone_instance_to_index(int zone_inst);
176extern void close_windows(void);
177
178#define PT_COLOR_DEFAULT 1
179#define PT_COLOR_HEADER_BAR 2
180#define PT_COLOR_ERROR 3
181#define PT_COLOR_RED 4
182#define PT_COLOR_YELLOW 5
183#define PT_COLOR_GREEN 6
184#define PT_COLOR_BRIGHT 7
185#define PT_COLOR_BLUE 8
186
187/* each thermal zone uses 12 chars, 8 for name, 2 for instance, 2 space
188 * also used to list trip points in forms of AAAC, which represents
189 * A: Active
190 * C: Critical
191 */
192#define TZONE_RECORD_SIZE 12
193#define TZ_LEFT_ALIGN 32
194#define CDEV_NAME_SIZE 20
195#define CDEV_FLAG_IN_CONTROL (1 << 0)
196
197/* dialogue box starts */
198#define DIAG_X 48
199#define DIAG_Y 8
200#define THERMAL_SYSFS "/sys/class/thermal"
201#define CDEV "cooling_device"
202#define TZONE "thermal_zone"
203#define TDATA_LEFT 16
204#endif /* TMON_H */
diff --git a/tools/thermal/tmon/tui.c b/tools/thermal/tmon/tui.c
new file mode 100644
index 000000000000..89f8ef0e15c8
--- /dev/null
+++ b/tools/thermal/tmon/tui.c
@@ -0,0 +1,638 @@
1/*
2 * tui.c ncurses text user interface for TMON program
3 *
4 * Copyright (C) 2013 Intel Corporation. All rights reserved.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License version
8 * 2 or later as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * Author: Jacob Pan <jacob.jun.pan@linux.intel.com>
16 *
17 */
18
19#include <unistd.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include <stdint.h>
24#include <ncurses.h>
25#include <time.h>
26#include <syslog.h>
27#include <panel.h>
28#include <pthread.h>
29#include <signal.h>
30
31#include "tmon.h"
32
33static PANEL *data_panel;
34static PANEL *dialogue_panel;
35static PANEL *top;
36
37static WINDOW *title_bar_window;
38static WINDOW *tz_sensor_window;
39static WINDOW *cooling_device_window;
40static WINDOW *control_window;
41static WINDOW *status_bar_window;
42static WINDOW *thermal_data_window;
43static WINDOW *dialogue_window;
44
45char status_bar_slots[10][40];
46static void draw_hbar(WINDOW *win, int y, int start, int len,
47 unsigned long pattern, bool end);
48
49static int maxx, maxy;
50static int maxwidth = 200;
51
52#define TITLE_BAR_HIGHT 1
53#define SENSOR_WIN_HIGHT 4 /* one row for tz name, one for trip points */
54
55
56/* daemon mode flag (set by startup parameter -d) */
57static int tui_disabled;
58
59static void close_panel(PANEL *p)
60{
61 if (p) {
62 del_panel(p);
63 p = NULL;
64 }
65}
66
67static void close_window(WINDOW *win)
68{
69 if (win) {
70 delwin(win);
71 win = NULL;
72 }
73}
74
75void close_windows(void)
76{
77 if (tui_disabled)
78 return;
79 /* must delete panels before their attached windows */
80 if (dialogue_window)
81 close_panel(dialogue_panel);
82 if (cooling_device_window)
83 close_panel(data_panel);
84
85 close_window(title_bar_window);
86 close_window(tz_sensor_window);
87 close_window(status_bar_window);
88 close_window(cooling_device_window);
89 close_window(control_window);
90 close_window(thermal_data_window);
91 close_window(dialogue_window);
92
93}
94
95void write_status_bar(int x, char *line)
96{
97 mvwprintw(status_bar_window, 0, x, "%s", line);
98 wrefresh(status_bar_window);
99}
100
101void setup_windows(void)
102{
103 int y_begin = 1;
104
105 if (tui_disabled)
106 return;
107
108 getmaxyx(stdscr, maxy, maxx);
109 resizeterm(maxy, maxx);
110
111 title_bar_window = subwin(stdscr, TITLE_BAR_HIGHT, maxx, 0, 0);
112 y_begin += TITLE_BAR_HIGHT;
113
114 tz_sensor_window = subwin(stdscr, SENSOR_WIN_HIGHT, maxx, y_begin, 0);
115 y_begin += SENSOR_WIN_HIGHT;
116
117 cooling_device_window = subwin(stdscr, ptdata.nr_cooling_dev + 3, maxx,
118 y_begin, 0);
119 y_begin += ptdata.nr_cooling_dev + 3; /* 2 lines for border */
120 /* two lines to show borders, one line per tz show trip point position
121 * and value.
122 * dialogue window is a pop-up, when needed it lays on top of cdev win
123 */
124
125 dialogue_window = subwin(stdscr, ptdata.nr_cooling_dev+5, maxx-50,
126 DIAG_Y, DIAG_X);
127
128 thermal_data_window = subwin(stdscr, ptdata.nr_tz_sensor *
129 NR_LINES_TZDATA + 3, maxx, y_begin, 0);
130 y_begin += ptdata.nr_tz_sensor * NR_LINES_TZDATA + 3;
131 control_window = subwin(stdscr, 4, maxx, y_begin, 0);
132
133 scrollok(cooling_device_window, TRUE);
134 maxwidth = maxx - 18;
135 status_bar_window = subwin(stdscr, 1, maxx, maxy-1, 0);
136
137 strcpy(status_bar_slots[0], " Ctrl-c - Quit ");
138 strcpy(status_bar_slots[1], " TAB - Tuning ");
139 wmove(status_bar_window, 1, 30);
140
141 /* prepare panels for dialogue, if panel already created then we must
142 * be doing resizing, so just replace windows with new ones, old ones
143 * should have been deleted by close_window
144 */
145 data_panel = new_panel(cooling_device_window);
146 if (!data_panel)
147 syslog(LOG_DEBUG, "No data panel\n");
148 else {
149 if (dialogue_window) {
150 dialogue_panel = new_panel(dialogue_window);
151 if (!dialogue_panel)
152 syslog(LOG_DEBUG, "No dialogue panel\n");
153 else {
154 /* Set up the user pointer to the next panel*/
155 set_panel_userptr(data_panel, dialogue_panel);
156 set_panel_userptr(dialogue_panel, data_panel);
157 top = data_panel;
158 }
159 } else
160 syslog(LOG_INFO, "no dialogue win, term too small\n");
161 }
162 doupdate();
163 werase(stdscr);
164 refresh();
165}
166
167void resize_handler(int sig)
168{
169 /* start over when term gets resized, but first we clean up */
170 close_windows();
171 endwin();
172 refresh();
173 clear();
174 getmaxyx(stdscr, maxy, maxx); /* get the new screen size */
175 setup_windows();
176 /* rate limit */
177 sleep(1);
178 syslog(LOG_DEBUG, "SIG %d, term resized to %d x %d\n",
179 sig, maxy, maxx);
180 signal(SIGWINCH, resize_handler);
181}
182
183const char cdev_title[] = " COOLING DEVICES ";
184void show_cooling_device(void)
185{
186 int i, j, x, y = 0;
187
188 if (tui_disabled || !cooling_device_window)
189 return;
190
191 werase(cooling_device_window);
192 wattron(cooling_device_window, A_BOLD);
193 mvwprintw(cooling_device_window, 1, 1,
194 "ID Cooling Dev Cur Max Thermal Zone Binding");
195 wattroff(cooling_device_window, A_BOLD);
196 for (j = 0; j < ptdata.nr_cooling_dev; j++) {
197 /* draw cooling device list on the left in the order of
198 * cooling device instances. skip unused idr.
199 */
200 mvwprintw(cooling_device_window, j + 2, 1,
201 "%02d %12.12s%6d %6d",
202 ptdata.cdi[j].instance,
203 ptdata.cdi[j].type,
204 ptdata.cdi[j].cur_state,
205 ptdata.cdi[j].max_state);
206 }
207
208 /* show cdev binding, y is the global cooling device instance */
209 for (i = 0; i < ptdata.nr_tz_sensor; i++) {
210 int tz_inst = ptdata.tzi[i].instance;
211 for (j = 0; j < ptdata.nr_cooling_dev; j++) {
212 int cdev_inst;
213 y = j;
214 x = tz_inst * TZONE_RECORD_SIZE + TZ_LEFT_ALIGN;
215
216 draw_hbar(cooling_device_window, y+2, x,
217 TZONE_RECORD_SIZE-1, ACS_VLINE, false);
218
219 /* draw a column of spaces to separate thermal zones */
220 mvwprintw(cooling_device_window, y+2, x-1, " ");
221 if (ptdata.tzi[i].cdev_binding) {
222 cdev_inst = ptdata.cdi[j].instance;
223 unsigned long trip_binding =
224 ptdata.tzi[i].trip_binding[cdev_inst];
225 int k = 0; /* per zone trip point id that
226 * binded to this cdev, one to
227 * many possible based on the
228 * binding bitmask.
229 */
230 syslog(LOG_DEBUG,
231 "bind tz%d cdev%d tp%lx %d cdev%lx\n",
232 i, j, trip_binding, y,
233 ptdata.tzi[i].cdev_binding);
234 /* draw each trip binding for the cdev */
235 while (trip_binding >>= 1) {
236 k++;
237 if (!(trip_binding & 1))
238 continue;
239 /* draw '*' to show binding */
240 mvwprintw(cooling_device_window,
241 y + 2,
242 x + ptdata.tzi[i].nr_trip_pts -
243 k - 1, "*");
244 }
245 }
246 }
247 }
248 /* draw border after data so that border will not be messed up
249 * even there is not enough space for all the data to be shown
250 */
251 wborder(cooling_device_window, 0, 0, 0, 0, 0, 0, 0, 0);
252 wattron(cooling_device_window, A_BOLD);
253 mvwprintw(cooling_device_window, 0, maxx/2 - sizeof(cdev_title),
254 cdev_title);
255 wattroff(cooling_device_window, A_BOLD);
256
257 wrefresh(cooling_device_window);
258}
259
260const char DIAG_TITLE[] = "[ TUNABLES ]";
261#define DIAG_DEV_ROWS 5
262void show_dialogue(void)
263{
264 int j, x = 0, y = 0;
265 WINDOW *w = dialogue_window;
266
267 if (tui_disabled || !w)
268 return;
269
270 werase(w);
271 box(w, 0, 0);
272 mvwprintw(w, 0, maxx/4, DIAG_TITLE);
273 /* list all the available tunables */
274 for (j = 0; j <= ptdata.nr_cooling_dev; j++) {
275 y = j % DIAG_DEV_ROWS;
276 if (y == 0 && j != 0)
277 x += 20;
278 if (j == ptdata.nr_cooling_dev)
279 /* save last choice for target temp */
280 mvwprintw(w, y+1, x+1, "%C-%.12s", 'A'+j, "Set Temp");
281 else
282 mvwprintw(w, y+1, x+1, "%C-%.10s-%2d", 'A'+j,
283 ptdata.cdi[j].type, ptdata.cdi[j].instance);
284 }
285 wattron(w, A_BOLD);
286 mvwprintw(w, DIAG_DEV_ROWS+1, 1, "Enter Choice [A-Z]?");
287 wattroff(w, A_BOLD);
288 /* y size of dialogue win is nr cdev + 5, so print legend
289 * at the bottom line
290 */
291 mvwprintw(w, ptdata.nr_cooling_dev+3, 1,
292 "Legend: A=Active, P=Passive, C=Critical");
293
294 wrefresh(dialogue_window);
295}
296
297void write_dialogue_win(char *buf, int y, int x)
298{
299 WINDOW *w = dialogue_window;
300
301 mvwprintw(w, y, x, "%s", buf);
302}
303
304const char control_title[] = " CONTROLS ";
305void show_control_w(void)
306{
307 unsigned long state;
308
309 get_ctrl_state(&state);
310
311 if (tui_disabled || !control_window)
312 return;
313
314 werase(control_window);
315 mvwprintw(control_window, 1, 1,
316 "PID gain: kp=%2.2f ki=%2.2f kd=%2.2f Output %2.2f",
317 p_param.kp, p_param.ki, p_param.kd, p_param.y_k);
318
319 mvwprintw(control_window, 2, 1,
320 "Target Temp: %2.1fC, Zone: %d, Control Device: %.12s",
321 p_param.t_target, target_thermal_zone, ctrl_cdev);
322
323 /* draw border last such that everything is within boundary */
324 wborder(control_window, 0, 0, 0, 0, 0, 0, 0, 0);
325 wattron(control_window, A_BOLD);
326 mvwprintw(control_window, 0, maxx/2 - sizeof(control_title),
327 control_title);
328 wattroff(control_window, A_BOLD);
329
330 wrefresh(control_window);
331}
332
333void initialize_curses(void)
334{
335 if (tui_disabled)
336 return;
337
338 initscr();
339 start_color();
340 keypad(stdscr, TRUE); /* enable keyboard mapping */
341 nonl(); /* tell curses not to do NL->CR/NL on output */
342 cbreak(); /* take input chars one at a time */
343 noecho(); /* dont echo input */
344 curs_set(0); /* turn off cursor */
345 use_default_colors();
346
347 init_pair(PT_COLOR_DEFAULT, COLOR_WHITE, COLOR_BLACK);
348 init_pair(PT_COLOR_HEADER_BAR, COLOR_BLACK, COLOR_WHITE);
349 init_pair(PT_COLOR_ERROR, COLOR_BLACK, COLOR_RED);
350 init_pair(PT_COLOR_RED, COLOR_WHITE, COLOR_RED);
351 init_pair(PT_COLOR_YELLOW, COLOR_WHITE, COLOR_YELLOW);
352 init_pair(PT_COLOR_GREEN, COLOR_WHITE, COLOR_GREEN);
353 init_pair(PT_COLOR_BLUE, COLOR_WHITE, COLOR_BLUE);
354 init_pair(PT_COLOR_BRIGHT, COLOR_WHITE, COLOR_BLACK);
355
356}
357
358void show_title_bar(void)
359{
360 int i;
361 int x = 0;
362
363 if (tui_disabled || !title_bar_window)
364 return;
365
366 wattrset(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR));
367 wbkgd(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR));
368 werase(title_bar_window);
369
370 mvwprintw(title_bar_window, 0, 0,
371 " TMON v%s", VERSION);
372
373 wrefresh(title_bar_window);
374
375 werase(status_bar_window);
376
377 for (i = 0; i < 10; i++) {
378 if (strlen(status_bar_slots[i]) == 0)
379 continue;
380 wattron(status_bar_window, A_REVERSE);
381 mvwprintw(status_bar_window, 0, x, "%s", status_bar_slots[i]);
382 wattroff(status_bar_window, A_REVERSE);
383 x += strlen(status_bar_slots[i]) + 1;
384 }
385 wrefresh(status_bar_window);
386}
387
388static void handle_input_val(int ch)
389{
390 char buf[32];
391 int val;
392 char path[256];
393 WINDOW *w = dialogue_window;
394
395 echo();
396 keypad(w, TRUE);
397 wgetnstr(w, buf, 31);
398 val = atoi(buf);
399
400 if (ch == ptdata.nr_cooling_dev) {
401 snprintf(buf, 31, "Invalid Temp %d! %d-%d", val,
402 MIN_CTRL_TEMP, MAX_CTRL_TEMP);
403 if (val < MIN_CTRL_TEMP || val > MAX_CTRL_TEMP)
404 write_status_bar(40, buf);
405 else {
406 p_param.t_target = val;
407 snprintf(buf, 31, "Set New Target Temp %d", val);
408 write_status_bar(40, buf);
409 }
410 } else {
411 snprintf(path, 256, "%s/%s%d", THERMAL_SYSFS,
412 CDEV, ptdata.cdi[ch].instance);
413 sysfs_set_ulong(path, "cur_state", val);
414 }
415 noecho();
416 dialogue_on = 0;
417 show_data_w();
418 show_control_w();
419
420 top = (PANEL *)panel_userptr(top);
421 top_panel(top);
422}
423
424static void handle_input_choice(int ch)
425{
426 char buf[48];
427 int base = 0;
428 int cdev_id = 0;
429
430 if ((ch >= 'A' && ch <= 'A' + ptdata.nr_cooling_dev) ||
431 (ch >= 'a' && ch <= 'a' + ptdata.nr_cooling_dev)) {
432 base = (ch < 'a') ? 'A' : 'a';
433 cdev_id = ch - base;
434 if (ptdata.nr_cooling_dev == cdev_id)
435 snprintf(buf, sizeof(buf), "New Target Temp:");
436 else
437 snprintf(buf, sizeof(buf), "New Value for %.10s-%2d: ",
438 ptdata.cdi[cdev_id].type,
439 ptdata.cdi[cdev_id].instance);
440 write_dialogue_win(buf, DIAG_DEV_ROWS+2, 2);
441 handle_input_val(cdev_id);
442 } else {
443 snprintf(buf, sizeof(buf), "Invalid selection %d", ch);
444 write_dialogue_win(buf, 8, 2);
445 }
446}
447
448void *handle_tui_events(void *arg)
449{
450 int ch;
451
452 keypad(cooling_device_window, TRUE);
453 while ((ch = wgetch(cooling_device_window)) != EOF) {
454 if (tmon_exit)
455 break;
456 /* when term size is too small, no dialogue panels are set.
457 * we need to filter out such cases.
458 */
459 if (!data_panel || !dialogue_panel ||
460 !cooling_device_window ||
461 !dialogue_window) {
462
463 continue;
464 }
465 pthread_mutex_lock(&input_lock);
466 if (dialogue_on) {
467 handle_input_choice(ch);
468 /* top panel filter */
469 if (ch == 'q' || ch == 'Q')
470 ch = 0;
471 }
472 switch (ch) {
473 case KEY_LEFT:
474 box(cooling_device_window, 10, 0);
475 break;
476 case 9: /* TAB */
477 top = (PANEL *)panel_userptr(top);
478 top_panel(top);
479 if (top == dialogue_panel) {
480 dialogue_on = 1;
481 show_dialogue();
482 } else {
483 dialogue_on = 0;
484 /* force refresh */
485 show_data_w();
486 show_control_w();
487 }
488 break;
489 case 'q':
490 case 'Q':
491 tmon_exit = 1;
492 break;
493 }
494 update_panels();
495 doupdate();
496 pthread_mutex_unlock(&input_lock);
497 }
498
499 if (arg)
500 *(int *)arg = 0; /* make gcc happy */
501
502 return NULL;
503}
504
505/* draw a horizontal bar in given pattern */
506static void draw_hbar(WINDOW *win, int y, int start, int len, unsigned long ptn,
507 bool end)
508{
509 mvwaddch(win, y, start, ptn);
510 whline(win, ptn, len);
511 if (end)
512 mvwaddch(win, y, MAX_DISP_TEMP+TDATA_LEFT, ']');
513}
514
515static char trip_type_to_char(int type)
516{
517 switch (type) {
518 case THERMAL_TRIP_CRITICAL: return 'C';
519 case THERMAL_TRIP_HOT: return 'H';
520 case THERMAL_TRIP_PASSIVE: return 'P';
521 case THERMAL_TRIP_ACTIVE: return 'A';
522 default:
523 return '?';
524 }
525}
526
527/* fill a string with trip point type and value in one line
528 * e.g. P(56) C(106)
529 * maintain the distance one degree per char
530 */
531static void draw_tp_line(int tz, int y)
532{
533 int j;
534 int x;
535
536 for (j = 0; j < ptdata.tzi[tz].nr_trip_pts; j++) {
537 x = ptdata.tzi[tz].tp[j].temp / 1000;
538 mvwprintw(thermal_data_window, y + 0, x + TDATA_LEFT,
539 "%c%d", trip_type_to_char(ptdata.tzi[tz].tp[j].type),
540 x);
541 syslog(LOG_INFO, "%s:tz %d tp %d temp = %lu\n", __func__,
542 tz, j, ptdata.tzi[tz].tp[j].temp);
543 }
544}
545
546const char data_win_title[] = " THERMAL DATA ";
547void show_data_w(void)
548{
549 int i;
550
551
552 if (tui_disabled || !thermal_data_window)
553 return;
554
555 werase(thermal_data_window);
556 wattron(thermal_data_window, A_BOLD);
557 mvwprintw(thermal_data_window, 0, maxx/2 - sizeof(data_win_title),
558 data_win_title);
559 wattroff(thermal_data_window, A_BOLD);
560 /* draw a line as ruler */
561 for (i = 10; i < MAX_DISP_TEMP; i += 10)
562 mvwprintw(thermal_data_window, 1, i+TDATA_LEFT, "%2d", i);
563
564 for (i = 0; i < ptdata.nr_tz_sensor; i++) {
565 int temp = trec[cur_thermal_record].temp[i] / 1000;
566 int y = 0;
567
568 y = i * NR_LINES_TZDATA + 2;
569 /* y at tz temp data line */
570 mvwprintw(thermal_data_window, y, 1, "%6.6s%2d:[%3d][",
571 ptdata.tzi[i].type,
572 ptdata.tzi[i].instance, temp);
573 draw_hbar(thermal_data_window, y, TDATA_LEFT, temp, ACS_RARROW,
574 true);
575 draw_tp_line(i, y);
576 }
577 wborder(thermal_data_window, 0, 0, 0, 0, 0, 0, 0, 0);
578 wrefresh(thermal_data_window);
579}
580
581const char tz_title[] = "THERMAL ZONES(SENSORS)";
582
583void show_sensors_w(void)
584{
585 int i, j;
586 char buffer[512];
587
588 if (tui_disabled || !tz_sensor_window)
589 return;
590
591 werase(tz_sensor_window);
592
593 memset(buffer, 0, sizeof(buffer));
594 wattron(tz_sensor_window, A_BOLD);
595 mvwprintw(tz_sensor_window, 1, 1, "Thermal Zones:");
596 wattroff(tz_sensor_window, A_BOLD);
597
598 mvwprintw(tz_sensor_window, 1, TZ_LEFT_ALIGN, "%s", buffer);
599 /* fill trip points for each tzone */
600 wattron(tz_sensor_window, A_BOLD);
601 mvwprintw(tz_sensor_window, 2, 1, "Trip Points:");
602 wattroff(tz_sensor_window, A_BOLD);
603
604 /* draw trip point from low to high for each tz */
605 for (i = 0; i < ptdata.nr_tz_sensor; i++) {
606 int inst = ptdata.tzi[i].instance;
607
608 mvwprintw(tz_sensor_window, 1,
609 TZ_LEFT_ALIGN+TZONE_RECORD_SIZE * inst, "%.9s%02d",
610 ptdata.tzi[i].type, ptdata.tzi[i].instance);
611 for (j = ptdata.tzi[i].nr_trip_pts - 1; j >= 0; j--) {
612 /* loop through all trip points */
613 char type;
614 int tp_pos;
615 /* reverse the order here since trips are sorted
616 * in ascending order in terms of temperature.
617 */
618 tp_pos = ptdata.tzi[i].nr_trip_pts - j - 1;
619
620 type = trip_type_to_char(ptdata.tzi[i].tp[j].type);
621 mvwaddch(tz_sensor_window, 2,
622 inst * TZONE_RECORD_SIZE + TZ_LEFT_ALIGN +
623 tp_pos, type);
624 syslog(LOG_DEBUG, "draw tz %d tp %d ch:%c\n",
625 inst, j, type);
626 }
627 }
628 wborder(tz_sensor_window, 0, 0, 0, 0, 0, 0, 0, 0);
629 wattron(tz_sensor_window, A_BOLD);
630 mvwprintw(tz_sensor_window, 0, maxx/2 - sizeof(tz_title), tz_title);
631 wattroff(tz_sensor_window, A_BOLD);
632 wrefresh(tz_sensor_window);
633}
634
635void disable_tui(void)
636{
637 tui_disabled = 1;
638}