1142577Snjl/*-
2142577Snjl * Copyright (c) 2004 Colin Percival
3142577Snjl * Copyright (c) 2005 Nate Lawson
4142577Snjl * All rights reserved.
5142577Snjl *
6142577Snjl * Redistribution and use in source and binary forms, with or without
7142577Snjl * modification, are permitted providing that the following conditions
8142577Snjl * are met:
9142577Snjl * 1. Redistributions of source code must retain the above copyright
10142577Snjl *    notice, this list of conditions and the following disclaimer.
11142577Snjl * 2. Redistributions in binary form must reproduce the above copyright
12142577Snjl *    notice, this list of conditions and the following disclaimer in the
13142577Snjl *    documentation and/or other materials provided with the distribution.
14142577Snjl *
15151461Snjl * THIS SOFTWARE IS PROVIDED BY THE AUTHOR``AS IS'' AND ANY EXPRESS OR
16151461Snjl * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17142577Snjl * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18151461Snjl * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
19142577Snjl * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20142577Snjl * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21142577Snjl * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22142577Snjl * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
23151461Snjl * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24151461Snjl * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25142577Snjl * POSSIBILITY OF SUCH DAMAGE.
26142577Snjl */
27142577Snjl
28142577Snjl#include <sys/cdefs.h>
29142577Snjl__FBSDID("$FreeBSD: stable/10/usr.sbin/powerd/powerd.c 342983 2019-01-13 02:27:10Z avos $");
30142577Snjl
31149428Spjd#include <sys/param.h>
32149428Spjd#include <sys/ioctl.h>
33149428Spjd#include <sys/sysctl.h>
34149428Spjd#include <sys/resource.h>
35151461Snjl#include <sys/socket.h>
36153999Sdes#include <sys/time.h>
37151461Snjl#include <sys/un.h>
38149428Spjd
39142577Snjl#include <err.h>
40148139Sume#include <errno.h>
41142577Snjl#include <fcntl.h>
42149428Spjd#include <libutil.h>
43144873Snjl#include <signal.h>
44142577Snjl#include <stdio.h>
45142577Snjl#include <stdlib.h>
46142577Snjl#include <string.h>
47234070Srmh#include <sysexits.h>
48142577Snjl#include <unistd.h>
49142577Snjl
50203482Simp#ifdef __i386__
51203482Simp#define USE_APM
52203482Simp#endif
53203482Simp
54153999Sdes#ifdef USE_APM
55142577Snjl#include <machine/apm_bios.h>
56142602Smarcel#endif
57142577Snjl
58185050Smav#define DEFAULT_ACTIVE_PERCENT	75
59185050Smav#define DEFAULT_IDLE_PERCENT	50
60185050Smav#define DEFAULT_POLL_INTERVAL	250	/* Poll interval in milliseconds */
61142577Snjl
62153999Sdestypedef enum {
63142577Snjl	MODE_MIN,
64142577Snjl	MODE_ADAPTIVE,
65185050Smav	MODE_HIADAPTIVE,
66142577Snjl	MODE_MAX,
67153999Sdes} modes_t;
68142577Snjl
69153999Sdestypedef enum {
70142577Snjl	SRC_AC,
71142577Snjl	SRC_BATTERY,
72142577Snjl	SRC_UNKNOWN,
73153999Sdes} power_src_t;
74142577Snjl
75228416Sedstatic const char *modes[] = {
76142577Snjl	"AC",
77142577Snjl	"battery",
78142577Snjl	"unknown"
79142577Snjl};
80142577Snjl
81142577Snjl#define ACPIAC		"hw.acpi.acline"
82193161Snwhitehorn#define PMUAC		"dev.pmu.0.acline"
83142577Snjl#define APMDEV		"/dev/apm"
84151461Snjl#define DEVDPIPE	"/var/run/devd.pipe"
85151461Snjl#define DEVCTL_MAXBUF	1024
86142577Snjl
87185050Smavstatic int	read_usage_times(int *load);
88211415Sbrucecstatic int	read_freqs(int *numfreqs, int **freqs, int **power,
89211415Sbrucec		    int minfreq, int maxfreq);
90142577Snjlstatic int	set_freq(int freq);
91142605Snjlstatic void	acline_init(void);
92153999Sdesstatic void	acline_read(void);
93151461Snjlstatic int	devd_init(void);
94151461Snjlstatic void	devd_close(void);
95144873Snjlstatic void	handle_sigs(int sig);
96142577Snjlstatic void	parse_mode(char *arg, int *mode, int ch);
97142577Snjlstatic void	usage(void);
98142577Snjl
99142577Snjl/* Sysctl data structures. */
100185050Smavstatic int	cp_times_mib[2];
101142577Snjlstatic int	freq_mib[4];
102142577Snjlstatic int	levels_mib[4];
103193161Snwhitehornstatic int	acline_mib[4];
104193161Snwhitehornstatic size_t	acline_mib_len;
105142577Snjl
106142577Snjl/* Configuration */
107142577Snjlstatic int	cpu_running_mark;
108142577Snjlstatic int	cpu_idle_mark;
109142577Snjlstatic int	poll_ival;
110151461Snjlstatic int	vflag;
111142577Snjl
112153999Sdesstatic volatile sig_atomic_t exit_requested;
113153999Sdesstatic power_src_t acline_status;
114342983Savostypedef enum {
115153999Sdes	ac_none,
116193161Snwhitehorn	ac_sysctl,
117153999Sdes	ac_acpi_devd,
118153999Sdes#ifdef USE_APM
119153999Sdes	ac_apm,
120153999Sdes#endif
121342983Savos} acline_mode_t;
122342983Savosstatic acline_mode_t acline_mode;
123342983Savosstatic acline_mode_t acline_mode_user = ac_none;
124153999Sdes#ifdef USE_APM
125153999Sdesstatic int	apm_fd = -1;
126153999Sdes#endif
127153999Sdesstatic int	devd_pipe = -1;
128142605Snjl
129153999Sdes#define DEVD_RETRY_INTERVAL 60 /* seconds */
130153999Sdesstatic struct timeval tried_devd;
131153999Sdes
132280751Smav/*
133280751Smav * This function returns summary load of all CPUs.  It was made so
134280751Smav * intentionally to not reduce performance in scenarios when several
135280751Smav * threads are processing requests as a pipeline -- running one at
136280751Smav * a time on different CPUs and waiting for each other.
137280751Smav */
138142577Snjlstatic int
139185050Smavread_usage_times(int *load)
140142577Snjl{
141185050Smav	static long *cp_times = NULL, *cp_times_old = NULL;
142185050Smav	static int ncpus = 0;
143185050Smav	size_t cp_times_len;
144185050Smav	int error, cpu, i, total;
145142577Snjl
146185050Smav	if (cp_times == NULL) {
147185050Smav		cp_times_len = 0;
148185050Smav		error = sysctl(cp_times_mib, 2, NULL, &cp_times_len, NULL, 0);
149185050Smav		if (error)
150185050Smav			return (error);
151185050Smav		if ((cp_times = malloc(cp_times_len)) == NULL)
152185050Smav			return (errno);
153185050Smav		if ((cp_times_old = malloc(cp_times_len)) == NULL) {
154185050Smav			free(cp_times);
155185050Smav			cp_times = NULL;
156185050Smav			return (errno);
157185050Smav		}
158185050Smav		ncpus = cp_times_len / (sizeof(long) * CPUSTATES);
159185050Smav	}
160185050Smav
161185050Smav	cp_times_len = sizeof(long) * CPUSTATES * ncpus;
162185050Smav	error = sysctl(cp_times_mib, 2, cp_times, &cp_times_len, NULL, 0);
163142577Snjl	if (error)
164142577Snjl		return (error);
165252713Swblock
166185050Smav	if (load) {
167185050Smav		*load = 0;
168185050Smav		for (cpu = 0; cpu < ncpus; cpu++) {
169185050Smav			total = 0;
170185050Smav			for (i = 0; i < CPUSTATES; i++) {
171185050Smav			    total += cp_times[cpu * CPUSTATES + i] -
172185050Smav				cp_times_old[cpu * CPUSTATES + i];
173185050Smav			}
174185050Smav			if (total == 0)
175185050Smav				continue;
176252713Swblock			*load += 100 - (cp_times[cpu * CPUSTATES + CP_IDLE] -
177185050Smav			    cp_times_old[cpu * CPUSTATES + CP_IDLE]) * 100 / total;
178185050Smav		}
179185050Smav	}
180142577Snjl
181185050Smav	memcpy(cp_times_old, cp_times, cp_times_len);
182142577Snjl
183142577Snjl	return (0);
184142577Snjl}
185142577Snjl
186142577Snjlstatic int
187211415Sbrucecread_freqs(int *numfreqs, int **freqs, int **power, int minfreq, int maxfreq)
188142577Snjl{
189142577Snjl	char *freqstr, *p, *q;
190211415Sbrucec	int i, j;
191142577Snjl	size_t len = 0;
192142577Snjl
193142577Snjl	if (sysctl(levels_mib, 4, NULL, &len, NULL, 0))
194142577Snjl		return (-1);
195142577Snjl	if ((freqstr = malloc(len)) == NULL)
196142577Snjl		return (-1);
197142577Snjl	if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0))
198142577Snjl		return (-1);
199142577Snjl
200142577Snjl	*numfreqs = 1;
201142577Snjl	for (p = freqstr; *p != '\0'; p++)
202142577Snjl		if (*p == ' ')
203142577Snjl			(*numfreqs)++;
204142577Snjl
205142577Snjl	if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) {
206142577Snjl		free(freqstr);
207142577Snjl		return (-1);
208142577Snjl	}
209144873Snjl	if ((*power = malloc(*numfreqs * sizeof(int))) == NULL) {
210144873Snjl		free(freqstr);
211144873Snjl		free(*freqs);
212144873Snjl		return (-1);
213144873Snjl	}
214211415Sbrucec	for (i = 0, j = 0, p = freqstr; i < *numfreqs; i++) {
215142577Snjl		q = strchr(p, ' ');
216142577Snjl		if (q != NULL)
217142577Snjl			*q = '\0';
218211415Sbrucec		if (sscanf(p, "%d/%d", &(*freqs)[j], &(*power)[i]) != 2) {
219142577Snjl			free(freqstr);
220142577Snjl			free(*freqs);
221144873Snjl			free(*power);
222142577Snjl			return (-1);
223142577Snjl		}
224211415Sbrucec		if (((*freqs)[j] >= minfreq || minfreq == -1) &&
225211415Sbrucec		    ((*freqs)[j] <= maxfreq || maxfreq == -1))
226211415Sbrucec			j++;
227142577Snjl		p = q + 1;
228142577Snjl	}
229142577Snjl
230211415Sbrucec	*numfreqs = j;
231211415Sbrucec	if ((*freqs = realloc(*freqs, *numfreqs * sizeof(int))) == NULL) {
232211415Sbrucec		free(freqstr);
233211415Sbrucec		free(*freqs);
234211415Sbrucec		free(*power);
235211415Sbrucec		return (-1);
236211415Sbrucec	}
237211415Sbrucec
238142577Snjl	free(freqstr);
239142577Snjl	return (0);
240142577Snjl}
241142577Snjl
242142577Snjlstatic int
243185050Smavget_freq(void)
244185050Smav{
245185050Smav	size_t len;
246185050Smav	int curfreq;
247252713Swblock
248185050Smav	len = sizeof(curfreq);
249185050Smav	if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0) != 0) {
250185050Smav		if (vflag)
251185050Smav			warn("error reading current CPU frequency");
252185050Smav		curfreq = 0;
253185050Smav	}
254185050Smav	return (curfreq);
255185050Smav}
256185050Smav
257185050Smavstatic int
258142577Snjlset_freq(int freq)
259142577Snjl{
260142577Snjl
261148139Sume	if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq))) {
262148139Sume		if (errno != EPERM)
263148139Sume			return (-1);
264148139Sume	}
265142577Snjl
266142577Snjl	return (0);
267142577Snjl}
268142577Snjl
269185050Smavstatic int
270185050Smavget_freq_id(int freq, int *freqs, int numfreqs)
271185050Smav{
272185050Smav	int i = 1;
273252713Swblock
274185050Smav	while (i < numfreqs) {
275185050Smav		if (freqs[i] < freq)
276185050Smav			break;
277185050Smav		i++;
278185050Smav	}
279185050Smav	return (i - 1);
280185050Smav}
281185050Smav
282142605Snjl/*
283142605Snjl * Try to use ACPI to find the AC line status.  If this fails, fall back
284153999Sdes * to APM.  If nothing succeeds, we'll just run in default mode.
285142605Snjl */
286142577Snjlstatic void
287201227Sedacline_init(void)
288142605Snjl{
289342983Savos	int skip_source_check;
290342983Savos
291193161Snwhitehorn	acline_mib_len = 4;
292262474Sbrueffer	acline_status = SRC_UNKNOWN;
293342983Savos	skip_source_check = (acline_mode_user == ac_none ||
294342983Savos			     acline_mode_user == ac_acpi_devd);
295142605Snjl
296342983Savos	if ((skip_source_check || acline_mode_user == ac_sysctl) &&
297342983Savos	    sysctlnametomib(ACPIAC, acline_mib, &acline_mib_len) == 0) {
298193161Snwhitehorn		acline_mode = ac_sysctl;
299153999Sdes		if (vflag)
300153999Sdes			warnx("using sysctl for AC line status");
301193161Snwhitehorn#if __powerpc__
302342983Savos	} else if ((skip_source_check || acline_mode_user == ac_sysctl) &&
303342983Savos		   sysctlnametomib(PMUAC, acline_mib, &acline_mib_len) == 0) {
304193161Snwhitehorn		acline_mode = ac_sysctl;
305193161Snwhitehorn		if (vflag)
306193161Snwhitehorn			warnx("using sysctl for AC line status");
307193161Snwhitehorn#endif
308153999Sdes#ifdef USE_APM
309342983Savos	} else if ((skip_source_check || acline_mode_user == ac_apm) &&
310342983Savos		   (apm_fd = open(APMDEV, O_RDONLY)) >= 0) {
311153999Sdes		if (vflag)
312153999Sdes			warnx("using APM for AC line status");
313153999Sdes		acline_mode = ac_apm;
314153999Sdes#endif
315142605Snjl	} else {
316153999Sdes		warnx("unable to determine AC line status");
317153999Sdes		acline_mode = ac_none;
318142605Snjl	}
319142605Snjl}
320142605Snjl
321153999Sdesstatic void
322153999Sdesacline_read(void)
323142605Snjl{
324153999Sdes	if (acline_mode == ac_acpi_devd) {
325153999Sdes		char buf[DEVCTL_MAXBUF], *ptr;
326153999Sdes		ssize_t rlen;
327153999Sdes		int notify;
328142605Snjl
329153999Sdes		rlen = read(devd_pipe, buf, sizeof(buf));
330153999Sdes		if (rlen == 0 || (rlen < 0 && errno != EWOULDBLOCK)) {
331153999Sdes			if (vflag)
332153999Sdes				warnx("lost devd connection, switching to sysctl");
333153999Sdes			devd_close();
334193161Snwhitehorn			acline_mode = ac_sysctl;
335153999Sdes			/* FALLTHROUGH */
336153999Sdes		}
337153999Sdes		if (rlen > 0 &&
338153999Sdes		    (ptr = strstr(buf, "system=ACPI")) != NULL &&
339153999Sdes		    (ptr = strstr(ptr, "subsystem=ACAD")) != NULL &&
340153999Sdes		    (ptr = strstr(ptr, "notify=")) != NULL &&
341153999Sdes		    sscanf(ptr, "notify=%x", &notify) == 1)
342153999Sdes			acline_status = (notify ? SRC_AC : SRC_BATTERY);
343153999Sdes	}
344193161Snwhitehorn	if (acline_mode == ac_sysctl) {
345153999Sdes		int acline;
346153999Sdes		size_t len;
347151461Snjl
348153999Sdes		len = sizeof(acline);
349193161Snwhitehorn		if (sysctl(acline_mib, acline_mib_len, &acline, &len,
350193161Snwhitehorn		    NULL, 0) == 0)
351153999Sdes			acline_status = (acline ? SRC_AC : SRC_BATTERY);
352153999Sdes		else
353153999Sdes			acline_status = SRC_UNKNOWN;
354153999Sdes	}
355153999Sdes#ifdef USE_APM
356153999Sdes	if (acline_mode == ac_apm) {
357153999Sdes		struct apm_info info;
358153999Sdes
359153999Sdes		if (ioctl(apm_fd, APMIO_GETINFO, &info) == 0) {
360153999Sdes			acline_status = (info.ai_acline ? SRC_AC : SRC_BATTERY);
361153999Sdes		} else {
362153999Sdes			close(apm_fd);
363153999Sdes			apm_fd = -1;
364153999Sdes			acline_mode = ac_none;
365153999Sdes			acline_status = SRC_UNKNOWN;
366153999Sdes		}
367153999Sdes	}
368142605Snjl#endif
369153999Sdes	/* try to (re)connect to devd */
370342983Savos#ifdef USE_APM
371342983Savos	if ((acline_mode == ac_sysctl &&
372342983Savos	    (acline_mode_user == ac_none ||
373342983Savos	     acline_mode_user == ac_acpi_devd)) ||
374342983Savos	    (acline_mode == ac_apm &&
375342983Savos	     acline_mode_user == ac_acpi_devd)) {
376342983Savos#else
377342983Savos	if (acline_mode == ac_sysctl &&
378342983Savos	    (acline_mode_user == ac_none ||
379342983Savos	     acline_mode_user == ac_acpi_devd)) {
380342983Savos#endif
381153999Sdes		struct timeval now;
382142605Snjl
383153999Sdes		gettimeofday(&now, NULL);
384153999Sdes		if (now.tv_sec > tried_devd.tv_sec + DEVD_RETRY_INTERVAL) {
385153999Sdes			if (devd_init() >= 0) {
386153999Sdes				if (vflag)
387153999Sdes					warnx("using devd for AC line status");
388153999Sdes				acline_mode = ac_acpi_devd;
389153999Sdes			}
390153999Sdes			tried_devd = now;
391153999Sdes		}
392153999Sdes	}
393151461Snjl}
394151461Snjl
395151461Snjlstatic int
396151461Snjldevd_init(void)
397151461Snjl{
398151461Snjl	struct sockaddr_un devd_addr;
399151461Snjl
400151461Snjl	bzero(&devd_addr, sizeof(devd_addr));
401153999Sdes	if ((devd_pipe = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) {
402151461Snjl		if (vflag)
403153999Sdes			warn("%s(): socket()", __func__);
404151461Snjl		return (-1);
405142605Snjl	}
406142605Snjl
407151461Snjl	devd_addr.sun_family = PF_LOCAL;
408151461Snjl	strlcpy(devd_addr.sun_path, DEVDPIPE, sizeof(devd_addr.sun_path));
409153999Sdes	if (connect(devd_pipe, (struct sockaddr *)&devd_addr,
410151461Snjl	    sizeof(devd_addr)) == -1) {
411153999Sdes		if (vflag)
412153999Sdes			warn("%s(): connect()", __func__);
413153999Sdes		close(devd_pipe);
414153999Sdes		devd_pipe = -1;
415151461Snjl		return (-1);
416151461Snjl	}
417151461Snjl
418153999Sdes	if (fcntl(devd_pipe, F_SETFL, O_NONBLOCK) == -1) {
419153999Sdes		if (vflag)
420153999Sdes			warn("%s(): fcntl()", __func__);
421153999Sdes		close(devd_pipe);
422153999Sdes		return (-1);
423153999Sdes	}
424153999Sdes
425153999Sdes	return (devd_pipe);
426142605Snjl}
427142605Snjl
428142605Snjlstatic void
429151461Snjldevd_close(void)
430151461Snjl{
431151461Snjl
432151461Snjl	close(devd_pipe);
433153999Sdes	devd_pipe = -1;
434151461Snjl}
435151461Snjl
436151461Snjlstatic void
437142577Snjlparse_mode(char *arg, int *mode, int ch)
438142577Snjl{
439142577Snjl
440150564Sdes	if (strcmp(arg, "minimum") == 0 || strcmp(arg, "min") == 0)
441142577Snjl		*mode = MODE_MIN;
442150564Sdes	else if (strcmp(arg, "maximum") == 0 || strcmp(arg, "max") == 0)
443142577Snjl		*mode = MODE_MAX;
444179932Sobrien	else if (strcmp(arg, "adaptive") == 0 || strcmp(arg, "adp") == 0)
445142577Snjl		*mode = MODE_ADAPTIVE;
446185050Smav	else if (strcmp(arg, "hiadaptive") == 0 || strcmp(arg, "hadp") == 0)
447185050Smav		*mode = MODE_HIADAPTIVE;
448142577Snjl	else
449142577Snjl		errx(1, "bad option: -%c %s", (char)ch, optarg);
450142577Snjl}
451142577Snjl
452142577Snjlstatic void
453342983Savosparse_acline_mode(char *arg, int ch)
454342983Savos{
455342983Savos	if (strcmp(arg, "sysctl") == 0)
456342983Savos		acline_mode_user = ac_sysctl;
457342983Savos	else if (strcmp(arg, "devd") == 0)
458342983Savos		acline_mode_user = ac_acpi_devd;
459342983Savos#ifdef USE_APM
460342983Savos	else if (strcmp(arg, "apm") == 0)
461342983Savos		acline_mode_user = ac_apm;
462342983Savos#endif
463342983Savos	else
464342983Savos		errx(1, "bad option: -%c %s", (char)ch, optarg);
465342983Savos}
466342983Savos
467342983Savosstatic void
468144873Snjlhandle_sigs(int __unused sig)
469144873Snjl{
470151461Snjl
471144873Snjl	exit_requested = 1;
472144873Snjl}
473144873Snjl
474144873Snjlstatic void
475142577Snjlusage(void)
476142577Snjl{
477142577Snjl
478142577Snjl	fprintf(stderr,
479342983Savos"usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-m freq] [-M freq] [-n mode] [-p ival] [-r %%] [-s source] [-P pidfile]\n");
480142577Snjl	exit(1);
481142577Snjl}
482142577Snjl
483142577Snjlint
484142577Snjlmain(int argc, char * argv[])
485142577Snjl{
486153999Sdes	struct timeval timeout;
487153999Sdes	fd_set fdset;
488153999Sdes	int nfds;
489149437Spjd	struct pidfh *pfh = NULL;
490149428Spjd	const char *pidfile = NULL;
491185053Smav	int freq, curfreq, initfreq, *freqs, i, j, *mwatts, numfreqs, load;
492211415Sbrucec	int minfreq = -1, maxfreq = -1;
493209234Smav	int ch, mode, mode_ac, mode_battery, mode_none, idle, to;
494144873Snjl	uint64_t mjoules_used;
495142577Snjl	size_t len;
496142577Snjl
497142577Snjl	/* Default mode for all AC states is adaptive. */
498185050Smav	mode_ac = mode_none = MODE_HIADAPTIVE;
499185050Smav	mode_battery = MODE_ADAPTIVE;
500142577Snjl	cpu_running_mark = DEFAULT_ACTIVE_PERCENT;
501142577Snjl	cpu_idle_mark = DEFAULT_IDLE_PERCENT;
502142577Snjl	poll_ival = DEFAULT_POLL_INTERVAL;
503144873Snjl	mjoules_used = 0;
504142577Snjl	vflag = 0;
505142577Snjl
506148395Snjl	/* User must be root to control frequencies. */
507148395Snjl	if (geteuid() != 0)
508148395Snjl		errx(1, "must be root to run");
509148395Snjl
510342983Savos	while ((ch = getopt(argc, argv, "a:b:i:m:M:n:p:P:r:s:v")) != -1)
511142577Snjl		switch (ch) {
512142577Snjl		case 'a':
513142577Snjl			parse_mode(optarg, &mode_ac, ch);
514142577Snjl			break;
515142577Snjl		case 'b':
516142577Snjl			parse_mode(optarg, &mode_battery, ch);
517142577Snjl			break;
518342983Savos		case 's':
519342983Savos			parse_acline_mode(optarg, ch);
520342983Savos			break;
521142577Snjl		case 'i':
522142577Snjl			cpu_idle_mark = atoi(optarg);
523142577Snjl			if (cpu_idle_mark < 0 || cpu_idle_mark > 100) {
524142577Snjl				warnx("%d is not a valid percent",
525142577Snjl				    cpu_idle_mark);
526142577Snjl				usage();
527142577Snjl			}
528142577Snjl			break;
529211415Sbrucec		case 'm':
530211415Sbrucec			minfreq = atoi(optarg);
531211415Sbrucec			if (minfreq < 0) {
532211415Sbrucec				warnx("%d is not a valid CPU frequency",
533211415Sbrucec				    minfreq);
534211415Sbrucec				usage();
535211415Sbrucec			}
536211415Sbrucec			break;
537211415Sbrucec		case 'M':
538211415Sbrucec			maxfreq = atoi(optarg);
539211415Sbrucec			if (maxfreq < 0) {
540211415Sbrucec				warnx("%d is not a valid CPU frequency",
541211415Sbrucec				    maxfreq);
542211415Sbrucec				usage();
543211415Sbrucec			}
544211415Sbrucec			break;
545142577Snjl		case 'n':
546142577Snjl			parse_mode(optarg, &mode_none, ch);
547142577Snjl			break;
548142577Snjl		case 'p':
549142577Snjl			poll_ival = atoi(optarg);
550142577Snjl			if (poll_ival < 5) {
551142577Snjl				warnx("poll interval is in units of ms");
552142577Snjl				usage();
553142577Snjl			}
554142577Snjl			break;
555149428Spjd		case 'P':
556149428Spjd			pidfile = optarg;
557149428Spjd			break;
558142577Snjl		case 'r':
559142577Snjl			cpu_running_mark = atoi(optarg);
560185050Smav			if (cpu_running_mark <= 0 || cpu_running_mark > 100) {
561142577Snjl				warnx("%d is not a valid percent",
562142577Snjl				    cpu_running_mark);
563142577Snjl				usage();
564142577Snjl			}
565142577Snjl			break;
566142577Snjl		case 'v':
567142577Snjl			vflag = 1;
568142577Snjl			break;
569142577Snjl		default:
570142577Snjl			usage();
571142577Snjl		}
572142577Snjl
573151461Snjl	mode = mode_none;
574151461Snjl
575142577Snjl	/* Poll interval is in units of ms. */
576142577Snjl	poll_ival *= 1000;
577142577Snjl
578142577Snjl	/* Look up various sysctl MIBs. */
579142577Snjl	len = 2;
580185050Smav	if (sysctlnametomib("kern.cp_times", cp_times_mib, &len))
581185050Smav		err(1, "lookup kern.cp_times");
582142577Snjl	len = 4;
583142577Snjl	if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len))
584234070Srmh		err(EX_UNAVAILABLE, "no cpufreq(4) support -- aborting");
585142577Snjl	len = 4;
586142577Snjl	if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len))
587142577Snjl		err(1, "lookup freq_levels");
588142577Snjl
589185050Smav	/* Check if we can read the load and supported freqs. */
590185050Smav	if (read_usage_times(NULL))
591142577Snjl		err(1, "read_usage_times");
592211415Sbrucec	if (read_freqs(&numfreqs, &freqs, &mwatts, minfreq, maxfreq))
593142577Snjl		err(1, "error reading supported CPU frequencies");
594211415Sbrucec	if (numfreqs == 0)
595211415Sbrucec		errx(1, "no CPU frequencies in user-specified range");
596142577Snjl
597142577Snjl	/* Run in the background unless in verbose mode. */
598149428Spjd	if (!vflag) {
599149428Spjd		pid_t otherpid;
600149428Spjd
601149428Spjd		pfh = pidfile_open(pidfile, 0600, &otherpid);
602149428Spjd		if (pfh == NULL) {
603149428Spjd			if (errno == EEXIST) {
604149428Spjd				errx(1, "powerd already running, pid: %d",
605149428Spjd				    otherpid);
606149428Spjd			}
607149428Spjd			warn("cannot open pid file");
608149428Spjd		}
609151491Snjl		if (daemon(0, 0) != 0) {
610151491Snjl			warn("cannot enter daemon mode, exiting");
611151491Snjl			pidfile_remove(pfh);
612151491Snjl			exit(EXIT_FAILURE);
613151491Snjl
614151491Snjl		}
615149428Spjd		pidfile_write(pfh);
616149428Spjd	}
617142577Snjl
618151628Snjl	/* Decide whether to use ACPI or APM to read the AC line status. */
619151628Snjl	acline_init();
620151628Snjl
621153999Sdes	/*
622153999Sdes	 * Exit cleanly on signals.
623153999Sdes	 */
624153999Sdes	signal(SIGINT, handle_sigs);
625153999Sdes	signal(SIGTERM, handle_sigs);
626153999Sdes
627209234Smav	freq = initfreq = curfreq = get_freq();
628209234Smav	i = get_freq_id(curfreq, freqs, numfreqs);
629185050Smav	if (freq < 1)
630185050Smav		freq = 1;
631211415Sbrucec
632211415Sbrucec	/*
633211415Sbrucec	 * If we are in adaptive mode and the current frequency is outside the
634211415Sbrucec	 * user-defined range, adjust it to be within the user-defined range.
635211415Sbrucec	 */
636211415Sbrucec	acline_read();
637211415Sbrucec	if (acline_status > SRC_UNKNOWN)
638211415Sbrucec		errx(1, "invalid AC line status %d", acline_status);
639211415Sbrucec	if ((acline_status == SRC_AC &&
640211415Sbrucec	    (mode_ac == MODE_ADAPTIVE || mode_ac == MODE_HIADAPTIVE)) ||
641211415Sbrucec	    (acline_status == SRC_BATTERY &&
642211415Sbrucec	    (mode_battery == MODE_ADAPTIVE || mode_battery == MODE_HIADAPTIVE)) ||
643211415Sbrucec	    (acline_status == SRC_UNKNOWN &&
644211415Sbrucec	    (mode_none == MODE_ADAPTIVE || mode_none == MODE_HIADAPTIVE))) {
645211415Sbrucec		/* Read the current frequency. */
646211415Sbrucec		len = sizeof(curfreq);
647211415Sbrucec		if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0) != 0) {
648211415Sbrucec			if (vflag)
649211415Sbrucec				warn("error reading current CPU frequency");
650211415Sbrucec		}
651211415Sbrucec		if (curfreq < freqs[numfreqs - 1]) {
652211415Sbrucec			if (vflag) {
653211415Sbrucec				printf("CPU frequency is below user-defined "
654211415Sbrucec				    "minimum; changing frequency to %d "
655211415Sbrucec				    "MHz\n", freqs[numfreqs - 1]);
656211415Sbrucec			}
657211415Sbrucec			if (set_freq(freqs[numfreqs - 1]) != 0) {
658211415Sbrucec				warn("error setting CPU freq %d",
659211415Sbrucec				    freqs[numfreqs - 1]);
660211415Sbrucec			}
661211415Sbrucec		} else if (curfreq > freqs[0]) {
662211415Sbrucec			if (vflag) {
663211415Sbrucec				printf("CPU frequency is above user-defined "
664211415Sbrucec				    "maximum; changing frequency to %d "
665211415Sbrucec				    "MHz\n", freqs[0]);
666211415Sbrucec			}
667211415Sbrucec			if (set_freq(freqs[0]) != 0) {
668211415Sbrucec				warn("error setting CPU freq %d",
669211415Sbrucec				    freqs[0]);
670211415Sbrucec			}
671211415Sbrucec		}
672211415Sbrucec	}
673211415Sbrucec
674209234Smav	idle = 0;
675142577Snjl	/* Main loop. */
676142577Snjl	for (;;) {
677153999Sdes		FD_ZERO(&fdset);
678153999Sdes		if (devd_pipe >= 0) {
679153999Sdes			FD_SET(devd_pipe, &fdset);
680153999Sdes			nfds = devd_pipe + 1;
681153999Sdes		} else {
682153999Sdes			nfds = 0;
683153999Sdes		}
684209234Smav		if (mode == MODE_HIADAPTIVE || idle < 120)
685209234Smav			to = poll_ival;
686209234Smav		else if (idle < 360)
687209234Smav			to = poll_ival * 2;
688209234Smav		else
689209234Smav			to = poll_ival * 4;
690209234Smav		timeout.tv_sec = to / 1000000;
691209234Smav		timeout.tv_usec = to % 1000000;
692153999Sdes		select(nfds, &fdset, NULL, &fdset, &timeout);
693142577Snjl
694144873Snjl		/* If the user requested we quit, print some statistics. */
695144873Snjl		if (exit_requested) {
696144873Snjl			if (vflag && mjoules_used != 0)
697144873Snjl				printf("total joules used: %u.%03u\n",
698144873Snjl				    (u_int)(mjoules_used / 1000),
699144873Snjl				    (int)mjoules_used % 1000);
700144873Snjl			break;
701144873Snjl		}
702144873Snjl
703142577Snjl		/* Read the current AC status and record the mode. */
704153999Sdes		acline_read();
705153999Sdes		switch (acline_status) {
706142577Snjl		case SRC_AC:
707142577Snjl			mode = mode_ac;
708142577Snjl			break;
709142577Snjl		case SRC_BATTERY:
710142577Snjl			mode = mode_battery;
711142577Snjl			break;
712142577Snjl		case SRC_UNKNOWN:
713142577Snjl			mode = mode_none;
714142577Snjl			break;
715142577Snjl		default:
716153999Sdes			errx(1, "invalid AC line status %d", acline_status);
717142577Snjl		}
718142577Snjl
719142577Snjl		/* Read the current frequency. */
720209234Smav		if (idle % 32 == 0) {
721209234Smav			if ((curfreq = get_freq()) == 0)
722209234Smav				continue;
723209234Smav			i = get_freq_id(curfreq, freqs, numfreqs);
724209234Smav		}
725209234Smav		idle++;
726144873Snjl		if (vflag) {
727144873Snjl			/* Keep a sum of all power actually used. */
728185050Smav			if (mwatts[i] != -1)
729144873Snjl				mjoules_used +=
730144873Snjl				    (mwatts[i] * (poll_ival / 1000)) / 1000;
731144873Snjl		}
732144873Snjl
733142577Snjl		/* Always switch to the lowest frequency in min mode. */
734142577Snjl		if (mode == MODE_MIN) {
735185050Smav			freq = freqs[numfreqs - 1];
736185050Smav			if (curfreq != freq) {
737142577Snjl				if (vflag) {
738142577Snjl					printf("now operating on %s power; "
739142577Snjl					    "changing frequency to %d MHz\n",
740185050Smav					    modes[acline_status], freq);
741142577Snjl				}
742209234Smav				idle = 0;
743185050Smav				if (set_freq(freq) != 0) {
744151461Snjl					warn("error setting CPU freq %d",
745185050Smav					    freq);
746151461Snjl					continue;
747151461Snjl				}
748142577Snjl			}
749142577Snjl			continue;
750142577Snjl		}
751142577Snjl
752142577Snjl		/* Always switch to the highest frequency in max mode. */
753142577Snjl		if (mode == MODE_MAX) {
754185050Smav			freq = freqs[0];
755185050Smav			if (curfreq != freq) {
756142577Snjl				if (vflag) {
757142605Snjl					printf("now operating on %s power; "
758142577Snjl					    "changing frequency to %d MHz\n",
759185050Smav					    modes[acline_status], freq);
760142577Snjl				}
761209234Smav				idle = 0;
762185050Smav				if (set_freq(freq) != 0) {
763151461Snjl					warn("error setting CPU freq %d",
764252713Swblock					    freq);
765151461Snjl					continue;
766151461Snjl				}
767142577Snjl			}
768142577Snjl			continue;
769142577Snjl		}
770142577Snjl
771142577Snjl		/* Adaptive mode; get the current CPU usage times. */
772185050Smav		if (read_usage_times(&load)) {
773151461Snjl			if (vflag)
774151461Snjl				warn("read_usage_times() failed");
775151461Snjl			continue;
776151461Snjl		}
777252713Swblock
778185050Smav		if (mode == MODE_ADAPTIVE) {
779185050Smav			if (load > cpu_running_mark) {
780185050Smav				if (load > 95 || load > cpu_running_mark * 2)
781185050Smav					freq *= 2;
782185050Smav				else
783185050Smav					freq = freq * load / cpu_running_mark;
784185050Smav				if (freq > freqs[0])
785185050Smav					freq = freqs[0];
786185050Smav			} else if (load < cpu_idle_mark &&
787185050Smav			    curfreq * load < freqs[get_freq_id(
788252713Swblock			    freq * 7 / 8, freqs, numfreqs)] *
789185050Smav			    cpu_running_mark) {
790185050Smav				freq = freq * 7 / 8;
791185050Smav				if (freq < freqs[numfreqs - 1])
792185050Smav					freq = freqs[numfreqs - 1];
793185050Smav			}
794185050Smav		} else { /* MODE_HIADAPTIVE */
795185050Smav			if (load > cpu_running_mark / 2) {
796185050Smav				if (load > 95 || load > cpu_running_mark)
797185050Smav					freq *= 4;
798185050Smav				else
799185050Smav					freq = freq * load * 2 / cpu_running_mark;
800185050Smav				if (freq > freqs[0] * 2)
801185050Smav					freq = freqs[0] * 2;
802185050Smav			} else if (load < cpu_idle_mark / 2 &&
803185050Smav			    curfreq * load < freqs[get_freq_id(
804252713Swblock			    freq * 31 / 32, freqs, numfreqs)] *
805185050Smav			    cpu_running_mark / 2) {
806185050Smav				freq = freq * 31 / 32;
807185050Smav				if (freq < freqs[numfreqs - 1])
808185050Smav					freq = freqs[numfreqs - 1];
809185050Smav			}
810149410Sbruno		}
811185050Smav		if (vflag) {
812185050Smav		    printf("load %3d%%, current freq %4d MHz (%2d), wanted freq %4d MHz\n",
813185050Smav			load, curfreq, i, freq);
814185050Smav		}
815185050Smav		j = get_freq_id(freq, freqs, numfreqs);
816185050Smav		if (i != j) {
817142577Snjl			if (vflag) {
818185050Smav				printf("changing clock"
819142577Snjl				    " speed from %d MHz to %d MHz\n",
820185050Smav				    freqs[i], freqs[j]);
821142577Snjl			}
822209234Smav			idle = 0;
823185050Smav			if (set_freq(freqs[j]))
824170682Smarck				warn("error setting CPU frequency %d",
825185050Smav				    freqs[j]);
826142577Snjl		}
827142577Snjl	}
828185053Smav	if (set_freq(initfreq))
829185053Smav		warn("error setting CPU frequency %d", initfreq);
830144873Snjl	free(freqs);
831144873Snjl	free(mwatts);
832151461Snjl	devd_close();
833149428Spjd	if (!vflag)
834149428Spjd		pidfile_remove(pfh);
835142577Snjl
836142577Snjl	exit(0);
837142577Snjl}
838