powerd.c revision 203482
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: head/usr.sbin/powerd/powerd.c 203482 2010-02-04 07:26:26Z imp $"); 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 61typedef enum { 62 MODE_MIN, 63 MODE_ADAPTIVE, 64 MODE_HIADAPTIVE, 65 MODE_MAX, 66} modes_t; 67 68typedef enum { 69 SRC_AC, 70 SRC_BATTERY, 71 SRC_UNKNOWN, 72} power_src_t; 73 74const 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 86static int read_usage_times(int *load); 87static int read_freqs(int *numfreqs, int **freqs, int **power); 88static int set_freq(int freq); 89static void acline_init(void); 90static void acline_read(void); 91static int devd_init(void); 92static void devd_close(void); 93static void handle_sigs(int sig); 94static void parse_mode(char *arg, int *mode, int ch); 95static void usage(void); 96 97/* Sysctl data structures. */ 98static int cp_times_mib[2]; 99static int freq_mib[4]; 100static int levels_mib[4]; 101static int acline_mib[4]; 102static size_t acline_mib_len; 103 104/* Configuration */ 105static int cpu_running_mark; 106static int cpu_idle_mark; 107static int poll_ival; 108static int vflag; 109 110static volatile sig_atomic_t exit_requested; 111static power_src_t acline_status; 112static 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 121static int apm_fd = -1; 122#endif 123static int devd_pipe = -1; 124 125#define DEVD_RETRY_INTERVAL 60 /* seconds */ 126static struct timeval tried_devd; 127 128static int 129read_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 176static int 177read_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 221static int 222get_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 236static int 237set_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 248static int 249get_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 */ 265static void 266acline_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 292static void 293acline_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 356static int 357devd_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 389static void 390devd_close(void) 391{ 392 393 close(devd_pipe); 394 devd_pipe = -1; 395} 396 397static void 398parse_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 413static void 414handle_sigs(int __unused sig) 415{ 416 417 exit_requested = 1; 418} 419 420static void 421usage(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 429int 430main(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