xref: /freebsd/usr.sbin/powerd/powerd.c (revision 397e83df75e0fcd0d3fcb95ae4d794cb7600fc89)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2004 Colin Percival
5  * Copyright (c) 2005 Nate Lawson
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted providing that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
25  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
26  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <sys/param.h>
31 #include <sys/ioctl.h>
32 #include <sys/sysctl.h>
33 #include <sys/resource.h>
34 #include <sys/socket.h>
35 #include <sys/time.h>
36 #include <sys/un.h>
37 
38 #include <err.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <libutil.h>
42 #include <signal.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <sysexits.h>
47 #include <unistd.h>
48 
49 #ifdef __i386__
50 #define USE_APM
51 #endif
52 
53 #ifdef USE_APM
54 #include <machine/apm_bios.h>
55 #endif
56 
57 #define DEFAULT_ACTIVE_PERCENT	75
58 #define DEFAULT_IDLE_PERCENT	50
59 #define DEFAULT_POLL_INTERVAL	250	/* Poll interval in milliseconds */
60 
61 typedef enum {
62 	MODE_MIN,
63 	MODE_ADAPTIVE,
64 	MODE_HIADAPTIVE,
65 	MODE_MAX,
66 } modes_t;
67 
68 typedef enum {
69 	SRC_AC,
70 	SRC_BATTERY,
71 	SRC_UNKNOWN,
72 } power_src_t;
73 
74 static const char *modes[] = {
75 	"AC",
76 	"battery",
77 	"unknown"
78 };
79 
80 #define ACPIAC		"hw.acpi.acline"
81 #define PMUAC		"dev.pmu.0.acline"
82 #define APMDEV		"/dev/apm"
83 #define DEVDPIPE	"/var/run/devd.pipe"
84 #define DEVCTL_MAXBUF	1024
85 
86 static int	read_usage_times(int *load, int nonice);
87 static int	read_freqs(int *numfreqs, int **freqs, int **power,
88 		    int minfreq, int maxfreq);
89 static int	set_freq(int freq);
90 static void	acline_init(void);
91 static void	acline_read(void);
92 static int	devd_init(void);
93 static void	devd_close(void);
94 static void	handle_sigs(int sig);
95 static void	parse_mode(char *arg, int *mode, int ch);
96 static void	usage(void);
97 
98 /* Sysctl data structures. */
99 static int	cp_times_mib[2];
100 static int	freq_mib[4];
101 static int	levels_mib[4];
102 static int	acline_mib[4];
103 static size_t	acline_mib_len;
104 
105 /* Configuration */
106 static int	cpu_running_mark;
107 static int	cpu_idle_mark;
108 static int	poll_ival;
109 static int	vflag;
110 
111 static volatile sig_atomic_t exit_requested;
112 static power_src_t acline_status;
113 typedef enum {
114 	ac_none,
115 	ac_sysctl,
116 	ac_acpi_devd,
117 #ifdef USE_APM
118 	ac_apm,
119 #endif
120 } acline_mode_t;
121 static acline_mode_t acline_mode;
122 static acline_mode_t acline_mode_user = ac_none;
123 #ifdef USE_APM
124 static int	apm_fd = -1;
125 #endif
126 static int	devd_pipe = -1;
127 
128 #define DEVD_RETRY_INTERVAL 60 /* seconds */
129 static struct timeval tried_devd;
130 
131 /*
132  * This function returns summary load of all CPUs.  It was made so
133  * intentionally to not reduce performance in scenarios when several
134  * threads are processing requests as a pipeline -- running one at
135  * a time on different CPUs and waiting for each other.  If nonice
136  * is nonzero, only user+sys+intr time will be counted as load; any
137  * nice time will be treated as if idle.
138  */
139 static int
140 read_usage_times(int *load, int nonice)
141 {
142 	static long *cp_times = NULL, *cp_times_old = NULL;
143 	static int ncpus = 0;
144 	size_t cp_times_len;
145 	int error, cpu, i, total, excl;
146 
147 	if (cp_times == NULL) {
148 		cp_times_len = 0;
149 		error = sysctl(cp_times_mib, 2, NULL, &cp_times_len, NULL, 0);
150 		if (error)
151 			return (error);
152 		if ((cp_times = malloc(cp_times_len)) == NULL)
153 			return (errno);
154 		if ((cp_times_old = malloc(cp_times_len)) == NULL) {
155 			free(cp_times);
156 			cp_times = NULL;
157 			return (errno);
158 		}
159 		ncpus = cp_times_len / (sizeof(long) * CPUSTATES);
160 	}
161 
162 	cp_times_len = sizeof(long) * CPUSTATES * ncpus;
163 	error = sysctl(cp_times_mib, 2, cp_times, &cp_times_len, NULL, 0);
164 	if (error)
165 		return (error);
166 
167 	if (load) {
168 		*load = 0;
169 		for (cpu = 0; cpu < ncpus; cpu++) {
170 			total = 0;
171 			for (i = 0; i < CPUSTATES; i++) {
172 			    total += cp_times[cpu * CPUSTATES + i] -
173 				cp_times_old[cpu * CPUSTATES + i];
174 			}
175 			if (total == 0)
176 				continue;
177 			excl = cp_times[cpu * CPUSTATES + CP_IDLE] -
178 			    cp_times_old[cpu * CPUSTATES + CP_IDLE];
179 			if (nonice)
180 				excl += cp_times[cpu * CPUSTATES + CP_NICE] -
181 				    cp_times_old[cpu * CPUSTATES + CP_NICE];
182 			*load += 100 - excl * 100 / total;
183 		}
184 	}
185 
186 	memcpy(cp_times_old, cp_times, cp_times_len);
187 
188 	return (0);
189 }
190 
191 static int
192 read_freqs(int *numfreqs, int **freqs, int **power, int minfreq, int maxfreq)
193 {
194 	char *freqstr, *p, *q;
195 	int i, j;
196 	size_t len = 0;
197 
198 	if (sysctl(levels_mib, 4, NULL, &len, NULL, 0))
199 		return (-1);
200 	if ((freqstr = malloc(len)) == NULL)
201 		return (-1);
202 	if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0)) {
203 		free(freqstr);
204 		return (-1);
205 	}
206 
207 	*numfreqs = 1;
208 	for (p = freqstr; *p != '\0'; p++)
209 		if (*p == ' ')
210 			(*numfreqs)++;
211 
212 	if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) {
213 		free(freqstr);
214 		return (-1);
215 	}
216 	if ((*power = malloc(*numfreqs * sizeof(int))) == NULL) {
217 		free(freqstr);
218 		free(*freqs);
219 		return (-1);
220 	}
221 	for (i = 0, j = 0, p = freqstr; i < *numfreqs; i++) {
222 		q = strchr(p, ' ');
223 		if (q != NULL)
224 			*q = '\0';
225 		if (sscanf(p, "%d/%d", &(*freqs)[j], &(*power)[i]) != 2) {
226 			free(freqstr);
227 			free(*freqs);
228 			free(*power);
229 			return (-1);
230 		}
231 		if (((*freqs)[j] >= minfreq || minfreq == -1) &&
232 		    ((*freqs)[j] <= maxfreq || maxfreq == -1))
233 			j++;
234 		p = q + 1;
235 	}
236 
237 	*numfreqs = j;
238 	if ((*freqs = realloc(*freqs, *numfreqs * sizeof(int))) == NULL) {
239 		free(freqstr);
240 		free(*freqs);
241 		free(*power);
242 		return (-1);
243 	}
244 
245 	free(freqstr);
246 	return (0);
247 }
248 
249 static int
250 get_freq(void)
251 {
252 	size_t len;
253 	int curfreq;
254 
255 	len = sizeof(curfreq);
256 	if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0) != 0) {
257 		if (vflag)
258 			warn("error reading current CPU frequency");
259 		curfreq = 0;
260 	}
261 	return (curfreq);
262 }
263 
264 static int
265 set_freq(int freq)
266 {
267 
268 	if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq))) {
269 		if (errno != EPERM)
270 			return (-1);
271 	}
272 
273 	return (0);
274 }
275 
276 static int
277 get_freq_id(int freq, int *freqs, int numfreqs)
278 {
279 	int i = 1;
280 
281 	while (i < numfreqs) {
282 		if (freqs[i] < freq)
283 			break;
284 		i++;
285 	}
286 	return (i - 1);
287 }
288 
289 /*
290  * Try to use ACPI to find the AC line status.  If this fails, fall back
291  * to APM.  If nothing succeeds, we'll just run in default mode.
292  */
293 static void
294 acline_init(void)
295 {
296 	int skip_source_check;
297 
298 	acline_mib_len = 4;
299 	acline_status = SRC_UNKNOWN;
300 	skip_source_check = (acline_mode_user == ac_none ||
301 			     acline_mode_user == ac_acpi_devd);
302 
303 	if ((skip_source_check || acline_mode_user == ac_sysctl) &&
304 	    sysctlnametomib(ACPIAC, acline_mib, &acline_mib_len) == 0) {
305 		acline_mode = ac_sysctl;
306 		if (vflag)
307 			warnx("using sysctl for AC line status");
308 #ifdef __powerpc__
309 	} else if ((skip_source_check || acline_mode_user == ac_sysctl) &&
310 		   sysctlnametomib(PMUAC, acline_mib, &acline_mib_len) == 0) {
311 		acline_mode = ac_sysctl;
312 		if (vflag)
313 			warnx("using sysctl for AC line status");
314 #endif
315 #ifdef USE_APM
316 	} else if ((skip_source_check || acline_mode_user == ac_apm) &&
317 		   (apm_fd = open(APMDEV, O_RDONLY)) >= 0) {
318 		if (vflag)
319 			warnx("using APM for AC line status");
320 		acline_mode = ac_apm;
321 #endif
322 	} else {
323 		warnx("unable to determine AC line status");
324 		acline_mode = ac_none;
325 	}
326 }
327 
328 static void
329 acline_read(void)
330 {
331 	if (acline_mode == ac_acpi_devd) {
332 		char buf[DEVCTL_MAXBUF], *ptr;
333 		ssize_t rlen;
334 		int notify;
335 
336 		rlen = read(devd_pipe, buf, sizeof(buf));
337 		if (rlen == 0 || (rlen < 0 && errno != EWOULDBLOCK)) {
338 			if (vflag)
339 				warnx("lost devd connection, switching to sysctl");
340 			devd_close();
341 			acline_mode = ac_sysctl;
342 			/* FALLTHROUGH */
343 		}
344 		if (rlen > 0 &&
345 		    (ptr = strstr(buf, "system=ACPI")) != NULL &&
346 		    (ptr = strstr(ptr, "subsystem=ACAD")) != NULL &&
347 		    (ptr = strstr(ptr, "notify=")) != NULL &&
348 		    sscanf(ptr, "notify=%x", &notify) == 1)
349 			acline_status = (notify ? SRC_AC : SRC_BATTERY);
350 	}
351 	if (acline_mode == ac_sysctl) {
352 		int acline;
353 		size_t len;
354 
355 		len = sizeof(acline);
356 		if (sysctl(acline_mib, acline_mib_len, &acline, &len,
357 		    NULL, 0) == 0)
358 			acline_status = (acline ? SRC_AC : SRC_BATTERY);
359 		else
360 			acline_status = SRC_UNKNOWN;
361 	}
362 #ifdef USE_APM
363 	if (acline_mode == ac_apm) {
364 		struct apm_info info;
365 
366 		if (ioctl(apm_fd, APMIO_GETINFO, &info) == 0) {
367 			acline_status = (info.ai_acline ? SRC_AC : SRC_BATTERY);
368 		} else {
369 			close(apm_fd);
370 			apm_fd = -1;
371 			acline_mode = ac_none;
372 			acline_status = SRC_UNKNOWN;
373 		}
374 	}
375 #endif
376 	/* try to (re)connect to devd */
377 #ifdef USE_APM
378 	if ((acline_mode == ac_sysctl &&
379 	    (acline_mode_user == ac_none ||
380 	     acline_mode_user == ac_acpi_devd)) ||
381 	    (acline_mode == ac_apm &&
382 	     acline_mode_user == ac_acpi_devd)) {
383 #else
384 	if (acline_mode == ac_sysctl &&
385 	    (acline_mode_user == ac_none ||
386 	     acline_mode_user == ac_acpi_devd)) {
387 #endif
388 		struct timeval now;
389 
390 		gettimeofday(&now, NULL);
391 		if (now.tv_sec > tried_devd.tv_sec + DEVD_RETRY_INTERVAL) {
392 			if (devd_init() >= 0) {
393 				if (vflag)
394 					warnx("using devd for AC line status");
395 				acline_mode = ac_acpi_devd;
396 			}
397 			tried_devd = now;
398 		}
399 	}
400 }
401 
402 static int
403 devd_init(void)
404 {
405 	struct sockaddr_un devd_addr;
406 
407 	bzero(&devd_addr, sizeof(devd_addr));
408 	if ((devd_pipe = socket(PF_LOCAL, SOCK_STREAM|SOCK_NONBLOCK, 0)) < 0) {
409 		if (vflag)
410 			warn("%s(): socket()", __func__);
411 		return (-1);
412 	}
413 
414 	devd_addr.sun_family = PF_LOCAL;
415 	strlcpy(devd_addr.sun_path, DEVDPIPE, sizeof(devd_addr.sun_path));
416 	if (connect(devd_pipe, (struct sockaddr *)&devd_addr,
417 	    sizeof(devd_addr)) == -1) {
418 		if (vflag)
419 			warn("%s(): connect()", __func__);
420 		close(devd_pipe);
421 		devd_pipe = -1;
422 		return (-1);
423 	}
424 
425 	return (devd_pipe);
426 }
427 
428 static void
429 devd_close(void)
430 {
431 
432 	close(devd_pipe);
433 	devd_pipe = -1;
434 }
435 
436 static void
437 parse_mode(char *arg, int *mode, int ch)
438 {
439 
440 	if (strcmp(arg, "minimum") == 0 || strcmp(arg, "min") == 0)
441 		*mode = MODE_MIN;
442 	else if (strcmp(arg, "maximum") == 0 || strcmp(arg, "max") == 0)
443 		*mode = MODE_MAX;
444 	else if (strcmp(arg, "adaptive") == 0 || strcmp(arg, "adp") == 0)
445 		*mode = MODE_ADAPTIVE;
446 	else if (strcmp(arg, "hiadaptive") == 0 || strcmp(arg, "hadp") == 0)
447 		*mode = MODE_HIADAPTIVE;
448 	else
449 		errx(1, "bad option: -%c %s", (char)ch, optarg);
450 }
451 
452 static void
453 parse_acline_mode(char *arg, int ch)
454 {
455 	if (strcmp(arg, "sysctl") == 0)
456 		acline_mode_user = ac_sysctl;
457 	else if (strcmp(arg, "devd") == 0)
458 		acline_mode_user = ac_acpi_devd;
459 #ifdef USE_APM
460 	else if (strcmp(arg, "apm") == 0)
461 		acline_mode_user = ac_apm;
462 #endif
463 	else
464 		errx(1, "bad option: -%c %s", (char)ch, optarg);
465 }
466 
467 static void
468 handle_sigs(int __unused sig)
469 {
470 
471 	exit_requested = 1;
472 }
473 
474 static void
475 usage(void)
476 {
477 
478 	fprintf(stderr,
479 "usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-m freq] [-M freq] [-N] [-n mode] [-p ival] [-r %%] [-s source] [-P pidfile]\n");
480 	exit(1);
481 }
482 
483 int
484 main(int argc, char * argv[])
485 {
486 	struct timeval timeout;
487 	fd_set fdset;
488 	int nfds;
489 	struct pidfh *pfh = NULL;
490 	const char *pidfile = NULL;
491 	int freq, curfreq, initfreq, *freqs, i, j, *mwatts, numfreqs, load;
492 	int minfreq = -1, maxfreq = -1;
493 	int ch, mode, mode_ac, mode_battery, mode_none, idle, to;
494 	uint64_t mjoules_used;
495 	size_t len;
496 	int nonice;
497 
498 	/* Default mode for all AC states is adaptive. */
499 	mode_ac = mode_none = MODE_HIADAPTIVE;
500 	mode_battery = MODE_ADAPTIVE;
501 	cpu_running_mark = DEFAULT_ACTIVE_PERCENT;
502 	cpu_idle_mark = DEFAULT_IDLE_PERCENT;
503 	poll_ival = DEFAULT_POLL_INTERVAL;
504 	mjoules_used = 0;
505 	vflag = 0;
506 	nonice = 0;
507 
508 	/* User must be root to control frequencies. */
509 	if (geteuid() != 0)
510 		errx(1, "must be root to run");
511 
512 	while ((ch = getopt(argc, argv, "a:b:i:m:M:Nn:p:P:r:s:v")) != -1)
513 		switch (ch) {
514 		case 'a':
515 			parse_mode(optarg, &mode_ac, ch);
516 			break;
517 		case 'b':
518 			parse_mode(optarg, &mode_battery, ch);
519 			break;
520 		case 's':
521 			parse_acline_mode(optarg, ch);
522 			break;
523 		case 'i':
524 			cpu_idle_mark = atoi(optarg);
525 			if (cpu_idle_mark < 0 || cpu_idle_mark > 100) {
526 				warnx("%d is not a valid percent",
527 				    cpu_idle_mark);
528 				usage();
529 			}
530 			break;
531 		case 'm':
532 			minfreq = atoi(optarg);
533 			if (minfreq < 0) {
534 				warnx("%d is not a valid CPU frequency",
535 				    minfreq);
536 				usage();
537 			}
538 			break;
539 		case 'M':
540 			maxfreq = atoi(optarg);
541 			if (maxfreq < 0) {
542 				warnx("%d is not a valid CPU frequency",
543 				    maxfreq);
544 				usage();
545 			}
546 			break;
547 		case 'N':
548 			nonice = 1;
549 			break;
550 		case 'n':
551 			parse_mode(optarg, &mode_none, ch);
552 			break;
553 		case 'p':
554 			poll_ival = atoi(optarg);
555 			if (poll_ival < 5) {
556 				warnx("poll interval is in units of ms");
557 				usage();
558 			}
559 			break;
560 		case 'P':
561 			pidfile = optarg;
562 			break;
563 		case 'r':
564 			cpu_running_mark = atoi(optarg);
565 			if (cpu_running_mark <= 0 || cpu_running_mark > 100) {
566 				warnx("%d is not a valid percent",
567 				    cpu_running_mark);
568 				usage();
569 			}
570 			break;
571 		case 'v':
572 			vflag = 1;
573 			break;
574 		default:
575 			usage();
576 		}
577 
578 	mode = mode_none;
579 
580 	/* Poll interval is in units of ms. */
581 	poll_ival *= 1000;
582 
583 	/* Look up various sysctl MIBs. */
584 	len = 2;
585 	if (sysctlnametomib("kern.cp_times", cp_times_mib, &len))
586 		err(1, "lookup kern.cp_times");
587 	len = 4;
588 	if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len))
589 		err(EX_UNAVAILABLE, "no cpufreq(4) support -- aborting");
590 	len = 4;
591 	if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len))
592 		err(1, "lookup freq_levels");
593 
594 	/* Check if we can read the load and supported freqs. */
595 	if (read_usage_times(NULL, nonice))
596 		err(1, "read_usage_times");
597 	if (read_freqs(&numfreqs, &freqs, &mwatts, minfreq, maxfreq))
598 		err(1, "error reading supported CPU frequencies");
599 	if (numfreqs == 0)
600 		errx(1, "no CPU frequencies in user-specified range");
601 
602 	/* Run in the background unless in verbose mode. */
603 	if (!vflag) {
604 		pid_t otherpid;
605 
606 		pfh = pidfile_open(pidfile, 0600, &otherpid);
607 		if (pfh == NULL) {
608 			if (errno == EEXIST) {
609 				errx(1, "powerd already running, pid: %d",
610 				    otherpid);
611 			}
612 			warn("cannot open pid file");
613 		}
614 		if (daemon(0, 0) != 0) {
615 			warn("cannot enter daemon mode, exiting");
616 			pidfile_remove(pfh);
617 			exit(EXIT_FAILURE);
618 
619 		}
620 		pidfile_write(pfh);
621 	}
622 
623 	/* Decide whether to use ACPI or APM to read the AC line status. */
624 	acline_init();
625 
626 	/*
627 	 * Exit cleanly on signals.
628 	 */
629 	signal(SIGINT, handle_sigs);
630 	signal(SIGTERM, handle_sigs);
631 
632 	freq = initfreq = curfreq = get_freq();
633 	i = get_freq_id(curfreq, freqs, numfreqs);
634 	if (freq < 1)
635 		freq = 1;
636 
637 	/*
638 	 * If we are in adaptive mode and the current frequency is outside the
639 	 * user-defined range, adjust it to be within the user-defined range.
640 	 */
641 	acline_read();
642 	if (acline_status > SRC_UNKNOWN)
643 		errx(1, "invalid AC line status %d", acline_status);
644 	if ((acline_status == SRC_AC &&
645 	    (mode_ac == MODE_ADAPTIVE || mode_ac == MODE_HIADAPTIVE)) ||
646 	    (acline_status == SRC_BATTERY &&
647 	    (mode_battery == MODE_ADAPTIVE || mode_battery == MODE_HIADAPTIVE)) ||
648 	    (acline_status == SRC_UNKNOWN &&
649 	    (mode_none == MODE_ADAPTIVE || mode_none == MODE_HIADAPTIVE))) {
650 		/* Read the current frequency. */
651 		len = sizeof(curfreq);
652 		if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0) != 0) {
653 			if (vflag)
654 				warn("error reading current CPU frequency");
655 		}
656 		if (curfreq < freqs[numfreqs - 1]) {
657 			if (vflag) {
658 				printf("CPU frequency is below user-defined "
659 				    "minimum; changing frequency to %d "
660 				    "MHz\n", freqs[numfreqs - 1]);
661 			}
662 			if (set_freq(freqs[numfreqs - 1]) != 0) {
663 				warn("error setting CPU freq %d",
664 				    freqs[numfreqs - 1]);
665 			}
666 		} else if (curfreq > freqs[0]) {
667 			if (vflag) {
668 				printf("CPU frequency is above user-defined "
669 				    "maximum; changing frequency to %d "
670 				    "MHz\n", freqs[0]);
671 			}
672 			if (set_freq(freqs[0]) != 0) {
673 				warn("error setting CPU freq %d",
674 				    freqs[0]);
675 			}
676 		}
677 	}
678 
679 	idle = 0;
680 	/* Main loop. */
681 	for (;;) {
682 		FD_ZERO(&fdset);
683 		if (devd_pipe >= 0) {
684 			FD_SET(devd_pipe, &fdset);
685 			nfds = devd_pipe + 1;
686 		} else {
687 			nfds = 0;
688 		}
689 		if (mode == MODE_HIADAPTIVE || idle < 120)
690 			to = poll_ival;
691 		else if (idle < 360)
692 			to = poll_ival * 2;
693 		else
694 			to = poll_ival * 4;
695 		timeout.tv_sec = to / 1000000;
696 		timeout.tv_usec = to % 1000000;
697 		select(nfds, &fdset, NULL, &fdset, &timeout);
698 
699 		/* If the user requested we quit, print some statistics. */
700 		if (exit_requested) {
701 			if (vflag && mjoules_used != 0)
702 				printf("total joules used: %u.%03u\n",
703 				    (u_int)(mjoules_used / 1000),
704 				    (int)mjoules_used % 1000);
705 			break;
706 		}
707 
708 		/* Read the current AC status and record the mode. */
709 		acline_read();
710 		switch (acline_status) {
711 		case SRC_AC:
712 			mode = mode_ac;
713 			break;
714 		case SRC_BATTERY:
715 			mode = mode_battery;
716 			break;
717 		case SRC_UNKNOWN:
718 			mode = mode_none;
719 			break;
720 		default:
721 			errx(1, "invalid AC line status %d", acline_status);
722 		}
723 
724 		/* Read the current frequency. */
725 		if (idle % 32 == 0) {
726 			if ((curfreq = get_freq()) == 0)
727 				continue;
728 			i = get_freq_id(curfreq, freqs, numfreqs);
729 		}
730 		idle++;
731 		if (vflag) {
732 			/* Keep a sum of all power actually used. */
733 			if (mwatts[i] != -1)
734 				mjoules_used +=
735 				    (mwatts[i] * (poll_ival / 1000)) / 1000;
736 		}
737 
738 		/* Always switch to the lowest frequency in min mode. */
739 		if (mode == MODE_MIN) {
740 			freq = freqs[numfreqs - 1];
741 			if (curfreq != freq) {
742 				if (vflag) {
743 					printf("now operating on %s power; "
744 					    "changing frequency to %d MHz\n",
745 					    modes[acline_status], freq);
746 				}
747 				idle = 0;
748 				if (set_freq(freq) != 0) {
749 					warn("error setting CPU freq %d",
750 					    freq);
751 					continue;
752 				}
753 			}
754 			continue;
755 		}
756 
757 		/* Always switch to the highest frequency in max mode. */
758 		if (mode == MODE_MAX) {
759 			freq = freqs[0];
760 			if (curfreq != freq) {
761 				if (vflag) {
762 					printf("now operating on %s power; "
763 					    "changing frequency to %d MHz\n",
764 					    modes[acline_status], freq);
765 				}
766 				idle = 0;
767 				if (set_freq(freq) != 0) {
768 					warn("error setting CPU freq %d",
769 					    freq);
770 					continue;
771 				}
772 			}
773 			continue;
774 		}
775 
776 		/* Adaptive mode; get the current CPU usage times. */
777 		if (read_usage_times(&load, nonice)) {
778 			if (vflag)
779 				warn("read_usage_times() failed");
780 			continue;
781 		}
782 
783 		if (mode == MODE_ADAPTIVE) {
784 			if (load > cpu_running_mark) {
785 				if (load > 95 || load > cpu_running_mark * 2)
786 					freq *= 2;
787 				else
788 					freq = freq * load / cpu_running_mark;
789 				if (freq > freqs[0])
790 					freq = freqs[0];
791 			} else if (load < cpu_idle_mark &&
792 			    curfreq * load < freqs[get_freq_id(
793 			    freq * 7 / 8, freqs, numfreqs)] *
794 			    cpu_running_mark) {
795 				freq = freq * 7 / 8;
796 				if (freq < freqs[numfreqs - 1])
797 					freq = freqs[numfreqs - 1];
798 			}
799 		} else { /* MODE_HIADAPTIVE */
800 			if (load > cpu_running_mark / 2) {
801 				if (load > 95 || load > cpu_running_mark)
802 					freq *= 4;
803 				else
804 					freq = freq * load * 2 / cpu_running_mark;
805 				if (freq > freqs[0] * 2)
806 					freq = freqs[0] * 2;
807 			} else if (load < cpu_idle_mark / 2 &&
808 			    curfreq * load < freqs[get_freq_id(
809 			    freq * 31 / 32, freqs, numfreqs)] *
810 			    cpu_running_mark / 2) {
811 				freq = freq * 31 / 32;
812 				if (freq < freqs[numfreqs - 1])
813 					freq = freqs[numfreqs - 1];
814 			}
815 		}
816 		if (vflag) {
817 		    printf("load %3d%%, current freq %4d MHz (%2d), wanted freq %4d MHz\n",
818 			load, curfreq, i, freq);
819 		}
820 		j = get_freq_id(freq, freqs, numfreqs);
821 		if (i != j) {
822 			if (vflag) {
823 				printf("changing clock"
824 				    " speed from %d MHz to %d MHz\n",
825 				    freqs[i], freqs[j]);
826 			}
827 			idle = 0;
828 			if (set_freq(freqs[j]))
829 				warn("error setting CPU frequency %d",
830 				    freqs[j]);
831 		}
832 	}
833 	if (set_freq(initfreq))
834 		warn("error setting CPU frequency %d", initfreq);
835 	free(freqs);
836 	free(mwatts);
837 	devd_close();
838 	if (!vflag)
839 		pidfile_remove(pfh);
840 
841 	exit(0);
842 }
843