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