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