xref: /freebsd/usr.sbin/powerd/powerd.c (revision 7a377edc318a1174297fece77894eb66fe05d8dd)
15883360bSNate Lawson /*-
25883360bSNate Lawson  * Copyright (c) 2004 Colin Percival
35883360bSNate Lawson  * Copyright (c) 2005 Nate Lawson
45883360bSNate Lawson  * All rights reserved.
55883360bSNate Lawson  *
65883360bSNate Lawson  * Redistribution and use in source and binary forms, with or without
75883360bSNate Lawson  * modification, are permitted providing that the following conditions
85883360bSNate Lawson  * are met:
95883360bSNate Lawson  * 1. Redistributions of source code must retain the above copyright
105883360bSNate Lawson  *    notice, this list of conditions and the following disclaimer.
115883360bSNate Lawson  * 2. Redistributions in binary form must reproduce the above copyright
125883360bSNate Lawson  *    notice, this list of conditions and the following disclaimer in the
135883360bSNate Lawson  *    documentation and/or other materials provided with the distribution.
145883360bSNate Lawson  *
155883360bSNate Lawson  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR``AS IS'' AND ANY EXPRESS OR
165883360bSNate Lawson  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
175883360bSNate Lawson  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
185883360bSNate Lawson  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
195883360bSNate Lawson  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
205883360bSNate Lawson  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
215883360bSNate Lawson  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
225883360bSNate Lawson  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
235883360bSNate Lawson  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
245883360bSNate Lawson  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
255883360bSNate Lawson  * POSSIBILITY OF SUCH DAMAGE.
265883360bSNate Lawson  */
275883360bSNate Lawson 
285883360bSNate Lawson #include <sys/cdefs.h>
295883360bSNate Lawson __FBSDID("$FreeBSD$");
305883360bSNate Lawson 
3184148fd1SPawel Jakub Dawidek #include <sys/param.h>
3284148fd1SPawel Jakub Dawidek #include <sys/ioctl.h>
3384148fd1SPawel Jakub Dawidek #include <sys/sysctl.h>
3484148fd1SPawel Jakub Dawidek #include <sys/resource.h>
35a1819ab5SNate Lawson #include <sys/socket.h>
362f7a934cSDag-Erling Smørgrav #include <sys/time.h>
37a1819ab5SNate Lawson #include <sys/un.h>
3884148fd1SPawel Jakub Dawidek 
395883360bSNate Lawson #include <err.h>
40f92a4b93SHajimu UMEMOTO #include <errno.h>
415883360bSNate Lawson #include <fcntl.h>
4284148fd1SPawel Jakub Dawidek #include <libutil.h>
43ab19351cSNate Lawson #include <signal.h>
445883360bSNate Lawson #include <stdio.h>
455883360bSNate Lawson #include <stdlib.h>
465883360bSNate Lawson #include <string.h>
475883360bSNate Lawson #include <unistd.h>
485883360bSNate Lawson 
497a377edcSWarner Losh #ifdef __i386__
507a377edcSWarner Losh #define USE_APM
517a377edcSWarner Losh #endif
527a377edcSWarner Losh 
532f7a934cSDag-Erling Smørgrav #ifdef USE_APM
545883360bSNate Lawson #include <machine/apm_bios.h>
555f4aa967SMarcel Moolenaar #endif
565883360bSNate Lawson 
57dbd31977SAlexander Motin #define DEFAULT_ACTIVE_PERCENT	75
58dbd31977SAlexander Motin #define DEFAULT_IDLE_PERCENT	50
59dbd31977SAlexander Motin #define DEFAULT_POLL_INTERVAL	250	/* Poll interval in milliseconds */
605883360bSNate Lawson 
612f7a934cSDag-Erling Smørgrav typedef enum {
625883360bSNate Lawson 	MODE_MIN,
635883360bSNate Lawson 	MODE_ADAPTIVE,
64dbd31977SAlexander Motin 	MODE_HIADAPTIVE,
655883360bSNate Lawson 	MODE_MAX,
662f7a934cSDag-Erling Smørgrav } modes_t;
675883360bSNate Lawson 
682f7a934cSDag-Erling Smørgrav typedef enum {
695883360bSNate Lawson 	SRC_AC,
705883360bSNate Lawson 	SRC_BATTERY,
715883360bSNate Lawson 	SRC_UNKNOWN,
722f7a934cSDag-Erling Smørgrav } power_src_t;
735883360bSNate Lawson 
745883360bSNate Lawson const char *modes[] = {
755883360bSNate Lawson 	"AC",
765883360bSNate Lawson 	"battery",
775883360bSNate Lawson 	"unknown"
785883360bSNate Lawson };
795883360bSNate Lawson 
805883360bSNate Lawson #define ACPIAC		"hw.acpi.acline"
81ebcc3763SNathan Whitehorn #define PMUAC		"dev.pmu.0.acline"
825883360bSNate Lawson #define APMDEV		"/dev/apm"
83a1819ab5SNate Lawson #define DEVDPIPE	"/var/run/devd.pipe"
84a1819ab5SNate Lawson #define DEVCTL_MAXBUF	1024
855883360bSNate Lawson 
86dbd31977SAlexander Motin static int	read_usage_times(int *load);
87ab19351cSNate Lawson static int	read_freqs(int *numfreqs, int **freqs, int **power);
885883360bSNate Lawson static int	set_freq(int freq);
8948bd7109SNate Lawson static void	acline_init(void);
902f7a934cSDag-Erling Smørgrav static void	acline_read(void);
91a1819ab5SNate Lawson static int	devd_init(void);
92a1819ab5SNate Lawson static void	devd_close(void);
93ab19351cSNate Lawson static void	handle_sigs(int sig);
945883360bSNate Lawson static void	parse_mode(char *arg, int *mode, int ch);
955883360bSNate Lawson static void	usage(void);
965883360bSNate Lawson 
975883360bSNate Lawson /* Sysctl data structures. */
98dbd31977SAlexander Motin static int	cp_times_mib[2];
995883360bSNate Lawson static int	freq_mib[4];
1005883360bSNate Lawson static int	levels_mib[4];
101ebcc3763SNathan Whitehorn static int	acline_mib[4];
102ebcc3763SNathan Whitehorn static size_t	acline_mib_len;
1035883360bSNate Lawson 
1045883360bSNate Lawson /* Configuration */
1055883360bSNate Lawson static int	cpu_running_mark;
1065883360bSNate Lawson static int	cpu_idle_mark;
1075883360bSNate Lawson static int	poll_ival;
108a1819ab5SNate Lawson static int	vflag;
1095883360bSNate Lawson 
1102f7a934cSDag-Erling Smørgrav static volatile sig_atomic_t exit_requested;
1112f7a934cSDag-Erling Smørgrav static power_src_t acline_status;
1122f7a934cSDag-Erling Smørgrav static enum {
1132f7a934cSDag-Erling Smørgrav 	ac_none,
114ebcc3763SNathan Whitehorn 	ac_sysctl,
1152f7a934cSDag-Erling Smørgrav 	ac_acpi_devd,
1162f7a934cSDag-Erling Smørgrav #ifdef USE_APM
1172f7a934cSDag-Erling Smørgrav 	ac_apm,
1182f7a934cSDag-Erling Smørgrav #endif
1192f7a934cSDag-Erling Smørgrav } acline_mode;
1202f7a934cSDag-Erling Smørgrav #ifdef USE_APM
1212f7a934cSDag-Erling Smørgrav static int	apm_fd = -1;
1222f7a934cSDag-Erling Smørgrav #endif
1232f7a934cSDag-Erling Smørgrav static int	devd_pipe = -1;
1242f7a934cSDag-Erling Smørgrav 
1252f7a934cSDag-Erling Smørgrav #define DEVD_RETRY_INTERVAL 60 /* seconds */
1262f7a934cSDag-Erling Smørgrav static struct timeval tried_devd;
12748bd7109SNate Lawson 
1285883360bSNate Lawson static int
129dbd31977SAlexander Motin read_usage_times(int *load)
1305883360bSNate Lawson {
131dbd31977SAlexander Motin 	static long *cp_times = NULL, *cp_times_old = NULL;
132dbd31977SAlexander Motin 	static int ncpus = 0;
133dbd31977SAlexander Motin 	size_t cp_times_len;
134dbd31977SAlexander Motin 	int error, cpu, i, total;
1355883360bSNate Lawson 
136dbd31977SAlexander Motin 	if (cp_times == NULL) {
137dbd31977SAlexander Motin 		cp_times_len = 0;
138dbd31977SAlexander Motin 		error = sysctl(cp_times_mib, 2, NULL, &cp_times_len, NULL, 0);
1395883360bSNate Lawson 		if (error)
1405883360bSNate Lawson 			return (error);
141dbd31977SAlexander Motin 		if ((cp_times = malloc(cp_times_len)) == NULL)
142dbd31977SAlexander Motin 			return (errno);
143dbd31977SAlexander Motin 		if ((cp_times_old = malloc(cp_times_len)) == NULL) {
144dbd31977SAlexander Motin 			free(cp_times);
145dbd31977SAlexander Motin 			cp_times = NULL;
146dbd31977SAlexander Motin 			return (errno);
147dbd31977SAlexander Motin 		}
148dbd31977SAlexander Motin 		ncpus = cp_times_len / (sizeof(long) * CPUSTATES);
149dbd31977SAlexander Motin 	}
1505883360bSNate Lawson 
151dbd31977SAlexander Motin 	cp_times_len = sizeof(long) * CPUSTATES * ncpus;
152dbd31977SAlexander Motin 	error = sysctl(cp_times_mib, 2, cp_times, &cp_times_len, NULL, 0);
153dbd31977SAlexander Motin 	if (error)
154dbd31977SAlexander Motin 		return (error);
1555883360bSNate Lawson 
156dbd31977SAlexander Motin 	if (load) {
157dbd31977SAlexander Motin 		*load = 0;
158dbd31977SAlexander Motin 		for (cpu = 0; cpu < ncpus; cpu++) {
159dbd31977SAlexander Motin 			total = 0;
160dbd31977SAlexander Motin 			for (i = 0; i < CPUSTATES; i++) {
161dbd31977SAlexander Motin 			    total += cp_times[cpu * CPUSTATES + i] -
162dbd31977SAlexander Motin 				cp_times_old[cpu * CPUSTATES + i];
163dbd31977SAlexander Motin 			}
164dbd31977SAlexander Motin 			if (total == 0)
165dbd31977SAlexander Motin 				continue;
166dbd31977SAlexander Motin 			*load += 100 - (cp_times[cpu * CPUSTATES + CP_IDLE] -
167dbd31977SAlexander Motin 			    cp_times_old[cpu * CPUSTATES + CP_IDLE]) * 100 / total;
168dbd31977SAlexander Motin 		}
169dbd31977SAlexander Motin 	}
170dbd31977SAlexander Motin 
171dbd31977SAlexander Motin 	memcpy(cp_times_old, cp_times, cp_times_len);
1725883360bSNate Lawson 
1735883360bSNate Lawson 	return (0);
1745883360bSNate Lawson }
1755883360bSNate Lawson 
1765883360bSNate Lawson static int
177ab19351cSNate Lawson read_freqs(int *numfreqs, int **freqs, int **power)
1785883360bSNate Lawson {
1795883360bSNate Lawson 	char *freqstr, *p, *q;
1805883360bSNate Lawson 	int i;
1815883360bSNate Lawson 	size_t len = 0;
1825883360bSNate Lawson 
1835883360bSNate Lawson 	if (sysctl(levels_mib, 4, NULL, &len, NULL, 0))
1845883360bSNate Lawson 		return (-1);
1855883360bSNate Lawson 	if ((freqstr = malloc(len)) == NULL)
1865883360bSNate Lawson 		return (-1);
1875883360bSNate Lawson 	if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0))
1885883360bSNate Lawson 		return (-1);
1895883360bSNate Lawson 
1905883360bSNate Lawson 	*numfreqs = 1;
1915883360bSNate Lawson 	for (p = freqstr; *p != '\0'; p++)
1925883360bSNate Lawson 		if (*p == ' ')
1935883360bSNate Lawson 			(*numfreqs)++;
1945883360bSNate Lawson 
1955883360bSNate Lawson 	if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) {
1965883360bSNate Lawson 		free(freqstr);
1975883360bSNate Lawson 		return (-1);
1985883360bSNate Lawson 	}
199ab19351cSNate Lawson 	if ((*power = malloc(*numfreqs * sizeof(int))) == NULL) {
200ab19351cSNate Lawson 		free(freqstr);
201ab19351cSNate Lawson 		free(*freqs);
202ab19351cSNate Lawson 		return (-1);
203ab19351cSNate Lawson 	}
2045883360bSNate Lawson 	for (i = 0, p = freqstr; i < *numfreqs; i++) {
2055883360bSNate Lawson 		q = strchr(p, ' ');
2065883360bSNate Lawson 		if (q != NULL)
2075883360bSNate Lawson 			*q = '\0';
208ab19351cSNate Lawson 		if (sscanf(p, "%d/%d", &(*freqs)[i], &(*power)[i]) != 2) {
2095883360bSNate Lawson 			free(freqstr);
2105883360bSNate Lawson 			free(*freqs);
211ab19351cSNate Lawson 			free(*power);
2125883360bSNate Lawson 			return (-1);
2135883360bSNate Lawson 		}
2145883360bSNate Lawson 		p = q + 1;
2155883360bSNate Lawson 	}
2165883360bSNate Lawson 
2175883360bSNate Lawson 	free(freqstr);
2185883360bSNate Lawson 	return (0);
2195883360bSNate Lawson }
2205883360bSNate Lawson 
2215883360bSNate Lawson static int
222dbd31977SAlexander Motin get_freq(void)
223dbd31977SAlexander Motin {
224dbd31977SAlexander Motin 	size_t len;
225dbd31977SAlexander Motin 	int curfreq;
226dbd31977SAlexander Motin 
227dbd31977SAlexander Motin 	len = sizeof(curfreq);
228dbd31977SAlexander Motin 	if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0) != 0) {
229dbd31977SAlexander Motin 		if (vflag)
230dbd31977SAlexander Motin 			warn("error reading current CPU frequency");
231dbd31977SAlexander Motin 		curfreq = 0;
232dbd31977SAlexander Motin 	}
233dbd31977SAlexander Motin 	return (curfreq);
234dbd31977SAlexander Motin }
235dbd31977SAlexander Motin 
236dbd31977SAlexander Motin static int
2375883360bSNate Lawson set_freq(int freq)
2385883360bSNate Lawson {
2395883360bSNate Lawson 
240f92a4b93SHajimu UMEMOTO 	if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq))) {
241f92a4b93SHajimu UMEMOTO 		if (errno != EPERM)
2425883360bSNate Lawson 			return (-1);
243f92a4b93SHajimu UMEMOTO 	}
2445883360bSNate Lawson 
2455883360bSNate Lawson 	return (0);
2465883360bSNate Lawson }
2475883360bSNate Lawson 
248dbd31977SAlexander Motin static int
249dbd31977SAlexander Motin get_freq_id(int freq, int *freqs, int numfreqs)
250dbd31977SAlexander Motin {
251dbd31977SAlexander Motin 	int i = 1;
252dbd31977SAlexander Motin 
253dbd31977SAlexander Motin 	while (i < numfreqs) {
254dbd31977SAlexander Motin 		if (freqs[i] < freq)
255dbd31977SAlexander Motin 			break;
256dbd31977SAlexander Motin 		i++;
257dbd31977SAlexander Motin 	}
258dbd31977SAlexander Motin 	return (i - 1);
259dbd31977SAlexander Motin }
260dbd31977SAlexander Motin 
26148bd7109SNate Lawson /*
26248bd7109SNate Lawson  * Try to use ACPI to find the AC line status.  If this fails, fall back
2632f7a934cSDag-Erling Smørgrav  * to APM.  If nothing succeeds, we'll just run in default mode.
26448bd7109SNate Lawson  */
26548bd7109SNate Lawson static void
26610bc3a7fSEd Schouten acline_init(void)
26748bd7109SNate Lawson {
268ebcc3763SNathan Whitehorn 	acline_mib_len = 4;
26948bd7109SNate Lawson 
270ebcc3763SNathan Whitehorn 	if (sysctlnametomib(ACPIAC, acline_mib, &acline_mib_len) == 0) {
271ebcc3763SNathan Whitehorn 		acline_mode = ac_sysctl;
2722f7a934cSDag-Erling Smørgrav 		if (vflag)
2732f7a934cSDag-Erling Smørgrav 			warnx("using sysctl for AC line status");
274ebcc3763SNathan Whitehorn #if __powerpc__
275ebcc3763SNathan Whitehorn 	} else if (sysctlnametomib(PMUAC, acline_mib, &acline_mib_len) == 0) {
276ebcc3763SNathan Whitehorn 		acline_mode = ac_sysctl;
277ebcc3763SNathan Whitehorn 		if (vflag)
278ebcc3763SNathan Whitehorn 			warnx("using sysctl for AC line status");
279ebcc3763SNathan Whitehorn #endif
2802f7a934cSDag-Erling Smørgrav #ifdef USE_APM
2812f7a934cSDag-Erling Smørgrav 	} else if ((apm_fd = open(APMDEV, O_RDONLY)) >= 0) {
2822f7a934cSDag-Erling Smørgrav 		if (vflag)
2832f7a934cSDag-Erling Smørgrav 			warnx("using APM for AC line status");
2842f7a934cSDag-Erling Smørgrav 		acline_mode = ac_apm;
2852f7a934cSDag-Erling Smørgrav #endif
28648bd7109SNate Lawson 	} else {
2872f7a934cSDag-Erling Smørgrav 		warnx("unable to determine AC line status");
2882f7a934cSDag-Erling Smørgrav 		acline_mode = ac_none;
28948bd7109SNate Lawson 	}
29048bd7109SNate Lawson }
29148bd7109SNate Lawson 
2922f7a934cSDag-Erling Smørgrav static void
2932f7a934cSDag-Erling Smørgrav acline_read(void)
29448bd7109SNate Lawson {
2952f7a934cSDag-Erling Smørgrav 	if (acline_mode == ac_acpi_devd) {
2962f7a934cSDag-Erling Smørgrav 		char buf[DEVCTL_MAXBUF], *ptr;
2972f7a934cSDag-Erling Smørgrav 		ssize_t rlen;
2982f7a934cSDag-Erling Smørgrav 		int notify;
2992f7a934cSDag-Erling Smørgrav 
3002f7a934cSDag-Erling Smørgrav 		rlen = read(devd_pipe, buf, sizeof(buf));
3012f7a934cSDag-Erling Smørgrav 		if (rlen == 0 || (rlen < 0 && errno != EWOULDBLOCK)) {
3022f7a934cSDag-Erling Smørgrav 			if (vflag)
3032f7a934cSDag-Erling Smørgrav 				warnx("lost devd connection, switching to sysctl");
3042f7a934cSDag-Erling Smørgrav 			devd_close();
305ebcc3763SNathan Whitehorn 			acline_mode = ac_sysctl;
3062f7a934cSDag-Erling Smørgrav 			/* FALLTHROUGH */
3072f7a934cSDag-Erling Smørgrav 		}
3082f7a934cSDag-Erling Smørgrav 		if (rlen > 0 &&
3092f7a934cSDag-Erling Smørgrav 		    (ptr = strstr(buf, "system=ACPI")) != NULL &&
3102f7a934cSDag-Erling Smørgrav 		    (ptr = strstr(ptr, "subsystem=ACAD")) != NULL &&
3112f7a934cSDag-Erling Smørgrav 		    (ptr = strstr(ptr, "notify=")) != NULL &&
3122f7a934cSDag-Erling Smørgrav 		    sscanf(ptr, "notify=%x", &notify) == 1)
3132f7a934cSDag-Erling Smørgrav 			acline_status = (notify ? SRC_AC : SRC_BATTERY);
3142f7a934cSDag-Erling Smørgrav 	}
315ebcc3763SNathan Whitehorn 	if (acline_mode == ac_sysctl) {
316a1819ab5SNate Lawson 		int acline;
31748bd7109SNate Lawson 		size_t len;
31848bd7109SNate Lawson 
319a1819ab5SNate Lawson 		len = sizeof(acline);
320ebcc3763SNathan Whitehorn 		if (sysctl(acline_mib, acline_mib_len, &acline, &len,
321ebcc3763SNathan Whitehorn 		    NULL, 0) == 0)
3222f7a934cSDag-Erling Smørgrav 			acline_status = (acline ? SRC_AC : SRC_BATTERY);
3232f7a934cSDag-Erling Smørgrav 		else
3242f7a934cSDag-Erling Smørgrav 			acline_status = SRC_UNKNOWN;
3252f7a934cSDag-Erling Smørgrav 	}
3262f7a934cSDag-Erling Smørgrav #ifdef USE_APM
3272f7a934cSDag-Erling Smørgrav 	if (acline_mode == ac_apm) {
3282f7a934cSDag-Erling Smørgrav 		struct apm_info info;
329a1819ab5SNate Lawson 
3302f7a934cSDag-Erling Smørgrav 		if (ioctl(apm_fd, APMIO_GETINFO, &info) == 0) {
3312f7a934cSDag-Erling Smørgrav 			acline_status = (info.ai_acline ? SRC_AC : SRC_BATTERY);
3322f7a934cSDag-Erling Smørgrav 		} else {
3332f7a934cSDag-Erling Smørgrav 			close(apm_fd);
3342f7a934cSDag-Erling Smørgrav 			apm_fd = -1;
3352f7a934cSDag-Erling Smørgrav 			acline_mode = ac_none;
3362f7a934cSDag-Erling Smørgrav 			acline_status = SRC_UNKNOWN;
3372f7a934cSDag-Erling Smørgrav 		}
3382f7a934cSDag-Erling Smørgrav 	}
33948bd7109SNate Lawson #endif
3402f7a934cSDag-Erling Smørgrav 	/* try to (re)connect to devd */
341ebcc3763SNathan Whitehorn 	if (acline_mode == ac_sysctl) {
3422f7a934cSDag-Erling Smørgrav 		struct timeval now;
34348bd7109SNate Lawson 
3442f7a934cSDag-Erling Smørgrav 		gettimeofday(&now, NULL);
3452f7a934cSDag-Erling Smørgrav 		if (now.tv_sec > tried_devd.tv_sec + DEVD_RETRY_INTERVAL) {
3462f7a934cSDag-Erling Smørgrav 			if (devd_init() >= 0) {
3472f7a934cSDag-Erling Smørgrav 				if (vflag)
3482f7a934cSDag-Erling Smørgrav 					warnx("using devd for AC line status");
3492f7a934cSDag-Erling Smørgrav 				acline_mode = ac_acpi_devd;
3502f7a934cSDag-Erling Smørgrav 			}
3512f7a934cSDag-Erling Smørgrav 			tried_devd = now;
3522f7a934cSDag-Erling Smørgrav 		}
3532f7a934cSDag-Erling Smørgrav 	}
35448bd7109SNate Lawson }
35548bd7109SNate Lawson 
356a1819ab5SNate Lawson static int
357a1819ab5SNate Lawson devd_init(void)
358a1819ab5SNate Lawson {
359a1819ab5SNate Lawson 	struct sockaddr_un devd_addr;
360a1819ab5SNate Lawson 
361a1819ab5SNate Lawson 	bzero(&devd_addr, sizeof(devd_addr));
3622f7a934cSDag-Erling Smørgrav 	if ((devd_pipe = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) {
363a1819ab5SNate Lawson 		if (vflag)
3642f7a934cSDag-Erling Smørgrav 			warn("%s(): socket()", __func__);
365a1819ab5SNate Lawson 		return (-1);
366a1819ab5SNate Lawson 	}
367a1819ab5SNate Lawson 
368a1819ab5SNate Lawson 	devd_addr.sun_family = PF_LOCAL;
369a1819ab5SNate Lawson 	strlcpy(devd_addr.sun_path, DEVDPIPE, sizeof(devd_addr.sun_path));
3702f7a934cSDag-Erling Smørgrav 	if (connect(devd_pipe, (struct sockaddr *)&devd_addr,
371a1819ab5SNate Lawson 	    sizeof(devd_addr)) == -1) {
3722f7a934cSDag-Erling Smørgrav 		if (vflag)
3732f7a934cSDag-Erling Smørgrav 			warn("%s(): connect()", __func__);
3742f7a934cSDag-Erling Smørgrav 		close(devd_pipe);
3752f7a934cSDag-Erling Smørgrav 		devd_pipe = -1;
376a1819ab5SNate Lawson 		return (-1);
377a1819ab5SNate Lawson 	}
378a1819ab5SNate Lawson 
3792f7a934cSDag-Erling Smørgrav 	if (fcntl(devd_pipe, F_SETFL, O_NONBLOCK) == -1) {
3802f7a934cSDag-Erling Smørgrav 		if (vflag)
3812f7a934cSDag-Erling Smørgrav 			warn("%s(): fcntl()", __func__);
3822f7a934cSDag-Erling Smørgrav 		close(devd_pipe);
3832f7a934cSDag-Erling Smørgrav 		return (-1);
3842f7a934cSDag-Erling Smørgrav 	}
3852f7a934cSDag-Erling Smørgrav 
3862f7a934cSDag-Erling Smørgrav 	return (devd_pipe);
387a1819ab5SNate Lawson }
388a1819ab5SNate Lawson 
389a1819ab5SNate Lawson static void
390a1819ab5SNate Lawson devd_close(void)
391a1819ab5SNate Lawson {
392a1819ab5SNate Lawson 
393a1819ab5SNate Lawson 	close(devd_pipe);
394a1819ab5SNate Lawson 	devd_pipe = -1;
39548bd7109SNate Lawson }
39648bd7109SNate Lawson 
3975883360bSNate Lawson static void
3985883360bSNate Lawson parse_mode(char *arg, int *mode, int ch)
3995883360bSNate Lawson {
4005883360bSNate Lawson 
401db20dc4dSDag-Erling Smørgrav 	if (strcmp(arg, "minimum") == 0 || strcmp(arg, "min") == 0)
4025883360bSNate Lawson 		*mode = MODE_MIN;
403db20dc4dSDag-Erling Smørgrav 	else if (strcmp(arg, "maximum") == 0 || strcmp(arg, "max") == 0)
4045883360bSNate Lawson 		*mode = MODE_MAX;
405dc70a966SDavid E. O'Brien 	else if (strcmp(arg, "adaptive") == 0 || strcmp(arg, "adp") == 0)
4065883360bSNate Lawson 		*mode = MODE_ADAPTIVE;
407dbd31977SAlexander Motin 	else if (strcmp(arg, "hiadaptive") == 0 || strcmp(arg, "hadp") == 0)
408dbd31977SAlexander Motin 		*mode = MODE_HIADAPTIVE;
4095883360bSNate Lawson 	else
4105883360bSNate Lawson 		errx(1, "bad option: -%c %s", (char)ch, optarg);
4115883360bSNate Lawson }
4125883360bSNate Lawson 
4135883360bSNate Lawson static void
414ab19351cSNate Lawson handle_sigs(int __unused sig)
415ab19351cSNate Lawson {
416a1819ab5SNate Lawson 
417ab19351cSNate Lawson 	exit_requested = 1;
418ab19351cSNate Lawson }
419ab19351cSNate Lawson 
420ab19351cSNate Lawson static void
4215883360bSNate Lawson usage(void)
4225883360bSNate Lawson {
4235883360bSNate Lawson 
4245883360bSNate Lawson 	fprintf(stderr,
42584148fd1SPawel Jakub Dawidek "usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-n mode] [-p ival] [-r %%] [-P pidfile]\n");
4265883360bSNate Lawson 	exit(1);
4275883360bSNate Lawson }
4285883360bSNate Lawson 
4295883360bSNate Lawson int
4305883360bSNate Lawson main(int argc, char * argv[])
4315883360bSNate Lawson {
4322f7a934cSDag-Erling Smørgrav 	struct timeval timeout;
4332f7a934cSDag-Erling Smørgrav 	fd_set fdset;
4342f7a934cSDag-Erling Smørgrav 	int nfds;
435a9ed1f7fSPawel Jakub Dawidek 	struct pidfh *pfh = NULL;
43684148fd1SPawel Jakub Dawidek 	const char *pidfile = NULL;
4378cb16fdbSAlexander Motin 	int freq, curfreq, initfreq, *freqs, i, j, *mwatts, numfreqs, load;
438a1819ab5SNate Lawson 	int ch, mode, mode_ac, mode_battery, mode_none;
439ab19351cSNate Lawson 	uint64_t mjoules_used;
4405883360bSNate Lawson 	size_t len;
4415883360bSNate Lawson 
4425883360bSNate Lawson 	/* Default mode for all AC states is adaptive. */
443dbd31977SAlexander Motin 	mode_ac = mode_none = MODE_HIADAPTIVE;
444dbd31977SAlexander Motin 	mode_battery = MODE_ADAPTIVE;
4455883360bSNate Lawson 	cpu_running_mark = DEFAULT_ACTIVE_PERCENT;
4465883360bSNate Lawson 	cpu_idle_mark = DEFAULT_IDLE_PERCENT;
4475883360bSNate Lawson 	poll_ival = DEFAULT_POLL_INTERVAL;
448ab19351cSNate Lawson 	mjoules_used = 0;
4495883360bSNate Lawson 	vflag = 0;
4505883360bSNate Lawson 
451c996f586SNate Lawson 	/* User must be root to control frequencies. */
452c996f586SNate Lawson 	if (geteuid() != 0)
453c996f586SNate Lawson 		errx(1, "must be root to run");
454c996f586SNate Lawson 
4558f9872ccSKevin Lo 	while ((ch = getopt(argc, argv, "a:b:i:n:p:P:r:v")) != -1)
4565883360bSNate Lawson 		switch (ch) {
4575883360bSNate Lawson 		case 'a':
4585883360bSNate Lawson 			parse_mode(optarg, &mode_ac, ch);
4595883360bSNate Lawson 			break;
4605883360bSNate Lawson 		case 'b':
4615883360bSNate Lawson 			parse_mode(optarg, &mode_battery, ch);
4625883360bSNate Lawson 			break;
4635883360bSNate Lawson 		case 'i':
4645883360bSNate Lawson 			cpu_idle_mark = atoi(optarg);
4655883360bSNate Lawson 			if (cpu_idle_mark < 0 || cpu_idle_mark > 100) {
4665883360bSNate Lawson 				warnx("%d is not a valid percent",
4675883360bSNate Lawson 				    cpu_idle_mark);
4685883360bSNate Lawson 				usage();
4695883360bSNate Lawson 			}
4705883360bSNate Lawson 			break;
4715883360bSNate Lawson 		case 'n':
4725883360bSNate Lawson 			parse_mode(optarg, &mode_none, ch);
4735883360bSNate Lawson 			break;
4745883360bSNate Lawson 		case 'p':
4755883360bSNate Lawson 			poll_ival = atoi(optarg);
4765883360bSNate Lawson 			if (poll_ival < 5) {
4775883360bSNate Lawson 				warnx("poll interval is in units of ms");
4785883360bSNate Lawson 				usage();
4795883360bSNate Lawson 			}
4805883360bSNate Lawson 			break;
48184148fd1SPawel Jakub Dawidek 		case 'P':
48284148fd1SPawel Jakub Dawidek 			pidfile = optarg;
48384148fd1SPawel Jakub Dawidek 			break;
4845883360bSNate Lawson 		case 'r':
4855883360bSNate Lawson 			cpu_running_mark = atoi(optarg);
486dbd31977SAlexander Motin 			if (cpu_running_mark <= 0 || cpu_running_mark > 100) {
4875883360bSNate Lawson 				warnx("%d is not a valid percent",
4885883360bSNate Lawson 				    cpu_running_mark);
4895883360bSNate Lawson 				usage();
4905883360bSNate Lawson 			}
4915883360bSNate Lawson 			break;
4925883360bSNate Lawson 		case 'v':
4935883360bSNate Lawson 			vflag = 1;
4945883360bSNate Lawson 			break;
4955883360bSNate Lawson 		default:
4965883360bSNate Lawson 			usage();
4975883360bSNate Lawson 		}
4985883360bSNate Lawson 
499a1819ab5SNate Lawson 	mode = mode_none;
500a1819ab5SNate Lawson 
5015883360bSNate Lawson 	/* Poll interval is in units of ms. */
5025883360bSNate Lawson 	poll_ival *= 1000;
5035883360bSNate Lawson 
5045883360bSNate Lawson 	/* Look up various sysctl MIBs. */
5055883360bSNate Lawson 	len = 2;
506dbd31977SAlexander Motin 	if (sysctlnametomib("kern.cp_times", cp_times_mib, &len))
507dbd31977SAlexander Motin 		err(1, "lookup kern.cp_times");
5085883360bSNate Lawson 	len = 4;
5095883360bSNate Lawson 	if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len))
5105883360bSNate Lawson 		err(1, "lookup freq");
5115883360bSNate Lawson 	len = 4;
5125883360bSNate Lawson 	if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len))
5135883360bSNate Lawson 		err(1, "lookup freq_levels");
5145883360bSNate Lawson 
515dbd31977SAlexander Motin 	/* Check if we can read the load and supported freqs. */
516dbd31977SAlexander Motin 	if (read_usage_times(NULL))
5175883360bSNate Lawson 		err(1, "read_usage_times");
518ab19351cSNate Lawson 	if (read_freqs(&numfreqs, &freqs, &mwatts))
5195883360bSNate Lawson 		err(1, "error reading supported CPU frequencies");
5205883360bSNate Lawson 
5215883360bSNate Lawson 	/* Run in the background unless in verbose mode. */
52284148fd1SPawel Jakub Dawidek 	if (!vflag) {
52384148fd1SPawel Jakub Dawidek 		pid_t otherpid;
52484148fd1SPawel Jakub Dawidek 
52584148fd1SPawel Jakub Dawidek 		pfh = pidfile_open(pidfile, 0600, &otherpid);
52684148fd1SPawel Jakub Dawidek 		if (pfh == NULL) {
52784148fd1SPawel Jakub Dawidek 			if (errno == EEXIST) {
52884148fd1SPawel Jakub Dawidek 				errx(1, "powerd already running, pid: %d",
52984148fd1SPawel Jakub Dawidek 				    otherpid);
53084148fd1SPawel Jakub Dawidek 			}
53184148fd1SPawel Jakub Dawidek 			warn("cannot open pid file");
53284148fd1SPawel Jakub Dawidek 		}
53372699a22SNate Lawson 		if (daemon(0, 0) != 0) {
53472699a22SNate Lawson 			warn("cannot enter daemon mode, exiting");
53572699a22SNate Lawson 			pidfile_remove(pfh);
53672699a22SNate Lawson 			exit(EXIT_FAILURE);
53772699a22SNate Lawson 
53872699a22SNate Lawson 		}
53984148fd1SPawel Jakub Dawidek 		pidfile_write(pfh);
54084148fd1SPawel Jakub Dawidek 	}
5415883360bSNate Lawson 
54233a00fc0SNate Lawson 	/* Decide whether to use ACPI or APM to read the AC line status. */
54333a00fc0SNate Lawson 	acline_init();
54433a00fc0SNate Lawson 
5452f7a934cSDag-Erling Smørgrav 	/*
5462f7a934cSDag-Erling Smørgrav 	 * Exit cleanly on signals.
5472f7a934cSDag-Erling Smørgrav 	 */
5482f7a934cSDag-Erling Smørgrav 	signal(SIGINT, handle_sigs);
5492f7a934cSDag-Erling Smørgrav 	signal(SIGTERM, handle_sigs);
5502f7a934cSDag-Erling Smørgrav 
5518cb16fdbSAlexander Motin 	freq = initfreq = get_freq();
552dbd31977SAlexander Motin 	if (freq < 1)
553dbd31977SAlexander Motin 		freq = 1;
5545883360bSNate Lawson 	/* Main loop. */
5555883360bSNate Lawson 	for (;;) {
5562f7a934cSDag-Erling Smørgrav 		FD_ZERO(&fdset);
5572f7a934cSDag-Erling Smørgrav 		if (devd_pipe >= 0) {
5582f7a934cSDag-Erling Smørgrav 			FD_SET(devd_pipe, &fdset);
5592f7a934cSDag-Erling Smørgrav 			nfds = devd_pipe + 1;
5602f7a934cSDag-Erling Smørgrav 		} else {
5612f7a934cSDag-Erling Smørgrav 			nfds = 0;
5622f7a934cSDag-Erling Smørgrav 		}
5632f7a934cSDag-Erling Smørgrav 		timeout.tv_sec = poll_ival / 1000000;
5642f7a934cSDag-Erling Smørgrav 		timeout.tv_usec = poll_ival % 1000000;
5652f7a934cSDag-Erling Smørgrav 		select(nfds, &fdset, NULL, &fdset, &timeout);
5665883360bSNate Lawson 
567ab19351cSNate Lawson 		/* If the user requested we quit, print some statistics. */
568ab19351cSNate Lawson 		if (exit_requested) {
569ab19351cSNate Lawson 			if (vflag && mjoules_used != 0)
570ab19351cSNate Lawson 				printf("total joules used: %u.%03u\n",
571ab19351cSNate Lawson 				    (u_int)(mjoules_used / 1000),
572ab19351cSNate Lawson 				    (int)mjoules_used % 1000);
573ab19351cSNate Lawson 			break;
574ab19351cSNate Lawson 		}
575ab19351cSNate Lawson 
5765883360bSNate Lawson 		/* Read the current AC status and record the mode. */
5772f7a934cSDag-Erling Smørgrav 		acline_read();
5782f7a934cSDag-Erling Smørgrav 		switch (acline_status) {
5795883360bSNate Lawson 		case SRC_AC:
5805883360bSNate Lawson 			mode = mode_ac;
5815883360bSNate Lawson 			break;
5825883360bSNate Lawson 		case SRC_BATTERY:
5835883360bSNate Lawson 			mode = mode_battery;
5845883360bSNate Lawson 			break;
5855883360bSNate Lawson 		case SRC_UNKNOWN:
5865883360bSNate Lawson 			mode = mode_none;
5875883360bSNate Lawson 			break;
5885883360bSNate Lawson 		default:
5892f7a934cSDag-Erling Smørgrav 			errx(1, "invalid AC line status %d", acline_status);
5905883360bSNate Lawson 		}
5915883360bSNate Lawson 
5925883360bSNate Lawson 		/* Read the current frequency. */
593dbd31977SAlexander Motin 		if ((curfreq = get_freq()) == 0)
594a1819ab5SNate Lawson 			continue;
595dbd31977SAlexander Motin 
596dbd31977SAlexander Motin 		i = get_freq_id(curfreq, freqs, numfreqs);
5975883360bSNate Lawson 
598ab19351cSNate Lawson 		if (vflag) {
599ab19351cSNate Lawson 			/* Keep a sum of all power actually used. */
600dbd31977SAlexander Motin 			if (mwatts[i] != -1)
601ab19351cSNate Lawson 				mjoules_used +=
602ab19351cSNate Lawson 				    (mwatts[i] * (poll_ival / 1000)) / 1000;
603ab19351cSNate Lawson 		}
604ab19351cSNate Lawson 
6055883360bSNate Lawson 		/* Always switch to the lowest frequency in min mode. */
6065883360bSNate Lawson 		if (mode == MODE_MIN) {
607dbd31977SAlexander Motin 			freq = freqs[numfreqs - 1];
608dbd31977SAlexander Motin 			if (curfreq != freq) {
6095883360bSNate Lawson 				if (vflag) {
6105883360bSNate Lawson 					printf("now operating on %s power; "
6115883360bSNate Lawson 					    "changing frequency to %d MHz\n",
612dbd31977SAlexander Motin 					    modes[acline_status], freq);
6135883360bSNate Lawson 				}
614dbd31977SAlexander Motin 				if (set_freq(freq) != 0) {
615a1819ab5SNate Lawson 					warn("error setting CPU freq %d",
616dbd31977SAlexander Motin 					    freq);
617a1819ab5SNate Lawson 					continue;
618a1819ab5SNate Lawson 				}
6195883360bSNate Lawson 			}
6205883360bSNate Lawson 			continue;
6215883360bSNate Lawson 		}
6225883360bSNate Lawson 
6235883360bSNate Lawson 		/* Always switch to the highest frequency in max mode. */
6245883360bSNate Lawson 		if (mode == MODE_MAX) {
625dbd31977SAlexander Motin 			freq = freqs[0];
626dbd31977SAlexander Motin 			if (curfreq != freq) {
6275883360bSNate Lawson 				if (vflag) {
62848bd7109SNate Lawson 					printf("now operating on %s power; "
6295883360bSNate Lawson 					    "changing frequency to %d MHz\n",
630dbd31977SAlexander Motin 					    modes[acline_status], freq);
6315883360bSNate Lawson 				}
632dbd31977SAlexander Motin 				if (set_freq(freq) != 0) {
633a1819ab5SNate Lawson 					warn("error setting CPU freq %d",
634dbd31977SAlexander Motin 				    	    freq);
635a1819ab5SNate Lawson 					continue;
636a1819ab5SNate Lawson 				}
6375883360bSNate Lawson 			}
6385883360bSNate Lawson 			continue;
6395883360bSNate Lawson 		}
6405883360bSNate Lawson 
6415883360bSNate Lawson 		/* Adaptive mode; get the current CPU usage times. */
642dbd31977SAlexander Motin 		if (read_usage_times(&load)) {
643a1819ab5SNate Lawson 			if (vflag)
644a1819ab5SNate Lawson 				warn("read_usage_times() failed");
645a1819ab5SNate Lawson 			continue;
646a1819ab5SNate Lawson 		}
6475883360bSNate Lawson 
648dbd31977SAlexander Motin 		if (mode == MODE_ADAPTIVE) {
649dbd31977SAlexander Motin 			if (load > cpu_running_mark) {
650dbd31977SAlexander Motin 				if (load > 95 || load > cpu_running_mark * 2)
651dbd31977SAlexander Motin 					freq *= 2;
652dbd31977SAlexander Motin 				else
653dbd31977SAlexander Motin 					freq = freq * load / cpu_running_mark;
654dbd31977SAlexander Motin 				if (freq > freqs[0])
655dbd31977SAlexander Motin 					freq = freqs[0];
656dbd31977SAlexander Motin 			} else if (load < cpu_idle_mark &&
657dbd31977SAlexander Motin 			    curfreq * load < freqs[get_freq_id(
658dbd31977SAlexander Motin 			    freq * 7 / 8, freqs, numfreqs)] *
659dbd31977SAlexander Motin 			    cpu_running_mark) {
660dbd31977SAlexander Motin 				freq = freq * 7 / 8;
661dbd31977SAlexander Motin 				if (freq < freqs[numfreqs - 1])
662dbd31977SAlexander Motin 					freq = freqs[numfreqs - 1];
6637065e0e9SBruno Ducrot 			}
664dbd31977SAlexander Motin 		} else { /* MODE_HIADAPTIVE */
665dbd31977SAlexander Motin 			if (load > cpu_running_mark / 2) {
666dbd31977SAlexander Motin 				if (load > 95 || load > cpu_running_mark)
667dbd31977SAlexander Motin 					freq *= 4;
668dbd31977SAlexander Motin 				else
669dbd31977SAlexander Motin 					freq = freq * load * 2 / cpu_running_mark;
670dbd31977SAlexander Motin 				if (freq > freqs[0] * 2)
671dbd31977SAlexander Motin 					freq = freqs[0] * 2;
672dbd31977SAlexander Motin 			} else if (load < cpu_idle_mark / 2 &&
673dbd31977SAlexander Motin 			    curfreq * load < freqs[get_freq_id(
674dbd31977SAlexander Motin 			    freq * 31 / 32, freqs, numfreqs)] *
675dbd31977SAlexander Motin 			    cpu_running_mark / 2) {
676dbd31977SAlexander Motin 				freq = freq * 31 / 32;
677dbd31977SAlexander Motin 				if (freq < freqs[numfreqs - 1])
678dbd31977SAlexander Motin 					freq = freqs[numfreqs - 1];
679dbd31977SAlexander Motin 			}
680dbd31977SAlexander Motin 		}
6815883360bSNate Lawson 		if (vflag) {
682dbd31977SAlexander Motin 		    printf("load %3d%%, current freq %4d MHz (%2d), wanted freq %4d MHz\n",
683dbd31977SAlexander Motin 			load, curfreq, i, freq);
6845883360bSNate Lawson 		}
685dbd31977SAlexander Motin 		j = get_freq_id(freq, freqs, numfreqs);
686dbd31977SAlexander Motin 		if (i != j) {
6875883360bSNate Lawson 			if (vflag) {
688dbd31977SAlexander Motin 				printf("changing clock"
6895883360bSNate Lawson 				    " speed from %d MHz to %d MHz\n",
690dbd31977SAlexander Motin 				    freqs[i], freqs[j]);
6915883360bSNate Lawson 			}
692dbd31977SAlexander Motin 			if (set_freq(freqs[j]))
693a1819ab5SNate Lawson 				warn("error setting CPU frequency %d",
694dbd31977SAlexander Motin 				    freqs[j]);
6955883360bSNate Lawson 		}
6965883360bSNate Lawson 	}
6978cb16fdbSAlexander Motin 	if (set_freq(initfreq))
6988cb16fdbSAlexander Motin 		warn("error setting CPU frequency %d", initfreq);
699ab19351cSNate Lawson 	free(freqs);
700ab19351cSNate Lawson 	free(mwatts);
701a1819ab5SNate Lawson 	devd_close();
70284148fd1SPawel Jakub Dawidek 	if (!vflag)
70384148fd1SPawel Jakub Dawidek 		pidfile_remove(pfh);
7045883360bSNate Lawson 
7055883360bSNate Lawson 	exit(0);
7065883360bSNate Lawson }
707