xref: /freebsd/usr.sbin/powerd/powerd.c (revision 39beb93c3f8bdbf72a61fda42300b5ebed7390c8)
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$");
30 
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>
36 #include <sys/time.h>
37 #include <sys/un.h>
38 
39 #include <err.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <libutil.h>
43 #include <signal.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <unistd.h>
48 
49 #ifdef USE_APM
50 #include <machine/apm_bios.h>
51 #endif
52 
53 #define DEFAULT_ACTIVE_PERCENT	75
54 #define DEFAULT_IDLE_PERCENT	50
55 #define DEFAULT_POLL_INTERVAL	250	/* Poll interval in milliseconds */
56 
57 typedef enum {
58 	MODE_MIN,
59 	MODE_ADAPTIVE,
60 	MODE_HIADAPTIVE,
61 	MODE_MAX,
62 } modes_t;
63 
64 typedef enum {
65 	SRC_AC,
66 	SRC_BATTERY,
67 	SRC_UNKNOWN,
68 } power_src_t;
69 
70 const char *modes[] = {
71 	"AC",
72 	"battery",
73 	"unknown"
74 };
75 
76 #define ACPIAC		"hw.acpi.acline"
77 #define APMDEV		"/dev/apm"
78 #define DEVDPIPE	"/var/run/devd.pipe"
79 #define DEVCTL_MAXBUF	1024
80 
81 static int	read_usage_times(int *load);
82 static int	read_freqs(int *numfreqs, int **freqs, int **power);
83 static int	set_freq(int freq);
84 static void	acline_init(void);
85 static void	acline_read(void);
86 static int	devd_init(void);
87 static void	devd_close(void);
88 static void	handle_sigs(int sig);
89 static void	parse_mode(char *arg, int *mode, int ch);
90 static void	usage(void);
91 
92 /* Sysctl data structures. */
93 static int	cp_times_mib[2];
94 static int	freq_mib[4];
95 static int	levels_mib[4];
96 static int	acline_mib[3];
97 
98 /* Configuration */
99 static int	cpu_running_mark;
100 static int	cpu_idle_mark;
101 static int	poll_ival;
102 static int	vflag;
103 
104 static volatile sig_atomic_t exit_requested;
105 static power_src_t acline_status;
106 static enum {
107 	ac_none,
108 	ac_acpi_sysctl,
109 	ac_acpi_devd,
110 #ifdef USE_APM
111 	ac_apm,
112 #endif
113 } acline_mode;
114 #ifdef USE_APM
115 static int	apm_fd = -1;
116 #endif
117 static int	devd_pipe = -1;
118 
119 #define DEVD_RETRY_INTERVAL 60 /* seconds */
120 static struct timeval tried_devd;
121 
122 static int
123 read_usage_times(int *load)
124 {
125 	static long *cp_times = NULL, *cp_times_old = NULL;
126 	static int ncpus = 0;
127 	size_t cp_times_len;
128 	int error, cpu, i, total;
129 
130 	if (cp_times == NULL) {
131 		cp_times_len = 0;
132 		error = sysctl(cp_times_mib, 2, NULL, &cp_times_len, NULL, 0);
133 		if (error)
134 			return (error);
135 		if ((cp_times = malloc(cp_times_len)) == NULL)
136 			return (errno);
137 		if ((cp_times_old = malloc(cp_times_len)) == NULL) {
138 			free(cp_times);
139 			cp_times = NULL;
140 			return (errno);
141 		}
142 		ncpus = cp_times_len / (sizeof(long) * CPUSTATES);
143 	}
144 
145 	cp_times_len = sizeof(long) * CPUSTATES * ncpus;
146 	error = sysctl(cp_times_mib, 2, cp_times, &cp_times_len, NULL, 0);
147 	if (error)
148 		return (error);
149 
150 	if (load) {
151 		*load = 0;
152 		for (cpu = 0; cpu < ncpus; cpu++) {
153 			total = 0;
154 			for (i = 0; i < CPUSTATES; i++) {
155 			    total += cp_times[cpu * CPUSTATES + i] -
156 				cp_times_old[cpu * CPUSTATES + i];
157 			}
158 			if (total == 0)
159 				continue;
160 			*load += 100 - (cp_times[cpu * CPUSTATES + CP_IDLE] -
161 			    cp_times_old[cpu * CPUSTATES + CP_IDLE]) * 100 / total;
162 		}
163 	}
164 
165 	memcpy(cp_times_old, cp_times, cp_times_len);
166 
167 	return (0);
168 }
169 
170 static int
171 read_freqs(int *numfreqs, int **freqs, int **power)
172 {
173 	char *freqstr, *p, *q;
174 	int i;
175 	size_t len = 0;
176 
177 	if (sysctl(levels_mib, 4, NULL, &len, NULL, 0))
178 		return (-1);
179 	if ((freqstr = malloc(len)) == NULL)
180 		return (-1);
181 	if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0))
182 		return (-1);
183 
184 	*numfreqs = 1;
185 	for (p = freqstr; *p != '\0'; p++)
186 		if (*p == ' ')
187 			(*numfreqs)++;
188 
189 	if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) {
190 		free(freqstr);
191 		return (-1);
192 	}
193 	if ((*power = malloc(*numfreqs * sizeof(int))) == NULL) {
194 		free(freqstr);
195 		free(*freqs);
196 		return (-1);
197 	}
198 	for (i = 0, p = freqstr; i < *numfreqs; i++) {
199 		q = strchr(p, ' ');
200 		if (q != NULL)
201 			*q = '\0';
202 		if (sscanf(p, "%d/%d", &(*freqs)[i], &(*power)[i]) != 2) {
203 			free(freqstr);
204 			free(*freqs);
205 			free(*power);
206 			return (-1);
207 		}
208 		p = q + 1;
209 	}
210 
211 	free(freqstr);
212 	return (0);
213 }
214 
215 static int
216 get_freq(void)
217 {
218 	size_t len;
219 	int curfreq;
220 
221 	len = sizeof(curfreq);
222 	if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0) != 0) {
223 		if (vflag)
224 			warn("error reading current CPU frequency");
225 		curfreq = 0;
226 	}
227 	return (curfreq);
228 }
229 
230 static int
231 set_freq(int freq)
232 {
233 
234 	if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq))) {
235 		if (errno != EPERM)
236 			return (-1);
237 	}
238 
239 	return (0);
240 }
241 
242 static int
243 get_freq_id(int freq, int *freqs, int numfreqs)
244 {
245 	int i = 1;
246 
247 	while (i < numfreqs) {
248 		if (freqs[i] < freq)
249 			break;
250 		i++;
251 	}
252 	return (i - 1);
253 }
254 
255 /*
256  * Try to use ACPI to find the AC line status.  If this fails, fall back
257  * to APM.  If nothing succeeds, we'll just run in default mode.
258  */
259 static void
260 acline_init()
261 {
262 	size_t len;
263 
264 	len = 3;
265 	if (sysctlnametomib(ACPIAC, acline_mib, &len) == 0) {
266 		acline_mode = ac_acpi_sysctl;
267 		if (vflag)
268 			warnx("using sysctl for AC line status");
269 #ifdef USE_APM
270 	} else if ((apm_fd = open(APMDEV, O_RDONLY)) >= 0) {
271 		if (vflag)
272 			warnx("using APM for AC line status");
273 		acline_mode = ac_apm;
274 #endif
275 	} else {
276 		warnx("unable to determine AC line status");
277 		acline_mode = ac_none;
278 	}
279 }
280 
281 static void
282 acline_read(void)
283 {
284 	if (acline_mode == ac_acpi_devd) {
285 		char buf[DEVCTL_MAXBUF], *ptr;
286 		ssize_t rlen;
287 		int notify;
288 
289 		rlen = read(devd_pipe, buf, sizeof(buf));
290 		if (rlen == 0 || (rlen < 0 && errno != EWOULDBLOCK)) {
291 			if (vflag)
292 				warnx("lost devd connection, switching to sysctl");
293 			devd_close();
294 			acline_mode = ac_acpi_sysctl;
295 			/* FALLTHROUGH */
296 		}
297 		if (rlen > 0 &&
298 		    (ptr = strstr(buf, "system=ACPI")) != NULL &&
299 		    (ptr = strstr(ptr, "subsystem=ACAD")) != NULL &&
300 		    (ptr = strstr(ptr, "notify=")) != NULL &&
301 		    sscanf(ptr, "notify=%x", &notify) == 1)
302 			acline_status = (notify ? SRC_AC : SRC_BATTERY);
303 	}
304 	if (acline_mode == ac_acpi_sysctl) {
305 		int acline;
306 		size_t len;
307 
308 		len = sizeof(acline);
309 		if (sysctl(acline_mib, 3, &acline, &len, NULL, 0) == 0)
310 			acline_status = (acline ? SRC_AC : SRC_BATTERY);
311 		else
312 			acline_status = SRC_UNKNOWN;
313 	}
314 #ifdef USE_APM
315 	if (acline_mode == ac_apm) {
316 		struct apm_info info;
317 
318 		if (ioctl(apm_fd, APMIO_GETINFO, &info) == 0) {
319 			acline_status = (info.ai_acline ? SRC_AC : SRC_BATTERY);
320 		} else {
321 			close(apm_fd);
322 			apm_fd = -1;
323 			acline_mode = ac_none;
324 			acline_status = SRC_UNKNOWN;
325 		}
326 	}
327 #endif
328 	/* try to (re)connect to devd */
329 	if (acline_mode == ac_acpi_sysctl) {
330 		struct timeval now;
331 
332 		gettimeofday(&now, NULL);
333 		if (now.tv_sec > tried_devd.tv_sec + DEVD_RETRY_INTERVAL) {
334 			if (devd_init() >= 0) {
335 				if (vflag)
336 					warnx("using devd for AC line status");
337 				acline_mode = ac_acpi_devd;
338 			}
339 			tried_devd = now;
340 		}
341 	}
342 }
343 
344 static int
345 devd_init(void)
346 {
347 	struct sockaddr_un devd_addr;
348 
349 	bzero(&devd_addr, sizeof(devd_addr));
350 	if ((devd_pipe = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) {
351 		if (vflag)
352 			warn("%s(): socket()", __func__);
353 		return (-1);
354 	}
355 
356 	devd_addr.sun_family = PF_LOCAL;
357 	strlcpy(devd_addr.sun_path, DEVDPIPE, sizeof(devd_addr.sun_path));
358 	if (connect(devd_pipe, (struct sockaddr *)&devd_addr,
359 	    sizeof(devd_addr)) == -1) {
360 		if (vflag)
361 			warn("%s(): connect()", __func__);
362 		close(devd_pipe);
363 		devd_pipe = -1;
364 		return (-1);
365 	}
366 
367 	if (fcntl(devd_pipe, F_SETFL, O_NONBLOCK) == -1) {
368 		if (vflag)
369 			warn("%s(): fcntl()", __func__);
370 		close(devd_pipe);
371 		return (-1);
372 	}
373 
374 	return (devd_pipe);
375 }
376 
377 static void
378 devd_close(void)
379 {
380 
381 	close(devd_pipe);
382 	devd_pipe = -1;
383 }
384 
385 static void
386 parse_mode(char *arg, int *mode, int ch)
387 {
388 
389 	if (strcmp(arg, "minimum") == 0 || strcmp(arg, "min") == 0)
390 		*mode = MODE_MIN;
391 	else if (strcmp(arg, "maximum") == 0 || strcmp(arg, "max") == 0)
392 		*mode = MODE_MAX;
393 	else if (strcmp(arg, "adaptive") == 0 || strcmp(arg, "adp") == 0)
394 		*mode = MODE_ADAPTIVE;
395 	else if (strcmp(arg, "hiadaptive") == 0 || strcmp(arg, "hadp") == 0)
396 		*mode = MODE_HIADAPTIVE;
397 	else
398 		errx(1, "bad option: -%c %s", (char)ch, optarg);
399 }
400 
401 static void
402 handle_sigs(int __unused sig)
403 {
404 
405 	exit_requested = 1;
406 }
407 
408 static void
409 usage(void)
410 {
411 
412 	fprintf(stderr,
413 "usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-n mode] [-p ival] [-r %%] [-P pidfile]\n");
414 	exit(1);
415 }
416 
417 int
418 main(int argc, char * argv[])
419 {
420 	struct timeval timeout;
421 	fd_set fdset;
422 	int nfds;
423 	struct pidfh *pfh = NULL;
424 	const char *pidfile = NULL;
425 	int freq, curfreq, initfreq, *freqs, i, j, *mwatts, numfreqs, load;
426 	int ch, mode, mode_ac, mode_battery, mode_none;
427 	uint64_t mjoules_used;
428 	size_t len;
429 
430 	/* Default mode for all AC states is adaptive. */
431 	mode_ac = mode_none = MODE_HIADAPTIVE;
432 	mode_battery = MODE_ADAPTIVE;
433 	cpu_running_mark = DEFAULT_ACTIVE_PERCENT;
434 	cpu_idle_mark = DEFAULT_IDLE_PERCENT;
435 	poll_ival = DEFAULT_POLL_INTERVAL;
436 	mjoules_used = 0;
437 	vflag = 0;
438 
439 	/* User must be root to control frequencies. */
440 	if (geteuid() != 0)
441 		errx(1, "must be root to run");
442 
443 	while ((ch = getopt(argc, argv, "a:b:i:n:p:P:r:v")) != -1)
444 		switch (ch) {
445 		case 'a':
446 			parse_mode(optarg, &mode_ac, ch);
447 			break;
448 		case 'b':
449 			parse_mode(optarg, &mode_battery, ch);
450 			break;
451 		case 'i':
452 			cpu_idle_mark = atoi(optarg);
453 			if (cpu_idle_mark < 0 || cpu_idle_mark > 100) {
454 				warnx("%d is not a valid percent",
455 				    cpu_idle_mark);
456 				usage();
457 			}
458 			break;
459 		case 'n':
460 			parse_mode(optarg, &mode_none, ch);
461 			break;
462 		case 'p':
463 			poll_ival = atoi(optarg);
464 			if (poll_ival < 5) {
465 				warnx("poll interval is in units of ms");
466 				usage();
467 			}
468 			break;
469 		case 'P':
470 			pidfile = optarg;
471 			break;
472 		case 'r':
473 			cpu_running_mark = atoi(optarg);
474 			if (cpu_running_mark <= 0 || cpu_running_mark > 100) {
475 				warnx("%d is not a valid percent",
476 				    cpu_running_mark);
477 				usage();
478 			}
479 			break;
480 		case 'v':
481 			vflag = 1;
482 			break;
483 		default:
484 			usage();
485 		}
486 
487 	mode = mode_none;
488 
489 	/* Poll interval is in units of ms. */
490 	poll_ival *= 1000;
491 
492 	/* Look up various sysctl MIBs. */
493 	len = 2;
494 	if (sysctlnametomib("kern.cp_times", cp_times_mib, &len))
495 		err(1, "lookup kern.cp_times");
496 	len = 4;
497 	if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len))
498 		err(1, "lookup freq");
499 	len = 4;
500 	if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len))
501 		err(1, "lookup freq_levels");
502 
503 	/* Check if we can read the load and supported freqs. */
504 	if (read_usage_times(NULL))
505 		err(1, "read_usage_times");
506 	if (read_freqs(&numfreqs, &freqs, &mwatts))
507 		err(1, "error reading supported CPU frequencies");
508 
509 	/* Run in the background unless in verbose mode. */
510 	if (!vflag) {
511 		pid_t otherpid;
512 
513 		pfh = pidfile_open(pidfile, 0600, &otherpid);
514 		if (pfh == NULL) {
515 			if (errno == EEXIST) {
516 				errx(1, "powerd already running, pid: %d",
517 				    otherpid);
518 			}
519 			warn("cannot open pid file");
520 		}
521 		if (daemon(0, 0) != 0) {
522 			warn("cannot enter daemon mode, exiting");
523 			pidfile_remove(pfh);
524 			exit(EXIT_FAILURE);
525 
526 		}
527 		pidfile_write(pfh);
528 	}
529 
530 	/* Decide whether to use ACPI or APM to read the AC line status. */
531 	acline_init();
532 
533 	/*
534 	 * Exit cleanly on signals.
535 	 */
536 	signal(SIGINT, handle_sigs);
537 	signal(SIGTERM, handle_sigs);
538 
539 	freq = initfreq = get_freq();
540 	if (freq < 1)
541 		freq = 1;
542 	/* Main loop. */
543 	for (;;) {
544 		FD_ZERO(&fdset);
545 		if (devd_pipe >= 0) {
546 			FD_SET(devd_pipe, &fdset);
547 			nfds = devd_pipe + 1;
548 		} else {
549 			nfds = 0;
550 		}
551 		timeout.tv_sec = poll_ival / 1000000;
552 		timeout.tv_usec = poll_ival % 1000000;
553 		select(nfds, &fdset, NULL, &fdset, &timeout);
554 
555 		/* If the user requested we quit, print some statistics. */
556 		if (exit_requested) {
557 			if (vflag && mjoules_used != 0)
558 				printf("total joules used: %u.%03u\n",
559 				    (u_int)(mjoules_used / 1000),
560 				    (int)mjoules_used % 1000);
561 			break;
562 		}
563 
564 		/* Read the current AC status and record the mode. */
565 		acline_read();
566 		switch (acline_status) {
567 		case SRC_AC:
568 			mode = mode_ac;
569 			break;
570 		case SRC_BATTERY:
571 			mode = mode_battery;
572 			break;
573 		case SRC_UNKNOWN:
574 			mode = mode_none;
575 			break;
576 		default:
577 			errx(1, "invalid AC line status %d", acline_status);
578 		}
579 
580 		/* Read the current frequency. */
581 		if ((curfreq = get_freq()) == 0)
582 			continue;
583 
584 		i = get_freq_id(curfreq, freqs, numfreqs);
585 
586 		if (vflag) {
587 			/* Keep a sum of all power actually used. */
588 			if (mwatts[i] != -1)
589 				mjoules_used +=
590 				    (mwatts[i] * (poll_ival / 1000)) / 1000;
591 		}
592 
593 		/* Always switch to the lowest frequency in min mode. */
594 		if (mode == MODE_MIN) {
595 			freq = freqs[numfreqs - 1];
596 			if (curfreq != freq) {
597 				if (vflag) {
598 					printf("now operating on %s power; "
599 					    "changing frequency to %d MHz\n",
600 					    modes[acline_status], freq);
601 				}
602 				if (set_freq(freq) != 0) {
603 					warn("error setting CPU freq %d",
604 					    freq);
605 					continue;
606 				}
607 			}
608 			continue;
609 		}
610 
611 		/* Always switch to the highest frequency in max mode. */
612 		if (mode == MODE_MAX) {
613 			freq = freqs[0];
614 			if (curfreq != freq) {
615 				if (vflag) {
616 					printf("now operating on %s power; "
617 					    "changing frequency to %d MHz\n",
618 					    modes[acline_status], freq);
619 				}
620 				if (set_freq(freq) != 0) {
621 					warn("error setting CPU freq %d",
622 				    	    freq);
623 					continue;
624 				}
625 			}
626 			continue;
627 		}
628 
629 		/* Adaptive mode; get the current CPU usage times. */
630 		if (read_usage_times(&load)) {
631 			if (vflag)
632 				warn("read_usage_times() failed");
633 			continue;
634 		}
635 
636 		if (mode == MODE_ADAPTIVE) {
637 			if (load > cpu_running_mark) {
638 				if (load > 95 || load > cpu_running_mark * 2)
639 					freq *= 2;
640 				else
641 					freq = freq * load / cpu_running_mark;
642 				if (freq > freqs[0])
643 					freq = freqs[0];
644 			} else if (load < cpu_idle_mark &&
645 			    curfreq * load < freqs[get_freq_id(
646 			    freq * 7 / 8, freqs, numfreqs)] *
647 			    cpu_running_mark) {
648 				freq = freq * 7 / 8;
649 				if (freq < freqs[numfreqs - 1])
650 					freq = freqs[numfreqs - 1];
651 			}
652 		} else { /* MODE_HIADAPTIVE */
653 			if (load > cpu_running_mark / 2) {
654 				if (load > 95 || load > cpu_running_mark)
655 					freq *= 4;
656 				else
657 					freq = freq * load * 2 / cpu_running_mark;
658 				if (freq > freqs[0] * 2)
659 					freq = freqs[0] * 2;
660 			} else if (load < cpu_idle_mark / 2 &&
661 			    curfreq * load < freqs[get_freq_id(
662 			    freq * 31 / 32, freqs, numfreqs)] *
663 			    cpu_running_mark / 2) {
664 				freq = freq * 31 / 32;
665 				if (freq < freqs[numfreqs - 1])
666 					freq = freqs[numfreqs - 1];
667 			}
668 		}
669 		if (vflag) {
670 		    printf("load %3d%%, current freq %4d MHz (%2d), wanted freq %4d MHz\n",
671 			load, curfreq, i, freq);
672 		}
673 		j = get_freq_id(freq, freqs, numfreqs);
674 		if (i != j) {
675 			if (vflag) {
676 				printf("changing clock"
677 				    " speed from %d MHz to %d MHz\n",
678 				    freqs[i], freqs[j]);
679 			}
680 			if (set_freq(freqs[j]))
681 				warn("error setting CPU frequency %d",
682 				    freqs[j]);
683 		}
684 	}
685 	if (set_freq(initfreq))
686 		warn("error setting CPU frequency %d", initfreq);
687 	free(freqs);
688 	free(mwatts);
689 	devd_close();
690 	if (!vflag)
691 		pidfile_remove(pfh);
692 
693 	exit(0);
694 }
695