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
This commit is contained in:
commit
549608eadb
|
@ -514,10 +514,9 @@ static void acpi_thermal_check(void *data)
|
|||
{
|
||||
struct acpi_thermal *tz = data;
|
||||
|
||||
if (!tz->tz_enabled) {
|
||||
pr_warn("thermal zone is disabled \n");
|
||||
if (!tz->tz_enabled)
|
||||
return;
|
||||
}
|
||||
|
||||
thermal_zone_device_update(tz->thermal_zone);
|
||||
}
|
||||
|
||||
|
@ -569,9 +568,10 @@ static int thermal_set_mode(struct thermal_zone_device *thermal,
|
|||
*/
|
||||
if (mode == THERMAL_DEVICE_ENABLED)
|
||||
enable = 1;
|
||||
else if (mode == THERMAL_DEVICE_DISABLED)
|
||||
else if (mode == THERMAL_DEVICE_DISABLED) {
|
||||
enable = 0;
|
||||
else
|
||||
pr_warn("thermal zone will be disabled\n");
|
||||
} else
|
||||
return -EINVAL;
|
||||
|
||||
if (enable != tz->tz_enabled) {
|
||||
|
|
|
@ -56,7 +56,7 @@ config THERMAL_DEFAULT_GOV_USER_SPACE
|
|||
select THERMAL_GOV_USER_SPACE
|
||||
help
|
||||
Select this if you want to let the user space manage the
|
||||
lpatform thermals.
|
||||
platform thermals.
|
||||
|
||||
endchoice
|
||||
|
||||
|
@ -69,6 +69,7 @@ config THERMAL_GOV_STEP_WISE
|
|||
bool "Step_wise thermal governor"
|
||||
help
|
||||
Enable this to manage platform thermals using a simple linear
|
||||
governor.
|
||||
|
||||
config THERMAL_GOV_USER_SPACE
|
||||
bool "User_space thermal governor"
|
||||
|
@ -116,14 +117,14 @@ config SPEAR_THERMAL
|
|||
depends on OF
|
||||
help
|
||||
Enable this to plug the SPEAr thermal sensor driver into the Linux
|
||||
thermal framework
|
||||
thermal framework.
|
||||
|
||||
config RCAR_THERMAL
|
||||
tristate "Renesas R-Car thermal driver"
|
||||
depends on ARCH_SHMOBILE
|
||||
help
|
||||
Enable this to plug the R-Car thermal sensor driver into the Linux
|
||||
thermal framework
|
||||
thermal framework.
|
||||
|
||||
config KIRKWOOD_THERMAL
|
||||
tristate "Temperature sensor on Marvell Kirkwood SoCs"
|
||||
|
|
|
@ -469,10 +469,10 @@ cpufreq_cooling_register(const struct cpumask *clip_cpus)
|
|||
|
||||
cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
|
||||
&cpufreq_cooling_ops);
|
||||
if (!cool_dev) {
|
||||
if (IS_ERR(cool_dev)) {
|
||||
release_idr(&cpufreq_idr, cpufreq_dev->id);
|
||||
kfree(cpufreq_dev);
|
||||
return ERR_PTR(-EINVAL);
|
||||
return cool_dev;
|
||||
}
|
||||
cpufreq_dev->cool_dev = cool_dev;
|
||||
cpufreq_dev->cpufreq_state = 0;
|
||||
|
|
|
@ -675,6 +675,11 @@ static const struct x86_cpu_id intel_powerclamp_ids[] = {
|
|||
{ X86_VENDOR_INTEL, 6, 0x2e},
|
||||
{ X86_VENDOR_INTEL, 6, 0x2f},
|
||||
{ X86_VENDOR_INTEL, 6, 0x3a},
|
||||
{ X86_VENDOR_INTEL, 6, 0x3c},
|
||||
{ X86_VENDOR_INTEL, 6, 0x3e},
|
||||
{ X86_VENDOR_INTEL, 6, 0x3f},
|
||||
{ X86_VENDOR_INTEL, 6, 0x45},
|
||||
{ X86_VENDOR_INTEL, 6, 0x46},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids);
|
||||
|
@ -758,21 +763,39 @@ static int powerclamp_init(void)
|
|||
/* probe cpu features and ids here */
|
||||
retval = powerclamp_probe();
|
||||
if (retval)
|
||||
return retval;
|
||||
goto exit_free;
|
||||
|
||||
/* set default limit, maybe adjusted during runtime based on feedback */
|
||||
window_size = 2;
|
||||
register_hotcpu_notifier(&powerclamp_cpu_notifier);
|
||||
|
||||
powerclamp_thread = alloc_percpu(struct task_struct *);
|
||||
if (!powerclamp_thread) {
|
||||
retval = -ENOMEM;
|
||||
goto exit_unregister;
|
||||
}
|
||||
|
||||
cooling_dev = thermal_cooling_device_register("intel_powerclamp", NULL,
|
||||
&powerclamp_cooling_ops);
|
||||
if (IS_ERR(cooling_dev))
|
||||
return -ENODEV;
|
||||
if (IS_ERR(cooling_dev)) {
|
||||
retval = -ENODEV;
|
||||
goto exit_free_thread;
|
||||
}
|
||||
|
||||
if (!duration)
|
||||
duration = jiffies_to_msecs(DEFAULT_DURATION_JIFFIES);
|
||||
|
||||
powerclamp_create_debug_files();
|
||||
|
||||
return 0;
|
||||
|
||||
exit_free_thread:
|
||||
free_percpu(powerclamp_thread);
|
||||
exit_unregister:
|
||||
unregister_hotcpu_notifier(&powerclamp_cpu_notifier);
|
||||
exit_free:
|
||||
kfree(cpu_clamping_mask);
|
||||
return retval;
|
||||
}
|
||||
module_init(powerclamp_init);
|
||||
|
||||
|
|
|
@ -247,10 +247,11 @@ static void bind_cdev(struct thermal_cooling_device *cdev)
|
|||
if (!pos->tzp && !pos->ops->bind)
|
||||
continue;
|
||||
|
||||
if (!pos->tzp && pos->ops->bind) {
|
||||
if (pos->ops->bind) {
|
||||
ret = pos->ops->bind(pos, cdev);
|
||||
if (ret)
|
||||
print_bind_err_msg(pos, cdev, ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
tzp = pos->tzp;
|
||||
|
@ -282,8 +283,8 @@ static void bind_tz(struct thermal_zone_device *tz)
|
|||
|
||||
mutex_lock(&thermal_list_lock);
|
||||
|
||||
/* If there is no platform data, try to use ops->bind */
|
||||
if (!tzp && tz->ops->bind) {
|
||||
/* If there is ops->bind, try to use ops->bind */
|
||||
if (tz->ops->bind) {
|
||||
list_for_each_entry(pos, &thermal_cdev_list, node) {
|
||||
ret = tz->ops->bind(tz, pos);
|
||||
if (ret)
|
||||
|
@ -1038,7 +1039,8 @@ static void thermal_release(struct device *dev)
|
|||
sizeof("thermal_zone") - 1)) {
|
||||
tz = to_thermal_zone(dev);
|
||||
kfree(tz);
|
||||
} else {
|
||||
} else if(!strncmp(dev_name(dev), "cooling_device",
|
||||
sizeof("cooling_device") - 1)){
|
||||
cdev = to_cooling_device(dev);
|
||||
kfree(cdev);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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)));
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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()
|
|
@ -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-interval sampling time interval, > 1 sec.\n");
|
||||
printf(" -v, --version show version\n");
|
||||
printf(" -z, --zone target thermal zone id\n");
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void version()
|
||||
{
|
||||
printf("TMON version %s\n", VERSION);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
static void tmon_cleanup(void)
|
||||
{
|
||||
|
||||
syslog(LOG_INFO, "TMON exit cleanup\n");
|
||||
fflush(stdout);
|
||||
refresh();
|
||||
if (tmon_log)
|
||||
fclose(tmon_log);
|
||||
if (event_tid) {
|
||||
pthread_mutex_lock(&input_lock);
|
||||
pthread_cancel(event_tid);
|
||||
pthread_mutex_unlock(&input_lock);
|
||||
pthread_mutex_destroy(&input_lock);
|
||||
}
|
||||
closelog();
|
||||
/* relax control knobs, undo throttling */
|
||||
set_ctrl_state(0);
|
||||
|
||||
keypad(stdscr, FALSE);
|
||||
echo();
|
||||
nocbreak();
|
||||
close_windows();
|
||||
endwin();
|
||||
free_thermal_data();
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
static void tmon_sig_handler(int sig)
|
||||
{
|
||||
syslog(LOG_INFO, "TMON caught signal %d\n", sig);
|
||||
refresh();
|
||||
switch (sig) {
|
||||
case SIGTERM:
|
||||
printf("sigterm, exit and clean up\n");
|
||||
fflush(stdout);
|
||||
break;
|
||||
case SIGKILL:
|
||||
printf("sigkill, exit and clean up\n");
|
||||
fflush(stdout);
|
||||
break;
|
||||
case SIGINT:
|
||||
printf("ctrl-c, exit and clean up\n");
|
||||
fflush(stdout);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
tmon_exit = true;
|
||||
}
|
||||
|
||||
|
||||
static void start_syslog(void)
|
||||
{
|
||||
if (debug_on)
|
||||
setlogmask(LOG_UPTO(LOG_DEBUG));
|
||||
else
|
||||
setlogmask(LOG_UPTO(LOG_ERR));
|
||||
openlog("tmon.log", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL0);
|
||||
syslog(LOG_NOTICE, "TMON started by User %d", getuid());
|
||||
}
|
||||
|
||||
static void prepare_logging(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!logging)
|
||||
return;
|
||||
/* open local data log file */
|
||||
tmon_log = fopen(TMON_LOG_FILE, "w+");
|
||||
if (!tmon_log) {
|
||||
syslog(LOG_ERR, "failed to open log file %s\n", TMON_LOG_FILE);
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(tmon_log, "#----------- THERMAL SYSTEM CONFIG -------------\n");
|
||||
for (i = 0; i < ptdata.nr_tz_sensor; i++) {
|
||||
char binding_str[33]; /* size of long + 1 */
|
||||
int j;
|
||||
|
||||
memset(binding_str, 0, sizeof(binding_str));
|
||||
for (j = 0; j < 32; j++)
|
||||
binding_str[j] = (ptdata.tzi[i].cdev_binding & 1<<j) ?
|
||||
'1' : '0';
|
||||
|
||||
fprintf(tmon_log, "#thermal zone %s%02d cdevs binding: %32s\n",
|
||||
ptdata.tzi[i].type,
|
||||
ptdata.tzi[i].instance,
|
||||
binding_str);
|
||||
for (j = 0; j < ptdata.tzi[i].nr_trip_pts; j++) {
|
||||
fprintf(tmon_log, "#\tTP%02d type:%s, temp:%lu\n", j,
|
||||
trip_type_name[ptdata.tzi[i].tp[j].type],
|
||||
ptdata.tzi[i].tp[j].temp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for (i = 0; i < ptdata.nr_cooling_dev; i++)
|
||||
fprintf(tmon_log, "#cooling devices%02d: %s\n",
|
||||
i, ptdata.cdi[i].type);
|
||||
|
||||
fprintf(tmon_log, "#---------- THERMAL DATA LOG STARTED -----------\n");
|
||||
fprintf(tmon_log, "Samples TargetTemp ");
|
||||
for (i = 0; i < ptdata.nr_tz_sensor; i++) {
|
||||
fprintf(tmon_log, "%s%d ", ptdata.tzi[i].type,
|
||||
ptdata.tzi[i].instance);
|
||||
}
|
||||
for (i = 0; i < ptdata.nr_cooling_dev; i++)
|
||||
fprintf(tmon_log, "%s%d ", ptdata.cdi[i].type,
|
||||
ptdata.cdi[i].instance);
|
||||
|
||||
fprintf(tmon_log, "\n");
|
||||
}
|
||||
|
||||
static struct option opts[] = {
|
||||
{ "control", 1, NULL, 'c' },
|
||||
{ "daemon", 0, NULL, 'd' },
|
||||
{ "time-interval", 1, NULL, 't' },
|
||||
{ "log", 0, NULL, 'l' },
|
||||
{ "help", 0, NULL, 'h' },
|
||||
{ "version", 0, NULL, 'v' },
|
||||
{ "debug", 0, NULL, 'g' },
|
||||
{ 0, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int err = 0;
|
||||
int id2 = 0, c;
|
||||
double yk = 0.0; /* controller output */
|
||||
int target_tz_index;
|
||||
|
||||
if (geteuid() != 0) {
|
||||
printf("TMON needs to be run as root\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
while ((c = getopt_long(argc, argv, "c:dlht:vgz:", opts, &id2)) != -1) {
|
||||
switch (c) {
|
||||
case 'c':
|
||||
no_control = 0;
|
||||
strncpy(ctrl_cdev, optarg, CDEV_NAME_SIZE);
|
||||
break;
|
||||
case 'd':
|
||||
start_daemon_mode();
|
||||
printf("Run TMON in daemon mode\n");
|
||||
break;
|
||||
case 't':
|
||||
ticktime = strtod(optarg, NULL);
|
||||
if (ticktime < 1)
|
||||
ticktime = 1;
|
||||
break;
|
||||
case 'l':
|
||||
printf("Logging data to /var/tmp/tmon.log\n");
|
||||
logging = 1;
|
||||
break;
|
||||
case 'h':
|
||||
usage();
|
||||
break;
|
||||
case 'v':
|
||||
version();
|
||||
break;
|
||||
case 'g':
|
||||
debug_on = 1;
|
||||
break;
|
||||
case 'z':
|
||||
target_thermal_zone = strtod(optarg, NULL);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pthread_mutex_init(&input_lock, NULL) != 0) {
|
||||
fprintf(stderr, "\n mutex init failed, exit\n");
|
||||
return 1;
|
||||
}
|
||||
start_syslog();
|
||||
if (signal(SIGINT, tmon_sig_handler) == SIG_ERR)
|
||||
syslog(LOG_DEBUG, "Cannot handle SIGINT\n");
|
||||
if (signal(SIGTERM, tmon_sig_handler) == SIG_ERR)
|
||||
syslog(LOG_DEBUG, "Cannot handle SIGINT\n");
|
||||
|
||||
if (probe_thermal_sysfs()) {
|
||||
pthread_mutex_destroy(&input_lock);
|
||||
closelog();
|
||||
return -1;
|
||||
}
|
||||
initialize_curses();
|
||||
setup_windows();
|
||||
signal(SIGWINCH, resize_handler);
|
||||
show_title_bar();
|
||||
show_sensors_w();
|
||||
show_cooling_device();
|
||||
update_thermal_data();
|
||||
show_data_w();
|
||||
prepare_logging();
|
||||
init_thermal_controller();
|
||||
|
||||
nodelay(stdscr, TRUE);
|
||||
err = pthread_create(&event_tid, NULL, &handle_tui_events, NULL);
|
||||
if (err != 0) {
|
||||
printf("\ncan't create thread :[%s]", strerror(err));
|
||||
tmon_cleanup();
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* validate range of user selected target zone, default to the first
|
||||
* instance if out of range
|
||||
*/
|
||||
target_tz_index = zone_instance_to_index(target_thermal_zone);
|
||||
if (target_tz_index < 0) {
|
||||
target_thermal_zone = ptdata.tzi[0].instance;
|
||||
syslog(LOG_ERR, "target zone is not found, default to %d\n",
|
||||
target_thermal_zone);
|
||||
}
|
||||
while (1) {
|
||||
sleep(ticktime);
|
||||
show_title_bar();
|
||||
show_sensors_w();
|
||||
update_thermal_data();
|
||||
if (!dialogue_on) {
|
||||
show_data_w();
|
||||
show_cooling_device();
|
||||
}
|
||||
cur_thermal_record++;
|
||||
time_elapsed += ticktime;
|
||||
controller_handler(trec[0].temp[target_tz_index] / 1000,
|
||||
&yk);
|
||||
trec[0].pid_out_pct = yk;
|
||||
if (!dialogue_on)
|
||||
show_control_w();
|
||||
if (tmon_exit)
|
||||
break;
|
||||
}
|
||||
tmon_cleanup();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void start_daemon_mode()
|
||||
{
|
||||
daemon_mode = 1;
|
||||
/* fork */
|
||||
pid_t sid, pid = fork();
|
||||
if (pid < 0) {
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (pid > 0)
|
||||
/* kill parent */
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
/* disable TUI, it may not be necessary, but saves some resource */
|
||||
disable_tui();
|
||||
|
||||
/* change the file mode mask */
|
||||
umask(0);
|
||||
|
||||
/* new SID for the daemon process */
|
||||
sid = setsid();
|
||||
if (sid < 0)
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
/* change working directory */
|
||||
if ((chdir("/")) < 0)
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
|
||||
sleep(10);
|
||||
|
||||
close(STDIN_FILENO);
|
||||
close(STDOUT_FILENO);
|
||||
close(STDERR_FILENO);
|
||||
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* tmon.h contains data structures and constants used by TMON
|
||||
*
|
||||
* Copyright (C) 2012 Intel Corporation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 or later as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Author Name Jacob Pan <jacob.jun.pan@linux.intel.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TMON_H
|
||||
#define TMON_H
|
||||
|
||||
#define MAX_DISP_TEMP 125
|
||||
#define MAX_CTRL_TEMP 105
|
||||
#define MIN_CTRL_TEMP 40
|
||||
#define MAX_NR_TZONE 16
|
||||
#define MAX_NR_CDEV 32
|
||||
#define MAX_NR_TRIP 16
|
||||
#define MAX_NR_CDEV_TRIP 12 /* number of cooling devices that can bind
|
||||
* to a thermal zone trip.
|
||||
*/
|
||||
#define MAX_TEMP_KC 140000
|
||||
/* starting char position to draw sensor data, such as tz names
|
||||
* trip point list, etc.
|
||||
*/
|
||||
#define DATA_LEFT_ALIGN 10
|
||||
#define NR_LINES_TZDATA 1
|
||||
#define TMON_LOG_FILE "/var/tmp/tmon.log"
|
||||
|
||||
extern unsigned long ticktime;
|
||||
extern double time_elapsed;
|
||||
extern unsigned long target_temp_user;
|
||||
extern int dialogue_on;
|
||||
extern char ctrl_cdev[];
|
||||
extern pthread_mutex_t input_lock;
|
||||
extern int tmon_exit;
|
||||
extern int target_thermal_zone;
|
||||
/* use fixed size record to simplify data processing and transfer
|
||||
* TBD: more info to be added, e.g. programmable trip point data.
|
||||
*/
|
||||
struct thermal_data_record {
|
||||
struct timeval tv;
|
||||
unsigned long temp[MAX_NR_TZONE];
|
||||
double pid_out_pct;
|
||||
};
|
||||
|
||||
struct cdev_info {
|
||||
char type[64];
|
||||
int instance;
|
||||
unsigned long max_state;
|
||||
unsigned long cur_state;
|
||||
unsigned long flag;
|
||||
};
|
||||
|
||||
enum trip_type {
|
||||
THERMAL_TRIP_CRITICAL,
|
||||
THERMAL_TRIP_HOT,
|
||||
THERMAL_TRIP_PASSIVE,
|
||||
THERMAL_TRIP_ACTIVE,
|
||||
NR_THERMAL_TRIP_TYPE,
|
||||
};
|
||||
|
||||
struct trip_point {
|
||||
enum trip_type type;
|
||||
unsigned long temp;
|
||||
unsigned long hysteresis;
|
||||
int attribute; /* programmability etc. */
|
||||
};
|
||||
|
||||
/* thermal zone configuration information, binding with cooling devices could
|
||||
* change at runtime.
|
||||
*/
|
||||
struct tz_info {
|
||||
char type[256]; /* e.g. acpitz */
|
||||
int instance;
|
||||
int passive; /* active zone has passive node to force passive mode */
|
||||
int nr_cdev; /* number of cooling device binded */
|
||||
int nr_trip_pts;
|
||||
struct trip_point tp[MAX_NR_TRIP];
|
||||
unsigned long cdev_binding; /* bitmap for attached cdevs */
|
||||
/* cdev bind trip points, allow one cdev bind to multiple trips */
|
||||
unsigned long trip_binding[MAX_NR_CDEV];
|
||||
};
|
||||
|
||||
struct tmon_platform_data {
|
||||
int nr_tz_sensor;
|
||||
int nr_cooling_dev;
|
||||
/* keep track of instance ids since there might be gaps */
|
||||
int max_tz_instance;
|
||||
int max_cdev_instance;
|
||||
struct tz_info *tzi;
|
||||
struct cdev_info *cdi;
|
||||
};
|
||||
|
||||
struct control_ops {
|
||||
void (*set_ratio)(unsigned long ratio);
|
||||
unsigned long (*get_ratio)(unsigned long ratio);
|
||||
|
||||
};
|
||||
|
||||
enum cdev_types {
|
||||
CDEV_TYPE_PROC,
|
||||
CDEV_TYPE_FAN,
|
||||
CDEV_TYPE_MEM,
|
||||
CDEV_TYPE_NR,
|
||||
};
|
||||
|
||||
/* REVISIT: the idea is to group sensors if possible, e.g. on intel mid
|
||||
* we have "skin0", "skin1", "sys", "msicdie"
|
||||
* on DPTF enabled systems, we might have PCH, TSKN, TAMB, etc.
|
||||
*/
|
||||
enum tzone_types {
|
||||
TZONE_TYPE_ACPI,
|
||||
TZONE_TYPE_PCH,
|
||||
TZONE_TYPE_NR,
|
||||
};
|
||||
|
||||
/* limit the output of PID controller adjustment */
|
||||
#define LIMIT_HIGH (95)
|
||||
#define LIMIT_LOW (2)
|
||||
|
||||
struct pid_params {
|
||||
double kp; /* Controller gain from Dialog Box */
|
||||
double ki; /* Time-constant for I action from Dialog Box */
|
||||
double kd; /* Time-constant for D action from Dialog Box */
|
||||
double ts;
|
||||
double k_lpf;
|
||||
|
||||
double t_target;
|
||||
double y_k;
|
||||
};
|
||||
|
||||
extern int init_thermal_controller(void);
|
||||
extern void controller_handler(const double xk, double *yk);
|
||||
|
||||
extern struct tmon_platform_data ptdata;
|
||||
extern struct pid_params p_param;
|
||||
|
||||
extern FILE *tmon_log;
|
||||
extern int cur_thermal_record; /* index to the trec array */
|
||||
extern struct thermal_data_record trec[];
|
||||
extern const char *trip_type_name[];
|
||||
extern unsigned long no_control;
|
||||
|
||||
extern void initialize_curses(void);
|
||||
extern void show_controller_stats(char *line);
|
||||
extern void show_title_bar(void);
|
||||
extern void setup_windows(void);
|
||||
extern void disable_tui(void);
|
||||
extern void show_sensors_w(void);
|
||||
extern void show_data_w(void);
|
||||
extern void write_status_bar(int x, char *line);
|
||||
extern void show_control_w();
|
||||
|
||||
extern void show_cooling_device(void);
|
||||
extern void show_dialogue(void);
|
||||
extern int update_thermal_data(void);
|
||||
|
||||
extern int probe_thermal_sysfs(void);
|
||||
extern void free_thermal_data(void);
|
||||
extern void resize_handler(int sig);
|
||||
extern void set_ctrl_state(unsigned long state);
|
||||
extern void get_ctrl_state(unsigned long *state);
|
||||
extern void *handle_tui_events(void *arg);
|
||||
extern int sysfs_set_ulong(char *path, char *filename, unsigned long val);
|
||||
extern int zone_instance_to_index(int zone_inst);
|
||||
extern void close_windows(void);
|
||||
|
||||
#define PT_COLOR_DEFAULT 1
|
||||
#define PT_COLOR_HEADER_BAR 2
|
||||
#define PT_COLOR_ERROR 3
|
||||
#define PT_COLOR_RED 4
|
||||
#define PT_COLOR_YELLOW 5
|
||||
#define PT_COLOR_GREEN 6
|
||||
#define PT_COLOR_BRIGHT 7
|
||||
#define PT_COLOR_BLUE 8
|
||||
|
||||
/* each thermal zone uses 12 chars, 8 for name, 2 for instance, 2 space
|
||||
* also used to list trip points in forms of AAAC, which represents
|
||||
* A: Active
|
||||
* C: Critical
|
||||
*/
|
||||
#define TZONE_RECORD_SIZE 12
|
||||
#define TZ_LEFT_ALIGN 32
|
||||
#define CDEV_NAME_SIZE 20
|
||||
#define CDEV_FLAG_IN_CONTROL (1 << 0)
|
||||
|
||||
/* dialogue box starts */
|
||||
#define DIAG_X 48
|
||||
#define DIAG_Y 8
|
||||
#define THERMAL_SYSFS "/sys/class/thermal"
|
||||
#define CDEV "cooling_device"
|
||||
#define TZONE "thermal_zone"
|
||||
#define TDATA_LEFT 16
|
||||
#endif /* TMON_H */
|
|
@ -0,0 +1,638 @@
|
|||
/*
|
||||
* tui.c ncurses text user interface for TMON program
|
||||
*
|
||||
* Copyright (C) 2013 Intel Corporation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 or later as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Author: Jacob Pan <jacob.jun.pan@linux.intel.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <ncurses.h>
|
||||
#include <time.h>
|
||||
#include <syslog.h>
|
||||
#include <panel.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "tmon.h"
|
||||
|
||||
static PANEL *data_panel;
|
||||
static PANEL *dialogue_panel;
|
||||
static PANEL *top;
|
||||
|
||||
static WINDOW *title_bar_window;
|
||||
static WINDOW *tz_sensor_window;
|
||||
static WINDOW *cooling_device_window;
|
||||
static WINDOW *control_window;
|
||||
static WINDOW *status_bar_window;
|
||||
static WINDOW *thermal_data_window;
|
||||
static WINDOW *dialogue_window;
|
||||
|
||||
char status_bar_slots[10][40];
|
||||
static void draw_hbar(WINDOW *win, int y, int start, int len,
|
||||
unsigned long pattern, bool end);
|
||||
|
||||
static int maxx, maxy;
|
||||
static int maxwidth = 200;
|
||||
|
||||
#define TITLE_BAR_HIGHT 1
|
||||
#define SENSOR_WIN_HIGHT 4 /* one row for tz name, one for trip points */
|
||||
|
||||
|
||||
/* daemon mode flag (set by startup parameter -d) */
|
||||
static int tui_disabled;
|
||||
|
||||
static void close_panel(PANEL *p)
|
||||
{
|
||||
if (p) {
|
||||
del_panel(p);
|
||||
p = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void close_window(WINDOW *win)
|
||||
{
|
||||
if (win) {
|
||||
delwin(win);
|
||||
win = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void close_windows(void)
|
||||
{
|
||||
if (tui_disabled)
|
||||
return;
|
||||
/* must delete panels before their attached windows */
|
||||
if (dialogue_window)
|
||||
close_panel(dialogue_panel);
|
||||
if (cooling_device_window)
|
||||
close_panel(data_panel);
|
||||
|
||||
close_window(title_bar_window);
|
||||
close_window(tz_sensor_window);
|
||||
close_window(status_bar_window);
|
||||
close_window(cooling_device_window);
|
||||
close_window(control_window);
|
||||
close_window(thermal_data_window);
|
||||
close_window(dialogue_window);
|
||||
|
||||
}
|
||||
|
||||
void write_status_bar(int x, char *line)
|
||||
{
|
||||
mvwprintw(status_bar_window, 0, x, "%s", line);
|
||||
wrefresh(status_bar_window);
|
||||
}
|
||||
|
||||
void setup_windows(void)
|
||||
{
|
||||
int y_begin = 1;
|
||||
|
||||
if (tui_disabled)
|
||||
return;
|
||||
|
||||
getmaxyx(stdscr, maxy, maxx);
|
||||
resizeterm(maxy, maxx);
|
||||
|
||||
title_bar_window = subwin(stdscr, TITLE_BAR_HIGHT, maxx, 0, 0);
|
||||
y_begin += TITLE_BAR_HIGHT;
|
||||
|
||||
tz_sensor_window = subwin(stdscr, SENSOR_WIN_HIGHT, maxx, y_begin, 0);
|
||||
y_begin += SENSOR_WIN_HIGHT;
|
||||
|
||||
cooling_device_window = subwin(stdscr, ptdata.nr_cooling_dev + 3, maxx,
|
||||
y_begin, 0);
|
||||
y_begin += ptdata.nr_cooling_dev + 3; /* 2 lines for border */
|
||||
/* two lines to show borders, one line per tz show trip point position
|
||||
* and value.
|
||||
* dialogue window is a pop-up, when needed it lays on top of cdev win
|
||||
*/
|
||||
|
||||
dialogue_window = subwin(stdscr, ptdata.nr_cooling_dev+5, maxx-50,
|
||||
DIAG_Y, DIAG_X);
|
||||
|
||||
thermal_data_window = subwin(stdscr, ptdata.nr_tz_sensor *
|
||||
NR_LINES_TZDATA + 3, maxx, y_begin, 0);
|
||||
y_begin += ptdata.nr_tz_sensor * NR_LINES_TZDATA + 3;
|
||||
control_window = subwin(stdscr, 4, maxx, y_begin, 0);
|
||||
|
||||
scrollok(cooling_device_window, TRUE);
|
||||
maxwidth = maxx - 18;
|
||||
status_bar_window = subwin(stdscr, 1, maxx, maxy-1, 0);
|
||||
|
||||
strcpy(status_bar_slots[0], " Ctrl-c - Quit ");
|
||||
strcpy(status_bar_slots[1], " TAB - Tuning ");
|
||||
wmove(status_bar_window, 1, 30);
|
||||
|
||||
/* prepare panels for dialogue, if panel already created then we must
|
||||
* be doing resizing, so just replace windows with new ones, old ones
|
||||
* should have been deleted by close_window
|
||||
*/
|
||||
data_panel = new_panel(cooling_device_window);
|
||||
if (!data_panel)
|
||||
syslog(LOG_DEBUG, "No data panel\n");
|
||||
else {
|
||||
if (dialogue_window) {
|
||||
dialogue_panel = new_panel(dialogue_window);
|
||||
if (!dialogue_panel)
|
||||
syslog(LOG_DEBUG, "No dialogue panel\n");
|
||||
else {
|
||||
/* Set up the user pointer to the next panel*/
|
||||
set_panel_userptr(data_panel, dialogue_panel);
|
||||
set_panel_userptr(dialogue_panel, data_panel);
|
||||
top = data_panel;
|
||||
}
|
||||
} else
|
||||
syslog(LOG_INFO, "no dialogue win, term too small\n");
|
||||
}
|
||||
doupdate();
|
||||
werase(stdscr);
|
||||
refresh();
|
||||
}
|
||||
|
||||
void resize_handler(int sig)
|
||||
{
|
||||
/* start over when term gets resized, but first we clean up */
|
||||
close_windows();
|
||||
endwin();
|
||||
refresh();
|
||||
clear();
|
||||
getmaxyx(stdscr, maxy, maxx); /* get the new screen size */
|
||||
setup_windows();
|
||||
/* rate limit */
|
||||
sleep(1);
|
||||
syslog(LOG_DEBUG, "SIG %d, term resized to %d x %d\n",
|
||||
sig, maxy, maxx);
|
||||
signal(SIGWINCH, resize_handler);
|
||||
}
|
||||
|
||||
const char cdev_title[] = " COOLING DEVICES ";
|
||||
void show_cooling_device(void)
|
||||
{
|
||||
int i, j, x, y = 0;
|
||||
|
||||
if (tui_disabled || !cooling_device_window)
|
||||
return;
|
||||
|
||||
werase(cooling_device_window);
|
||||
wattron(cooling_device_window, A_BOLD);
|
||||
mvwprintw(cooling_device_window, 1, 1,
|
||||
"ID Cooling Dev Cur Max Thermal Zone Binding");
|
||||
wattroff(cooling_device_window, A_BOLD);
|
||||
for (j = 0; j < ptdata.nr_cooling_dev; j++) {
|
||||
/* draw cooling device list on the left in the order of
|
||||
* cooling device instances. skip unused idr.
|
||||
*/
|
||||
mvwprintw(cooling_device_window, j + 2, 1,
|
||||
"%02d %12.12s%6d %6d",
|
||||
ptdata.cdi[j].instance,
|
||||
ptdata.cdi[j].type,
|
||||
ptdata.cdi[j].cur_state,
|
||||
ptdata.cdi[j].max_state);
|
||||
}
|
||||
|
||||
/* show cdev binding, y is the global cooling device instance */
|
||||
for (i = 0; i < ptdata.nr_tz_sensor; i++) {
|
||||
int tz_inst = ptdata.tzi[i].instance;
|
||||
for (j = 0; j < ptdata.nr_cooling_dev; j++) {
|
||||
int cdev_inst;
|
||||
y = j;
|
||||
x = tz_inst * TZONE_RECORD_SIZE + TZ_LEFT_ALIGN;
|
||||
|
||||
draw_hbar(cooling_device_window, y+2, x,
|
||||
TZONE_RECORD_SIZE-1, ACS_VLINE, false);
|
||||
|
||||
/* draw a column of spaces to separate thermal zones */
|
||||
mvwprintw(cooling_device_window, y+2, x-1, " ");
|
||||
if (ptdata.tzi[i].cdev_binding) {
|
||||
cdev_inst = ptdata.cdi[j].instance;
|
||||
unsigned long trip_binding =
|
||||
ptdata.tzi[i].trip_binding[cdev_inst];
|
||||
int k = 0; /* per zone trip point id that
|
||||
* binded to this cdev, one to
|
||||
* many possible based on the
|
||||
* binding bitmask.
|
||||
*/
|
||||
syslog(LOG_DEBUG,
|
||||
"bind tz%d cdev%d tp%lx %d cdev%lx\n",
|
||||
i, j, trip_binding, y,
|
||||
ptdata.tzi[i].cdev_binding);
|
||||
/* draw each trip binding for the cdev */
|
||||
while (trip_binding >>= 1) {
|
||||
k++;
|
||||
if (!(trip_binding & 1))
|
||||
continue;
|
||||
/* draw '*' to show binding */
|
||||
mvwprintw(cooling_device_window,
|
||||
y + 2,
|
||||
x + ptdata.tzi[i].nr_trip_pts -
|
||||
k - 1, "*");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* draw border after data so that border will not be messed up
|
||||
* even there is not enough space for all the data to be shown
|
||||
*/
|
||||
wborder(cooling_device_window, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
wattron(cooling_device_window, A_BOLD);
|
||||
mvwprintw(cooling_device_window, 0, maxx/2 - sizeof(cdev_title),
|
||||
cdev_title);
|
||||
wattroff(cooling_device_window, A_BOLD);
|
||||
|
||||
wrefresh(cooling_device_window);
|
||||
}
|
||||
|
||||
const char DIAG_TITLE[] = "[ TUNABLES ]";
|
||||
#define DIAG_DEV_ROWS 5
|
||||
void show_dialogue(void)
|
||||
{
|
||||
int j, x = 0, y = 0;
|
||||
WINDOW *w = dialogue_window;
|
||||
|
||||
if (tui_disabled || !w)
|
||||
return;
|
||||
|
||||
werase(w);
|
||||
box(w, 0, 0);
|
||||
mvwprintw(w, 0, maxx/4, DIAG_TITLE);
|
||||
/* list all the available tunables */
|
||||
for (j = 0; j <= ptdata.nr_cooling_dev; j++) {
|
||||
y = j % DIAG_DEV_ROWS;
|
||||
if (y == 0 && j != 0)
|
||||
x += 20;
|
||||
if (j == ptdata.nr_cooling_dev)
|
||||
/* save last choice for target temp */
|
||||
mvwprintw(w, y+1, x+1, "%C-%.12s", 'A'+j, "Set Temp");
|
||||
else
|
||||
mvwprintw(w, y+1, x+1, "%C-%.10s-%2d", 'A'+j,
|
||||
ptdata.cdi[j].type, ptdata.cdi[j].instance);
|
||||
}
|
||||
wattron(w, A_BOLD);
|
||||
mvwprintw(w, DIAG_DEV_ROWS+1, 1, "Enter Choice [A-Z]?");
|
||||
wattroff(w, A_BOLD);
|
||||
/* y size of dialogue win is nr cdev + 5, so print legend
|
||||
* at the bottom line
|
||||
*/
|
||||
mvwprintw(w, ptdata.nr_cooling_dev+3, 1,
|
||||
"Legend: A=Active, P=Passive, C=Critical");
|
||||
|
||||
wrefresh(dialogue_window);
|
||||
}
|
||||
|
||||
void write_dialogue_win(char *buf, int y, int x)
|
||||
{
|
||||
WINDOW *w = dialogue_window;
|
||||
|
||||
mvwprintw(w, y, x, "%s", buf);
|
||||
}
|
||||
|
||||
const char control_title[] = " CONTROLS ";
|
||||
void show_control_w(void)
|
||||
{
|
||||
unsigned long state;
|
||||
|
||||
get_ctrl_state(&state);
|
||||
|
||||
if (tui_disabled || !control_window)
|
||||
return;
|
||||
|
||||
werase(control_window);
|
||||
mvwprintw(control_window, 1, 1,
|
||||
"PID gain: kp=%2.2f ki=%2.2f kd=%2.2f Output %2.2f",
|
||||
p_param.kp, p_param.ki, p_param.kd, p_param.y_k);
|
||||
|
||||
mvwprintw(control_window, 2, 1,
|
||||
"Target Temp: %2.1fC, Zone: %d, Control Device: %.12s",
|
||||
p_param.t_target, target_thermal_zone, ctrl_cdev);
|
||||
|
||||
/* draw border last such that everything is within boundary */
|
||||
wborder(control_window, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
wattron(control_window, A_BOLD);
|
||||
mvwprintw(control_window, 0, maxx/2 - sizeof(control_title),
|
||||
control_title);
|
||||
wattroff(control_window, A_BOLD);
|
||||
|
||||
wrefresh(control_window);
|
||||
}
|
||||
|
||||
void initialize_curses(void)
|
||||
{
|
||||
if (tui_disabled)
|
||||
return;
|
||||
|
||||
initscr();
|
||||
start_color();
|
||||
keypad(stdscr, TRUE); /* enable keyboard mapping */
|
||||
nonl(); /* tell curses not to do NL->CR/NL on output */
|
||||
cbreak(); /* take input chars one at a time */
|
||||
noecho(); /* dont echo input */
|
||||
curs_set(0); /* turn off cursor */
|
||||
use_default_colors();
|
||||
|
||||
init_pair(PT_COLOR_DEFAULT, COLOR_WHITE, COLOR_BLACK);
|
||||
init_pair(PT_COLOR_HEADER_BAR, COLOR_BLACK, COLOR_WHITE);
|
||||
init_pair(PT_COLOR_ERROR, COLOR_BLACK, COLOR_RED);
|
||||
init_pair(PT_COLOR_RED, COLOR_WHITE, COLOR_RED);
|
||||
init_pair(PT_COLOR_YELLOW, COLOR_WHITE, COLOR_YELLOW);
|
||||
init_pair(PT_COLOR_GREEN, COLOR_WHITE, COLOR_GREEN);
|
||||
init_pair(PT_COLOR_BLUE, COLOR_WHITE, COLOR_BLUE);
|
||||
init_pair(PT_COLOR_BRIGHT, COLOR_WHITE, COLOR_BLACK);
|
||||
|
||||
}
|
||||
|
||||
void show_title_bar(void)
|
||||
{
|
||||
int i;
|
||||
int x = 0;
|
||||
|
||||
if (tui_disabled || !title_bar_window)
|
||||
return;
|
||||
|
||||
wattrset(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR));
|
||||
wbkgd(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR));
|
||||
werase(title_bar_window);
|
||||
|
||||
mvwprintw(title_bar_window, 0, 0,
|
||||
" TMON v%s", VERSION);
|
||||
|
||||
wrefresh(title_bar_window);
|
||||
|
||||
werase(status_bar_window);
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
if (strlen(status_bar_slots[i]) == 0)
|
||||
continue;
|
||||
wattron(status_bar_window, A_REVERSE);
|
||||
mvwprintw(status_bar_window, 0, x, "%s", status_bar_slots[i]);
|
||||
wattroff(status_bar_window, A_REVERSE);
|
||||
x += strlen(status_bar_slots[i]) + 1;
|
||||
}
|
||||
wrefresh(status_bar_window);
|
||||
}
|
||||
|
||||
static void handle_input_val(int ch)
|
||||
{
|
||||
char buf[32];
|
||||
int val;
|
||||
char path[256];
|
||||
WINDOW *w = dialogue_window;
|
||||
|
||||
echo();
|
||||
keypad(w, TRUE);
|
||||
wgetnstr(w, buf, 31);
|
||||
val = atoi(buf);
|
||||
|
||||
if (ch == ptdata.nr_cooling_dev) {
|
||||
snprintf(buf, 31, "Invalid Temp %d! %d-%d", val,
|
||||
MIN_CTRL_TEMP, MAX_CTRL_TEMP);
|
||||
if (val < MIN_CTRL_TEMP || val > MAX_CTRL_TEMP)
|
||||
write_status_bar(40, buf);
|
||||
else {
|
||||
p_param.t_target = val;
|
||||
snprintf(buf, 31, "Set New Target Temp %d", val);
|
||||
write_status_bar(40, buf);
|
||||
}
|
||||
} else {
|
||||
snprintf(path, 256, "%s/%s%d", THERMAL_SYSFS,
|
||||
CDEV, ptdata.cdi[ch].instance);
|
||||
sysfs_set_ulong(path, "cur_state", val);
|
||||
}
|
||||
noecho();
|
||||
dialogue_on = 0;
|
||||
show_data_w();
|
||||
show_control_w();
|
||||
|
||||
top = (PANEL *)panel_userptr(top);
|
||||
top_panel(top);
|
||||
}
|
||||
|
||||
static void handle_input_choice(int ch)
|
||||
{
|
||||
char buf[48];
|
||||
int base = 0;
|
||||
int cdev_id = 0;
|
||||
|
||||
if ((ch >= 'A' && ch <= 'A' + ptdata.nr_cooling_dev) ||
|
||||
(ch >= 'a' && ch <= 'a' + ptdata.nr_cooling_dev)) {
|
||||
base = (ch < 'a') ? 'A' : 'a';
|
||||
cdev_id = ch - base;
|
||||
if (ptdata.nr_cooling_dev == cdev_id)
|
||||
snprintf(buf, sizeof(buf), "New Target Temp:");
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "New Value for %.10s-%2d: ",
|
||||
ptdata.cdi[cdev_id].type,
|
||||
ptdata.cdi[cdev_id].instance);
|
||||
write_dialogue_win(buf, DIAG_DEV_ROWS+2, 2);
|
||||
handle_input_val(cdev_id);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "Invalid selection %d", ch);
|
||||
write_dialogue_win(buf, 8, 2);
|
||||
}
|
||||
}
|
||||
|
||||
void *handle_tui_events(void *arg)
|
||||
{
|
||||
int ch;
|
||||
|
||||
keypad(cooling_device_window, TRUE);
|
||||
while ((ch = wgetch(cooling_device_window)) != EOF) {
|
||||
if (tmon_exit)
|
||||
break;
|
||||
/* when term size is too small, no dialogue panels are set.
|
||||
* we need to filter out such cases.
|
||||
*/
|
||||
if (!data_panel || !dialogue_panel ||
|
||||
!cooling_device_window ||
|
||||
!dialogue_window) {
|
||||
|
||||
continue;
|
||||
}
|
||||
pthread_mutex_lock(&input_lock);
|
||||
if (dialogue_on) {
|
||||
handle_input_choice(ch);
|
||||
/* top panel filter */
|
||||
if (ch == 'q' || ch == 'Q')
|
||||
ch = 0;
|
||||
}
|
||||
switch (ch) {
|
||||
case KEY_LEFT:
|
||||
box(cooling_device_window, 10, 0);
|
||||
break;
|
||||
case 9: /* TAB */
|
||||
top = (PANEL *)panel_userptr(top);
|
||||
top_panel(top);
|
||||
if (top == dialogue_panel) {
|
||||
dialogue_on = 1;
|
||||
show_dialogue();
|
||||
} else {
|
||||
dialogue_on = 0;
|
||||
/* force refresh */
|
||||
show_data_w();
|
||||
show_control_w();
|
||||
}
|
||||
break;
|
||||
case 'q':
|
||||
case 'Q':
|
||||
tmon_exit = 1;
|
||||
break;
|
||||
}
|
||||
update_panels();
|
||||
doupdate();
|
||||
pthread_mutex_unlock(&input_lock);
|
||||
}
|
||||
|
||||
if (arg)
|
||||
*(int *)arg = 0; /* make gcc happy */
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* draw a horizontal bar in given pattern */
|
||||
static void draw_hbar(WINDOW *win, int y, int start, int len, unsigned long ptn,
|
||||
bool end)
|
||||
{
|
||||
mvwaddch(win, y, start, ptn);
|
||||
whline(win, ptn, len);
|
||||
if (end)
|
||||
mvwaddch(win, y, MAX_DISP_TEMP+TDATA_LEFT, ']');
|
||||
}
|
||||
|
||||
static char trip_type_to_char(int type)
|
||||
{
|
||||
switch (type) {
|
||||
case THERMAL_TRIP_CRITICAL: return 'C';
|
||||
case THERMAL_TRIP_HOT: return 'H';
|
||||
case THERMAL_TRIP_PASSIVE: return 'P';
|
||||
case THERMAL_TRIP_ACTIVE: return 'A';
|
||||
default:
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
|
||||
/* fill a string with trip point type and value in one line
|
||||
* e.g. P(56) C(106)
|
||||
* maintain the distance one degree per char
|
||||
*/
|
||||
static void draw_tp_line(int tz, int y)
|
||||
{
|
||||
int j;
|
||||
int x;
|
||||
|
||||
for (j = 0; j < ptdata.tzi[tz].nr_trip_pts; j++) {
|
||||
x = ptdata.tzi[tz].tp[j].temp / 1000;
|
||||
mvwprintw(thermal_data_window, y + 0, x + TDATA_LEFT,
|
||||
"%c%d", trip_type_to_char(ptdata.tzi[tz].tp[j].type),
|
||||
x);
|
||||
syslog(LOG_INFO, "%s:tz %d tp %d temp = %lu\n", __func__,
|
||||
tz, j, ptdata.tzi[tz].tp[j].temp);
|
||||
}
|
||||
}
|
||||
|
||||
const char data_win_title[] = " THERMAL DATA ";
|
||||
void show_data_w(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
|
||||
if (tui_disabled || !thermal_data_window)
|
||||
return;
|
||||
|
||||
werase(thermal_data_window);
|
||||
wattron(thermal_data_window, A_BOLD);
|
||||
mvwprintw(thermal_data_window, 0, maxx/2 - sizeof(data_win_title),
|
||||
data_win_title);
|
||||
wattroff(thermal_data_window, A_BOLD);
|
||||
/* draw a line as ruler */
|
||||
for (i = 10; i < MAX_DISP_TEMP; i += 10)
|
||||
mvwprintw(thermal_data_window, 1, i+TDATA_LEFT, "%2d", i);
|
||||
|
||||
for (i = 0; i < ptdata.nr_tz_sensor; i++) {
|
||||
int temp = trec[cur_thermal_record].temp[i] / 1000;
|
||||
int y = 0;
|
||||
|
||||
y = i * NR_LINES_TZDATA + 2;
|
||||
/* y at tz temp data line */
|
||||
mvwprintw(thermal_data_window, y, 1, "%6.6s%2d:[%3d][",
|
||||
ptdata.tzi[i].type,
|
||||
ptdata.tzi[i].instance, temp);
|
||||
draw_hbar(thermal_data_window, y, TDATA_LEFT, temp, ACS_RARROW,
|
||||
true);
|
||||
draw_tp_line(i, y);
|
||||
}
|
||||
wborder(thermal_data_window, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
wrefresh(thermal_data_window);
|
||||
}
|
||||
|
||||
const char tz_title[] = "THERMAL ZONES(SENSORS)";
|
||||
|
||||
void show_sensors_w(void)
|
||||
{
|
||||
int i, j;
|
||||
char buffer[512];
|
||||
|
||||
if (tui_disabled || !tz_sensor_window)
|
||||
return;
|
||||
|
||||
werase(tz_sensor_window);
|
||||
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
wattron(tz_sensor_window, A_BOLD);
|
||||
mvwprintw(tz_sensor_window, 1, 1, "Thermal Zones:");
|
||||
wattroff(tz_sensor_window, A_BOLD);
|
||||
|
||||
mvwprintw(tz_sensor_window, 1, TZ_LEFT_ALIGN, "%s", buffer);
|
||||
/* fill trip points for each tzone */
|
||||
wattron(tz_sensor_window, A_BOLD);
|
||||
mvwprintw(tz_sensor_window, 2, 1, "Trip Points:");
|
||||
wattroff(tz_sensor_window, A_BOLD);
|
||||
|
||||
/* draw trip point from low to high for each tz */
|
||||
for (i = 0; i < ptdata.nr_tz_sensor; i++) {
|
||||
int inst = ptdata.tzi[i].instance;
|
||||
|
||||
mvwprintw(tz_sensor_window, 1,
|
||||
TZ_LEFT_ALIGN+TZONE_RECORD_SIZE * inst, "%.9s%02d",
|
||||
ptdata.tzi[i].type, ptdata.tzi[i].instance);
|
||||
for (j = ptdata.tzi[i].nr_trip_pts - 1; j >= 0; j--) {
|
||||
/* loop through all trip points */
|
||||
char type;
|
||||
int tp_pos;
|
||||
/* reverse the order here since trips are sorted
|
||||
* in ascending order in terms of temperature.
|
||||
*/
|
||||
tp_pos = ptdata.tzi[i].nr_trip_pts - j - 1;
|
||||
|
||||
type = trip_type_to_char(ptdata.tzi[i].tp[j].type);
|
||||
mvwaddch(tz_sensor_window, 2,
|
||||
inst * TZONE_RECORD_SIZE + TZ_LEFT_ALIGN +
|
||||
tp_pos, type);
|
||||
syslog(LOG_DEBUG, "draw tz %d tp %d ch:%c\n",
|
||||
inst, j, type);
|
||||
}
|
||||
}
|
||||
wborder(tz_sensor_window, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
wattron(tz_sensor_window, A_BOLD);
|
||||
mvwprintw(tz_sensor_window, 0, maxx/2 - sizeof(tz_title), tz_title);
|
||||
wattroff(tz_sensor_window, A_BOLD);
|
||||
wrefresh(tz_sensor_window);
|
||||
}
|
||||
|
||||
void disable_tui(void)
|
||||
{
|
||||
tui_disabled = 1;
|
||||
}
|
Loading…
Reference in New Issue