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