2 * Copyright (c) 2004 Colin Percival
3 * Copyright (c) 2005 Nate Lawson
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted providing that the following conditions
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.
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.
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
40 #include <machine/apm_bios.h>
43 #include <sys/ioctl.h>
44 #include <sys/sysctl.h>
45 #include <sys/resource.h>
47 #define DEFAULT_ACTIVE_PERCENT 65
48 #define DEFAULT_IDLE_PERCENT 90
49 #define DEFAULT_POLL_INTERVAL 500 /* Poll interval in milliseconds */
63 const char *modes[] = {
69 #define ACPIAC "hw.acpi.acline"
70 #define APMDEV "/dev/apm"
72 static int read_usage_times(long *idle, long *total);
73 static int read_freqs(int *numfreqs, int **freqs, int **power);
74 static int set_freq(int freq);
75 static void acline_init(void);
76 static int acline_read(void);
77 static void handle_sigs(int sig);
78 static void parse_mode(char *arg, int *mode, int ch);
79 static void usage(void);
81 /* Sysctl data structures. */
82 static int cp_time_mib[2];
83 static int freq_mib[4];
84 static int levels_mib[4];
85 static int acline_mib[3];
88 static int cpu_running_mark;
89 static int cpu_idle_mark;
93 static int exit_requested;
96 read_usage_times(long *idle, long *total)
98 static long idle_old, total_old;
99 long cp_time[CPUSTATES], i, total_new;
103 cp_time_len = sizeof(cp_time);
104 error = sysctl(cp_time_mib, 2, cp_time, &cp_time_len, NULL, 0);
107 for (total_new = 0, i = 0; i < CPUSTATES; i++)
108 total_new += cp_time[i];
111 *idle = cp_time[CP_IDLE] - idle_old;
113 *total = total_new - total_old;
115 idle_old = cp_time[CP_IDLE];
116 total_old = total_new;
122 read_freqs(int *numfreqs, int **freqs, int **power)
124 char *freqstr, *p, *q;
128 if (sysctl(levels_mib, 4, NULL, &len, NULL, 0))
130 if ((freqstr = malloc(len)) == NULL)
132 if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0))
136 for (p = freqstr; *p != '\0'; p++)
140 if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) {
144 if ((*power = malloc(*numfreqs * sizeof(int))) == NULL) {
149 for (i = 0, p = freqstr; i < *numfreqs; i++) {
153 if (sscanf(p, "%d/%d", &(*freqs)[i], &(*power)[i]) != 2) {
170 if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq)))
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.
187 len = sizeof(acline);
188 if (sysctlbyname(ACPIAC, &acline, &len, NULL, 0) == 0) {
190 if (sysctlnametomib(ACPIAC, acline_mib, &len))
191 err(1, "lookup acline");
193 apm_fd = open(APMDEV, O_RDONLY);
196 "cannot read AC line status, using default settings");
203 int acline = SRC_UNKNOWN;
207 struct apm_info info;
209 if (apm_fd != -1 && ioctl(apm_fd, APMIO_GETINFO, &info) == 0)
210 acline = info.ai_acline ? SRC_AC : SRC_BATTERY;
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;
223 parse_mode(char *arg, int *mode, int ch)
226 if (strcmp(arg, "min") == 0)
228 else if (strcmp(arg, "max") == 0)
230 else if (strcmp(arg, "adaptive") == 0)
231 *mode = MODE_ADAPTIVE;
233 errx(1, "bad option: -%c %s", (char)ch, optarg);
237 handle_sigs(int __unused sig)
247 "usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-n mode] [-p ival] [-r %%]\n");
252 main(int argc, char * argv[])
255 int curfreq, *freqs, i, *mwatts, numfreqs;
256 int ch, mode_ac, mode_battery, mode_none, acline, mode, vflag;
257 uint64_t mjoules_used;
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;
269 while ((ch = getopt(argc, argv, "a:b:i:n:p:r:v")) != EOF)
272 parse_mode(optarg, &mode_ac, ch);
275 parse_mode(optarg, &mode_battery, ch);
278 cpu_idle_mark = atoi(optarg);
279 if (cpu_idle_mark < 0 || cpu_idle_mark > 100) {
280 warnx("%d is not a valid percent",
286 parse_mode(optarg, &mode_none, ch);
289 poll_ival = atoi(optarg);
291 warnx("poll interval is in units of ms");
296 cpu_running_mark = atoi(optarg);
297 if (cpu_running_mark < 0 || cpu_running_mark > 100) {
298 warnx("%d is not a valid percent",
310 /* Poll interval is in units of ms. */
313 /* Look up various sysctl MIBs. */
315 if (sysctlnametomib("kern.cp_time", cp_time_mib, &len))
316 err(1, "lookup kern.cp_time");
318 if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len))
319 err(1, "lookup freq");
321 if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len))
322 err(1, "lookup freq_levels");
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");
330 /* Decide whether to use ACPI or APM to read the AC line status. */
333 /* Run in the background unless in verbose mode. */
336 signal(SIGINT, handle_sigs);
337 signal(SIGTERM, handle_sigs);
341 /* Check status every few milliseconds. */
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);
353 /* Read the current AC status and record the mode. */
354 acline = acline_read();
366 errx(1, "invalid AC line status %d", acline);
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");
375 for (i = 0; i < numfreqs; i++) {
376 if (freqs[i] == curfreq)
380 /* Keep a sum of all power actually used. */
381 if (i < numfreqs && mwatts[i] != -1)
383 (mwatts[i] * (poll_ival / 1000)) / 1000;
386 /* Always switch to the lowest frequency in min mode. */
387 if (mode == MODE_MIN) {
388 if (curfreq != freqs[numfreqs - 1]) {
390 printf("now operating on %s power; "
391 "changing frequency to %d MHz\n",
392 modes[acline], freqs[numfreqs - 1]);
394 if (set_freq(freqs[numfreqs - 1]))
395 err(1, "error setting CPU freq %d",
396 freqs[numfreqs - 1]);
401 /* Always switch to the highest frequency in max mode. */
402 if (mode == MODE_MAX) {
403 if (curfreq != freqs[0]) {
405 printf("now operating on %s power; "
406 "changing frequency to %d MHz\n",
407 modes[acline], freqs[0]);
409 if (set_freq(freqs[0]))
410 err(1, "error setting CPU freq %d",
416 /* Adaptive mode; get the current CPU usage times. */
417 if (read_usage_times(&idle, &total))
418 err(1, "read_usage_times");
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).
426 if (idle < (total * cpu_running_mark) / 100 &&
427 curfreq < freqs[0]) {
429 printf("idle time < %d%%, increasing clock"
430 " speed from %d MHz to %d MHz\n",
431 cpu_running_mark, curfreq, freqs[0]);
433 if (set_freq(freqs[0]))
434 err(1, "error setting CPU frequency %d",
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)
443 printf("idle time > %d%%, decreasing clock"
444 " speed from %d MHz to %d MHz\n",
445 cpu_idle_mark, curfreq, freqs[i]);
447 if (set_freq(freqs[i]))
448 err(1, "error setting CPU frequency %d",