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 75 54 #define DEFAULT_IDLE_PERCENT 50 55 #define DEFAULT_POLL_INTERVAL 250 /* Poll interval in milliseconds */ 56 57 typedef enum { 58 MODE_MIN, 59 MODE_ADAPTIVE, 60 MODE_HIADAPTIVE, 61 MODE_MAX, 62 } modes_t; 63 64 typedef enum { 65 SRC_AC, 66 SRC_BATTERY, 67 SRC_UNKNOWN, 68 } power_src_t; 69 70 const char *modes[] = { 71 "AC", 72 "battery", 73 "unknown" 74 }; 75 76 #define ACPIAC "hw.acpi.acline" 77 #define APMDEV "/dev/apm" 78 #define DEVDPIPE "/var/run/devd.pipe" 79 #define DEVCTL_MAXBUF 1024 80 81 static int read_usage_times(int *load); 82 static int read_freqs(int *numfreqs, int **freqs, int **power); 83 static int set_freq(int freq); 84 static void acline_init(void); 85 static void acline_read(void); 86 static int devd_init(void); 87 static void devd_close(void); 88 static void handle_sigs(int sig); 89 static void parse_mode(char *arg, int *mode, int ch); 90 static void usage(void); 91 92 /* Sysctl data structures. */ 93 static int cp_times_mib[2]; 94 static int freq_mib[4]; 95 static int levels_mib[4]; 96 static int acline_mib[3]; 97 98 /* Configuration */ 99 static int cpu_running_mark; 100 static int cpu_idle_mark; 101 static int poll_ival; 102 static int vflag; 103 104 static volatile sig_atomic_t exit_requested; 105 static power_src_t acline_status; 106 static enum { 107 ac_none, 108 ac_acpi_sysctl, 109 ac_acpi_devd, 110 #ifdef USE_APM 111 ac_apm, 112 #endif 113 } acline_mode; 114 #ifdef USE_APM 115 static int apm_fd = -1; 116 #endif 117 static int devd_pipe = -1; 118 119 #define DEVD_RETRY_INTERVAL 60 /* seconds */ 120 static struct timeval tried_devd; 121 122 static int 123 read_usage_times(int *load) 124 { 125 static long *cp_times = NULL, *cp_times_old = NULL; 126 static int ncpus = 0; 127 size_t cp_times_len; 128 int error, cpu, i, total; 129 130 if (cp_times == NULL) { 131 cp_times_len = 0; 132 error = sysctl(cp_times_mib, 2, NULL, &cp_times_len, NULL, 0); 133 if (error) 134 return (error); 135 if ((cp_times = malloc(cp_times_len)) == NULL) 136 return (errno); 137 if ((cp_times_old = malloc(cp_times_len)) == NULL) { 138 free(cp_times); 139 cp_times = NULL; 140 return (errno); 141 } 142 ncpus = cp_times_len / (sizeof(long) * CPUSTATES); 143 } 144 145 cp_times_len = sizeof(long) * CPUSTATES * ncpus; 146 error = sysctl(cp_times_mib, 2, cp_times, &cp_times_len, NULL, 0); 147 if (error) 148 return (error); 149 150 if (load) { 151 *load = 0; 152 for (cpu = 0; cpu < ncpus; cpu++) { 153 total = 0; 154 for (i = 0; i < CPUSTATES; i++) { 155 total += cp_times[cpu * CPUSTATES + i] - 156 cp_times_old[cpu * CPUSTATES + i]; 157 } 158 if (total == 0) 159 continue; 160 *load += 100 - (cp_times[cpu * CPUSTATES + CP_IDLE] - 161 cp_times_old[cpu * CPUSTATES + CP_IDLE]) * 100 / total; 162 } 163 } 164 165 memcpy(cp_times_old, cp_times, cp_times_len); 166 167 return (0); 168 } 169 170 static int 171 read_freqs(int *numfreqs, int **freqs, int **power) 172 { 173 char *freqstr, *p, *q; 174 int i; 175 size_t len = 0; 176 177 if (sysctl(levels_mib, 4, NULL, &len, NULL, 0)) 178 return (-1); 179 if ((freqstr = malloc(len)) == NULL) 180 return (-1); 181 if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0)) 182 return (-1); 183 184 *numfreqs = 1; 185 for (p = freqstr; *p != '\0'; p++) 186 if (*p == ' ') 187 (*numfreqs)++; 188 189 if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) { 190 free(freqstr); 191 return (-1); 192 } 193 if ((*power = malloc(*numfreqs * sizeof(int))) == NULL) { 194 free(freqstr); 195 free(*freqs); 196 return (-1); 197 } 198 for (i = 0, p = freqstr; i < *numfreqs; i++) { 199 q = strchr(p, ' '); 200 if (q != NULL) 201 *q = '\0'; 202 if (sscanf(p, "%d/%d", &(*freqs)[i], &(*power)[i]) != 2) { 203 free(freqstr); 204 free(*freqs); 205 free(*power); 206 return (-1); 207 } 208 p = q + 1; 209 } 210 211 free(freqstr); 212 return (0); 213 } 214 215 static int 216 get_freq(void) 217 { 218 size_t len; 219 int curfreq; 220 221 len = sizeof(curfreq); 222 if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0) != 0) { 223 if (vflag) 224 warn("error reading current CPU frequency"); 225 curfreq = 0; 226 } 227 return (curfreq); 228 } 229 230 static int 231 set_freq(int freq) 232 { 233 234 if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq))) { 235 if (errno != EPERM) 236 return (-1); 237 } 238 239 return (0); 240 } 241 242 static int 243 get_freq_id(int freq, int *freqs, int numfreqs) 244 { 245 int i = 1; 246 247 while (i < numfreqs) { 248 if (freqs[i] < freq) 249 break; 250 i++; 251 } 252 return (i - 1); 253 } 254 255 /* 256 * Try to use ACPI to find the AC line status. If this fails, fall back 257 * to APM. If nothing succeeds, we'll just run in default mode. 258 */ 259 static void 260 acline_init() 261 { 262 size_t len; 263 264 len = 3; 265 if (sysctlnametomib(ACPIAC, acline_mib, &len) == 0) { 266 acline_mode = ac_acpi_sysctl; 267 if (vflag) 268 warnx("using sysctl for AC line status"); 269 #ifdef USE_APM 270 } else if ((apm_fd = open(APMDEV, O_RDONLY)) >= 0) { 271 if (vflag) 272 warnx("using APM for AC line status"); 273 acline_mode = ac_apm; 274 #endif 275 } else { 276 warnx("unable to determine AC line status"); 277 acline_mode = ac_none; 278 } 279 } 280 281 static void 282 acline_read(void) 283 { 284 if (acline_mode == ac_acpi_devd) { 285 char buf[DEVCTL_MAXBUF], *ptr; 286 ssize_t rlen; 287 int notify; 288 289 rlen = read(devd_pipe, buf, sizeof(buf)); 290 if (rlen == 0 || (rlen < 0 && errno != EWOULDBLOCK)) { 291 if (vflag) 292 warnx("lost devd connection, switching to sysctl"); 293 devd_close(); 294 acline_mode = ac_acpi_sysctl; 295 /* FALLTHROUGH */ 296 } 297 if (rlen > 0 && 298 (ptr = strstr(buf, "system=ACPI")) != NULL && 299 (ptr = strstr(ptr, "subsystem=ACAD")) != NULL && 300 (ptr = strstr(ptr, "notify=")) != NULL && 301 sscanf(ptr, "notify=%x", ¬ify) == 1) 302 acline_status = (notify ? SRC_AC : SRC_BATTERY); 303 } 304 if (acline_mode == ac_acpi_sysctl) { 305 int acline; 306 size_t len; 307 308 len = sizeof(acline); 309 if (sysctl(acline_mib, 3, &acline, &len, NULL, 0) == 0) 310 acline_status = (acline ? SRC_AC : SRC_BATTERY); 311 else 312 acline_status = SRC_UNKNOWN; 313 } 314 #ifdef USE_APM 315 if (acline_mode == ac_apm) { 316 struct apm_info info; 317 318 if (ioctl(apm_fd, APMIO_GETINFO, &info) == 0) { 319 acline_status = (info.ai_acline ? SRC_AC : SRC_BATTERY); 320 } else { 321 close(apm_fd); 322 apm_fd = -1; 323 acline_mode = ac_none; 324 acline_status = SRC_UNKNOWN; 325 } 326 } 327 #endif 328 /* try to (re)connect to devd */ 329 if (acline_mode == ac_acpi_sysctl) { 330 struct timeval now; 331 332 gettimeofday(&now, NULL); 333 if (now.tv_sec > tried_devd.tv_sec + DEVD_RETRY_INTERVAL) { 334 if (devd_init() >= 0) { 335 if (vflag) 336 warnx("using devd for AC line status"); 337 acline_mode = ac_acpi_devd; 338 } 339 tried_devd = now; 340 } 341 } 342 } 343 344 static int 345 devd_init(void) 346 { 347 struct sockaddr_un devd_addr; 348 349 bzero(&devd_addr, sizeof(devd_addr)); 350 if ((devd_pipe = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) { 351 if (vflag) 352 warn("%s(): socket()", __func__); 353 return (-1); 354 } 355 356 devd_addr.sun_family = PF_LOCAL; 357 strlcpy(devd_addr.sun_path, DEVDPIPE, sizeof(devd_addr.sun_path)); 358 if (connect(devd_pipe, (struct sockaddr *)&devd_addr, 359 sizeof(devd_addr)) == -1) { 360 if (vflag) 361 warn("%s(): connect()", __func__); 362 close(devd_pipe); 363 devd_pipe = -1; 364 return (-1); 365 } 366 367 if (fcntl(devd_pipe, F_SETFL, O_NONBLOCK) == -1) { 368 if (vflag) 369 warn("%s(): fcntl()", __func__); 370 close(devd_pipe); 371 return (-1); 372 } 373 374 return (devd_pipe); 375 } 376 377 static void 378 devd_close(void) 379 { 380 381 close(devd_pipe); 382 devd_pipe = -1; 383 } 384 385 static void 386 parse_mode(char *arg, int *mode, int ch) 387 { 388 389 if (strcmp(arg, "minimum") == 0 || strcmp(arg, "min") == 0) 390 *mode = MODE_MIN; 391 else if (strcmp(arg, "maximum") == 0 || strcmp(arg, "max") == 0) 392 *mode = MODE_MAX; 393 else if (strcmp(arg, "adaptive") == 0 || strcmp(arg, "adp") == 0) 394 *mode = MODE_ADAPTIVE; 395 else if (strcmp(arg, "hiadaptive") == 0 || strcmp(arg, "hadp") == 0) 396 *mode = MODE_HIADAPTIVE; 397 else 398 errx(1, "bad option: -%c %s", (char)ch, optarg); 399 } 400 401 static void 402 handle_sigs(int __unused sig) 403 { 404 405 exit_requested = 1; 406 } 407 408 static void 409 usage(void) 410 { 411 412 fprintf(stderr, 413 "usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-n mode] [-p ival] [-r %%] [-P pidfile]\n"); 414 exit(1); 415 } 416 417 int 418 main(int argc, char * argv[]) 419 { 420 struct timeval timeout; 421 fd_set fdset; 422 int nfds; 423 struct pidfh *pfh = NULL; 424 const char *pidfile = NULL; 425 int freq, curfreq, initfreq, *freqs, i, j, *mwatts, numfreqs, load; 426 int ch, mode, mode_ac, mode_battery, mode_none; 427 uint64_t mjoules_used; 428 size_t len; 429 430 /* Default mode for all AC states is adaptive. */ 431 mode_ac = mode_none = MODE_HIADAPTIVE; 432 mode_battery = MODE_ADAPTIVE; 433 cpu_running_mark = DEFAULT_ACTIVE_PERCENT; 434 cpu_idle_mark = DEFAULT_IDLE_PERCENT; 435 poll_ival = DEFAULT_POLL_INTERVAL; 436 mjoules_used = 0; 437 vflag = 0; 438 439 /* User must be root to control frequencies. */ 440 if (geteuid() != 0) 441 errx(1, "must be root to run"); 442 443 while ((ch = getopt(argc, argv, "a:b:i:n:p:P:r:v")) != -1) 444 switch (ch) { 445 case 'a': 446 parse_mode(optarg, &mode_ac, ch); 447 break; 448 case 'b': 449 parse_mode(optarg, &mode_battery, ch); 450 break; 451 case 'i': 452 cpu_idle_mark = atoi(optarg); 453 if (cpu_idle_mark < 0 || cpu_idle_mark > 100) { 454 warnx("%d is not a valid percent", 455 cpu_idle_mark); 456 usage(); 457 } 458 break; 459 case 'n': 460 parse_mode(optarg, &mode_none, ch); 461 break; 462 case 'p': 463 poll_ival = atoi(optarg); 464 if (poll_ival < 5) { 465 warnx("poll interval is in units of ms"); 466 usage(); 467 } 468 break; 469 case 'P': 470 pidfile = optarg; 471 break; 472 case 'r': 473 cpu_running_mark = atoi(optarg); 474 if (cpu_running_mark <= 0 || cpu_running_mark > 100) { 475 warnx("%d is not a valid percent", 476 cpu_running_mark); 477 usage(); 478 } 479 break; 480 case 'v': 481 vflag = 1; 482 break; 483 default: 484 usage(); 485 } 486 487 mode = mode_none; 488 489 /* Poll interval is in units of ms. */ 490 poll_ival *= 1000; 491 492 /* Look up various sysctl MIBs. */ 493 len = 2; 494 if (sysctlnametomib("kern.cp_times", cp_times_mib, &len)) 495 err(1, "lookup kern.cp_times"); 496 len = 4; 497 if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len)) 498 err(1, "lookup freq"); 499 len = 4; 500 if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len)) 501 err(1, "lookup freq_levels"); 502 503 /* Check if we can read the load and supported freqs. */ 504 if (read_usage_times(NULL)) 505 err(1, "read_usage_times"); 506 if (read_freqs(&numfreqs, &freqs, &mwatts)) 507 err(1, "error reading supported CPU frequencies"); 508 509 /* Run in the background unless in verbose mode. */ 510 if (!vflag) { 511 pid_t otherpid; 512 513 pfh = pidfile_open(pidfile, 0600, &otherpid); 514 if (pfh == NULL) { 515 if (errno == EEXIST) { 516 errx(1, "powerd already running, pid: %d", 517 otherpid); 518 } 519 warn("cannot open pid file"); 520 } 521 if (daemon(0, 0) != 0) { 522 warn("cannot enter daemon mode, exiting"); 523 pidfile_remove(pfh); 524 exit(EXIT_FAILURE); 525 526 } 527 pidfile_write(pfh); 528 } 529 530 /* Decide whether to use ACPI or APM to read the AC line status. */ 531 acline_init(); 532 533 /* 534 * Exit cleanly on signals. 535 */ 536 signal(SIGINT, handle_sigs); 537 signal(SIGTERM, handle_sigs); 538 539 freq = initfreq = get_freq(); 540 if (freq < 1) 541 freq = 1; 542 /* Main loop. */ 543 for (;;) { 544 FD_ZERO(&fdset); 545 if (devd_pipe >= 0) { 546 FD_SET(devd_pipe, &fdset); 547 nfds = devd_pipe + 1; 548 } else { 549 nfds = 0; 550 } 551 timeout.tv_sec = poll_ival / 1000000; 552 timeout.tv_usec = poll_ival % 1000000; 553 select(nfds, &fdset, NULL, &fdset, &timeout); 554 555 /* If the user requested we quit, print some statistics. */ 556 if (exit_requested) { 557 if (vflag && mjoules_used != 0) 558 printf("total joules used: %u.%03u\n", 559 (u_int)(mjoules_used / 1000), 560 (int)mjoules_used % 1000); 561 break; 562 } 563 564 /* Read the current AC status and record the mode. */ 565 acline_read(); 566 switch (acline_status) { 567 case SRC_AC: 568 mode = mode_ac; 569 break; 570 case SRC_BATTERY: 571 mode = mode_battery; 572 break; 573 case SRC_UNKNOWN: 574 mode = mode_none; 575 break; 576 default: 577 errx(1, "invalid AC line status %d", acline_status); 578 } 579 580 /* Read the current frequency. */ 581 if ((curfreq = get_freq()) == 0) 582 continue; 583 584 i = get_freq_id(curfreq, freqs, numfreqs); 585 586 if (vflag) { 587 /* Keep a sum of all power actually used. */ 588 if (mwatts[i] != -1) 589 mjoules_used += 590 (mwatts[i] * (poll_ival / 1000)) / 1000; 591 } 592 593 /* Always switch to the lowest frequency in min mode. */ 594 if (mode == MODE_MIN) { 595 freq = freqs[numfreqs - 1]; 596 if (curfreq != freq) { 597 if (vflag) { 598 printf("now operating on %s power; " 599 "changing frequency to %d MHz\n", 600 modes[acline_status], freq); 601 } 602 if (set_freq(freq) != 0) { 603 warn("error setting CPU freq %d", 604 freq); 605 continue; 606 } 607 } 608 continue; 609 } 610 611 /* Always switch to the highest frequency in max mode. */ 612 if (mode == MODE_MAX) { 613 freq = freqs[0]; 614 if (curfreq != freq) { 615 if (vflag) { 616 printf("now operating on %s power; " 617 "changing frequency to %d MHz\n", 618 modes[acline_status], freq); 619 } 620 if (set_freq(freq) != 0) { 621 warn("error setting CPU freq %d", 622 freq); 623 continue; 624 } 625 } 626 continue; 627 } 628 629 /* Adaptive mode; get the current CPU usage times. */ 630 if (read_usage_times(&load)) { 631 if (vflag) 632 warn("read_usage_times() failed"); 633 continue; 634 } 635 636 if (mode == MODE_ADAPTIVE) { 637 if (load > cpu_running_mark) { 638 if (load > 95 || load > cpu_running_mark * 2) 639 freq *= 2; 640 else 641 freq = freq * load / cpu_running_mark; 642 if (freq > freqs[0]) 643 freq = freqs[0]; 644 } else if (load < cpu_idle_mark && 645 curfreq * load < freqs[get_freq_id( 646 freq * 7 / 8, freqs, numfreqs)] * 647 cpu_running_mark) { 648 freq = freq * 7 / 8; 649 if (freq < freqs[numfreqs - 1]) 650 freq = freqs[numfreqs - 1]; 651 } 652 } else { /* MODE_HIADAPTIVE */ 653 if (load > cpu_running_mark / 2) { 654 if (load > 95 || load > cpu_running_mark) 655 freq *= 4; 656 else 657 freq = freq * load * 2 / cpu_running_mark; 658 if (freq > freqs[0] * 2) 659 freq = freqs[0] * 2; 660 } else if (load < cpu_idle_mark / 2 && 661 curfreq * load < freqs[get_freq_id( 662 freq * 31 / 32, freqs, numfreqs)] * 663 cpu_running_mark / 2) { 664 freq = freq * 31 / 32; 665 if (freq < freqs[numfreqs - 1]) 666 freq = freqs[numfreqs - 1]; 667 } 668 } 669 if (vflag) { 670 printf("load %3d%%, current freq %4d MHz (%2d), wanted freq %4d MHz\n", 671 load, curfreq, i, freq); 672 } 673 j = get_freq_id(freq, freqs, numfreqs); 674 if (i != j) { 675 if (vflag) { 676 printf("changing clock" 677 " speed from %d MHz to %d MHz\n", 678 freqs[i], freqs[j]); 679 } 680 if (set_freq(freqs[j])) 681 warn("error setting CPU frequency %d", 682 freqs[j]); 683 } 684 } 685 if (set_freq(initfreq)) 686 warn("error setting CPU frequency %d", initfreq); 687 free(freqs); 688 free(mwatts); 689 devd_close(); 690 if (!vflag) 691 pidfile_remove(pfh); 692 693 exit(0); 694 } 695