1 /* 2 * tmon.c Thermal Monitor (TMON) main function and entry point 3 * 4 * Copyright (C) 2012 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 <getopt.h> 20 #include <unistd.h> 21 #include <stdio.h> 22 #include <stdlib.h> 23 #include <string.h> 24 #include <sys/types.h> 25 #include <sys/stat.h> 26 #include <ncurses.h> 27 #include <ctype.h> 28 #include <time.h> 29 #include <signal.h> 30 #include <limits.h> 31 #include <sys/time.h> 32 #include <pthread.h> 33 #include <math.h> 34 #include <stdarg.h> 35 #include <syslog.h> 36 37 #include "tmon.h" 38 39 unsigned long ticktime = 1; /* seconds */ 40 unsigned long no_control = 1; /* monitoring only or use cooling device for 41 * temperature control. 42 */ 43 double time_elapsed = 0.0; 44 unsigned long target_temp_user = 65; /* can be select by tui later */ 45 int dialogue_on; 46 int tmon_exit; 47 static short daemon_mode; 48 static int logging; /* for recording thermal data to a file */ 49 static int debug_on; 50 FILE *tmon_log; 51 /*cooling device used for the PID controller */ 52 char ctrl_cdev[CDEV_NAME_SIZE] = "None"; 53 int target_thermal_zone; /* user selected target zone instance */ 54 static void start_daemon_mode(void); 55 56 pthread_t event_tid; 57 pthread_mutex_t input_lock; 58 void usage() 59 { 60 printf("Usage: tmon [OPTION...]\n"); 61 printf(" -c, --control cooling device in control\n"); 62 printf(" -d, --daemon run as daemon, no TUI\n"); 63 printf(" -g, --debug debug message in syslog\n"); 64 printf(" -h, --help show this help message\n"); 65 printf(" -l, --log log data to /var/tmp/tmon.log\n"); 66 printf(" -t, --time-interval sampling time interval, > 1 sec.\n"); 67 printf(" -T, --target-temp initial target temperature\n"); 68 printf(" -v, --version show version\n"); 69 printf(" -z, --zone target thermal zone id\n"); 70 71 exit(0); 72 } 73 74 void version() 75 { 76 printf("TMON version %s\n", VERSION); 77 exit(EXIT_SUCCESS); 78 } 79 80 static void tmon_cleanup(void) 81 { 82 83 syslog(LOG_INFO, "TMON exit cleanup\n"); 84 fflush(stdout); 85 refresh(); 86 if (tmon_log) 87 fclose(tmon_log); 88 if (event_tid) { 89 pthread_mutex_lock(&input_lock); 90 pthread_cancel(event_tid); 91 pthread_mutex_unlock(&input_lock); 92 pthread_mutex_destroy(&input_lock); 93 } 94 closelog(); 95 /* relax control knobs, undo throttling */ 96 set_ctrl_state(0); 97 98 keypad(stdscr, FALSE); 99 echo(); 100 nocbreak(); 101 close_windows(); 102 endwin(); 103 free_thermal_data(); 104 105 exit(1); 106 } 107 108 109 static void tmon_sig_handler(int sig) 110 { 111 syslog(LOG_INFO, "TMON caught signal %d\n", sig); 112 refresh(); 113 switch (sig) { 114 case SIGTERM: 115 printf("sigterm, exit and clean up\n"); 116 fflush(stdout); 117 break; 118 case SIGKILL: 119 printf("sigkill, exit and clean up\n"); 120 fflush(stdout); 121 break; 122 case SIGINT: 123 printf("ctrl-c, exit and clean up\n"); 124 fflush(stdout); 125 break; 126 default: 127 break; 128 } 129 tmon_exit = true; 130 } 131 132 133 static void start_syslog(void) 134 { 135 if (debug_on) 136 setlogmask(LOG_UPTO(LOG_DEBUG)); 137 else 138 setlogmask(LOG_UPTO(LOG_ERR)); 139 openlog("tmon.log", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL0); 140 syslog(LOG_NOTICE, "TMON started by User %d", getuid()); 141 } 142 143 static void prepare_logging(void) 144 { 145 int i; 146 struct stat logstat; 147 148 if (!logging) 149 return; 150 /* open local data log file */ 151 tmon_log = fopen(TMON_LOG_FILE, "w+"); 152 if (!tmon_log) { 153 syslog(LOG_ERR, "failed to open log file %s\n", TMON_LOG_FILE); 154 return; 155 } 156 157 if (lstat(TMON_LOG_FILE, &logstat) < 0) { 158 syslog(LOG_ERR, "Unable to stat log file %s\n", TMON_LOG_FILE); 159 fclose(tmon_log); 160 tmon_log = NULL; 161 return; 162 } 163 164 /* The log file must be a regular file owned by us */ 165 if (S_ISLNK(logstat.st_mode)) { 166 syslog(LOG_ERR, "Log file is a symlink. Will not log\n"); 167 fclose(tmon_log); 168 tmon_log = NULL; 169 return; 170 } 171 172 if (logstat.st_uid != getuid()) { 173 syslog(LOG_ERR, "We don't own the log file. Not logging\n"); 174 fclose(tmon_log); 175 tmon_log = NULL; 176 return; 177 } 178 179 180 fprintf(tmon_log, "#----------- THERMAL SYSTEM CONFIG -------------\n"); 181 for (i = 0; i < ptdata.nr_tz_sensor; i++) { 182 char binding_str[33]; /* size of long + 1 */ 183 int j; 184 185 memset(binding_str, 0, sizeof(binding_str)); 186 for (j = 0; j < 32; j++) 187 binding_str[j] = (ptdata.tzi[i].cdev_binding & 1<<j) ? 188 '1' : '0'; 189 190 fprintf(tmon_log, "#thermal zone %s%02d cdevs binding: %32s\n", 191 ptdata.tzi[i].type, 192 ptdata.tzi[i].instance, 193 binding_str); 194 for (j = 0; j < ptdata.tzi[i].nr_trip_pts; j++) { 195 fprintf(tmon_log, "#\tTP%02d type:%s, temp:%lu\n", j, 196 trip_type_name[ptdata.tzi[i].tp[j].type], 197 ptdata.tzi[i].tp[j].temp); 198 } 199 200 } 201 202 for (i = 0; i < ptdata.nr_cooling_dev; i++) 203 fprintf(tmon_log, "#cooling devices%02d: %s\n", 204 i, ptdata.cdi[i].type); 205 206 fprintf(tmon_log, "#---------- THERMAL DATA LOG STARTED -----------\n"); 207 fprintf(tmon_log, "Samples TargetTemp "); 208 for (i = 0; i < ptdata.nr_tz_sensor; i++) { 209 fprintf(tmon_log, "%s%d ", ptdata.tzi[i].type, 210 ptdata.tzi[i].instance); 211 } 212 for (i = 0; i < ptdata.nr_cooling_dev; i++) 213 fprintf(tmon_log, "%s%d ", ptdata.cdi[i].type, 214 ptdata.cdi[i].instance); 215 216 fprintf(tmon_log, "\n"); 217 } 218 219 static struct option opts[] = { 220 { "control", 1, NULL, 'c' }, 221 { "daemon", 0, NULL, 'd' }, 222 { "time-interval", 1, NULL, 't' }, 223 { "target-temp", 1, NULL, 'T' }, 224 { "log", 0, NULL, 'l' }, 225 { "help", 0, NULL, 'h' }, 226 { "version", 0, NULL, 'v' }, 227 { "debug", 0, NULL, 'g' }, 228 { 0, 0, NULL, 0 } 229 }; 230 231 232 int main(int argc, char **argv) 233 { 234 int err = 0; 235 int id2 = 0, c; 236 double yk = 0.0, temp; /* controller output */ 237 int target_tz_index; 238 239 if (geteuid() != 0) { 240 printf("TMON needs to be run as root\n"); 241 exit(EXIT_FAILURE); 242 } 243 244 while ((c = getopt_long(argc, argv, "c:dlht:T:vgz:", opts, &id2)) != -1) { 245 switch (c) { 246 case 'c': 247 no_control = 0; 248 strncpy(ctrl_cdev, optarg, CDEV_NAME_SIZE); 249 break; 250 case 'd': 251 start_daemon_mode(); 252 printf("Run TMON in daemon mode\n"); 253 break; 254 case 't': 255 ticktime = strtod(optarg, NULL); 256 if (ticktime < 1) 257 ticktime = 1; 258 break; 259 case 'T': 260 temp = strtod(optarg, NULL); 261 if (temp < 0) { 262 fprintf(stderr, "error: temperature must be positive\n"); 263 return 1; 264 } 265 target_temp_user = temp; 266 break; 267 case 'l': 268 printf("Logging data to /var/tmp/tmon.log\n"); 269 logging = 1; 270 break; 271 case 'h': 272 usage(); 273 break; 274 case 'v': 275 version(); 276 break; 277 case 'g': 278 debug_on = 1; 279 break; 280 case 'z': 281 target_thermal_zone = strtod(optarg, NULL); 282 break; 283 default: 284 break; 285 } 286 } 287 if (pthread_mutex_init(&input_lock, NULL) != 0) { 288 fprintf(stderr, "\n mutex init failed, exit\n"); 289 return 1; 290 } 291 start_syslog(); 292 if (signal(SIGINT, tmon_sig_handler) == SIG_ERR) 293 syslog(LOG_DEBUG, "Cannot handle SIGINT\n"); 294 if (signal(SIGTERM, tmon_sig_handler) == SIG_ERR) 295 syslog(LOG_DEBUG, "Cannot handle SIGINT\n"); 296 297 if (probe_thermal_sysfs()) { 298 pthread_mutex_destroy(&input_lock); 299 closelog(); 300 return -1; 301 } 302 initialize_curses(); 303 setup_windows(); 304 signal(SIGWINCH, resize_handler); 305 show_title_bar(); 306 show_sensors_w(); 307 show_cooling_device(); 308 update_thermal_data(); 309 show_data_w(); 310 prepare_logging(); 311 init_thermal_controller(); 312 313 nodelay(stdscr, TRUE); 314 err = pthread_create(&event_tid, NULL, &handle_tui_events, NULL); 315 if (err != 0) { 316 printf("\ncan't create thread :[%s]", strerror(err)); 317 tmon_cleanup(); 318 exit(EXIT_FAILURE); 319 } 320 321 /* validate range of user selected target zone, default to the first 322 * instance if out of range 323 */ 324 target_tz_index = zone_instance_to_index(target_thermal_zone); 325 if (target_tz_index < 0) { 326 target_thermal_zone = ptdata.tzi[0].instance; 327 syslog(LOG_ERR, "target zone is not found, default to %d\n", 328 target_thermal_zone); 329 } 330 while (1) { 331 sleep(ticktime); 332 show_title_bar(); 333 show_sensors_w(); 334 update_thermal_data(); 335 if (!dialogue_on) { 336 show_data_w(); 337 show_cooling_device(); 338 } 339 cur_thermal_record++; 340 time_elapsed += ticktime; 341 controller_handler(trec[0].temp[target_tz_index] / 1000, 342 &yk); 343 trec[0].pid_out_pct = yk; 344 if (!dialogue_on) 345 show_control_w(); 346 if (tmon_exit) 347 break; 348 } 349 tmon_cleanup(); 350 return 0; 351 } 352 353 static void start_daemon_mode() 354 { 355 daemon_mode = 1; 356 /* fork */ 357 pid_t sid, pid = fork(); 358 if (pid < 0) { 359 exit(EXIT_FAILURE); 360 } else if (pid > 0) 361 /* kill parent */ 362 exit(EXIT_SUCCESS); 363 364 /* disable TUI, it may not be necessary, but saves some resource */ 365 disable_tui(); 366 367 /* change the file mode mask */ 368 umask(S_IWGRP | S_IWOTH); 369 370 /* new SID for the daemon process */ 371 sid = setsid(); 372 if (sid < 0) 373 exit(EXIT_FAILURE); 374 375 /* change working directory */ 376 if ((chdir("/")) < 0) 377 exit(EXIT_FAILURE); 378 379 380 sleep(10); 381 382 close(STDIN_FILENO); 383 close(STDOUT_FILENO); 384 close(STDERR_FILENO); 385 386 } 387