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