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