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