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$");
31 #include <sys/param.h>
32 #include <sys/ioctl.h>
33 #include <sys/sysctl.h>
34 #include <sys/resource.h>
35 #include <sys/socket.h>
50 #include <machine/apm_bios.h>
53 #define DEFAULT_ACTIVE_PERCENT 65
54 #define DEFAULT_IDLE_PERCENT 90
55 #define DEFAULT_POLL_INTERVAL 500 /* Poll interval in milliseconds */
69 const char *modes[] = {
75 #define ACPIAC "hw.acpi.acline"
76 #define APMDEV "/dev/apm"
77 #define DEVDPIPE "/var/run/devd.pipe"
78 #define DEVCTL_MAXBUF 1024
80 static int read_usage_times(long *idle, long *total);
81 static int read_freqs(int *numfreqs, int **freqs, int **power);
82 static int set_freq(int freq);
83 static void acline_init(void);
84 static void acline_read(void);
85 static int devd_init(void);
86 static void devd_close(void);
87 static void handle_sigs(int sig);
88 static void parse_mode(char *arg, int *mode, int ch);
89 static void usage(void);
91 /* Sysctl data structures. */
92 static int cp_time_mib[2];
93 static int freq_mib[4];
94 static int levels_mib[4];
95 static int acline_mib[3];
98 static int cpu_running_mark;
99 static int cpu_idle_mark;
100 static int poll_ival;
103 static volatile sig_atomic_t exit_requested;
104 static power_src_t acline_status;
114 static int apm_fd = -1;
116 static int devd_pipe = -1;
118 #define DEVD_RETRY_INTERVAL 60 /* seconds */
119 static struct timeval tried_devd;
122 read_usage_times(long *idle, long *total)
124 static long idle_old, total_old;
125 long cp_time[CPUSTATES], i, total_new;
129 cp_time_len = sizeof(cp_time);
130 error = sysctl(cp_time_mib, 2, cp_time, &cp_time_len, NULL, 0);
133 for (total_new = 0, i = 0; i < CPUSTATES; i++)
134 total_new += cp_time[i];
137 *idle = cp_time[CP_IDLE] - idle_old;
139 *total = total_new - total_old;
141 idle_old = cp_time[CP_IDLE];
142 total_old = total_new;
148 read_freqs(int *numfreqs, int **freqs, int **power)
150 char *freqstr, *p, *q;
154 if (sysctl(levels_mib, 4, NULL, &len, NULL, 0))
156 if ((freqstr = malloc(len)) == NULL)
158 if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0))
162 for (p = freqstr; *p != '\0'; p++)
166 if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) {
170 if ((*power = malloc(*numfreqs * sizeof(int))) == NULL) {
175 for (i = 0, p = freqstr; i < *numfreqs; i++) {
179 if (sscanf(p, "%d/%d", &(*freqs)[i], &(*power)[i]) != 2) {
196 if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq))) {
205 * Try to use ACPI to find the AC line status. If this fails, fall back
206 * to APM. If nothing succeeds, we'll just run in default mode.
214 if (sysctlnametomib(ACPIAC, acline_mib, &len) == 0) {
215 acline_mode = ac_acpi_sysctl;
217 warnx("using sysctl for AC line status");
219 } else if ((apm_fd = open(APMDEV, O_RDONLY)) >= 0) {
221 warnx("using APM for AC line status");
222 acline_mode = ac_apm;
225 warnx("unable to determine AC line status");
226 acline_mode = ac_none;
233 if (acline_mode == ac_acpi_devd) {
234 char buf[DEVCTL_MAXBUF], *ptr;
238 rlen = read(devd_pipe, buf, sizeof(buf));
239 if (rlen == 0 || (rlen < 0 && errno != EWOULDBLOCK)) {
241 warnx("lost devd connection, switching to sysctl");
243 acline_mode = ac_acpi_sysctl;
247 (ptr = strstr(buf, "system=ACPI")) != NULL &&
248 (ptr = strstr(ptr, "subsystem=ACAD")) != NULL &&
249 (ptr = strstr(ptr, "notify=")) != NULL &&
250 sscanf(ptr, "notify=%x", ¬ify) == 1)
251 acline_status = (notify ? SRC_AC : SRC_BATTERY);
253 if (acline_mode == ac_acpi_sysctl) {
257 len = sizeof(acline);
258 if (sysctl(acline_mib, 3, &acline, &len, NULL, 0) == 0)
259 acline_status = (acline ? SRC_AC : SRC_BATTERY);
261 acline_status = SRC_UNKNOWN;
264 if (acline_mode == ac_apm) {
265 struct apm_info info;
267 if (ioctl(apm_fd, APMIO_GETINFO, &info) == 0) {
268 acline_status = (info.ai_acline ? SRC_AC : SRC_BATTERY);
272 acline_mode = ac_none;
273 acline_status = SRC_UNKNOWN;
277 /* try to (re)connect to devd */
278 if (acline_mode == ac_acpi_sysctl) {
281 gettimeofday(&now, NULL);
282 if (now.tv_sec > tried_devd.tv_sec + DEVD_RETRY_INTERVAL) {
283 if (devd_init() >= 0) {
285 warnx("using devd for AC line status");
286 acline_mode = ac_acpi_devd;
296 struct sockaddr_un devd_addr;
298 bzero(&devd_addr, sizeof(devd_addr));
299 if ((devd_pipe = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) {
301 warn("%s(): socket()", __func__);
305 devd_addr.sun_family = PF_LOCAL;
306 strlcpy(devd_addr.sun_path, DEVDPIPE, sizeof(devd_addr.sun_path));
307 if (connect(devd_pipe, (struct sockaddr *)&devd_addr,
308 sizeof(devd_addr)) == -1) {
310 warn("%s(): connect()", __func__);
316 if (fcntl(devd_pipe, F_SETFL, O_NONBLOCK) == -1) {
318 warn("%s(): fcntl()", __func__);
335 parse_mode(char *arg, int *mode, int ch)
338 if (strcmp(arg, "minimum") == 0 || strcmp(arg, "min") == 0)
340 else if (strcmp(arg, "maximum") == 0 || strcmp(arg, "max") == 0)
342 else if (strcmp(arg, "adaptive") == 0)
343 *mode = MODE_ADAPTIVE;
345 errx(1, "bad option: -%c %s", (char)ch, optarg);
349 handle_sigs(int __unused sig)
360 "usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-n mode] [-p ival] [-r %%] [-P pidfile]\n");
365 main(int argc, char * argv[])
367 struct timeval timeout;
370 struct pidfh *pfh = NULL;
371 const char *pidfile = NULL;
373 int curfreq, *freqs, i, *mwatts, numfreqs;
374 int ch, mode, mode_ac, mode_battery, mode_none;
375 uint64_t mjoules_used;
378 /* Default mode for all AC states is adaptive. */
379 mode_ac = mode_battery = mode_none = MODE_ADAPTIVE;
380 cpu_running_mark = DEFAULT_ACTIVE_PERCENT;
381 cpu_idle_mark = DEFAULT_IDLE_PERCENT;
382 poll_ival = DEFAULT_POLL_INTERVAL;
386 /* User must be root to control frequencies. */
388 errx(1, "must be root to run");
390 while ((ch = getopt(argc, argv, "a:b:i:n:p:P:r:v")) != EOF)
393 parse_mode(optarg, &mode_ac, ch);
396 parse_mode(optarg, &mode_battery, ch);
399 cpu_idle_mark = atoi(optarg);
400 if (cpu_idle_mark < 0 || cpu_idle_mark > 100) {
401 warnx("%d is not a valid percent",
407 parse_mode(optarg, &mode_none, ch);
410 poll_ival = atoi(optarg);
412 warnx("poll interval is in units of ms");
420 cpu_running_mark = atoi(optarg);
421 if (cpu_running_mark < 0 || cpu_running_mark > 100) {
422 warnx("%d is not a valid percent",
436 /* Poll interval is in units of ms. */
439 /* Look up various sysctl MIBs. */
441 if (sysctlnametomib("kern.cp_time", cp_time_mib, &len))
442 err(1, "lookup kern.cp_time");
444 if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len))
445 err(1, "lookup freq");
447 if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len))
448 err(1, "lookup freq_levels");
450 /* Check if we can read the idle time and supported freqs. */
451 if (read_usage_times(NULL, NULL))
452 err(1, "read_usage_times");
453 if (read_freqs(&numfreqs, &freqs, &mwatts))
454 err(1, "error reading supported CPU frequencies");
456 /* Run in the background unless in verbose mode. */
460 pfh = pidfile_open(pidfile, 0600, &otherpid);
462 if (errno == EEXIST) {
463 errx(1, "powerd already running, pid: %d",
466 warn("cannot open pid file");
468 if (daemon(0, 0) != 0) {
469 warn("cannot enter daemon mode, exiting");
477 /* Decide whether to use ACPI or APM to read the AC line status. */
481 * Exit cleanly on signals.
483 signal(SIGINT, handle_sigs);
484 signal(SIGTERM, handle_sigs);
489 if (devd_pipe >= 0) {
490 FD_SET(devd_pipe, &fdset);
491 nfds = devd_pipe + 1;
495 timeout.tv_sec = poll_ival / 1000000;
496 timeout.tv_usec = poll_ival % 1000000;
497 select(nfds, &fdset, NULL, &fdset, &timeout);
499 /* If the user requested we quit, print some statistics. */
500 if (exit_requested) {
501 if (vflag && mjoules_used != 0)
502 printf("total joules used: %u.%03u\n",
503 (u_int)(mjoules_used / 1000),
504 (int)mjoules_used % 1000);
508 /* Read the current AC status and record the mode. */
510 switch (acline_status) {
521 errx(1, "invalid AC line status %d", acline_status);
524 /* Read the current frequency. */
525 len = sizeof(curfreq);
526 if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0) != 0) {
528 warn("error reading current CPU frequency");
533 for (i = 0; i < numfreqs; i++) {
534 if (freqs[i] == curfreq)
538 /* Keep a sum of all power actually used. */
539 if (i < numfreqs && mwatts[i] != -1)
541 (mwatts[i] * (poll_ival / 1000)) / 1000;
544 /* Always switch to the lowest frequency in min mode. */
545 if (mode == MODE_MIN) {
546 if (curfreq != freqs[numfreqs - 1]) {
548 printf("now operating on %s power; "
549 "changing frequency to %d MHz\n",
550 modes[acline_status],
551 freqs[numfreqs - 1]);
553 if (set_freq(freqs[numfreqs - 1]) != 0) {
554 warn("error setting CPU freq %d",
555 freqs[numfreqs - 1]);
562 /* Always switch to the highest frequency in max mode. */
563 if (mode == MODE_MAX) {
564 if (curfreq != freqs[0]) {
566 printf("now operating on %s power; "
567 "changing frequency to %d MHz\n",
568 modes[acline_status],
571 if (set_freq(freqs[0]) != 0) {
572 warn("error setting CPU freq %d",
580 /* Adaptive mode; get the current CPU usage times. */
581 if (read_usage_times(&idle, &total)) {
583 warn("read_usage_times() failed");
588 * If we're idle less than the active mark, bump up two levels.
589 * If we're idle more than the idle mark, drop down one level.
591 for (i = 0; i < numfreqs - 1; i++) {
592 if (freqs[i] == curfreq)
595 if (idle < (total * cpu_running_mark) / 100 &&
596 curfreq < freqs[0]) {
601 printf("idle time < %d%%, increasing clock"
602 " speed from %d MHz to %d MHz\n",
603 cpu_running_mark, curfreq, freqs[i]);
605 if (set_freq(freqs[i]))
606 err(1, "error setting CPU frequency %d",
608 } else if (idle > (total * cpu_idle_mark) / 100 &&
609 curfreq > freqs[numfreqs - 1]) {
612 printf("idle time > %d%%, decreasing clock"
613 " speed from %d MHz to %d MHz\n",
614 cpu_idle_mark, curfreq, freqs[i]);
616 if (set_freq(freqs[i]) != 0)
617 warn("error setting CPU frequency %d",