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