powerd.c revision 149428
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 149428 2005-08-24 17:32:41Z pjd $"); 30 31#include <sys/param.h> 32#include <sys/ioctl.h> 33#include <sys/sysctl.h> 34#include <sys/resource.h> 35 36#include <err.h> 37#include <errno.h> 38#include <fcntl.h> 39#include <libutil.h> 40#include <signal.h> 41#include <stdio.h> 42#include <stdlib.h> 43#include <string.h> 44#include <unistd.h> 45 46#ifdef __i386__ 47#include <machine/apm_bios.h> 48#endif 49 50#define DEFAULT_ACTIVE_PERCENT 65 51#define DEFAULT_IDLE_PERCENT 90 52#define DEFAULT_POLL_INTERVAL 500 /* Poll interval in milliseconds */ 53 54enum modes_t { 55 MODE_MIN, 56 MODE_ADAPTIVE, 57 MODE_MAX, 58}; 59 60enum power_src_t { 61 SRC_AC, 62 SRC_BATTERY, 63 SRC_UNKNOWN, 64}; 65 66const char *modes[] = { 67 "AC", 68 "battery", 69 "unknown" 70}; 71 72#define ACPIAC "hw.acpi.acline" 73#define APMDEV "/dev/apm" 74 75static int read_usage_times(long *idle, long *total); 76static int read_freqs(int *numfreqs, int **freqs, int **power); 77static int set_freq(int freq); 78static void acline_init(void); 79static int acline_read(void); 80static void handle_sigs(int sig); 81static void parse_mode(char *arg, int *mode, int ch); 82static void usage(void); 83 84/* Sysctl data structures. */ 85static int cp_time_mib[2]; 86static int freq_mib[4]; 87static int levels_mib[4]; 88static int acline_mib[3]; 89 90/* Configuration */ 91static int cpu_running_mark; 92static int cpu_idle_mark; 93static int poll_ival; 94 95static int apm_fd; 96static int exit_requested; 97 98static int 99read_usage_times(long *idle, long *total) 100{ 101 static long idle_old, total_old; 102 long cp_time[CPUSTATES], i, total_new; 103 size_t cp_time_len; 104 int error; 105 106 cp_time_len = sizeof(cp_time); 107 error = sysctl(cp_time_mib, 2, cp_time, &cp_time_len, NULL, 0); 108 if (error) 109 return (error); 110 for (total_new = 0, i = 0; i < CPUSTATES; i++) 111 total_new += cp_time[i]; 112 113 if (idle) 114 *idle = cp_time[CP_IDLE] - idle_old; 115 if (total) 116 *total = total_new - total_old; 117 118 idle_old = cp_time[CP_IDLE]; 119 total_old = total_new; 120 121 return (0); 122} 123 124static int 125read_freqs(int *numfreqs, int **freqs, int **power) 126{ 127 char *freqstr, *p, *q; 128 int i; 129 size_t len = 0; 130 131 if (sysctl(levels_mib, 4, NULL, &len, NULL, 0)) 132 return (-1); 133 if ((freqstr = malloc(len)) == NULL) 134 return (-1); 135 if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0)) 136 return (-1); 137 138 *numfreqs = 1; 139 for (p = freqstr; *p != '\0'; p++) 140 if (*p == ' ') 141 (*numfreqs)++; 142 143 if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) { 144 free(freqstr); 145 return (-1); 146 } 147 if ((*power = malloc(*numfreqs * sizeof(int))) == NULL) { 148 free(freqstr); 149 free(*freqs); 150 return (-1); 151 } 152 for (i = 0, p = freqstr; i < *numfreqs; i++) { 153 q = strchr(p, ' '); 154 if (q != NULL) 155 *q = '\0'; 156 if (sscanf(p, "%d/%d", &(*freqs)[i], &(*power)[i]) != 2) { 157 free(freqstr); 158 free(*freqs); 159 free(*power); 160 return (-1); 161 } 162 p = q + 1; 163 } 164 165 free(freqstr); 166 return (0); 167} 168 169static int 170set_freq(int freq) 171{ 172 173 if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq))) { 174 if (errno != EPERM) 175 return (-1); 176 } 177 178 return (0); 179} 180 181/* 182 * Try to use ACPI to find the AC line status. If this fails, fall back 183 * to APM. If nothing succeeds, we'll just run in default mode. 184 */ 185static void 186acline_init() 187{ 188 int acline; 189 size_t len; 190 191 apm_fd = -1; 192 len = sizeof(acline); 193 if (sysctlbyname(ACPIAC, &acline, &len, NULL, 0) == 0) { 194 len = 3; 195 if (sysctlnametomib(ACPIAC, acline_mib, &len)) 196 err(1, "lookup acline"); 197 } else { 198 apm_fd = open(APMDEV, O_RDONLY); 199 if (apm_fd == -1) 200 warnx( 201 "cannot read AC line status, using default settings"); 202 } 203} 204 205static int 206acline_read() 207{ 208 int acline = SRC_UNKNOWN; 209 size_t len; 210 211#ifdef __i386__ 212 struct apm_info info; 213 214 if (apm_fd != -1 && ioctl(apm_fd, APMIO_GETINFO, &info) == 0) 215 acline = info.ai_acline ? SRC_AC : SRC_BATTERY; 216#endif 217 218 if (acline == SRC_UNKNOWN) { 219 len = sizeof(acline); 220 if (sysctl(acline_mib, 3, &acline, &len, NULL, 0) == 0) 221 acline = acline ? SRC_AC : SRC_BATTERY; 222 } 223 224 return (acline); 225} 226 227static void 228parse_mode(char *arg, int *mode, int ch) 229{ 230 231 if (strcmp(arg, "min") == 0) 232 *mode = MODE_MIN; 233 else if (strcmp(arg, "max") == 0) 234 *mode = MODE_MAX; 235 else if (strcmp(arg, "adaptive") == 0) 236 *mode = MODE_ADAPTIVE; 237 else 238 errx(1, "bad option: -%c %s", (char)ch, optarg); 239} 240 241static void 242handle_sigs(int __unused sig) 243{ 244 exit_requested = 1; 245} 246 247static void 248usage(void) 249{ 250 251 fprintf(stderr, 252"usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-n mode] [-p ival] [-r %%] [-P pidfile]\n"); 253 exit(1); 254} 255 256int 257main(int argc, char * argv[]) 258{ 259 struct pidfh *pfh; 260 const char *pidfile = NULL; 261 long idle, total; 262 int curfreq, *freqs, i, *mwatts, numfreqs; 263 int ch, mode_ac, mode_battery, mode_none, acline, mode, vflag; 264 uint64_t mjoules_used; 265 size_t len; 266 267 /* Default mode for all AC states is adaptive. */ 268 mode_ac = mode_battery = mode_none = MODE_ADAPTIVE; 269 cpu_running_mark = DEFAULT_ACTIVE_PERCENT; 270 cpu_idle_mark = DEFAULT_IDLE_PERCENT; 271 poll_ival = DEFAULT_POLL_INTERVAL; 272 mjoules_used = 0; 273 vflag = 0; 274 apm_fd = -1; 275 276 /* User must be root to control frequencies. */ 277 if (geteuid() != 0) 278 errx(1, "must be root to run"); 279 280 while ((ch = getopt(argc, argv, "a:b:i:n:p:P:r:v")) != EOF) 281 switch (ch) { 282 case 'a': 283 parse_mode(optarg, &mode_ac, ch); 284 break; 285 case 'b': 286 parse_mode(optarg, &mode_battery, ch); 287 break; 288 case 'i': 289 cpu_idle_mark = atoi(optarg); 290 if (cpu_idle_mark < 0 || cpu_idle_mark > 100) { 291 warnx("%d is not a valid percent", 292 cpu_idle_mark); 293 usage(); 294 } 295 break; 296 case 'n': 297 parse_mode(optarg, &mode_none, ch); 298 break; 299 case 'p': 300 poll_ival = atoi(optarg); 301 if (poll_ival < 5) { 302 warnx("poll interval is in units of ms"); 303 usage(); 304 } 305 break; 306 case 'P': 307 pidfile = optarg; 308 break; 309 case 'r': 310 cpu_running_mark = atoi(optarg); 311 if (cpu_running_mark < 0 || cpu_running_mark > 100) { 312 warnx("%d is not a valid percent", 313 cpu_running_mark); 314 usage(); 315 } 316 break; 317 case 'v': 318 vflag = 1; 319 break; 320 default: 321 usage(); 322 } 323 324 /* Poll interval is in units of ms. */ 325 poll_ival *= 1000; 326 327 /* Look up various sysctl MIBs. */ 328 len = 2; 329 if (sysctlnametomib("kern.cp_time", cp_time_mib, &len)) 330 err(1, "lookup kern.cp_time"); 331 len = 4; 332 if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len)) 333 err(1, "lookup freq"); 334 len = 4; 335 if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len)) 336 err(1, "lookup freq_levels"); 337 338 /* Check if we can read the idle time and supported freqs. */ 339 if (read_usage_times(NULL, NULL)) 340 err(1, "read_usage_times"); 341 if (read_freqs(&numfreqs, &freqs, &mwatts)) 342 err(1, "error reading supported CPU frequencies"); 343 344 /* Decide whether to use ACPI or APM to read the AC line status. */ 345 acline_init(); 346 347 /* Run in the background unless in verbose mode. */ 348 if (!vflag) { 349 pid_t otherpid; 350 351 pfh = pidfile_open(pidfile, 0600, &otherpid); 352 if (pfh == NULL) { 353 if (errno == EEXIST) { 354 errx(1, "powerd already running, pid: %d", 355 otherpid); 356 } 357 warn("cannot open pid file"); 358 } 359 daemon(0, 0); 360 pidfile_write(pfh); 361 } 362 signal(SIGINT, handle_sigs); 363 signal(SIGTERM, handle_sigs); 364 365 /* Main loop. */ 366 for (;;) { 367 /* Check status every few milliseconds. */ 368 usleep(poll_ival); 369 370 /* If the user requested we quit, print some statistics. */ 371 if (exit_requested) { 372 if (vflag && mjoules_used != 0) 373 printf("total joules used: %u.%03u\n", 374 (u_int)(mjoules_used / 1000), 375 (int)mjoules_used % 1000); 376 break; 377 } 378 379 /* Read the current AC status and record the mode. */ 380 acline = acline_read(); 381 switch (acline) { 382 case SRC_AC: 383 mode = mode_ac; 384 break; 385 case SRC_BATTERY: 386 mode = mode_battery; 387 break; 388 case SRC_UNKNOWN: 389 mode = mode_none; 390 break; 391 default: 392 errx(1, "invalid AC line status %d", acline); 393 } 394 395 /* Read the current frequency. */ 396 len = sizeof(curfreq); 397 if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0)) 398 err(1, "error reading current CPU frequency"); 399 400 if (vflag) { 401 for (i = 0; i < numfreqs; i++) { 402 if (freqs[i] == curfreq) 403 break; 404 } 405 406 /* Keep a sum of all power actually used. */ 407 if (i < numfreqs && mwatts[i] != -1) 408 mjoules_used += 409 (mwatts[i] * (poll_ival / 1000)) / 1000; 410 } 411 412 /* Always switch to the lowest frequency in min mode. */ 413 if (mode == MODE_MIN) { 414 if (curfreq != freqs[numfreqs - 1]) { 415 if (vflag) { 416 printf("now operating on %s power; " 417 "changing frequency to %d MHz\n", 418 modes[acline], freqs[numfreqs - 1]); 419 } 420 if (set_freq(freqs[numfreqs - 1])) 421 err(1, "error setting CPU freq %d", 422 freqs[numfreqs - 1]); 423 } 424 continue; 425 } 426 427 /* Always switch to the highest frequency in max mode. */ 428 if (mode == MODE_MAX) { 429 if (curfreq != freqs[0]) { 430 if (vflag) { 431 printf("now operating on %s power; " 432 "changing frequency to %d MHz\n", 433 modes[acline], freqs[0]); 434 } 435 if (set_freq(freqs[0])) 436 err(1, "error setting CPU freq %d", 437 freqs[0]); 438 } 439 continue; 440 } 441 442 /* Adaptive mode; get the current CPU usage times. */ 443 if (read_usage_times(&idle, &total)) 444 err(1, "read_usage_times"); 445 446 /* 447 * If we're idle less than the active mark, bump up two levels. 448 * If we're idle more than the idle mark, drop down one level. 449 */ 450 for (i = 0; i < numfreqs - 1; i++) { 451 if (freqs[i] == curfreq) 452 break; 453 } 454 if (idle < (total * cpu_running_mark) / 100 && 455 curfreq < freqs[0]) { 456 i -= 2; 457 if (i < 0) 458 i = 0; 459 if (vflag) { 460 printf("idle time < %d%%, increasing clock" 461 " speed from %d MHz to %d MHz\n", 462 cpu_running_mark, curfreq, freqs[i]); 463 } 464 if (set_freq(freqs[i])) 465 err(1, "error setting CPU frequency %d", 466 freqs[i]); 467 } else if (idle > (total * cpu_idle_mark) / 100 && 468 curfreq > freqs[numfreqs - 1]) { 469 i++; 470 if (vflag) { 471 printf("idle time > %d%%, decreasing clock" 472 " speed from %d MHz to %d MHz\n", 473 cpu_idle_mark, curfreq, freqs[i]); 474 } 475 if (set_freq(freqs[i])) 476 err(1, "error setting CPU frequency %d", 477 freqs[i]); 478 } 479 } 480 free(freqs); 481 free(mwatts); 482 if (!vflag) 483 pidfile_remove(pfh); 484 485 exit(0); 486} 487