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