powerd.c revision 155810
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 155810 2006-02-18 16:01:07Z des $"); 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 57typedef enum { 58 MODE_MIN, 59 MODE_ADAPTIVE, 60 MODE_MAX, 61} modes_t; 62 63typedef enum { 64 SRC_AC, 65 SRC_BATTERY, 66 SRC_UNKNOWN, 67} power_src_t; 68 69const 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 80static int read_usage_times(long *idle, long *total); 81static int read_freqs(int *numfreqs, int **freqs, int **power); 82static int set_freq(int freq); 83static void acline_init(void); 84static void acline_read(void); 85static int devd_init(void); 86static void devd_close(void); 87static void handle_sigs(int sig); 88static void parse_mode(char *arg, int *mode, int ch); 89static void usage(void); 90 91/* Sysctl data structures. */ 92static int cp_time_mib[2]; 93static int freq_mib[4]; 94static int levels_mib[4]; 95static int acline_mib[3]; 96 97/* Configuration */ 98static int cpu_running_mark; 99static int cpu_idle_mark; 100static int poll_ival; 101static int vflag; 102 103static volatile sig_atomic_t exit_requested; 104static power_src_t acline_status; 105static 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 114static int apm_fd = -1; 115#endif 116static int devd_pipe = -1; 117 118#define DEVD_RETRY_INTERVAL 60 /* seconds */ 119static struct timeval tried_devd; 120 121static int 122read_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 147static int 148read_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 192static int 193set_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 */ 208static void 209acline_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 230static void 231acline_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 293static int 294devd_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 326static void 327devd_close(void) 328{ 329 330 close(devd_pipe); 331 devd_pipe = -1; 332} 333 334static void 335parse_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 348static void 349handle_sigs(int __unused sig) 350{ 351 352 exit_requested = 1; 353} 354 355static void 356usage(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 364int 365main(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 /* Make sure the cpufreq module is loaded */ 437 if (!kld_isloaded("cpu/ichss") && kld_load("cpufreq") == -1) 438 err(1, "failed to load cpufreq module"); 439 440 /* Poll interval is in units of ms. */ 441 poll_ival *= 1000; 442 443 /* Look up various sysctl MIBs. */ 444 len = 2; 445 if (sysctlnametomib("kern.cp_time", cp_time_mib, &len)) 446 err(1, "lookup kern.cp_time"); 447 len = 4; 448 if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len)) 449 err(1, "lookup freq"); 450 len = 4; 451 if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len)) 452 err(1, "lookup freq_levels"); 453 454 /* Check if we can read the idle time and supported freqs. */ 455 if (read_usage_times(NULL, NULL)) 456 err(1, "read_usage_times"); 457 if (read_freqs(&numfreqs, &freqs, &mwatts)) 458 err(1, "error reading supported CPU frequencies"); 459 460 /* Run in the background unless in verbose mode. */ 461 if (!vflag) { 462 pid_t otherpid; 463 464 pfh = pidfile_open(pidfile, 0600, &otherpid); 465 if (pfh == NULL) { 466 if (errno == EEXIST) { 467 errx(1, "powerd already running, pid: %d", 468 otherpid); 469 } 470 warn("cannot open pid file"); 471 } 472 if (daemon(0, 0) != 0) { 473 warn("cannot enter daemon mode, exiting"); 474 pidfile_remove(pfh); 475 exit(EXIT_FAILURE); 476 477 } 478 pidfile_write(pfh); 479 } 480 481 /* Decide whether to use ACPI or APM to read the AC line status. */ 482 acline_init(); 483 484 /* 485 * Exit cleanly on signals. 486 */ 487 signal(SIGINT, handle_sigs); 488 signal(SIGTERM, handle_sigs); 489 490 /* Main loop. */ 491 for (;;) { 492 FD_ZERO(&fdset); 493 if (devd_pipe >= 0) { 494 FD_SET(devd_pipe, &fdset); 495 nfds = devd_pipe + 1; 496 } else { 497 nfds = 0; 498 } 499 timeout.tv_sec = poll_ival / 1000000; 500 timeout.tv_usec = poll_ival % 1000000; 501 select(nfds, &fdset, NULL, &fdset, &timeout); 502 503 /* If the user requested we quit, print some statistics. */ 504 if (exit_requested) { 505 if (vflag && mjoules_used != 0) 506 printf("total joules used: %u.%03u\n", 507 (u_int)(mjoules_used / 1000), 508 (int)mjoules_used % 1000); 509 break; 510 } 511 512 /* Read the current AC status and record the mode. */ 513 acline_read(); 514 switch (acline_status) { 515 case SRC_AC: 516 mode = mode_ac; 517 break; 518 case SRC_BATTERY: 519 mode = mode_battery; 520 break; 521 case SRC_UNKNOWN: 522 mode = mode_none; 523 break; 524 default: 525 errx(1, "invalid AC line status %d", acline_status); 526 } 527 528 /* Read the current frequency. */ 529 len = sizeof(curfreq); 530 if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0) != 0) { 531 if (vflag) 532 warn("error reading current CPU frequency"); 533 continue; 534 } 535 536 if (vflag) { 537 for (i = 0; i < numfreqs; i++) { 538 if (freqs[i] == curfreq) 539 break; 540 } 541 542 /* Keep a sum of all power actually used. */ 543 if (i < numfreqs && mwatts[i] != -1) 544 mjoules_used += 545 (mwatts[i] * (poll_ival / 1000)) / 1000; 546 } 547 548 /* Always switch to the lowest frequency in min mode. */ 549 if (mode == MODE_MIN) { 550 if (curfreq != freqs[numfreqs - 1]) { 551 if (vflag) { 552 printf("now operating on %s power; " 553 "changing frequency to %d MHz\n", 554 modes[acline_status], 555 freqs[numfreqs - 1]); 556 } 557 if (set_freq(freqs[numfreqs - 1]) != 0) { 558 warn("error setting CPU freq %d", 559 freqs[numfreqs - 1]); 560 continue; 561 } 562 } 563 continue; 564 } 565 566 /* Always switch to the highest frequency in max mode. */ 567 if (mode == MODE_MAX) { 568 if (curfreq != freqs[0]) { 569 if (vflag) { 570 printf("now operating on %s power; " 571 "changing frequency to %d MHz\n", 572 modes[acline_status], 573 freqs[0]); 574 } 575 if (set_freq(freqs[0]) != 0) { 576 warn("error setting CPU freq %d", 577 freqs[0]); 578 continue; 579 } 580 } 581 continue; 582 } 583 584 /* Adaptive mode; get the current CPU usage times. */ 585 if (read_usage_times(&idle, &total)) { 586 if (vflag) 587 warn("read_usage_times() failed"); 588 continue; 589 } 590 591 /* 592 * If we're idle less than the active mark, bump up two levels. 593 * If we're idle more than the idle mark, drop down one level. 594 */ 595 for (i = 0; i < numfreqs - 1; i++) { 596 if (freqs[i] == curfreq) 597 break; 598 } 599 if (idle < (total * cpu_running_mark) / 100 && 600 curfreq < freqs[0]) { 601 i -= 2; 602 if (i < 0) 603 i = 0; 604 if (vflag) { 605 printf("idle time < %d%%, increasing clock" 606 " speed from %d MHz to %d MHz\n", 607 cpu_running_mark, curfreq, freqs[i]); 608 } 609 if (set_freq(freqs[i])) 610 err(1, "error setting CPU frequency %d", 611 freqs[i]); 612 } else if (idle > (total * cpu_idle_mark) / 100 && 613 curfreq > freqs[numfreqs - 1]) { 614 i++; 615 if (vflag) { 616 printf("idle time > %d%%, decreasing clock" 617 " speed from %d MHz to %d MHz\n", 618 cpu_idle_mark, curfreq, freqs[i]); 619 } 620 if (set_freq(freqs[i]) != 0) 621 warn("error setting CPU frequency %d", 622 freqs[i]); 623 } 624 } 625 free(freqs); 626 free(mwatts); 627 devd_close(); 628 if (!vflag) 629 pidfile_remove(pfh); 630 631 exit(0); 632} 633