1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Thermal monitoring tool based on the thermal netlink events. 4 * 5 * Copyright (C) 2022 Linaro Ltd. 6 * 7 * Author: Daniel Lezcano <daniel.lezcano@kernel.org> 8 */ 9 #include <errno.h> 10 #include <fcntl.h> 11 #include <getopt.h> 12 #include <libgen.h> 13 #include <limits.h> 14 #include <stdio.h> 15 #include <stdlib.h> 16 #include <string.h> 17 #include <signal.h> 18 #include <unistd.h> 19 20 #include <syslog.h> 21 22 #include <sys/epoll.h> 23 #include <sys/stat.h> 24 #include <sys/types.h> 25 26 #include <thermal.h> 27 #include "thermal-tools.h" 28 29 struct options { 30 int loglevel; 31 int logopt; 32 int interactive; 33 int daemonize; 34 }; 35 36 struct thermal_data { 37 struct thermal_zone *tz; 38 struct thermal_handler *th; 39 }; 40 41 static int show_trip(struct thermal_trip *tt, __maybe_unused void *arg) 42 { 43 INFO("trip id=%d, type=%d, temp=%d, hyst=%d\n", 44 tt->id, tt->type, tt->temp, tt->hyst); 45 46 return 0; 47 } 48 49 static int show_temp(struct thermal_zone *tz, __maybe_unused void *arg) 50 { 51 thermal_cmd_get_temp(arg, tz); 52 53 INFO("temperature: %d\n", tz->temp); 54 55 return 0; 56 } 57 58 static int show_governor(struct thermal_zone *tz, __maybe_unused void *arg) 59 { 60 thermal_cmd_get_governor(arg, tz); 61 62 INFO("governor: '%s'\n", tz->governor); 63 64 return 0; 65 } 66 67 static int show_tz(struct thermal_zone *tz, __maybe_unused void *arg) 68 { 69 INFO("thermal zone '%s', id=%d\n", tz->name, tz->id); 70 71 for_each_thermal_trip(tz->trip, show_trip, NULL); 72 73 show_temp(tz, arg); 74 75 show_governor(tz, arg); 76 77 return 0; 78 } 79 80 static int tz_create(const char *name, int tz_id, __maybe_unused void *arg) 81 { 82 INFO("Thermal zone '%s'/%d created\n", name, tz_id); 83 84 return 0; 85 } 86 87 static int tz_delete(int tz_id, __maybe_unused void *arg) 88 { 89 INFO("Thermal zone %d deleted\n", tz_id); 90 91 return 0; 92 } 93 94 static int tz_disable(int tz_id, void *arg) 95 { 96 struct thermal_data *td = arg; 97 struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); 98 99 INFO("Thermal zone %d ('%s') disabled\n", tz_id, tz->name); 100 101 return 0; 102 } 103 104 static int tz_enable(int tz_id, void *arg) 105 { 106 struct thermal_data *td = arg; 107 struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); 108 109 INFO("Thermal zone %d ('%s') enabled\n", tz_id, tz->name); 110 111 return 0; 112 } 113 114 static int trip_high(int tz_id, int trip_id, int temp, void *arg) 115 { 116 struct thermal_data *td = arg; 117 struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); 118 119 INFO("Thermal zone %d ('%s'): trip point %d crossed way up with %d °C\n", 120 tz_id, tz->name, trip_id, temp); 121 122 return 0; 123 } 124 125 static int trip_low(int tz_id, int trip_id, int temp, void *arg) 126 { 127 struct thermal_data *td = arg; 128 struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); 129 130 INFO("Thermal zone %d ('%s'): trip point %d crossed way down with %d °C\n", 131 tz_id, tz->name, trip_id, temp); 132 133 return 0; 134 } 135 136 static int trip_add(int tz_id, int trip_id, int type, int temp, int hyst, __maybe_unused void *arg) 137 { 138 INFO("Trip point added %d: id=%d, type=%d, temp=%d, hyst=%d\n", 139 tz_id, trip_id, type, temp, hyst); 140 141 return 0; 142 } 143 144 static int trip_delete(int tz_id, int trip_id, __maybe_unused void *arg) 145 { 146 INFO("Trip point deleted %d: id=%d\n", tz_id, trip_id); 147 148 return 0; 149 } 150 151 static int trip_change(int tz_id, int trip_id, int type, int temp, 152 int hyst, __maybe_unused void *arg) 153 { 154 struct thermal_data *td = arg; 155 struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); 156 157 INFO("Trip point changed %d: id=%d, type=%d, temp=%d, hyst=%d\n", 158 tz_id, trip_id, type, temp, hyst); 159 160 tz->trip[trip_id].type = type; 161 tz->trip[trip_id].temp = temp; 162 tz->trip[trip_id].hyst = hyst; 163 164 return 0; 165 } 166 167 static int cdev_add(const char *name, int cdev_id, int max_state, __maybe_unused void *arg) 168 { 169 INFO("Cooling device '%s'/%d (max state=%d) added\n", name, cdev_id, max_state); 170 171 return 0; 172 } 173 174 static int cdev_delete(int cdev_id, __maybe_unused void *arg) 175 { 176 INFO("Cooling device %d deleted", cdev_id); 177 178 return 0; 179 } 180 181 static int cdev_update(int cdev_id, int cur_state, __maybe_unused void *arg) 182 { 183 INFO("cdev:%d state:%d\n", cdev_id, cur_state); 184 185 return 0; 186 } 187 188 static int gov_change(int tz_id, const char *name, __maybe_unused void *arg) 189 { 190 struct thermal_data *td = arg; 191 struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); 192 193 INFO("%s: governor changed %s -> %s\n", tz->name, tz->governor, name); 194 195 strcpy(tz->governor, name); 196 197 return 0; 198 } 199 200 static struct thermal_ops ops = { 201 .events.tz_create = tz_create, 202 .events.tz_delete = tz_delete, 203 .events.tz_disable = tz_disable, 204 .events.tz_enable = tz_enable, 205 .events.trip_high = trip_high, 206 .events.trip_low = trip_low, 207 .events.trip_add = trip_add, 208 .events.trip_delete = trip_delete, 209 .events.trip_change = trip_change, 210 .events.cdev_add = cdev_add, 211 .events.cdev_delete = cdev_delete, 212 .events.cdev_update = cdev_update, 213 .events.gov_change = gov_change 214 }; 215 216 static int thermal_event(__maybe_unused int fd, __maybe_unused void *arg) 217 { 218 struct thermal_data *td = arg; 219 220 return thermal_events_handle(td->th, td); 221 } 222 223 static void usage(const char *cmd) 224 { 225 printf("%s : A thermal monitoring engine based on notifications\n", cmd); 226 printf("Usage: %s [options]\n", cmd); 227 printf("\t-h, --help\t\tthis help\n"); 228 printf("\t-d, --daemonize\n"); 229 printf("\t-l <level>, --loglevel <level>\tlog level: "); 230 printf("DEBUG, INFO, NOTICE, WARN, ERROR\n"); 231 printf("\t-s, --syslog\t\toutput to syslog\n"); 232 printf("\n"); 233 exit(0); 234 } 235 236 static int options_init(int argc, char *argv[], struct options *options) 237 { 238 int opt; 239 240 struct option long_options[] = { 241 { "help", no_argument, NULL, 'h' }, 242 { "daemonize", no_argument, NULL, 'd' }, 243 { "syslog", no_argument, NULL, 's' }, 244 { "loglevel", required_argument, NULL, 'l' }, 245 { 0, 0, 0, 0 } 246 }; 247 248 while (1) { 249 250 int optindex = 0; 251 252 opt = getopt_long(argc, argv, "l:dhs", long_options, &optindex); 253 if (opt == -1) 254 break; 255 256 switch (opt) { 257 case 'l': 258 options->loglevel = log_str2level(optarg); 259 break; 260 case 'd': 261 options->daemonize = 1; 262 break; 263 case 's': 264 options->logopt = TO_SYSLOG; 265 break; 266 case 'h': 267 usage(basename(argv[0])); 268 break; 269 default: /* '?' */ 270 return -1; 271 } 272 } 273 274 return 0; 275 } 276 277 enum { 278 THERMAL_ENGINE_SUCCESS = 0, 279 THERMAL_ENGINE_OPTION_ERROR, 280 THERMAL_ENGINE_DAEMON_ERROR, 281 THERMAL_ENGINE_LOG_ERROR, 282 THERMAL_ENGINE_THERMAL_ERROR, 283 THERMAL_ENGINE_MAINLOOP_ERROR, 284 }; 285 286 int main(int argc, char *argv[]) 287 { 288 struct thermal_data td; 289 struct options options = { 290 .loglevel = LOG_INFO, 291 .logopt = TO_STDOUT, 292 }; 293 294 if (options_init(argc, argv, &options)) { 295 ERROR("Usage: %s --help\n", argv[0]); 296 return THERMAL_ENGINE_OPTION_ERROR; 297 } 298 299 if (options.daemonize && daemon(0, 0)) { 300 ERROR("Failed to daemonize: %p\n"); 301 return THERMAL_ENGINE_DAEMON_ERROR; 302 } 303 304 if (log_init(options.loglevel, basename(argv[0]), options.logopt)) { 305 ERROR("Failed to initialize logging facility\n"); 306 return THERMAL_ENGINE_LOG_ERROR; 307 } 308 309 td.th = thermal_init(&ops); 310 if (!td.th) { 311 ERROR("Failed to initialize the thermal library\n"); 312 return THERMAL_ENGINE_THERMAL_ERROR; 313 } 314 315 td.tz = thermal_zone_discover(td.th); 316 if (!td.tz) { 317 ERROR("No thermal zone available\n"); 318 return THERMAL_ENGINE_THERMAL_ERROR; 319 } 320 321 for_each_thermal_zone(td.tz, show_tz, td.th); 322 323 if (mainloop_init()) { 324 ERROR("Failed to initialize the mainloop\n"); 325 return THERMAL_ENGINE_MAINLOOP_ERROR; 326 } 327 328 if (mainloop_add(thermal_events_fd(td.th), thermal_event, &td)) { 329 ERROR("Failed to setup the mainloop\n"); 330 return THERMAL_ENGINE_MAINLOOP_ERROR; 331 } 332 333 INFO("Waiting for thermal events ...\n"); 334 335 if (mainloop(-1)) { 336 ERROR("Mainloop failed\n"); 337 return THERMAL_ENGINE_MAINLOOP_ERROR; 338 } 339 340 return THERMAL_ENGINE_SUCCESS; 341 } 342