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