1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * tui.c ncurses text user interface for TMON program
5 * Copyright (C) 2013 Intel Corporation. All rights reserved.
7 * Author: Jacob Pan <jacob.jun.pan@linux.intel.com>
24 #define min(x, y) ({ \
25 typeof(x) _min1 = (x); \
26 typeof(y) _min2 = (y); \
27 (void) (&_min1 == &_min2); \
28 _min1 < _min2 ? _min1 : _min2; })
30 #define max(x, y) ({ \
31 typeof(x) _max1 = (x); \
32 typeof(y) _max2 = (y); \
33 (void) (&_max1 == &_max2); \
34 _max1 > _max2 ? _max1 : _max2; })
36 static PANEL
*data_panel
;
37 static PANEL
*dialogue_panel
;
40 static WINDOW
*title_bar_window
;
41 static WINDOW
*tz_sensor_window
;
42 static WINDOW
*cooling_device_window
;
43 static WINDOW
*control_window
;
44 static WINDOW
*status_bar_window
;
45 static WINDOW
*thermal_data_window
;
46 static WINDOW
*dialogue_window
;
48 char status_bar_slots
[10][40];
49 static void draw_hbar(WINDOW
*win
, int y
, int start
, int len
,
50 unsigned long pattern
, bool end
);
52 static int maxx
, maxy
;
53 static int maxwidth
= 200;
55 #define TITLE_BAR_HIGHT 1
56 #define SENSOR_WIN_HIGHT 4 /* one row for tz name, one for trip points */
59 /* daemon mode flag (set by startup parameter -d) */
60 static int tui_disabled
;
62 static void close_panel(PANEL
*p
)
70 static void close_window(WINDOW
*win
)
78 void close_windows(void)
82 /* must delete panels before their attached windows */
84 close_panel(dialogue_panel
);
85 if (cooling_device_window
)
86 close_panel(data_panel
);
88 close_window(title_bar_window
);
89 close_window(tz_sensor_window
);
90 close_window(status_bar_window
);
91 close_window(cooling_device_window
);
92 close_window(control_window
);
93 close_window(thermal_data_window
);
94 close_window(dialogue_window
);
98 void write_status_bar(int x
, char *line
)
100 mvwprintw(status_bar_window
, 0, x
, "%s", line
);
101 wrefresh(status_bar_window
);
105 #define DIAG_DEV_ROWS 5
107 * list cooling devices + "set temp" entry; wraps after 5 rows, if they fit
109 static int diag_dev_rows(void)
111 int entries
= ptdata
.nr_cooling_dev
+ 1;
112 int rows
= max(DIAG_DEV_ROWS
, (entries
+ 1) / 2);
113 return min(rows
, entries
);
116 void setup_windows(void)
123 getmaxyx(stdscr
, maxy
, maxx
);
124 resizeterm(maxy
, maxx
);
126 title_bar_window
= subwin(stdscr
, TITLE_BAR_HIGHT
, maxx
, 0, 0);
127 y_begin
+= TITLE_BAR_HIGHT
;
129 tz_sensor_window
= subwin(stdscr
, SENSOR_WIN_HIGHT
, maxx
, y_begin
, 0);
130 y_begin
+= SENSOR_WIN_HIGHT
;
132 cooling_device_window
= subwin(stdscr
, ptdata
.nr_cooling_dev
+ 3, maxx
,
134 y_begin
+= ptdata
.nr_cooling_dev
+ 3; /* 2 lines for border */
135 /* two lines to show borders, one line per tz show trip point position
137 * dialogue window is a pop-up, when needed it lays on top of cdev win
140 dialogue_window
= subwin(stdscr
, diag_dev_rows() + 5, maxx
-50,
143 thermal_data_window
= subwin(stdscr
, ptdata
.nr_tz_sensor
*
144 NR_LINES_TZDATA
+ 3, maxx
, y_begin
, 0);
145 y_begin
+= ptdata
.nr_tz_sensor
* NR_LINES_TZDATA
+ 3;
146 control_window
= subwin(stdscr
, 4, maxx
, y_begin
, 0);
148 scrollok(cooling_device_window
, TRUE
);
149 maxwidth
= maxx
- 18;
150 status_bar_window
= subwin(stdscr
, 1, maxx
, maxy
-1, 0);
152 strcpy(status_bar_slots
[0], " Ctrl-c - Quit ");
153 strcpy(status_bar_slots
[1], " TAB - Tuning ");
154 wmove(status_bar_window
, 1, 30);
156 /* prepare panels for dialogue, if panel already created then we must
157 * be doing resizing, so just replace windows with new ones, old ones
158 * should have been deleted by close_window
160 data_panel
= new_panel(cooling_device_window
);
162 syslog(LOG_DEBUG
, "No data panel\n");
164 if (dialogue_window
) {
165 dialogue_panel
= new_panel(dialogue_window
);
167 syslog(LOG_DEBUG
, "No dialogue panel\n");
169 /* Set up the user pointer to the next panel*/
170 set_panel_userptr(data_panel
, dialogue_panel
);
171 set_panel_userptr(dialogue_panel
, data_panel
);
175 syslog(LOG_INFO
, "no dialogue win, term too small\n");
182 void resize_handler(int sig
)
184 /* start over when term gets resized, but first we clean up */
189 getmaxyx(stdscr
, maxy
, maxx
); /* get the new screen size */
193 syslog(LOG_DEBUG
, "SIG %d, term resized to %d x %d\n",
195 signal(SIGWINCH
, resize_handler
);
198 const char cdev_title
[] = " COOLING DEVICES ";
199 void show_cooling_device(void)
203 if (tui_disabled
|| !cooling_device_window
)
206 werase(cooling_device_window
);
207 wattron(cooling_device_window
, A_BOLD
);
208 mvwprintw(cooling_device_window
, 1, 1,
209 "ID Cooling Dev Cur Max Thermal Zone Binding");
210 wattroff(cooling_device_window
, A_BOLD
);
211 for (j
= 0; j
< ptdata
.nr_cooling_dev
; j
++) {
212 /* draw cooling device list on the left in the order of
213 * cooling device instances. skip unused idr.
215 mvwprintw(cooling_device_window
, j
+ 2, 1,
216 "%02d %12.12s%6d %6d",
217 ptdata
.cdi
[j
].instance
,
219 ptdata
.cdi
[j
].cur_state
,
220 ptdata
.cdi
[j
].max_state
);
223 /* show cdev binding, y is the global cooling device instance */
224 for (i
= 0; i
< ptdata
.nr_tz_sensor
; i
++) {
225 int tz_inst
= ptdata
.tzi
[i
].instance
;
226 for (j
= 0; j
< ptdata
.nr_cooling_dev
; j
++) {
229 x
= tz_inst
* TZONE_RECORD_SIZE
+ TZ_LEFT_ALIGN
;
231 draw_hbar(cooling_device_window
, y
+2, x
,
232 TZONE_RECORD_SIZE
-1, ACS_VLINE
, false);
234 /* draw a column of spaces to separate thermal zones */
235 mvwprintw(cooling_device_window
, y
+2, x
-1, " ");
236 if (ptdata
.tzi
[i
].cdev_binding
) {
237 cdev_inst
= ptdata
.cdi
[j
].instance
;
238 unsigned long trip_binding
=
239 ptdata
.tzi
[i
].trip_binding
[cdev_inst
];
240 int k
= 0; /* per zone trip point id that
241 * binded to this cdev, one to
242 * many possible based on the
246 "bind tz%d cdev%d tp%lx %d cdev%lx\n",
247 i
, j
, trip_binding
, y
,
248 ptdata
.tzi
[i
].cdev_binding
);
249 /* draw each trip binding for the cdev */
250 while (trip_binding
>>= 1) {
252 if (!(trip_binding
& 1))
254 /* draw '*' to show binding */
255 mvwprintw(cooling_device_window
,
257 x
+ ptdata
.tzi
[i
].nr_trip_pts
-
263 /* draw border after data so that border will not be messed up
264 * even there is not enough space for all the data to be shown
266 wborder(cooling_device_window
, 0, 0, 0, 0, 0, 0, 0, 0);
267 wattron(cooling_device_window
, A_BOLD
);
268 mvwprintw(cooling_device_window
, 0, maxx
/2 - sizeof(cdev_title
),
270 wattroff(cooling_device_window
, A_BOLD
);
272 wrefresh(cooling_device_window
);
275 const char DIAG_TITLE
[] = "[ TUNABLES ]";
276 void show_dialogue(void)
280 WINDOW
*w
= dialogue_window
;
282 if (tui_disabled
|| !w
)
285 getmaxyx(w
, rows
, cols
);
287 /* Silence compiler 'unused' warnings */
292 mvwprintw(w
, 0, maxx
/4, DIAG_TITLE
);
293 /* list all the available tunables */
294 for (j
= 0; j
<= ptdata
.nr_cooling_dev
; j
++) {
295 y
= j
% diag_dev_rows();
296 if (y
== 0 && j
!= 0)
298 if (j
== ptdata
.nr_cooling_dev
)
299 /* save last choice for target temp */
300 mvwprintw(w
, y
+1, x
+1, "%C-%.12s", 'A'+j
, "Set Temp");
302 mvwprintw(w
, y
+1, x
+1, "%C-%.10s-%2d", 'A'+j
,
303 ptdata
.cdi
[j
].type
, ptdata
.cdi
[j
].instance
);
306 mvwprintw(w
, diag_dev_rows()+1, 1, "Enter Choice [A-Z]?");
308 /* print legend at the bottom line */
309 mvwprintw(w
, rows
- 2, 1,
310 "Legend: A=Active, P=Passive, C=Critical");
312 wrefresh(dialogue_window
);
315 void write_dialogue_win(char *buf
, int y
, int x
)
317 WINDOW
*w
= dialogue_window
;
319 mvwprintw(w
, y
, x
, "%s", buf
);
322 const char control_title
[] = " CONTROLS ";
323 void show_control_w(void)
327 get_ctrl_state(&state
);
329 if (tui_disabled
|| !control_window
)
332 werase(control_window
);
333 mvwprintw(control_window
, 1, 1,
334 "PID gain: kp=%2.2f ki=%2.2f kd=%2.2f Output %2.2f",
335 p_param
.kp
, p_param
.ki
, p_param
.kd
, p_param
.y_k
);
337 mvwprintw(control_window
, 2, 1,
338 "Target Temp: %2.1fC, Zone: %d, Control Device: %.12s",
339 p_param
.t_target
, target_thermal_zone
, ctrl_cdev
);
341 /* draw border last such that everything is within boundary */
342 wborder(control_window
, 0, 0, 0, 0, 0, 0, 0, 0);
343 wattron(control_window
, A_BOLD
);
344 mvwprintw(control_window
, 0, maxx
/2 - sizeof(control_title
),
346 wattroff(control_window
, A_BOLD
);
348 wrefresh(control_window
);
351 void initialize_curses(void)
358 keypad(stdscr
, TRUE
); /* enable keyboard mapping */
359 nonl(); /* tell curses not to do NL->CR/NL on output */
360 cbreak(); /* take input chars one at a time */
361 noecho(); /* dont echo input */
362 curs_set(0); /* turn off cursor */
363 use_default_colors();
365 init_pair(PT_COLOR_DEFAULT
, COLOR_WHITE
, COLOR_BLACK
);
366 init_pair(PT_COLOR_HEADER_BAR
, COLOR_BLACK
, COLOR_WHITE
);
367 init_pair(PT_COLOR_ERROR
, COLOR_BLACK
, COLOR_RED
);
368 init_pair(PT_COLOR_RED
, COLOR_WHITE
, COLOR_RED
);
369 init_pair(PT_COLOR_YELLOW
, COLOR_WHITE
, COLOR_YELLOW
);
370 init_pair(PT_COLOR_GREEN
, COLOR_WHITE
, COLOR_GREEN
);
371 init_pair(PT_COLOR_BLUE
, COLOR_WHITE
, COLOR_BLUE
);
372 init_pair(PT_COLOR_BRIGHT
, COLOR_WHITE
, COLOR_BLACK
);
376 void show_title_bar(void)
381 if (tui_disabled
|| !title_bar_window
)
384 wattrset(title_bar_window
, COLOR_PAIR(PT_COLOR_HEADER_BAR
));
385 wbkgd(title_bar_window
, COLOR_PAIR(PT_COLOR_HEADER_BAR
));
386 werase(title_bar_window
);
388 mvwprintw(title_bar_window
, 0, 0,
389 " TMON v%s", VERSION
);
391 wrefresh(title_bar_window
);
393 werase(status_bar_window
);
395 for (i
= 0; i
< 10; i
++) {
396 if (strlen(status_bar_slots
[i
]) == 0)
398 wattron(status_bar_window
, A_REVERSE
);
399 mvwprintw(status_bar_window
, 0, x
, "%s", status_bar_slots
[i
]);
400 wattroff(status_bar_window
, A_REVERSE
);
401 x
+= strlen(status_bar_slots
[i
]) + 1;
403 wrefresh(status_bar_window
);
406 static void handle_input_val(int ch
)
411 WINDOW
*w
= dialogue_window
;
415 wgetnstr(w
, buf
, 31);
418 if (ch
== ptdata
.nr_cooling_dev
) {
419 snprintf(buf
, 31, "Invalid Temp %d! %d-%d", val
,
420 MIN_CTRL_TEMP
, MAX_CTRL_TEMP
);
421 if (val
< MIN_CTRL_TEMP
|| val
> MAX_CTRL_TEMP
)
422 write_status_bar(40, buf
);
424 p_param
.t_target
= val
;
425 snprintf(buf
, 31, "Set New Target Temp %d", val
);
426 write_status_bar(40, buf
);
429 snprintf(path
, 256, "%s/%s%d", THERMAL_SYSFS
,
430 CDEV
, ptdata
.cdi
[ch
].instance
);
431 sysfs_set_ulong(path
, "cur_state", val
);
438 top
= (PANEL
*)panel_userptr(top
);
442 static void handle_input_choice(int ch
)
448 if ((ch
>= 'A' && ch
<= 'A' + ptdata
.nr_cooling_dev
) ||
449 (ch
>= 'a' && ch
<= 'a' + ptdata
.nr_cooling_dev
)) {
450 base
= (ch
< 'a') ? 'A' : 'a';
452 if (ptdata
.nr_cooling_dev
== cdev_id
)
453 snprintf(buf
, sizeof(buf
), "New Target Temp:");
455 snprintf(buf
, sizeof(buf
), "New Value for %.10s-%2d: ",
456 ptdata
.cdi
[cdev_id
].type
,
457 ptdata
.cdi
[cdev_id
].instance
);
458 write_dialogue_win(buf
, diag_dev_rows() + 2, 2);
459 handle_input_val(cdev_id
);
461 snprintf(buf
, sizeof(buf
), "Invalid selection %d", ch
);
462 write_dialogue_win(buf
, 8, 2);
466 void *handle_tui_events(void *arg
)
470 keypad(cooling_device_window
, TRUE
);
471 while ((ch
= wgetch(cooling_device_window
)) != EOF
) {
474 /* when term size is too small, no dialogue panels are set.
475 * we need to filter out such cases.
477 if (!data_panel
|| !dialogue_panel
||
478 !cooling_device_window
||
483 pthread_mutex_lock(&input_lock
);
485 handle_input_choice(ch
);
486 /* top panel filter */
487 if (ch
== 'q' || ch
== 'Q')
492 box(cooling_device_window
, 10, 0);
495 top
= (PANEL
*)panel_userptr(top
);
497 if (top
== dialogue_panel
) {
514 pthread_mutex_unlock(&input_lock
);
518 *(int *)arg
= 0; /* make gcc happy */
523 /* draw a horizontal bar in given pattern */
524 static void draw_hbar(WINDOW
*win
, int y
, int start
, int len
, unsigned long ptn
,
527 mvwaddch(win
, y
, start
, ptn
);
528 whline(win
, ptn
, len
);
530 mvwaddch(win
, y
, MAX_DISP_TEMP
+TDATA_LEFT
, ']');
533 static char trip_type_to_char(int type
)
536 case THERMAL_TRIP_CRITICAL
: return 'C';
537 case THERMAL_TRIP_HOT
: return 'H';
538 case THERMAL_TRIP_PASSIVE
: return 'P';
539 case THERMAL_TRIP_ACTIVE
: return 'A';
545 /* fill a string with trip point type and value in one line
547 * maintain the distance one degree per char
549 static void draw_tp_line(int tz
, int y
)
554 for (j
= 0; j
< ptdata
.tzi
[tz
].nr_trip_pts
; j
++) {
555 x
= ptdata
.tzi
[tz
].tp
[j
].temp
/ 1000;
556 mvwprintw(thermal_data_window
, y
+ 0, x
+ TDATA_LEFT
,
557 "%c%d", trip_type_to_char(ptdata
.tzi
[tz
].tp
[j
].type
),
559 syslog(LOG_INFO
, "%s:tz %d tp %d temp = %lu\n", __func__
,
560 tz
, j
, ptdata
.tzi
[tz
].tp
[j
].temp
);
564 const char data_win_title
[] = " THERMAL DATA ";
565 void show_data_w(void)
570 if (tui_disabled
|| !thermal_data_window
)
573 werase(thermal_data_window
);
574 wattron(thermal_data_window
, A_BOLD
);
575 mvwprintw(thermal_data_window
, 0, maxx
/2 - sizeof(data_win_title
),
577 wattroff(thermal_data_window
, A_BOLD
);
578 /* draw a line as ruler */
579 for (i
= 10; i
< MAX_DISP_TEMP
; i
+= 10)
580 mvwprintw(thermal_data_window
, 1, i
+TDATA_LEFT
, "%2d", i
);
582 for (i
= 0; i
< ptdata
.nr_tz_sensor
; i
++) {
583 int temp
= trec
[cur_thermal_record
].temp
[i
] / 1000;
586 y
= i
* NR_LINES_TZDATA
+ 2;
587 /* y at tz temp data line */
588 mvwprintw(thermal_data_window
, y
, 1, "%6.6s%2d:[%3d][",
590 ptdata
.tzi
[i
].instance
, temp
);
591 draw_hbar(thermal_data_window
, y
, TDATA_LEFT
, temp
, ACS_RARROW
,
595 wborder(thermal_data_window
, 0, 0, 0, 0, 0, 0, 0, 0);
596 wrefresh(thermal_data_window
);
599 const char tz_title
[] = "THERMAL ZONES(SENSORS)";
601 void show_sensors_w(void)
606 if (tui_disabled
|| !tz_sensor_window
)
609 werase(tz_sensor_window
);
611 memset(buffer
, 0, sizeof(buffer
));
612 wattron(tz_sensor_window
, A_BOLD
);
613 mvwprintw(tz_sensor_window
, 1, 1, "Thermal Zones:");
614 wattroff(tz_sensor_window
, A_BOLD
);
616 mvwprintw(tz_sensor_window
, 1, TZ_LEFT_ALIGN
, "%s", buffer
);
617 /* fill trip points for each tzone */
618 wattron(tz_sensor_window
, A_BOLD
);
619 mvwprintw(tz_sensor_window
, 2, 1, "Trip Points:");
620 wattroff(tz_sensor_window
, A_BOLD
);
622 /* draw trip point from low to high for each tz */
623 for (i
= 0; i
< ptdata
.nr_tz_sensor
; i
++) {
624 int inst
= ptdata
.tzi
[i
].instance
;
626 mvwprintw(tz_sensor_window
, 1,
627 TZ_LEFT_ALIGN
+TZONE_RECORD_SIZE
* inst
, "%.9s%02d",
628 ptdata
.tzi
[i
].type
, ptdata
.tzi
[i
].instance
);
629 for (j
= ptdata
.tzi
[i
].nr_trip_pts
- 1; j
>= 0; j
--) {
630 /* loop through all trip points */
633 /* reverse the order here since trips are sorted
634 * in ascending order in terms of temperature.
636 tp_pos
= ptdata
.tzi
[i
].nr_trip_pts
- j
- 1;
638 type
= trip_type_to_char(ptdata
.tzi
[i
].tp
[j
].type
);
639 mvwaddch(tz_sensor_window
, 2,
640 inst
* TZONE_RECORD_SIZE
+ TZ_LEFT_ALIGN
+
642 syslog(LOG_DEBUG
, "draw tz %d tp %d ch:%c\n",
646 wborder(tz_sensor_window
, 0, 0, 0, 0, 0, 0, 0, 0);
647 wattron(tz_sensor_window
, A_BOLD
);
648 mvwprintw(tz_sensor_window
, 0, maxx
/2 - sizeof(tz_title
), tz_title
);
649 wattroff(tz_sensor_window
, A_BOLD
);
650 wrefresh(tz_sensor_window
);
653 void disable_tui(void)