powerd.c revision 148139
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 148139 2005-07-18 20:15:31Z ume $"); 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 while ((ch = getopt(argc, argv, "a:b:i:n:p:r:v")) != EOF) 273 switch (ch) { 274 case 'a': 275 parse_mode(optarg, &mode_ac, ch); 276 break; 277 case 'b': 278 parse_mode(optarg, &mode_battery, ch); 279 break; 280 case 'i': 281 cpu_idle_mark = atoi(optarg); 282 if (cpu_idle_mark < 0 || cpu_idle_mark > 100) { 283 warnx("%d is not a valid percent", 284 cpu_idle_mark); 285 usage(); 286 } 287 break; 288 case 'n': 289 parse_mode(optarg, &mode_none, ch); 290 break; 291 case 'p': 292 poll_ival = atoi(optarg); 293 if (poll_ival < 5) { 294 warnx("poll interval is in units of ms"); 295 usage(); 296 } 297 break; 298 case 'r': 299 cpu_running_mark = atoi(optarg); 300 if (cpu_running_mark < 0 || cpu_running_mark > 100) { 301 warnx("%d is not a valid percent", 302 cpu_running_mark); 303 usage(); 304 } 305 break; 306 case 'v': 307 vflag = 1; 308 break; 309 default: 310 usage(); 311 } 312 313 /* Poll interval is in units of ms. */ 314 poll_ival *= 1000; 315 316 /* Look up various sysctl MIBs. */ 317 len = 2; 318 if (sysctlnametomib("kern.cp_time", cp_time_mib, &len)) 319 err(1, "lookup kern.cp_time"); 320 len = 4; 321 if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len)) 322 err(1, "lookup freq"); 323 len = 4; 324 if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len)) 325 err(1, "lookup freq_levels"); 326 327 /* Check if we can read the idle time and supported freqs. */ 328 if (read_usage_times(NULL, NULL)) 329 err(1, "read_usage_times"); 330 if (read_freqs(&numfreqs, &freqs, &mwatts)) 331 err(1, "error reading supported CPU frequencies"); 332 333 /* Decide whether to use ACPI or APM to read the AC line status. */ 334 acline_init(); 335 336 /* Run in the background unless in verbose mode. */ 337 if (!vflag) 338 daemon(0, 0); 339 signal(SIGINT, handle_sigs); 340 signal(SIGTERM, handle_sigs); 341 342 /* Main loop. */ 343 for (;;) { 344 /* Check status every few milliseconds. */ 345 usleep(poll_ival); 346 347 /* If the user requested we quit, print some statistics. */ 348 if (exit_requested) { 349 if (vflag && mjoules_used != 0) 350 printf("total joules used: %u.%03u\n", 351 (u_int)(mjoules_used / 1000), 352 (int)mjoules_used % 1000); 353 break; 354 } 355 356 /* Read the current AC status and record the mode. */ 357 acline = acline_read(); 358 switch (acline) { 359 case SRC_AC: 360 mode = mode_ac; 361 break; 362 case SRC_BATTERY: 363 mode = mode_battery; 364 break; 365 case SRC_UNKNOWN: 366 mode = mode_none; 367 break; 368 default: 369 errx(1, "invalid AC line status %d", acline); 370 } 371 372 /* Read the current frequency. */ 373 len = sizeof(curfreq); 374 if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0)) 375 err(1, "error reading current CPU frequency"); 376 377 if (vflag) { 378 for (i = 0; i < numfreqs; i++) { 379 if (freqs[i] == curfreq) 380 break; 381 } 382 383 /* Keep a sum of all power actually used. */ 384 if (i < numfreqs && mwatts[i] != -1) 385 mjoules_used += 386 (mwatts[i] * (poll_ival / 1000)) / 1000; 387 } 388 389 /* Always switch to the lowest frequency in min mode. */ 390 if (mode == MODE_MIN) { 391 if (curfreq != freqs[numfreqs - 1]) { 392 if (vflag) { 393 printf("now operating on %s power; " 394 "changing frequency to %d MHz\n", 395 modes[acline], freqs[numfreqs - 1]); 396 } 397 if (set_freq(freqs[numfreqs - 1])) 398 err(1, "error setting CPU freq %d", 399 freqs[numfreqs - 1]); 400 } 401 continue; 402 } 403 404 /* Always switch to the highest frequency in max mode. */ 405 if (mode == MODE_MAX) { 406 if (curfreq != freqs[0]) { 407 if (vflag) { 408 printf("now operating on %s power; " 409 "changing frequency to %d MHz\n", 410 modes[acline], freqs[0]); 411 } 412 if (set_freq(freqs[0])) 413 err(1, "error setting CPU freq %d", 414 freqs[0]); 415 } 416 continue; 417 } 418 419 /* Adaptive mode; get the current CPU usage times. */ 420 if (read_usage_times(&idle, &total)) 421 err(1, "read_usage_times"); 422 423 /* 424 * If we're idle less than the active mark, jump the CPU to 425 * its fastest speed if we're not there yet. If we're idle 426 * more than the idle mark, drop down to the first setting 427 * that is half the current speed (exponential backoff). 428 */ 429 if (idle < (total * cpu_running_mark) / 100 && 430 curfreq < freqs[0]) { 431 if (vflag) { 432 printf("idle time < %d%%, increasing clock" 433 " speed from %d MHz to %d MHz\n", 434 cpu_running_mark, curfreq, freqs[0]); 435 } 436 if (set_freq(freqs[0])) 437 err(1, "error setting CPU frequency %d", 438 freqs[0]); 439 } else if (idle > (total * cpu_idle_mark) / 100 && 440 curfreq > freqs[numfreqs - 1]) { 441 for (i = 0; i < numfreqs - 1; i++) { 442 if (freqs[i] <= curfreq / 2) 443 break; 444 } 445 if (vflag) { 446 printf("idle time > %d%%, decreasing clock" 447 " speed from %d MHz to %d MHz\n", 448 cpu_idle_mark, curfreq, freqs[i]); 449 } 450 if (set_freq(freqs[i])) 451 err(1, "error setting CPU frequency %d", 452 freqs[i]); 453 } 454 } 455 free(freqs); 456 free(mwatts); 457 458 exit(0); 459} 460