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(zone_inst: 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(xk: trec[0].temp[target_tz_index] / 1000, yk: &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 | |