1 /*- 2 * Copyright (c) 2004 Colin Percival 3 * Copyright (c) 2005 Nate Lawson 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted providing that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 19 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 23 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 24 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 * POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include <sys/cdefs.h> 29 __FBSDID("$FreeBSD$"); 30 31 #include <sys/param.h> 32 #include <sys/ioctl.h> 33 #include <sys/sysctl.h> 34 #include <sys/resource.h> 35 #include <sys/socket.h> 36 #include <sys/time.h> 37 #include <sys/un.h> 38 39 #include <err.h> 40 #include <errno.h> 41 #include <fcntl.h> 42 #include <libutil.h> 43 #include <signal.h> 44 #include <stdio.h> 45 #include <stdlib.h> 46 #include <string.h> 47 #include <unistd.h> 48 49 #ifdef USE_APM 50 #include <machine/apm_bios.h> 51 #endif 52 53 #define DEFAULT_ACTIVE_PERCENT 65 54 #define DEFAULT_IDLE_PERCENT 90 55 #define DEFAULT_POLL_INTERVAL 500 /* Poll interval in milliseconds */ 56 57 typedef enum { 58 MODE_MIN, 59 MODE_ADAPTIVE, 60 MODE_MAX, 61 } modes_t; 62 63 typedef enum { 64 SRC_AC, 65 SRC_BATTERY, 66 SRC_UNKNOWN, 67 } power_src_t; 68 69 const char *modes[] = { 70 "AC", 71 "battery", 72 "unknown" 73 }; 74 75 #define ACPIAC "hw.acpi.acline" 76 #define APMDEV "/dev/apm" 77 #define DEVDPIPE "/var/run/devd.pipe" 78 #define DEVCTL_MAXBUF 1024 79 80 static int read_usage_times(long *idle, long *total); 81 static int read_freqs(int *numfreqs, int **freqs, int **power); 82 static int set_freq(int freq); 83 static void acline_init(void); 84 static void acline_read(void); 85 static int devd_init(void); 86 static void devd_close(void); 87 static void handle_sigs(int sig); 88 static void parse_mode(char *arg, int *mode, int ch); 89 static void usage(void); 90 91 /* Sysctl data structures. */ 92 static int cp_time_mib[2]; 93 static int freq_mib[4]; 94 static int levels_mib[4]; 95 static int acline_mib[3]; 96 97 /* Configuration */ 98 static int cpu_running_mark; 99 static int cpu_idle_mark; 100 static int poll_ival; 101 static int vflag; 102 103 static volatile sig_atomic_t exit_requested; 104 static power_src_t acline_status; 105 static enum { 106 ac_none, 107 ac_acpi_sysctl, 108 ac_acpi_devd, 109 #ifdef USE_APM 110 ac_apm, 111 #endif 112 } acline_mode; 113 #ifdef USE_APM 114 static int apm_fd = -1; 115 #endif 116 static int devd_pipe = -1; 117 118 #define DEVD_RETRY_INTERVAL 60 /* seconds */ 119 static struct timeval tried_devd; 120 121 static int 122 read_usage_times(long *idle, long *total) 123 { 124 static long idle_old, total_old; 125 long cp_time[CPUSTATES], i, total_new; 126 size_t cp_time_len; 127 int error; 128 129 cp_time_len = sizeof(cp_time); 130 error = sysctl(cp_time_mib, 2, cp_time, &cp_time_len, NULL, 0); 131 if (error) 132 return (error); 133 for (total_new = 0, i = 0; i < CPUSTATES; i++) 134 total_new += cp_time[i]; 135 136 if (idle) 137 *idle = cp_time[CP_IDLE] - idle_old; 138 if (total) 139 *total = total_new - total_old; 140 141 idle_old = cp_time[CP_IDLE]; 142 total_old = total_new; 143 144 return (0); 145 } 146 147 static int 148 read_freqs(int *numfreqs, int **freqs, int **power) 149 { 150 char *freqstr, *p, *q; 151 int i; 152 size_t len = 0; 153 154 if (sysctl(levels_mib, 4, NULL, &len, NULL, 0)) 155 return (-1); 156 if ((freqstr = malloc(len)) == NULL) 157 return (-1); 158 if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0)) 159 return (-1); 160 161 *numfreqs = 1; 162 for (p = freqstr; *p != '\0'; p++) 163 if (*p == ' ') 164 (*numfreqs)++; 165 166 if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) { 167 free(freqstr); 168 return (-1); 169 } 170 if ((*power = malloc(*numfreqs * sizeof(int))) == NULL) { 171 free(freqstr); 172 free(*freqs); 173 return (-1); 174 } 175 for (i = 0, p = freqstr; i < *numfreqs; i++) { 176 q = strchr(p, ' '); 177 if (q != NULL) 178 *q = '\0'; 179 if (sscanf(p, "%d/%d", &(*freqs)[i], &(*power)[i]) != 2) { 180 free(freqstr); 181 free(*freqs); 182 free(*power); 183 return (-1); 184 } 185 p = q + 1; 186 } 187 188 free(freqstr); 189 return (0); 190 } 191 192 static int 193 set_freq(int freq) 194 { 195 196 if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq))) { 197 if (errno != EPERM) 198 return (-1); 199 } 200 201 return (0); 202 } 203 204 /* 205 * Try to use ACPI to find the AC line status. If this fails, fall back 206 * to APM. If nothing succeeds, we'll just run in default mode. 207 */ 208 static void 209 acline_init() 210 { 211 size_t len; 212 213 len = 3; 214 if (sysctlnametomib(ACPIAC, acline_mib, &len) == 0) { 215 acline_mode = ac_acpi_sysctl; 216 if (vflag) 217 warnx("using sysctl for AC line status"); 218 #ifdef USE_APM 219 } else if ((apm_fd = open(APMDEV, O_RDONLY)) >= 0) { 220 if (vflag) 221 warnx("using APM for AC line status"); 222 acline_mode = ac_apm; 223 #endif 224 } else { 225 warnx("unable to determine AC line status"); 226 acline_mode = ac_none; 227 } 228 } 229 230 static void 231 acline_read(void) 232 { 233 if (acline_mode == ac_acpi_devd) { 234 char buf[DEVCTL_MAXBUF], *ptr; 235 ssize_t rlen; 236 int notify; 237 238 rlen = read(devd_pipe, buf, sizeof(buf)); 239 if (rlen == 0 || (rlen < 0 && errno != EWOULDBLOCK)) { 240 if (vflag) 241 warnx("lost devd connection, switching to sysctl"); 242 devd_close(); 243 acline_mode = ac_acpi_sysctl; 244 /* FALLTHROUGH */ 245 } 246 if (rlen > 0 && 247 (ptr = strstr(buf, "system=ACPI")) != NULL && 248 (ptr = strstr(ptr, "subsystem=ACAD")) != NULL && 249 (ptr = strstr(ptr, "notify=")) != NULL && 250 sscanf(ptr, "notify=%x", ¬ify) == 1) 251 acline_status = (notify ? SRC_AC : SRC_BATTERY); 252 } 253 if (acline_mode == ac_acpi_sysctl) { 254 int acline; 255 size_t len; 256 257 len = sizeof(acline); 258 if (sysctl(acline_mib, 3, &acline, &len, NULL, 0) == 0) 259 acline_status = (acline ? SRC_AC : SRC_BATTERY); 260 else 261 acline_status = SRC_UNKNOWN; 262 } 263 #ifdef USE_APM 264 if (acline_mode == ac_apm) { 265 struct apm_info info; 266 267 if (ioctl(apm_fd, APMIO_GETINFO, &info) == 0) { 268 acline_status = (info.ai_acline ? SRC_AC : SRC_BATTERY); 269 } else { 270 close(apm_fd); 271 apm_fd = -1; 272 acline_mode = ac_none; 273 acline_status = SRC_UNKNOWN; 274 } 275 } 276 #endif 277 /* try to (re)connect to devd */ 278 if (acline_mode == ac_acpi_sysctl) { 279 struct timeval now; 280 281 gettimeofday(&now, NULL); 282 if (now.tv_sec > tried_devd.tv_sec + DEVD_RETRY_INTERVAL) { 283 if (devd_init() >= 0) { 284 if (vflag) 285 warnx("using devd for AC line status"); 286 acline_mode = ac_acpi_devd; 287 } 288 tried_devd = now; 289 } 290 } 291 } 292 293 static int 294 devd_init(void) 295 { 296 struct sockaddr_un devd_addr; 297 298 bzero(&devd_addr, sizeof(devd_addr)); 299 if ((devd_pipe = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) { 300 if (vflag) 301 warn("%s(): socket()", __func__); 302 return (-1); 303 } 304 305 devd_addr.sun_family = PF_LOCAL; 306 strlcpy(devd_addr.sun_path, DEVDPIPE, sizeof(devd_addr.sun_path)); 307 if (connect(devd_pipe, (struct sockaddr *)&devd_addr, 308 sizeof(devd_addr)) == -1) { 309 if (vflag) 310 warn("%s(): connect()", __func__); 311 close(devd_pipe); 312 devd_pipe = -1; 313 return (-1); 314 } 315 316 if (fcntl(devd_pipe, F_SETFL, O_NONBLOCK) == -1) { 317 if (vflag) 318 warn("%s(): fcntl()", __func__); 319 close(devd_pipe); 320 return (-1); 321 } 322 323 return (devd_pipe); 324 } 325 326 static void 327 devd_close(void) 328 { 329 330 close(devd_pipe); 331 devd_pipe = -1; 332 } 333 334 static void 335 parse_mode(char *arg, int *mode, int ch) 336 { 337 338 if (strcmp(arg, "minimum") == 0 || strcmp(arg, "min") == 0) 339 *mode = MODE_MIN; 340 else if (strcmp(arg, "maximum") == 0 || strcmp(arg, "max") == 0) 341 *mode = MODE_MAX; 342 else if (strcmp(arg, "adaptive") == 0) 343 *mode = MODE_ADAPTIVE; 344 else 345 errx(1, "bad option: -%c %s", (char)ch, optarg); 346 } 347 348 static void 349 handle_sigs(int __unused sig) 350 { 351 352 exit_requested = 1; 353 } 354 355 static void 356 usage(void) 357 { 358 359 fprintf(stderr, 360 "usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-n mode] [-p ival] [-r %%] [-P pidfile]\n"); 361 exit(1); 362 } 363 364 int 365 main(int argc, char * argv[]) 366 { 367 struct timeval timeout; 368 fd_set fdset; 369 int nfds; 370 struct pidfh *pfh = NULL; 371 const char *pidfile = NULL; 372 long idle, total; 373 int curfreq, *freqs, i, *mwatts, numfreqs; 374 int ch, mode, mode_ac, mode_battery, mode_none; 375 uint64_t mjoules_used; 376 size_t len; 377 378 /* Default mode for all AC states is adaptive. */ 379 mode_ac = mode_battery = mode_none = MODE_ADAPTIVE; 380 cpu_running_mark = DEFAULT_ACTIVE_PERCENT; 381 cpu_idle_mark = DEFAULT_IDLE_PERCENT; 382 poll_ival = DEFAULT_POLL_INTERVAL; 383 mjoules_used = 0; 384 vflag = 0; 385 386 /* User must be root to control frequencies. */ 387 if (geteuid() != 0) 388 errx(1, "must be root to run"); 389 390 while ((ch = getopt(argc, argv, "a:b:i:n:p:P:r:v")) != EOF) 391 switch (ch) { 392 case 'a': 393 parse_mode(optarg, &mode_ac, ch); 394 break; 395 case 'b': 396 parse_mode(optarg, &mode_battery, ch); 397 break; 398 case 'i': 399 cpu_idle_mark = atoi(optarg); 400 if (cpu_idle_mark < 0 || cpu_idle_mark > 100) { 401 warnx("%d is not a valid percent", 402 cpu_idle_mark); 403 usage(); 404 } 405 break; 406 case 'n': 407 parse_mode(optarg, &mode_none, ch); 408 break; 409 case 'p': 410 poll_ival = atoi(optarg); 411 if (poll_ival < 5) { 412 warnx("poll interval is in units of ms"); 413 usage(); 414 } 415 break; 416 case 'P': 417 pidfile = optarg; 418 break; 419 case 'r': 420 cpu_running_mark = atoi(optarg); 421 if (cpu_running_mark < 0 || cpu_running_mark > 100) { 422 warnx("%d is not a valid percent", 423 cpu_running_mark); 424 usage(); 425 } 426 break; 427 case 'v': 428 vflag = 1; 429 break; 430 default: 431 usage(); 432 } 433 434 mode = mode_none; 435 436 /* Poll interval is in units of ms. */ 437 poll_ival *= 1000; 438 439 /* Look up various sysctl MIBs. */ 440 len = 2; 441 if (sysctlnametomib("kern.cp_time", cp_time_mib, &len)) 442 err(1, "lookup kern.cp_time"); 443 len = 4; 444 if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len)) 445 err(1, "lookup freq"); 446 len = 4; 447 if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len)) 448 err(1, "lookup freq_levels"); 449 450 /* Check if we can read the idle time and supported freqs. */ 451 if (read_usage_times(NULL, NULL)) 452 err(1, "read_usage_times"); 453 if (read_freqs(&numfreqs, &freqs, &mwatts)) 454 err(1, "error reading supported CPU frequencies"); 455 456 /* Run in the background unless in verbose mode. */ 457 if (!vflag) { 458 pid_t otherpid; 459 460 pfh = pidfile_open(pidfile, 0600, &otherpid); 461 if (pfh == NULL) { 462 if (errno == EEXIST) { 463 errx(1, "powerd already running, pid: %d", 464 otherpid); 465 } 466 warn("cannot open pid file"); 467 } 468 if (daemon(0, 0) != 0) { 469 warn("cannot enter daemon mode, exiting"); 470 pidfile_remove(pfh); 471 exit(EXIT_FAILURE); 472 473 } 474 pidfile_write(pfh); 475 } 476 477 /* Decide whether to use ACPI or APM to read the AC line status. */ 478 acline_init(); 479 480 /* 481 * Exit cleanly on signals. 482 */ 483 signal(SIGINT, handle_sigs); 484 signal(SIGTERM, handle_sigs); 485 486 /* Main loop. */ 487 for (;;) { 488 FD_ZERO(&fdset); 489 if (devd_pipe >= 0) { 490 FD_SET(devd_pipe, &fdset); 491 nfds = devd_pipe + 1; 492 } else { 493 nfds = 0; 494 } 495 timeout.tv_sec = poll_ival / 1000000; 496 timeout.tv_usec = poll_ival % 1000000; 497 select(nfds, &fdset, NULL, &fdset, &timeout); 498 499 /* If the user requested we quit, print some statistics. */ 500 if (exit_requested) { 501 if (vflag && mjoules_used != 0) 502 printf("total joules used: %u.%03u\n", 503 (u_int)(mjoules_used / 1000), 504 (int)mjoules_used % 1000); 505 break; 506 } 507 508 /* Read the current AC status and record the mode. */ 509 acline_read(); 510 switch (acline_status) { 511 case SRC_AC: 512 mode = mode_ac; 513 break; 514 case SRC_BATTERY: 515 mode = mode_battery; 516 break; 517 case SRC_UNKNOWN: 518 mode = mode_none; 519 break; 520 default: 521 errx(1, "invalid AC line status %d", acline_status); 522 } 523 524 /* Read the current frequency. */ 525 len = sizeof(curfreq); 526 if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0) != 0) { 527 if (vflag) 528 warn("error reading current CPU frequency"); 529 continue; 530 } 531 532 if (vflag) { 533 for (i = 0; i < numfreqs; i++) { 534 if (freqs[i] == curfreq) 535 break; 536 } 537 538 /* Keep a sum of all power actually used. */ 539 if (i < numfreqs && mwatts[i] != -1) 540 mjoules_used += 541 (mwatts[i] * (poll_ival / 1000)) / 1000; 542 } 543 544 /* Always switch to the lowest frequency in min mode. */ 545 if (mode == MODE_MIN) { 546 if (curfreq != freqs[numfreqs - 1]) { 547 if (vflag) { 548 printf("now operating on %s power; " 549 "changing frequency to %d MHz\n", 550 modes[acline_status], 551 freqs[numfreqs - 1]); 552 } 553 if (set_freq(freqs[numfreqs - 1]) != 0) { 554 warn("error setting CPU freq %d", 555 freqs[numfreqs - 1]); 556 continue; 557 } 558 } 559 continue; 560 } 561 562 /* Always switch to the highest frequency in max mode. */ 563 if (mode == MODE_MAX) { 564 if (curfreq != freqs[0]) { 565 if (vflag) { 566 printf("now operating on %s power; " 567 "changing frequency to %d MHz\n", 568 modes[acline_status], 569 freqs[0]); 570 } 571 if (set_freq(freqs[0]) != 0) { 572 warn("error setting CPU freq %d", 573 freqs[0]); 574 continue; 575 } 576 } 577 continue; 578 } 579 580 /* Adaptive mode; get the current CPU usage times. */ 581 if (read_usage_times(&idle, &total)) { 582 if (vflag) 583 warn("read_usage_times() failed"); 584 continue; 585 } 586 587 /* 588 * If we're idle less than the active mark, bump up two levels. 589 * If we're idle more than the idle mark, drop down one level. 590 */ 591 for (i = 0; i < numfreqs - 1; i++) { 592 if (freqs[i] == curfreq) 593 break; 594 } 595 if (idle < (total * cpu_running_mark) / 100 && 596 curfreq < freqs[0]) { 597 i -= 2; 598 if (i < 0) 599 i = 0; 600 if (vflag) { 601 printf("idle time < %d%%, increasing clock" 602 " speed from %d MHz to %d MHz\n", 603 cpu_running_mark, curfreq, freqs[i]); 604 } 605 if (set_freq(freqs[i])) 606 warn("error setting CPU frequency %d", 607 freqs[i]); 608 } else if (idle > (total * cpu_idle_mark) / 100 && 609 curfreq > freqs[numfreqs - 1]) { 610 i++; 611 if (vflag) { 612 printf("idle time > %d%%, decreasing clock" 613 " speed from %d MHz to %d MHz\n", 614 cpu_idle_mark, curfreq, freqs[i]); 615 } 616 if (set_freq(freqs[i]) != 0) 617 warn("error setting CPU frequency %d", 618 freqs[i]); 619 } 620 } 621 free(freqs); 622 free(mwatts); 623 devd_close(); 624 if (!vflag) 625 pidfile_remove(pfh); 626 627 exit(0); 628 } 629