lkml.org 
[lkml]   [2013]   [Oct]   [9]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
Date
From
SubjectRe: [PATCH] tools/thermal: Introduce tmon, a tool for thermal subsystem
On 09-10-2013 13:52, Jacob Pan wrote:
> On Tue, 8 Oct 2013 21:03:38 -0400
> Eduardo Valentin <eduardo.valentin@ti.com> 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.

>
> 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.

On Panda, I have only one thermal zone:
[root@(none) ~]# ls /sys/class/thermal/
cooling_device0@ thermal_zone0@
[root@(none) ~]# ls /sys/class/thermal/thermal_zone0/
emul_temp power/ trip_point_0_hyst trip_point_1_hyst type
mode subsystem@ trip_point_0_temp trip_point_1_temp uevent
policy temp trip_point_0_type trip_point_1_type
[root@(none) ~]# ls /sys/class/thermal/cooling_device0/
cur_state max_state power/ subsystem@ type uevent


>
>> I still need to have a proper look on your code though. Looks like you
>> do not add it to tools/Makefile?
>>
> i missed that.

no issues.

>> Also, please copy people that get_maintainer.pl -f tools/ outputs:
>> ./scripts/get_maintainer.pl -f tools/
>> Arnaldo Carvalho de Melo <acme@redhat.com> (commit_signer:724/902=80%)
>> Namhyung Kim <namhyung@kernel.org> (commit_signer:237/902=26%)
>> Jiri Olsa <jolsa@redhat.com> (commit_signer:219/902=24%)
>> David Ahern <dsahern@gmail.com> (commit_signer:69/902=8%)
>> Adrian Hunter <adrian.hunter@intel.com> (commit_signer:59/902=
>>
> I did run get_maintainer.pl on the patch but it did not find any. I
> guess it is because I missed the Makefile.
> Will fix it in the next round.

ok


>>>
>>> Signed-off-by: Jacob Pan <jacob.jun.pan@linux.intel.com>
>>> ---
>>> tools/thermal/tmon/Makefile | 47 ++++
>>> tools/thermal/tmon/README | 50 ++++
>>> tools/thermal/tmon/pid.c | 131 +++++++++
>>> tools/thermal/tmon/sysfs.c | 585
>>> +++++++++++++++++++++++++++++++++++++++ tools/thermal/tmon/tmon.8
>>> | 142 ++++++++++ tools/thermal/tmon/tmon.c | 350
>>> ++++++++++++++++++++++++ tools/thermal/tmon/tmon.h | 204
>>> ++++++++++++++ tools/thermal/tmon/tui.c | 631
>>> +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 2140
>>> insertions(+) create mode 100644 tools/thermal/tmon/Makefile
>>> create mode 100644 tools/thermal/tmon/README
>>> create mode 100644 tools/thermal/tmon/pid.c
>>> create mode 100644 tools/thermal/tmon/sysfs.c
>>> create mode 100644 tools/thermal/tmon/tmon.8
>>> create mode 100644 tools/thermal/tmon/tmon.c
>>> create mode 100644 tools/thermal/tmon/tmon.h
>>> create mode 100644 tools/thermal/tmon/tui.c
>>>
>>> diff --git a/tools/thermal/tmon/Makefile
>>> b/tools/thermal/tmon/Makefile new file mode 100644
>>> index 0000000..c17131b
>>> --- /dev/null
>>> +++ b/tools/thermal/tmon/Makefile
>>> @@ -0,0 +1,47 @@
>>> +VERSION = 1.0
>>> +
>>> +BINDIR=usr/bin
>>> +WARNFLAGS=-Wall -Wshadow -W -Wformat
>>> -Wimplicit-function-declaration -Wimplicit-int +CFLAGS= -O1
>>> ${WARNFLAGS} -fstack-protector +CC=gcc
>>> +
>>> +CFLAGS+=-D VERSION=\"$(VERSION)\"
>>> +LDFLAGS+=
>>> +TARGET=tmon
>>> +
>>> +INSTALL_PROGRAM=install -m 755 -p
>>> +DEL_FILE=rm -f
>>> +
>>> +INSTALL_CONFIGFILE=install -m 644 -p
>>> +CONFIG_FILE=
>>> +CONFIG_PATH=
>>> +
>>> +
>>> +OBJS = tmon.o tui.o sysfs.o pid.o
>>> +OBJS +=
>>> +
>>> +tmon: $(OBJS) Makefile tmon.h
>>> + $(CC) ${CFLAGS} $(LDFLAGS) $(OBJS) -o $(TARGET)
>>> -lncursesw -lm -lpanel -lpthread +
>>> +valgrind: tmon
>>> + sudo valgrind -v --track-origins=yes --tool=memcheck
>>> --leak-check=yes --show-reachable=yes --num-callers=20
>>> --track-fds=yes ./$(TARGET) 1> /dev/null + +install:
>>> + - mkdir -p $(INSTALL_ROOT)/$(BINDIR)
>>> + - $(INSTALL_PROGRAM) "$(TARGET)"
>>> "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)"
>>> + - mkdir -p $(INSTALL_ROOT)/$(CONFIG_PATH)
>>> + - $(INSTALL_CONFIGFILE) "$(CONFIG_FILE)"
>>> "$(INSTALL_ROOT)/$(CONFIG_PATH)" +
>>> +uninstall:
>>> + $(DEL_FILE) "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)"
>>> + $(CONFIG_FILE) "$(CONFIG_PATH)"
>>> +
>>> +
>>> +clean:
>>> + find . -name "*.o" | xargs $(DEL_FILE)
>>> + rm -f $(TARGET)
>>> +
>>> +dist:
>>> + git tag v$(VERSION)
>>> + git archive --format=tar --prefix="$(TARGET)-$(VERSION)/"
>>> v$(VERSION) | \
>>> + gzip > $(TARGET)-$(VERSION).tar.gz
>>> diff --git a/tools/thermal/tmon/README b/tools/thermal/tmon/README
>>> new file mode 100644
>>> index 0000000..4579498
>>> --- /dev/null
>>> +++ b/tools/thermal/tmon/README
>>> @@ -0,0 +1,50 @@
>>> +TMON - A Monitoring and Testing Tool for Linux kernel thermal
>>> subsystem +
>>> +Why TMON?
>>> +==========
>>> +Increasingly, Linux is running on thermally constrained devices.
>>> The simple +thermal relationship between processor and fan has
>>> become past for modern +computers.
>>> +
>>> +As hardware vendors cope with the thermal constraints on their
>>> products, more +and more sensors are added, new cooling
>>> capabilities are introduced. The +complexity of the thermal
>>> relationship can grow exponentially among cooling +devices, zones,
>>> sensors, and trip points. They can also change dynamically. +
>>> +To expose such relationship to the userspace, Linux generic
>>> thermal layer +introduced sysfs entry at /sys/class/thermal with a
>>> matrix of symbolic +links, trip point bindings, and device
>>> instances. To traverse such +matrix by hand is not a trivial task.
>>> Testing is also difficult in that +thermal conditions are often
>>> exception cases that hard to reach in +normal operations.
>>> +
>>> +TMON is conceived as a tool to help visualize, tune, and test the
>>> +complex thermal subsystem.
>>> +
>>> +Files
>>> +=====
>>> + tmon.c : main function for set up and configurations.
>>> + tui.c : handles ncurses based user interface
>>> + sysfs.c : access to the generic thermal sysfs
>>> + pid.c : a proportional-integral-derivative (PID) controller
>>> + that can be used for thermal relationship training.
>>> +
>>> +Requirements
>>> +============
>>> +Depends on ncurses
>>> +
>>> +Build
>>> +=========
>>> +$ make
>>> +$ sudo ./tmon -h
>>> +Usage: tmon [OPTION...]
>>> + -c, --control cooling device in control
>>> + -d, --daemon run as daemon, no TUI
>>> + -l, --log log data to /var/tmp/tmon.log
>>> + -h, --help show this help message
>>> + -t, --time-interval set time interval for sampling
>>> + -v, --version show version
>>> + -g, --debug debug message in syslog
>>> +
>>> +1. For monitoring only:
>>> +$ sudo ./tmon
>>> diff --git a/tools/thermal/tmon/pid.c b/tools/thermal/tmon/pid.c
>>> new file mode 100644
>>> index 0000000..fd7e9e9
>>> --- /dev/null
>>> +++ b/tools/thermal/tmon/pid.c
>>> @@ -0,0 +1,131 @@
>>> +/*
>>> + * pid.c PID controller for testing cooling devices
>>> + *
>>> + *
>>> + *
>>> + * Copyright (C) 2012 Intel Corporation. All rights reserved.
>>> + *
>>> + * This program is free software; you can redistribute it and/or
>>> + * modify it under the terms of the GNU General Public License
>>> version
>>> + * 2 or later as published by the Free Software Foundation.
>>> + *
>>> + * This program is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>>> + * GNU General Public License for more details.
>>> + *
>>> + * Author Name Jacob Pan <jacob.jun.pan@linux.intel.com>
>>> + *
>>> + */
>>> +
>>> +#include <unistd.h>
>>> +#include <stdio.h>
>>> +#include <stdlib.h>
>>> +#include <string.h>
>>> +#include <stdint.h>
>>> +#include <sys/types.h>
>>> +#include <dirent.h>
>>> +#include <libintl.h>
>>> +#include <ctype.h>
>>> +#include <assert.h>
>>> +#include <time.h>
>>> +#include <limits.h>
>>> +#include <math.h>
>>> +#include <sys/stat.h>
>>> +#include <syslog.h>
>>> +
>>> +#include "tmon.h"
>>> +
>>> +/**************************************************************************
>>> + * PID (Proportional-Integral-Derivative) controller is commonly
>>> used in
>>> + * linear control system, consider the the process.
>>> + * G(s) = U(s)/E(s)
>>> + * kp = proportional gain
>>> + * ki = integral gain
>>> + * kd = derivative gain
>>> + * Ts
>>> + * We use type C Alan Bradley equation which takes set point off
>>> the
>>> + * output dependency in P and D term.
>>> + *
>>> + * y[k] = y[k-1] - kp*(x[k] - x[k-1]) + Ki*Ts*e[k] - Kd*(x[k]
>>> + * - 2*x[k-1]+x[k-2])/Ts
>>> + *
>>> + *
>>> +
>>> ***********************************************************************/
>>> +struct pid_params p_param; +/* cached data from previous loop */
>>> +static double xk_1, xk_2; /* input temperature x[k-#] */
>>> +
>>> +/*
>>> + * TODO: make PID parameters tuned automatically,
>>> + * 1. use CPU burn to produce open loop unit step response
>>> + * 2. calculate PID based on Ziegler-Nichols rule
>>> + *
>>> + * add a flag for tuning PID
>>> + */
>>> +int init_thermal_controller(void)
>>> +{
>>> + int ret = 0;
>>> +
>>> + /* init pid params */
>>> + p_param.ts = ticktime;
>>> + /* TODO: get it from TUI tuning tab */
>>> + p_param.kp = .36;
>>> + p_param.ki = 5.0;
>>> + p_param.kd = 0.19;
>>> +
>>> + p_param.t_target = target_temp_user;
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +void controller_reset(void)
>>> +{
>>> + /* TODO: relax control data when not over thermal limit */
>>> + syslog(LOG_DEBUG, "TC inactive, relax p-state\n");
>>> + p_param.y_k = 0.0;
>>> + xk_1 = 0.0;
>>> + xk_2 = 0.0;
>>> + set_ctrl_state(0);
>>> +}
>>> +
>>> +/* To be called at time interval Ts. Type C PID controller.
>>> + * y[k] = y[k-1] - kp*(x[k] - x[k-1]) + Ki*Ts*e[k] - Kd*(x[k]
>>> + * - 2*x[k-1]+x[k-2])/Ts
>>> + * TODO: add low pass filter for D term
>>> + */
>>> +#define GUARD_BAND (2)
>>> +void controller_handler(const double xk, double *yk)
>>> +{
>>> + double ek;
>>> + double p_term, i_term, d_term;
>>> +
>>> + ek = p_param.t_target - xk; /* error */
>>> + if (ek >= 3.0) {
>>> + syslog(LOG_DEBUG, "PID: %3.1f Below set point
>>> %3.1f, stop\n",
>>> + xk, p_param.t_target);
>>> + controller_reset();
>>> + *yk = 0.0;
>>> + return;
>>> + }
>>> + /* compute intermediate PID terms */
>>> + p_term = -p_param.kp * (xk - xk_1);
>>> + i_term = p_param.kp * p_param.ki * p_param.ts * ek;
>>> + d_term = -p_param.kp * p_param.kd * (xk - 2 * xk_1 +
>>> xk_2) / p_param.ts;
>>> + /* compute output */
>>> + *yk += p_term + i_term + d_term;
>>> + /* update sample data */
>>> + xk_1 = xk;
>>> + xk_2 = xk_1;
>>> +
>>> + /* clamp output adjustment range */
>>> + if (*yk < -LIMIT_HIGH)
>>> + *yk = -LIMIT_HIGH;
>>> + else if (*yk > -LIMIT_LOW)
>>> + *yk = -LIMIT_LOW;
>>> +
>>> + p_param.y_k = *yk;
>>> +
>>> + set_ctrl_state(lround(fabs(p_param.y_k)));
>>> +
>>> +}
>>> diff --git a/tools/thermal/tmon/sysfs.c b/tools/thermal/tmon/sysfs.c
>>> new file mode 100644
>>> index 0000000..54e24b3
>>> --- /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 <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) {
>>> + 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 <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;
>>> +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<<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) {
>>> + 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 <jacob.jun.pan@linux.intel.com>
>>> + *
>>> + */
>>> +
>>> +#ifndef TMON_H
>>> +#define TMON_H
>>> +
>>> +#define MAX_DISP_TEMP 125
>>> +#define MAX_CTRL_TEMP 105
>>> +#define MIN_CTRL_TEMP 40
>>> +#define MAX_NR_TZONE 16
>>> +#define MAX_NR_CDEV 32
>>> +#define MAX_NR_TRIP 16
>>> +#define MAX_NR_CDEV_TRIP 12 /* number of cooling devices that can
>>> bind
>>> + * to a thermal zone trip.
>>> + */
>>> +#define MAX_TEMP_KC 140000
>>> +/* starting char position to draw sensor data, such as tz names
>>> + * trip point list, etc.
>>> + */
>>> +#define DATA_LEFT_ALIGN 10
>>> +#define NR_LINES_TZDATA 1
>>> +#define TMON_LOG_FILE "/var/tmp/tmon.log"
>>> +
>>> +extern unsigned long ticktime;
>>> +extern double time_elapsed;
>>> +extern unsigned long target_temp_user;
>>> +extern int dialogue_on;
>>> +extern char ctrl_cdev[];
>>> +extern pthread_mutex_t input_lock;
>>> +extern int tmon_exit;
>>> +extern int target_thermal_zone;
>>> +/* use fixed size record to simplify data processing and transfer
>>> + * TBD: more info to be added, e.g. programmable trip point data.
>>> +*/
>>> +struct thermal_data_record {
>>> + struct timeval tv;
>>> + unsigned long temp[MAX_NR_TZONE];
>>> + double pid_out_pct;
>>> +};
>>> +
>>> +struct cdev_info {
>>> + char type[64];
>>> + int instance;
>>> + unsigned long max_state;
>>> + unsigned long cur_state;
>>> + unsigned long flag;
>>> +};
>>> +
>>> +enum trip_type {
>>> + THERMAL_TRIP_CRITICAL,
>>> + THERMAL_TRIP_HOT,
>>> + THERMAL_TRIP_PASSIVE,
>>> + THERMAL_TRIP_ACTIVE,
>>> + NR_THERMAL_TRIP_TYPE,
>>> +};
>>> +
>>> +struct trip_point {
>>> + enum trip_type type;
>>> + unsigned long temp;
>>> + unsigned long hysteresis;
>>> + int attribute; /* programmability etc. */
>>> +};
>>> +
>>> +/* thermal zone configuration information, binding with cooling
>>> devices could
>>> + * change at runtime.
>>> + */
>>> +struct tz_info {
>>> + char type[256]; /* e.g. acpitz */
>>> + int instance;
>>> + int passive; /* active zone has passive node to force
>>> passive mode */
>>> + int nr_cdev; /* number of cooling device binded */
>>> + int nr_trip_pts;
>>> + struct trip_point tp[MAX_NR_TRIP];
>>> + unsigned long cdev_binding; /* bitmap for attached cdevs */
>>> + /* cdev bind trip points, allow one cdev bind to multiple
>>> trips */
>>> + unsigned long trip_binding[MAX_NR_CDEV];
>>> +};
>>> +
>>> +struct tmon_platform_data {
>>> + int nr_tz_sensor;
>>> + int nr_cooling_dev;
>>> + /* keep track of instance ids since there might be gaps */
>>> + int max_tz_instance;
>>> + int max_cdev_instance;
>>> + struct tz_info *tzi;
>>> + struct cdev_info *cdi;
>>> +};
>>> +
>>> +struct control_ops {
>>> + void (*set_ratio)(unsigned long ratio);
>>> + unsigned long (*get_ratio)(unsigned long ratio);
>>> +
>>> +};
>>> +
>>> +enum cdev_types {
>>> + CDEV_TYPE_PROC,
>>> + CDEV_TYPE_FAN,
>>> + CDEV_TYPE_MEM,
>>> + CDEV_TYPE_NR,
>>> +};
>>> +
>>> +/* REVISIT: the idea is to group sensors if possible, e.g. on
>>> intel mid
>>> + * we have "skin0", "skin1", "sys", "msicdie"
>>> + * on DPTF enabled systems, we might have PCH, TSKN, TAMB, etc.
>>> + */
>>> +enum tzone_types {
>>> + TZONE_TYPE_ACPI,
>>> + TZONE_TYPE_PCH,
>>> + TZONE_TYPE_NR,
>>> +};
>>> +
>>> +/* limit the output of PID controller adjustment */
>>> +#define LIMIT_HIGH (95)
>>> +#define LIMIT_LOW (2)
>>> +
>>> +struct pid_params {
>>> + double kp; /* Controller gain from Dialog Box */
>>> + double ki; /* Time-constant for I action from Dialog Box
>>> */
>>> + double kd; /* Time-constant for D action from Dialog Box
>>> */
>>> + double ts;
>>> + double k_lpf;
>>> +
>>> + double t_target;
>>> + double y_k;
>>> +};
>>> +
>>> +extern int init_thermal_controller(void);
>>> +extern void controller_handler(const double xk, double *yk);
>>> +
>>> +extern struct tmon_platform_data ptdata;
>>> +extern struct pid_params p_param;
>>> +
>>> +extern FILE *tmon_log;
>>> +extern int cur_thermal_record; /* index to the trec array */
>>> +extern struct thermal_data_record trec[];
>>> +extern const char *trip_type_name[];
>>> +extern unsigned long no_control;
>>> +
>>> +extern void initialize_curses(void);
>>> +extern void show_controller_stats(char *line);
>>> +extern void show_title_bar(void);
>>> +extern void setup_windows(void);
>>> +extern void disable_tui(void);
>>> +extern void show_sensors_w(void);
>>> +extern void show_data_w(void);
>>> +extern void write_status_bar(int x, char *line);
>>> +extern void show_control_w();
>>> +
>>> +extern void show_cooling_device(void);
>>> +extern void show_dialogue(void);
>>> +extern int update_thermal_data(void);
>>> +
>>> +extern int probe_thermal_sysfs(void);
>>> +extern void free_thermal_data(void);
>>> +extern void resize_handler(int sig);
>>> +extern void set_ctrl_state(unsigned long state);
>>> +extern void get_ctrl_state(unsigned long *state);
>>> +extern void *handle_tui_events(void *arg);
>>> +extern int sysfs_set_ulong(char *path, char *filename, unsigned
>>> long val); +extern int zone_instance_to_index(int zone_inst);
>>> +extern void close_windows(void);
>>> +
>>> +#define PT_COLOR_DEFAULT 1
>>> +#define PT_COLOR_HEADER_BAR 2
>>> +#define PT_COLOR_ERROR 3
>>> +#define PT_COLOR_RED 4
>>> +#define PT_COLOR_YELLOW 5
>>> +#define PT_COLOR_GREEN 6
>>> +#define PT_COLOR_BRIGHT 7
>>> +#define PT_COLOR_BLUE 8
>>> +
>>> +/* each thermal zone uses 12 chars, 8 for name, 2 for instance, 2
>>> space
>>> + * also used to list trip points in forms of AAAC, which represents
>>> + * A: Active
>>> + * C: Critical
>>> + */
>>> +#define TZONE_RECORD_SIZE 12
>>> +#define TZ_LEFT_ALIGN 32
>>> +#define CDEV_NAME_SIZE 20
>>> +#define CDEV_FLAG_IN_CONTROL (1 << 0)
>>> +
>>> +/* dialogue box starts */
>>> +#define DIAG_X 48
>>> +#define DIAG_Y 8
>>> +#define THERMAL_SYSFS "/sys/class/thermal"
>>> +#define CDEV "cooling_device"
>>> +#define TZONE "thermal_zone"
>>> +#define TDATA_LEFT 16
>>> +#endif /* TMON_H */
>>> diff --git a/tools/thermal/tmon/tui.c b/tools/thermal/tmon/tui.c
>>> new file mode 100644
>>> index 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 <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, 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]
>
>


--
You have got to be excited about what you are doing. (L. Lamport)

Eduardo Valentin

[unhandled content-type:application/pgp-signature]
\
 
 \ /
  Last update: 2013-10-10 02:01    [W:0.159 / U:0.260 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site