tools/thermal: tmon: add min/max macros
[cascardo/linux.git] / tools / thermal / tmon / tui.c
1 /*
2  * tui.c ncurses text user interface for TMON program
3  *
4  * Copyright (C) 2013 Intel Corporation. All rights reserved.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License version
8  * 2 or later as published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * Author: Jacob Pan <jacob.jun.pan@linux.intel.com>
16  *
17  */
18
19 #include <unistd.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <stdint.h>
24 #include <ncurses.h>
25 #include <time.h>
26 #include <syslog.h>
27 #include <panel.h>
28 #include <pthread.h>
29 #include <signal.h>
30
31 #include "tmon.h"
32
33 #define min(x, y) ({                            \
34         typeof(x) _min1 = (x);                  \
35         typeof(y) _min2 = (y);                  \
36         (void) (&_min1 == &_min2);              \
37         _min1 < _min2 ? _min1 : _min2; })
38
39 #define max(x, y) ({                            \
40         typeof(x) _max1 = (x);                  \
41         typeof(y) _max2 = (y);                  \
42         (void) (&_max1 == &_max2);              \
43         _max1 > _max2 ? _max1 : _max2; })
44
45 static PANEL *data_panel;
46 static PANEL *dialogue_panel;
47 static PANEL *top;
48
49 static WINDOW *title_bar_window;
50 static WINDOW *tz_sensor_window;
51 static WINDOW *cooling_device_window;
52 static WINDOW *control_window;
53 static WINDOW *status_bar_window;
54 static WINDOW *thermal_data_window;
55 static WINDOW *dialogue_window;
56
57 char status_bar_slots[10][40];
58 static void draw_hbar(WINDOW *win, int y, int start, int len,
59                 unsigned long pattern, bool end);
60
61 static int maxx, maxy;
62 static int maxwidth = 200;
63
64 #define TITLE_BAR_HIGHT 1
65 #define SENSOR_WIN_HIGHT 4 /* one row for tz name, one for trip points */
66
67
68 /* daemon mode flag (set by startup parameter -d) */
69 static int  tui_disabled;
70
71 static void close_panel(PANEL *p)
72 {
73         if (p) {
74                 del_panel(p);
75                 p = NULL;
76         }
77 }
78
79 static void close_window(WINDOW *win)
80 {
81         if (win) {
82                 delwin(win);
83                 win = NULL;
84         }
85 }
86
87 void close_windows(void)
88 {
89         if (tui_disabled)
90                 return;
91         /* must delete panels before their attached windows */
92         if (dialogue_window)
93                 close_panel(dialogue_panel);
94         if (cooling_device_window)
95                 close_panel(data_panel);
96
97         close_window(title_bar_window);
98         close_window(tz_sensor_window);
99         close_window(status_bar_window);
100         close_window(cooling_device_window);
101         close_window(control_window);
102         close_window(thermal_data_window);
103         close_window(dialogue_window);
104
105 }
106
107 void write_status_bar(int x, char *line)
108 {
109         mvwprintw(status_bar_window, 0, x, "%s", line);
110         wrefresh(status_bar_window);
111 }
112
113 void setup_windows(void)
114 {
115         int y_begin = 1;
116
117         if (tui_disabled)
118                 return;
119
120         getmaxyx(stdscr, maxy, maxx);
121         resizeterm(maxy, maxx);
122
123         title_bar_window = subwin(stdscr, TITLE_BAR_HIGHT, maxx, 0, 0);
124         y_begin += TITLE_BAR_HIGHT;
125
126         tz_sensor_window = subwin(stdscr, SENSOR_WIN_HIGHT, maxx, y_begin, 0);
127         y_begin += SENSOR_WIN_HIGHT;
128
129         cooling_device_window = subwin(stdscr, ptdata.nr_cooling_dev + 3, maxx,
130                                 y_begin, 0);
131         y_begin += ptdata.nr_cooling_dev + 3; /* 2 lines for border */
132         /* two lines to show borders, one line per tz show trip point position
133          * and value.
134          * dialogue window is a pop-up, when needed it lays on top of cdev win
135          */
136
137         dialogue_window = subwin(stdscr, ptdata.nr_cooling_dev+5, maxx-50,
138                                 DIAG_Y, DIAG_X);
139
140         thermal_data_window = subwin(stdscr, ptdata.nr_tz_sensor *
141                                 NR_LINES_TZDATA + 3, maxx, y_begin, 0);
142         y_begin += ptdata.nr_tz_sensor * NR_LINES_TZDATA + 3;
143         control_window = subwin(stdscr, 4, maxx, y_begin, 0);
144
145         scrollok(cooling_device_window, TRUE);
146         maxwidth = maxx - 18;
147         status_bar_window = subwin(stdscr, 1, maxx, maxy-1, 0);
148
149         strcpy(status_bar_slots[0], " Ctrl-c - Quit ");
150         strcpy(status_bar_slots[1], " TAB - Tuning ");
151         wmove(status_bar_window, 1, 30);
152
153         /* prepare panels for dialogue, if panel already created then we must
154          * be doing resizing, so just replace windows with new ones, old ones
155          * should have been deleted by close_window
156          */
157         data_panel = new_panel(cooling_device_window);
158         if (!data_panel)
159                 syslog(LOG_DEBUG, "No data panel\n");
160         else {
161                 if (dialogue_window) {
162                         dialogue_panel = new_panel(dialogue_window);
163                         if (!dialogue_panel)
164                                 syslog(LOG_DEBUG, "No dialogue panel\n");
165                         else {
166                                 /* Set up the user pointer to the next panel*/
167                                 set_panel_userptr(data_panel, dialogue_panel);
168                                 set_panel_userptr(dialogue_panel, data_panel);
169                                 top = data_panel;
170                         }
171                 } else
172                         syslog(LOG_INFO, "no dialogue win, term too small\n");
173         }
174         doupdate();
175         werase(stdscr);
176         refresh();
177 }
178
179 void resize_handler(int sig)
180 {
181         /* start over when term gets resized, but first we clean up */
182         close_windows();
183         endwin();
184         refresh();
185         clear();
186         getmaxyx(stdscr, maxy, maxx);  /* get the new screen size */
187         setup_windows();
188         /* rate limit */
189         sleep(1);
190         syslog(LOG_DEBUG, "SIG %d, term resized to %d x %d\n",
191                 sig, maxy, maxx);
192         signal(SIGWINCH, resize_handler);
193 }
194
195 const char cdev_title[] = " COOLING DEVICES ";
196 void show_cooling_device(void)
197 {
198         int i, j, x, y = 0;
199
200         if (tui_disabled || !cooling_device_window)
201                 return;
202
203         werase(cooling_device_window);
204         wattron(cooling_device_window, A_BOLD);
205         mvwprintw(cooling_device_window,  1, 1,
206                 "ID  Cooling Dev   Cur    Max   Thermal Zone Binding");
207         wattroff(cooling_device_window, A_BOLD);
208         for (j = 0; j < ptdata.nr_cooling_dev; j++) {
209                 /* draw cooling device list on the left in the order of
210                  * cooling device instances. skip unused idr.
211                  */
212                 mvwprintw(cooling_device_window, j + 2, 1,
213                         "%02d %12.12s%6d %6d",
214                         ptdata.cdi[j].instance,
215                         ptdata.cdi[j].type,
216                         ptdata.cdi[j].cur_state,
217                         ptdata.cdi[j].max_state);
218         }
219
220         /* show cdev binding, y is the global cooling device instance */
221         for (i = 0; i < ptdata.nr_tz_sensor; i++) {
222                 int tz_inst = ptdata.tzi[i].instance;
223                 for (j = 0; j < ptdata.nr_cooling_dev; j++) {
224                         int cdev_inst;
225                         y = j;
226                         x = tz_inst * TZONE_RECORD_SIZE + TZ_LEFT_ALIGN;
227
228                         draw_hbar(cooling_device_window, y+2, x,
229                                 TZONE_RECORD_SIZE-1, ACS_VLINE, false);
230
231                         /* draw a column of spaces to separate thermal zones */
232                         mvwprintw(cooling_device_window, y+2, x-1, " ");
233                         if (ptdata.tzi[i].cdev_binding) {
234                                 cdev_inst = ptdata.cdi[j].instance;
235                                 unsigned long trip_binding =
236                                         ptdata.tzi[i].trip_binding[cdev_inst];
237                                 int k = 0; /* per zone trip point id that
238                                             * binded to this cdev, one to
239                                             * many possible based on the
240                                             * binding bitmask.
241                                             */
242                                 syslog(LOG_DEBUG,
243                                         "bind tz%d cdev%d tp%lx %d cdev%lx\n",
244                                         i, j, trip_binding, y,
245                                         ptdata.tzi[i].cdev_binding);
246                                 /* draw each trip binding for the cdev */
247                                 while (trip_binding >>= 1) {
248                                         k++;
249                                         if (!(trip_binding & 1))
250                                                 continue;
251                                         /* draw '*' to show binding */
252                                         mvwprintw(cooling_device_window,
253                                                 y + 2,
254                                                 x + ptdata.tzi[i].nr_trip_pts -
255                                                 k - 1, "*");
256                                 }
257                         }
258                 }
259         }
260         /* draw border after data so that border will not be messed up
261          * even there is not enough space for all the data to be shown
262          */
263         wborder(cooling_device_window, 0, 0, 0, 0, 0, 0, 0, 0);
264         wattron(cooling_device_window, A_BOLD);
265         mvwprintw(cooling_device_window, 0, maxx/2 - sizeof(cdev_title),
266                 cdev_title);
267         wattroff(cooling_device_window, A_BOLD);
268
269         wrefresh(cooling_device_window);
270 }
271
272 const char DIAG_TITLE[] = "[ TUNABLES ]";
273 #define DIAG_DEV_ROWS  5
274 void show_dialogue(void)
275 {
276         int j, x = 0, y = 0;
277         WINDOW *w = dialogue_window;
278
279         if (tui_disabled || !w)
280                 return;
281
282         werase(w);
283         box(w, 0, 0);
284         mvwprintw(w, 0, maxx/4, DIAG_TITLE);
285         /* list all the available tunables */
286         for (j = 0; j <= ptdata.nr_cooling_dev; j++) {
287                 y = j % DIAG_DEV_ROWS;
288                 if (y == 0 && j != 0)
289                         x += 20;
290                 if (j == ptdata.nr_cooling_dev)
291                         /* save last choice for target temp */
292                         mvwprintw(w, y+1, x+1, "%C-%.12s", 'A'+j, "Set Temp");
293                 else
294                         mvwprintw(w, y+1, x+1, "%C-%.10s-%2d", 'A'+j,
295                                 ptdata.cdi[j].type, ptdata.cdi[j].instance);
296         }
297         wattron(w, A_BOLD);
298         mvwprintw(w, DIAG_DEV_ROWS+1, 1, "Enter Choice [A-Z]?");
299         wattroff(w, A_BOLD);
300         /* y size of dialogue win is nr cdev + 5, so print legend
301          * at the bottom line
302          */
303         mvwprintw(w, ptdata.nr_cooling_dev+3, 1,
304                 "Legend: A=Active, P=Passive, C=Critical");
305
306         wrefresh(dialogue_window);
307 }
308
309 void write_dialogue_win(char *buf, int y, int x)
310 {
311         WINDOW *w = dialogue_window;
312
313         mvwprintw(w, y, x, "%s", buf);
314 }
315
316 const char control_title[] = " CONTROLS ";
317 void show_control_w(void)
318 {
319         unsigned long state;
320
321         get_ctrl_state(&state);
322
323         if (tui_disabled || !control_window)
324                 return;
325
326         werase(control_window);
327         mvwprintw(control_window, 1, 1,
328                 "PID gain: kp=%2.2f ki=%2.2f kd=%2.2f Output %2.2f",
329                 p_param.kp, p_param.ki, p_param.kd, p_param.y_k);
330
331         mvwprintw(control_window, 2, 1,
332                 "Target Temp: %2.1fC, Zone: %d, Control Device: %.12s",
333                 p_param.t_target, target_thermal_zone, ctrl_cdev);
334
335         /* draw border last such that everything is within boundary */
336         wborder(control_window, 0, 0, 0, 0, 0, 0, 0, 0);
337         wattron(control_window, A_BOLD);
338         mvwprintw(control_window, 0, maxx/2 - sizeof(control_title),
339                 control_title);
340         wattroff(control_window, A_BOLD);
341
342         wrefresh(control_window);
343 }
344
345 void initialize_curses(void)
346 {
347         if (tui_disabled)
348                 return;
349
350         initscr();
351         start_color();
352         keypad(stdscr, TRUE);   /* enable keyboard mapping */
353         nonl();                 /* tell curses not to do NL->CR/NL on output */
354         cbreak();               /* take input chars one at a time */
355         noecho();               /* dont echo input */
356         curs_set(0);            /* turn off cursor */
357         use_default_colors();
358
359         init_pair(PT_COLOR_DEFAULT, COLOR_WHITE, COLOR_BLACK);
360         init_pair(PT_COLOR_HEADER_BAR, COLOR_BLACK, COLOR_WHITE);
361         init_pair(PT_COLOR_ERROR, COLOR_BLACK, COLOR_RED);
362         init_pair(PT_COLOR_RED, COLOR_WHITE, COLOR_RED);
363         init_pair(PT_COLOR_YELLOW, COLOR_WHITE, COLOR_YELLOW);
364         init_pair(PT_COLOR_GREEN, COLOR_WHITE, COLOR_GREEN);
365         init_pair(PT_COLOR_BLUE, COLOR_WHITE, COLOR_BLUE);
366         init_pair(PT_COLOR_BRIGHT, COLOR_WHITE, COLOR_BLACK);
367
368 }
369
370 void show_title_bar(void)
371 {
372         int i;
373         int x = 0;
374
375         if (tui_disabled || !title_bar_window)
376                 return;
377
378         wattrset(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR));
379         wbkgd(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR));
380         werase(title_bar_window);
381
382         mvwprintw(title_bar_window, 0, 0,
383                 "     TMON v%s", VERSION);
384
385         wrefresh(title_bar_window);
386
387         werase(status_bar_window);
388
389         for (i = 0; i < 10; i++) {
390                 if (strlen(status_bar_slots[i]) == 0)
391                         continue;
392                 wattron(status_bar_window, A_REVERSE);
393                 mvwprintw(status_bar_window, 0, x, "%s", status_bar_slots[i]);
394                 wattroff(status_bar_window, A_REVERSE);
395                 x += strlen(status_bar_slots[i]) + 1;
396         }
397         wrefresh(status_bar_window);
398 }
399
400 static void handle_input_val(int ch)
401 {
402         char buf[32];
403         int val;
404         char path[256];
405         WINDOW *w = dialogue_window;
406
407         echo();
408         keypad(w, TRUE);
409         wgetnstr(w, buf, 31);
410         val = atoi(buf);
411
412         if (ch == ptdata.nr_cooling_dev) {
413                 snprintf(buf, 31, "Invalid Temp %d! %d-%d", val,
414                         MIN_CTRL_TEMP, MAX_CTRL_TEMP);
415                 if (val < MIN_CTRL_TEMP || val > MAX_CTRL_TEMP)
416                         write_status_bar(40, buf);
417                 else {
418                         p_param.t_target = val;
419                         snprintf(buf, 31, "Set New Target Temp %d", val);
420                         write_status_bar(40, buf);
421                 }
422         } else {
423                 snprintf(path, 256, "%s/%s%d", THERMAL_SYSFS,
424                         CDEV, ptdata.cdi[ch].instance);
425                 sysfs_set_ulong(path, "cur_state", val);
426         }
427         noecho();
428         dialogue_on = 0;
429         show_data_w();
430         show_control_w();
431
432         top = (PANEL *)panel_userptr(top);
433         top_panel(top);
434 }
435
436 static void handle_input_choice(int ch)
437 {
438         char buf[48];
439         int base = 0;
440         int cdev_id = 0;
441
442         if ((ch >= 'A' && ch <= 'A' + ptdata.nr_cooling_dev) ||
443                 (ch >= 'a' && ch <= 'a' + ptdata.nr_cooling_dev)) {
444                 base = (ch < 'a') ? 'A' : 'a';
445                 cdev_id = ch - base;
446                 if (ptdata.nr_cooling_dev == cdev_id)
447                         snprintf(buf, sizeof(buf), "New Target Temp:");
448                 else
449                         snprintf(buf, sizeof(buf), "New Value for %.10s-%2d: ",
450                                 ptdata.cdi[cdev_id].type,
451                                 ptdata.cdi[cdev_id].instance);
452                 write_dialogue_win(buf, DIAG_DEV_ROWS+2, 2);
453                 handle_input_val(cdev_id);
454         } else {
455                 snprintf(buf, sizeof(buf), "Invalid selection %d", ch);
456                 write_dialogue_win(buf, 8, 2);
457         }
458 }
459
460 void *handle_tui_events(void *arg)
461 {
462         int ch;
463
464         keypad(cooling_device_window, TRUE);
465         while ((ch = wgetch(cooling_device_window)) != EOF) {
466                 if (tmon_exit)
467                         break;
468                 /* when term size is too small, no dialogue panels are set.
469                  * we need to filter out such cases.
470                  */
471                 if (!data_panel || !dialogue_panel ||
472                         !cooling_device_window ||
473                         !dialogue_window) {
474
475                         continue;
476                 }
477                 pthread_mutex_lock(&input_lock);
478                 if (dialogue_on) {
479                         handle_input_choice(ch);
480                         /* top panel filter */
481                         if (ch == 'q' || ch == 'Q')
482                                 ch = 0;
483                 }
484                 switch (ch) {
485                 case KEY_LEFT:
486                         box(cooling_device_window, 10, 0);
487                         break;
488                 case 9: /* TAB */
489                         top = (PANEL *)panel_userptr(top);
490                         top_panel(top);
491                         if (top == dialogue_panel) {
492                                 dialogue_on = 1;
493                                 show_dialogue();
494                         } else {
495                                 dialogue_on = 0;
496                                 /* force refresh */
497                                 show_data_w();
498                                 show_control_w();
499                         }
500                         break;
501                 case 'q':
502                 case 'Q':
503                         tmon_exit = 1;
504                         break;
505                 }
506                 update_panels();
507                 doupdate();
508                 pthread_mutex_unlock(&input_lock);
509         }
510
511         if (arg)
512                 *(int *)arg = 0; /* make gcc happy */
513
514         return NULL;
515 }
516
517 /* draw a horizontal bar in given pattern */
518 static void draw_hbar(WINDOW *win, int y, int start, int len, unsigned long ptn,
519                 bool end)
520 {
521         mvwaddch(win, y, start, ptn);
522         whline(win, ptn, len);
523         if (end)
524                 mvwaddch(win, y, MAX_DISP_TEMP+TDATA_LEFT, ']');
525 }
526
527 static char trip_type_to_char(int type)
528 {
529         switch (type) {
530         case THERMAL_TRIP_CRITICAL: return 'C';
531         case THERMAL_TRIP_HOT: return 'H';
532         case THERMAL_TRIP_PASSIVE: return 'P';
533         case THERMAL_TRIP_ACTIVE: return 'A';
534         default:
535                 return '?';
536         }
537 }
538
539 /* fill a string with trip point type and value in one line
540  * e.g.      P(56)    C(106)
541  * maintain the distance one degree per char
542  */
543 static void draw_tp_line(int tz, int y)
544 {
545         int j;
546         int x;
547
548         for (j = 0; j < ptdata.tzi[tz].nr_trip_pts; j++) {
549                 x = ptdata.tzi[tz].tp[j].temp / 1000;
550                 mvwprintw(thermal_data_window, y + 0, x + TDATA_LEFT,
551                         "%c%d", trip_type_to_char(ptdata.tzi[tz].tp[j].type),
552                         x);
553                 syslog(LOG_INFO, "%s:tz %d tp %d temp = %lu\n", __func__,
554                         tz, j, ptdata.tzi[tz].tp[j].temp);
555         }
556 }
557
558 const char data_win_title[] = " THERMAL DATA ";
559 void show_data_w(void)
560 {
561         int i;
562
563
564         if (tui_disabled || !thermal_data_window)
565                 return;
566
567         werase(thermal_data_window);
568         wattron(thermal_data_window, A_BOLD);
569         mvwprintw(thermal_data_window, 0, maxx/2 - sizeof(data_win_title),
570                 data_win_title);
571         wattroff(thermal_data_window, A_BOLD);
572         /* draw a line as ruler */
573         for (i = 10; i < MAX_DISP_TEMP; i += 10)
574                 mvwprintw(thermal_data_window, 1, i+TDATA_LEFT, "%2d", i);
575
576         for (i = 0; i < ptdata.nr_tz_sensor; i++) {
577                 int temp = trec[cur_thermal_record].temp[i] / 1000;
578                 int y = 0;
579
580                 y = i * NR_LINES_TZDATA + 2;
581                 /* y at tz temp data line */
582                 mvwprintw(thermal_data_window, y, 1, "%6.6s%2d:[%3d][",
583                         ptdata.tzi[i].type,
584                         ptdata.tzi[i].instance, temp);
585                 draw_hbar(thermal_data_window, y, TDATA_LEFT, temp, ACS_RARROW,
586                         true);
587                 draw_tp_line(i, y);
588         }
589         wborder(thermal_data_window, 0, 0, 0, 0, 0, 0, 0, 0);
590         wrefresh(thermal_data_window);
591 }
592
593 const char tz_title[] = "THERMAL ZONES(SENSORS)";
594
595 void show_sensors_w(void)
596 {
597         int i, j;
598         char buffer[512];
599
600         if (tui_disabled || !tz_sensor_window)
601                 return;
602
603         werase(tz_sensor_window);
604
605         memset(buffer, 0, sizeof(buffer));
606         wattron(tz_sensor_window, A_BOLD);
607         mvwprintw(tz_sensor_window, 1, 1, "Thermal Zones:");
608         wattroff(tz_sensor_window, A_BOLD);
609
610         mvwprintw(tz_sensor_window, 1, TZ_LEFT_ALIGN, "%s", buffer);
611         /* fill trip points for each tzone */
612         wattron(tz_sensor_window, A_BOLD);
613         mvwprintw(tz_sensor_window, 2, 1, "Trip Points:");
614         wattroff(tz_sensor_window, A_BOLD);
615
616         /* draw trip point from low to high for each tz */
617         for (i = 0; i < ptdata.nr_tz_sensor; i++) {
618                 int inst = ptdata.tzi[i].instance;
619
620                 mvwprintw(tz_sensor_window, 1,
621                         TZ_LEFT_ALIGN+TZONE_RECORD_SIZE * inst, "%.9s%02d",
622                         ptdata.tzi[i].type, ptdata.tzi[i].instance);
623                 for (j = ptdata.tzi[i].nr_trip_pts - 1; j >= 0; j--) {
624                         /* loop through all trip points */
625                         char type;
626                         int tp_pos;
627                         /* reverse the order here since trips are sorted
628                          * in ascending order in terms of temperature.
629                          */
630                         tp_pos = ptdata.tzi[i].nr_trip_pts - j - 1;
631
632                         type = trip_type_to_char(ptdata.tzi[i].tp[j].type);
633                         mvwaddch(tz_sensor_window, 2,
634                                 inst * TZONE_RECORD_SIZE + TZ_LEFT_ALIGN +
635                                 tp_pos, type);
636                         syslog(LOG_DEBUG, "draw tz %d tp %d ch:%c\n",
637                                 inst, j, type);
638                 }
639         }
640         wborder(tz_sensor_window, 0, 0, 0, 0, 0, 0, 0, 0);
641         wattron(tz_sensor_window, A_BOLD);
642         mvwprintw(tz_sensor_window, 0, maxx/2 - sizeof(tz_title), tz_title);
643         wattroff(tz_sensor_window, A_BOLD);
644         wrefresh(tz_sensor_window);
645 }
646
647 void disable_tui(void)
648 {
649         tui_disabled = 1;
650 }