On Wed, 9 Oct 2013 19:37:08 -0400 Eduardo Valentin wrote: > On 09-10-2013 13:52, Jacob Pan wrote: > > On Tue, 8 Oct 2013 21:03:38 -0400 > > Eduardo Valentin wrote: > > > >> On 08-10-2013 15:03, Jacob Pan wrote: > >>> 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 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. > >> > >> Jacob, > >> > >> Very nice initiative. Thanks for providing tools on thermal area. > >> We are lacking them. I have been using the linaro thermal testing > >> scripts for smoking testing the systems I am working on. But I have > >> been considering writing a ncurses based tool for long time. It is > >> good anyway that you have started and even shared it already. > >> > >> I gave a very quick shot on my OMAP4460 panda board and tmon is > >> crashing with segfault: > >>> TMON v1.0 > >>> > >>> ┌──────────────────────────────────────────────────────────────────────────────┐ > >>> │Thermal Zones: > >>> cpu_therm00 │ │Trip > >>> Points: > >>> CP │ > >>> └──────────────────────────────────────────────────────────────────────────────┘ > >>> ┌──────────────────────────────────────────────────────────────────────────────┐ > >>> │ID Cooling Dev Cur Max Thermal Zone > >>> Binding │ │00 thermal-cpuf 0 3 > >>> Segmentation fault │ > >>> └─────────────────────────────────────────────────[root@(none) ~]# > >>> ────────────┘ [root@(none) ~]# ./tmontmon > >>> ───────────────────────────────────────────────────┐ > >>> │ 10 20 30 40 > >>> 50 60 │ │cpu_th > >>> 0:[ 0][> > >>> │ > >>> └──────────────────────────────────────────────────────────────────────────────┘ > >>> > >>> > >> > >> I believe it is while updating the progress bar you've written to > >> represent temperature on the thermal zone temperature. > >> > > Sorry about the crash, I admit i only tested on x86 systesm. I just > > borrowed a Panda board. Could you tell me where you get the image to > > run it on? > > I am using a busybox based filesystem. And I have statically compiled > tmon. It is against 3.12-rc1. > oh, I have a ubuntu image on the panda board. I guess the kernel is old so the sensors do not show even when i enabled the following options. CONFIG_SENSORS_OMAP_TEMP_SENSOR=y CONFIG_OMAP_THERMAL=y what are the kernel options to enable OMAP thermal? > > > > At the same time, could you help me debug with showing me the > > result of " tree -l -L 2 /sys/class/thermal/" > > I tried also on my x86 dell laptop. I gave up on this track to because > tmon does not progress when there is no thermal zone. > ebv@besouro:/sys/class/thermal$ > $ tree > . > ├── cooling_device0 -> ../../devices/virtual/thermal/cooling_device0 > ├── cooling_device1 -> ../../devices/virtual/thermal/cooling_device1 > ├── cooling_device2 -> ../../devices/virtual/thermal/cooling_device2 > ├── cooling_device3 -> ../../devices/virtual/thermal/cooling_device3 > └── cooling_device4 -> ../../devices/virtual/thermal/cooling_device4 > > 5 directories, 0 files > > The only problem is that tmon exists silently. I would be nice to have > at least a message saying why it is not continuing. > I will fix that in the next version. also attached a screen shot of a more loaded ultrabook. I will try to reproduce the configuration on your omap and fix seg fault. thanks for the data. > >>> --- /dev/null > >>> +++ b/tools/thermal/tmon/sysfs.c > >>> @@ -0,0 +1,585 @@ > >>> +/* > >>> + * 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 > >>> + * > >>> + */ > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> + > >>> +#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) { > >>> + syslog(LOG_ERR, "No thermal zones found!\n"); > >>> + 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; > >>> + > >>> + 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) { > >>> + syslog(LOG_ERR, "No thermal sysfs\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); > >>> + > >>> + ptdata.tzi = calloc(sizeof(struct tz_info), > >>> ptdata.nr_tz_sensor+1); > >>> + if (!ptdata.tzi) { > >>> + syslog(LOG_ERR, "Err: allocate tz_info\n"); > >>> + return -1; > >>> + } > >>> + > >>> + ptdata.cdi = calloc(sizeof(struct cdev_info), > >>> ptdata.nr_cooling_dev+1); > >>> + if (!ptdata.cdi) { > >>> + syslog(LOG_ERR, "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) { > >>> + syslog(LOG_WARNING, > >>> + "not enough states in > >>> control cdev\n"); > >>> + return; > >>> + } > >>> + /* scale to percentage of max_state */ > >>> + cdev_state = state * > >>> ptdata.cdi[i].max_state/100; > >>> + syslog(LOG_DEBUG, > >>> + "ctrl cdev %d set state %lu > >>> scaled to %lu\n", > >>> + ptdata.cdi[i].instance, state, > >>> cdev_state); > >>> + snprintf(ctrl_cdev_path, 256, "%s/%s%d", > >>> THERMAL_SYSFS, > >>> + CDEV, ptdata.cdi[i].instance); > >>> + syslog(LOG_DEBUG, "ctrl cdev path %s", > >>> ctrl_cdev_path); > >>> + sysfs_set_ulong(ctrl_cdev_path, > >>> "cur_state", > >>> + cdev_state); > >>> + } > >>> + } > >>> +} > >>> + > >>> +void get_ctrl_state(unsigned long *state) > >>> +{ > >>> + char ctrl_cdev_path[256]; > >>> + int ctrl_cdev_id = -1; > >>> + int i; > >>> + > >>> + /* TODO: take average of all ctrl types. also consider > >>> change based on > >>> + * uevent. Take the first reading for now. > >>> + */ > >>> + for (i = 0; i < ptdata.nr_cooling_dev; i++) { > >>> + if (ptdata.cdi[i].flag & CDEV_FLAG_IN_CONTROL) { > >>> + ctrl_cdev_id = ptdata.cdi[i].instance; > >>> + syslog(LOG_INFO, "ctrl cdev %d get > >>> state\n", > >>> + ptdata.cdi[i].instance); > >>> + break; > >>> + } > >>> + } > >>> + if (ctrl_cdev_id == -1) { > >>> + *state = 0; > >>> + return; > >>> + } > >>> + snprintf(ctrl_cdev_path, 256, "%s/%s%d", THERMAL_SYSFS, > >>> + CDEV, ctrl_cdev_id); > >>> + sysfs_get_ulong(ctrl_cdev_path, "cur_state", state); > >>> +} > >>> + > >>> +void free_thermal_data(void) > >>> +{ > >>> + free(ptdata.tzi); > >>> + free(ptdata.cdi); > >>> +} > >>> diff --git a/tools/thermal/tmon/tmon.8 b/tools/thermal/tmon/tmon.8 > >>> new file mode 100644 > >>> index 0000000..0be727c > >>> --- /dev/null > >>> +++ b/tools/thermal/tmon/tmon.8 > >>> @@ -0,0 +1,142 @@ > >>> +.TH TMON 8 > >>> +.SH NAME > >>> +\fBtmon\fP - A monitoring and testing tool for Linux kernel > >>> thermal subsystem + > >>> +.SH SYNOPSIS > >>> +.ft B > >>> +.B tmon > >>> +.RB [ Options ] > >>> +.br > >>> +.SH DESCRIPTION > >>> +\fBtmon \fP can be used to visualize thermal relationship and > >>> +real-time thermal data; tune > >>> +and test cooling devices and sensors; collect thermal data for > >>> offline +analysis and plot. \fBtmon\fP must be run as root in > >>> order to control device +states via sysfs. > >>> +.PP > >>> +\fBFunctions\fP > >>> +.PP > >>> +.nf > >>> +1. Thermal relationships: > >>> +- show thermal zone information > >>> +- show cooling device information > >>> +- show trip point binding within each thermal zone > >>> +- show trip point and cooling device instance bindings > >>> +.PP > >>> +2. Real time data display > >>> +- show temperature of all thermal zones w.r.t. its trip points > >>> and types +- show states of all cooling devices > >>> +.PP > >>> +3. Thermal relationship learning and device tuning > >>> +- with a built-in Proportional Integral Derivative (\fBPID\fP) > >>> +controller, user can pair a cooling device to a thermal sensor > >>> for +testing the effectiveness and learn about the thermal > >>> distance between the two +- allow manual control of cooling > >>> device states and target temperature +.PP > >>> +4. Data logging in /var/tmp/tmon.log > >>> +- contains thermal configuration data, i.e. cooling device, > >>> thermal > >>> + zones, and trip points. Can be used for data collection in > >>> remote > >>> + debugging. > >>> +- log real-time thermal data into space separated format that can > >>> be > >>> + directly consumed by plotting tools such as Rscript. > >>> + > >>> +.SS Options > >>> +.PP > >>> +The \fB-c --control\fP option sets a cooling device type to > >>> control temperature +of a thermal zone > >>> +.PP > >>> +The \fB-d --daemon\fP option runs \fBtmon \fP as daemon without > >>> user interface +.PP > >>> +The \fB-g --debug\fP option allow debug messages to be stored in > >>> syslog +.PP > >>> +The \fB-h --help\fP option shows help message > >>> +.PP > >>> +The \fB-l --log\fP option write data to /var/tmp/tmon.log > >>> +.PP > >>> +The \fB-t --time-interval\fP option sets the polling interval in > >>> seconds +.PP > >>> +The \fB-v --version\fP option shows the version of \fBtmon \fP > >>> +.PP > >>> +The \fB-z --zone\fP option sets the target therma zone instance > >>> to be controlled +.PP > >>> + > >>> +.SH FIELD DESCRIPTIONS > >>> +.nf > >>> +.PP > >>> +\fBP \fP passive cooling trip point type > >>> +\fBA \fP active cooling trip point type (fan) > >>> +\fBC \fP critical trip point type > >>> +\fBA \fP hot trip point type > >>> +\fBkp \fP proportional gain of \fBPID\fP controller > >>> +\fBki \fP integral gain of \fBPID\fP controller > >>> +\fBkd \fP derivative gain of \fBPID\fP controller > >>> + > >>> +.SH REQUIREMENT > >>> +Build depends on ncurses > >>> +.PP > >>> +Runtime depends on window size large enough to show the number of > >>> +devices found on the system. > >>> + > >>> +.PP > >>> + > >>> +.SH INTERACTIVE COMMANDS > >>> +.pp > >>> +.nf > >>> +\fBCtrl-C, q/Q\fP stops \fBtmon\fP > >>> +\fBTAB\fP shows tuning pop up panel, choose a letter to modify > >>> + > >>> +.SH EXAMPLES > >>> +Without any parameters, tmon is in monitoring only mode and > >>> refresh +screen every 1 second. > >>> +.PP > >>> +1. For monitoring only: > >>> +.nf > >>> +$ sudo ./tmon > >>> + > >>> +2. Use Processor cooling device to control thermal zone 0 at > >>> default 65C. +$ sudo ./tmon -c Processor -z 0 > >>> + > >>> +3. Use intel_powerclamp(idle injection) cooling device to control > >>> thermal zone 1 +$ sudo ./tmon -c intel_powerclamp -z 1 > >>> + > >>> +4. Turn on debug and collect data log at /var/tmp/tmon.log > >>> +$ sudo ./tmon -g -l > >>> + > >>> +For example, the log below shows PID controller was adjusting > >>> current states +for all cooling devices with "Processor" type such > >>> that thermal zone 0 +can stay below 65 dC. > >>> + > >>> +#---------- THERMAL DATA LOG STARTED ----------- > >>> +Samples TargetTemp acpitz0 acpitz1 Fan0 Fan1 Fan2 Fan3 Fan4 > >>> Fan5 +Fan6 Fan7 Fan8 Fan9 Processor10 Processor11 Processor12 > >>> Processor13 +LCD14 intel_powerclamp15 1 65.0 65 65 0 0 0 0 0 0 0 0 > >>> 0 0 0 0 0 0 6 0 2 +65.0 66 65 0 0 0 0 0 0 0 0 0 0 4 4 4 4 6 0 3 > >>> 65.0 60 54 0 0 0 0 0 0 0 0 +0 0 4 4 4 4 6 0 4 65.0 53 53 0 0 0 0 0 > >>> 0 0 0 0 0 4 4 4 4 6 0 +5 65.0 52 52 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 > >>> 0 +6 65.0 53 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 > >>> +7 65.0 68 70 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 > >>> +8 65.0 68 68 0 0 0 0 0 0 0 0 0 0 5 5 5 5 6 0 > >>> +9 65.0 68 68 0 0 0 0 0 0 0 0 0 0 6 6 6 6 6 0 > >>> +10 65.0 67 67 0 0 0 0 0 0 0 0 0 0 7 7 7 7 6 0 > >>> +11 65.0 67 67 0 0 0 0 0 0 0 0 0 0 8 8 8 8 6 0 > >>> +12 65.0 67 67 0 0 0 0 0 0 0 0 0 0 8 8 8 8 6 0 > >>> +13 65.0 67 67 0 0 0 0 0 0 0 0 0 0 9 9 9 9 6 0 > >>> +14 65.0 66 66 0 0 0 0 0 0 0 0 0 0 10 10 10 10 6 0 > >>> +15 65.0 66 67 0 0 0 0 0 0 0 0 0 0 10 10 10 10 6 0 > >>> +16 65.0 66 66 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0 > >>> +17 65.0 66 66 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0 > >>> +18 65.0 64 61 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0 > >>> +19 65.0 60 59 0 0 0 0 0 0 0 0 0 0 12 12 12 12 6 0 > >>> + > >>> +Data can be read directly into an array by an example R-script > >>> below: + > >>> +#!/usr/bin/Rscript > >>> +tdata <- read.table("/var/tmp/tmon.log", header=T, > >>> comment.char="#") +attach(tdata) > >>> +jpeg("tmon.jpg") > >>> +X11() > >>> +g_range <- range(0, intel_powerclamp15, TargetTemp, acpitz0) > >>> +plot( Samples, intel_powerclamp15, col="blue", ylim=g_range, > >>> axes=FALSE, ann=FALSE) +par(new=TRUE) > >>> +lines(TargetTemp, type="o", pch=22, lty=2, col="red") > >>> +dev.off() > >>> diff --git a/tools/thermal/tmon/tmon.c b/tools/thermal/tmon/tmon.c > >>> new file mode 100644 > >>> index 0000000..5f13fb1 > >>> --- /dev/null > >>> +++ b/tools/thermal/tmon/tmon.c > >>> @@ -0,0 +1,350 @@ > >>> +/* > >>> + * 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 > >>> + * > >>> + */ > >>> + > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> + > >>> +#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; > >>> +char ctrl_cdev[CDEV_NAME_SIZE]; /*cooling device used for the PID > >>> controller */ +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< >>> + '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) { > >>> + printf("\n mutex init failed\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()) { > >>> + 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); > >>> + > >>> +} > >>> diff --git a/tools/thermal/tmon/tmon.h b/tools/thermal/tmon/tmon.h > >>> new file mode 100644 > >>> index 0000000..9e3c49c > >>> --- /dev/null > >>> +++ b/tools/thermal/tmon/tmon.h > >>> @@ -0,0 +1,204 @@ > >>> +/* > >>> + * tmon.h contains data structures and constants used by TMON > >>> + * > >>> + * Copyright (C) 2012 Intel Corporation. All rights reserved. > >>> + * > >>> + * This program is free software; you can redistribute it and/or > >>> + * modify it under the terms of the GNU General Public License > >>> version > >>> + * 2 or later as published by the Free Software Foundation. > >>> + * > >>> + * This program is distributed in the hope that it will be > >>> useful, > >>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of > >>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > >>> + * GNU General Public License for more details. > >>> + * > >>> + * Author Name Jacob Pan > >>> + * > >>> + */ > >>> + > >>> +#ifndef TMON_H > >>> +#define TMON_H > >>> + > >>> +#define MAX_DISP_TEMP 125 > >>> +#define MAX_CTRL_TEMP 105 > >>> +#define MIN_CTRL_TEMP 40 > >>> +#define MAX_NR_TZONE 16 > >>> +#define MAX_NR_CDEV 32 > >>> +#define MAX_NR_TRIP 16 > >>> +#define MAX_NR_CDEV_TRIP 12 /* number of cooling devices that can > >>> bind > >>> + * to a thermal zone trip. > >>> + */ > >>> +#define MAX_TEMP_KC 140000 > >>> +/* starting char position to draw sensor data, such as tz names > >>> + * trip point list, etc. > >>> + */ > >>> +#define DATA_LEFT_ALIGN 10 > >>> +#define NR_LINES_TZDATA 1 > >>> +#define TMON_LOG_FILE "/var/tmp/tmon.log" > >>> + > >>> +extern unsigned long ticktime; > >>> +extern double time_elapsed; > >>> +extern unsigned long target_temp_user; > >>> +extern int dialogue_on; > >>> +extern char ctrl_cdev[]; > >>> +extern pthread_mutex_t input_lock; > >>> +extern int tmon_exit; > >>> +extern int target_thermal_zone; > >>> +/* use fixed size record to simplify data processing and transfer > >>> + * TBD: more info to be added, e.g. programmable trip point data. > >>> +*/ > >>> +struct thermal_data_record { > >>> + struct timeval tv; > >>> + unsigned long temp[MAX_NR_TZONE]; > >>> + double pid_out_pct; > >>> +}; > >>> + > >>> +struct cdev_info { > >>> + char type[64]; > >>> + int instance; > >>> + unsigned long max_state; > >>> + unsigned long cur_state; > >>> + unsigned long flag; > >>> +}; > >>> + > >>> +enum trip_type { > >>> + THERMAL_TRIP_CRITICAL, > >>> + THERMAL_TRIP_HOT, > >>> + THERMAL_TRIP_PASSIVE, > >>> + THERMAL_TRIP_ACTIVE, > >>> + NR_THERMAL_TRIP_TYPE, > >>> +}; > >>> + > >>> +struct trip_point { > >>> + enum trip_type type; > >>> + unsigned long temp; > >>> + unsigned long hysteresis; > >>> + int attribute; /* programmability etc. */ > >>> +}; > >>> + > >>> +/* thermal zone configuration information, binding with cooling > >>> devices could > >>> + * change at runtime. > >>> + */ > >>> +struct tz_info { > >>> + char type[256]; /* e.g. acpitz */ > >>> + int instance; > >>> + int passive; /* active zone has passive node to force > >>> passive mode */ > >>> + int nr_cdev; /* number of cooling device binded */ > >>> + int nr_trip_pts; > >>> + struct trip_point tp[MAX_NR_TRIP]; > >>> + unsigned long cdev_binding; /* bitmap for attached cdevs > >>> */ > >>> + /* cdev bind trip points, allow one cdev bind to multiple > >>> trips */ > >>> + unsigned long trip_binding[MAX_NR_CDEV]; > >>> +}; > >>> + > >>> +struct tmon_platform_data { > >>> + int nr_tz_sensor; > >>> + int nr_cooling_dev; > >>> + /* keep track of instance ids since there might be gaps > >>> */ > >>> + int max_tz_instance; > >>> + int max_cdev_instance; > >>> + struct tz_info *tzi; > >>> + struct cdev_info *cdi; > >>> +}; > >>> + > >>> +struct control_ops { > >>> + void (*set_ratio)(unsigned long ratio); > >>> + unsigned long (*get_ratio)(unsigned long ratio); > >>> + > >>> +}; > >>> + > >>> +enum cdev_types { > >>> + CDEV_TYPE_PROC, > >>> + CDEV_TYPE_FAN, > >>> + CDEV_TYPE_MEM, > >>> + CDEV_TYPE_NR, > >>> +}; > >>> + > >>> +/* REVISIT: the idea is to group sensors if possible, e.g. on > >>> intel mid > >>> + * we have "skin0", "skin1", "sys", "msicdie" > >>> + * on DPTF enabled systems, we might have PCH, TSKN, TAMB, etc. > >>> + */ > >>> +enum tzone_types { > >>> + TZONE_TYPE_ACPI, > >>> + TZONE_TYPE_PCH, > >>> + TZONE_TYPE_NR, > >>> +}; > >>> + > >>> +/* limit the output of PID controller adjustment */ > >>> +#define LIMIT_HIGH (95) > >>> +#define LIMIT_LOW (2) > >>> + > >>> +struct pid_params { > >>> + double kp; /* Controller gain from Dialog Box */ > >>> + double ki; /* Time-constant for I action from Dialog Box > >>> */ > >>> + double kd; /* Time-constant for D action from Dialog Box > >>> */ > >>> + double ts; > >>> + double k_lpf; > >>> + > >>> + double t_target; > >>> + double y_k; > >>> +}; > >>> + > >>> +extern int init_thermal_controller(void); > >>> +extern void controller_handler(const double xk, double *yk); > >>> + > >>> +extern struct tmon_platform_data ptdata; > >>> +extern struct pid_params p_param; > >>> + > >>> +extern FILE *tmon_log; > >>> +extern int cur_thermal_record; /* index to the trec array */ > >>> +extern struct thermal_data_record trec[]; > >>> +extern const char *trip_type_name[]; > >>> +extern unsigned long no_control; > >>> + > >>> +extern void initialize_curses(void); > >>> +extern void show_controller_stats(char *line); > >>> +extern void show_title_bar(void); > >>> +extern void setup_windows(void); > >>> +extern void disable_tui(void); > >>> +extern void show_sensors_w(void); > >>> +extern void show_data_w(void); > >>> +extern void write_status_bar(int x, char *line); > >>> +extern void show_control_w(); > >>> + > >>> +extern void show_cooling_device(void); > >>> +extern void show_dialogue(void); > >>> +extern int update_thermal_data(void); > >>> + > >>> +extern int probe_thermal_sysfs(void); > >>> +extern void free_thermal_data(void); > >>> +extern void resize_handler(int sig); > >>> +extern void set_ctrl_state(unsigned long state); > >>> +extern void get_ctrl_state(unsigned long *state); > >>> +extern void *handle_tui_events(void *arg); > >>> +extern int sysfs_set_ulong(char *path, char *filename, unsigned > >>> long val); +extern int zone_instance_to_index(int zone_inst); > >>> +extern void close_windows(void); > >>> + > >>> +#define PT_COLOR_DEFAULT 1 > >>> +#define PT_COLOR_HEADER_BAR 2 > >>> +#define PT_COLOR_ERROR 3 > >>> +#define PT_COLOR_RED 4 > >>> +#define PT_COLOR_YELLOW 5 > >>> +#define PT_COLOR_GREEN 6 > >>> +#define PT_COLOR_BRIGHT 7 > >>> +#define PT_COLOR_BLUE 8 > >>> + > >>> +/* each thermal zone uses 12 chars, 8 for name, 2 for instance, 2 > >>> space > >>> + * also used to list trip points in forms of AAAC, which > >>> represents > >>> + * A: Active > >>> + * C: Critical > >>> + */ > >>> +#define TZONE_RECORD_SIZE 12 > >>> +#define TZ_LEFT_ALIGN 32 > >>> +#define CDEV_NAME_SIZE 20 > >>> +#define CDEV_FLAG_IN_CONTROL (1 << 0) > >>> + > >>> +/* dialogue box starts */ > >>> +#define DIAG_X 48 > >>> +#define DIAG_Y 8 > >>> +#define THERMAL_SYSFS "/sys/class/thermal" > >>> +#define CDEV "cooling_device" > >>> +#define TZONE "thermal_zone" > >>> +#define TDATA_LEFT 16 > >>> +#endif /* TMON_H */ > >>> diff --git a/tools/thermal/tmon/tui.c b/tools/thermal/tmon/tui.c > >>> new file mode 100644 > >>> index 0000000..957ecf3 > >>> --- /dev/null > >>> +++ b/tools/thermal/tmon/tui.c > >>> @@ -0,0 +1,631 @@ > >>> +/* > >>> + * 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 > >>> + * > >>> + */ > >>> + > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> +#include > >>> + > >>> +#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, 0, maxx/2 - > >>> sizeof(cdev_title), > >>> + cdev_title); > >>> + > >>> + 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, "*"); > >>> + } > >>> + } > >>> + } > >>> + } > >>> + wborder(cooling_device_window, 0, 0, 0, 0, 0, 0, 0, 0); > >>> + 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); > >>> + wattron(control_window, A_BOLD); > >>> + mvwprintw(control_window, 0, maxx/2 - > >>> sizeof(control_title), > >>> + control_title); > >>> + wattroff(control_window, A_BOLD); > >>> + > >>> + mvwprintw(control_window, 1, 1, "PID gain: kp=%2.2f > >>> ki=%2.2f, kd=%2.2f", > >>> + p_param.kp, p_param.ki, p_param.kd); > >>> + > >>> + mvwprintw(control_window, 2, 1, > >>> + "Target Temp: %2.1f, Zone: %d, Control Device: > >>> %.12s, PID output: %2.2f, state: %d", > >>> + target_thermal_zone, ctrl_cdev, > >>> + p_param.t_target, p_param.y_k, state); > >>> + /* draw border last such that everything is within > >>> boundary */ > >>> + wborder(control_window, 0, 0, 0, 0, 0, 0, 0, 0); > >>> + 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, 0, maxx/2 - sizeof(tz_title), > >>> tz_title); > >>> + 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); > >>> + wrefresh(tz_sensor_window); > >>> +} > >>> + > >>> +void disable_tui(void) > >>> +{ > >>> + tui_disabled = 1; > >>> +} > >>> > >> > >> > > > > [Jacob Pan] > > > > > > [Jacob Pan]