powerd.c revision 151461
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: head/usr.sbin/powerd/powerd.c 151461 2005-10-19 04:48:44Z njl $");
30
31#include <sys/types.h>
32#include <sys/param.h>
33#include <sys/ioctl.h>
34#include <sys/sysctl.h>
35#include <sys/resource.h>
36#include <sys/socket.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 <pthread.h>
44#include <signal.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <unistd.h>
49
50#ifdef __i386__
51#include <machine/apm_bios.h>
52#endif
53
54#define DEFAULT_ACTIVE_PERCENT	65
55#define DEFAULT_IDLE_PERCENT	90
56#define DEFAULT_POLL_INTERVAL	500	/* Poll interval in milliseconds */
57
58enum modes_t {
59	MODE_MIN,
60	MODE_ADAPTIVE,
61	MODE_MAX,
62};
63
64enum power_src_t {
65	SRC_AC,
66	SRC_BATTERY,
67	SRC_UNKNOWN,
68};
69
70const 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
81static int	read_usage_times(long *idle, long *total);
82static int	read_freqs(int *numfreqs, int **freqs, int **power);
83static int	set_freq(int freq);
84static void	acline_init(void);
85static int	acline_read(void);
86static int	devd_init(void);
87static void	devd_close(void);
88static void	*devd_read(void *arg);
89static void	handle_sigs(int sig);
90static void	parse_mode(char *arg, int *mode, int ch);
91static void	usage(void);
92
93/* Sysctl data structures. */
94static int	cp_time_mib[2];
95static int	freq_mib[4];
96static int	levels_mib[4];
97static int	acline_mib[3];
98
99/* devd-cached value provided by our thread. */
100static int	devd_acline;
101
102/* Configuration */
103static int	cpu_running_mark;
104static int	cpu_idle_mark;
105static int	poll_ival;
106static int	vflag;
107
108static int	apm_fd;
109static int	devd_pipe;
110static pthread_t devd_thread;
111static int	exit_requested;
112
113static int
114read_usage_times(long *idle, long *total)
115{
116	static long idle_old, total_old;
117	long cp_time[CPUSTATES], i, total_new;
118	size_t cp_time_len;
119	int error;
120
121	cp_time_len = sizeof(cp_time);
122	error = sysctl(cp_time_mib, 2, cp_time, &cp_time_len, NULL, 0);
123	if (error)
124		return (error);
125	for (total_new = 0, i = 0; i < CPUSTATES; i++)
126		total_new += cp_time[i];
127
128	if (idle)
129		*idle = cp_time[CP_IDLE] - idle_old;
130	if (total)
131		*total = total_new - total_old;
132
133	idle_old = cp_time[CP_IDLE];
134	total_old = total_new;
135
136	return (0);
137}
138
139static int
140read_freqs(int *numfreqs, int **freqs, int **power)
141{
142	char *freqstr, *p, *q;
143	int i;
144	size_t len = 0;
145
146	if (sysctl(levels_mib, 4, NULL, &len, NULL, 0))
147		return (-1);
148	if ((freqstr = malloc(len)) == NULL)
149		return (-1);
150	if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0))
151		return (-1);
152
153	*numfreqs = 1;
154	for (p = freqstr; *p != '\0'; p++)
155		if (*p == ' ')
156			(*numfreqs)++;
157
158	if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) {
159		free(freqstr);
160		return (-1);
161	}
162	if ((*power = malloc(*numfreqs * sizeof(int))) == NULL) {
163		free(freqstr);
164		free(*freqs);
165		return (-1);
166	}
167	for (i = 0, p = freqstr; i < *numfreqs; i++) {
168		q = strchr(p, ' ');
169		if (q != NULL)
170			*q = '\0';
171		if (sscanf(p, "%d/%d", &(*freqs)[i], &(*power)[i]) != 2) {
172			free(freqstr);
173			free(*freqs);
174			free(*power);
175			return (-1);
176		}
177		p = q + 1;
178	}
179
180	free(freqstr);
181	return (0);
182}
183
184static int
185set_freq(int freq)
186{
187
188	if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq))) {
189		if (errno != EPERM)
190			return (-1);
191	}
192
193	return (0);
194}
195
196/*
197 * Try to use ACPI to find the AC line status.  If this fails, fall back
198 * to APM.  If nothing succeeds, we'll just run in default mode.  If we are
199 * using ACPI, try opening a pipe to devd to detect AC line events.
200 */
201static void
202acline_init()
203{
204	int acline;
205	size_t len;
206
207	apm_fd = -1;
208	devd_pipe = -1;
209	len = sizeof(acline);
210	if (sysctlbyname(ACPIAC, &acline, &len, NULL, 0) == 0) {
211		len = 3;
212		if (sysctlnametomib(ACPIAC, acline_mib, &len))
213			err(1, "lookup acline");
214
215		/* Read line status once so that we have an initial value. */
216		devd_acline = acline_read();
217
218		/*
219		 * Try connecting to the devd pipe and start a read thread
220		 * if we succeed.
221		 */
222		if ((devd_pipe = devd_init()) >= 0) {
223			if (pthread_create(&devd_thread, NULL, devd_read,
224			    &devd_pipe))
225				err(1, "pthread_create devd thread");
226		} else if (vflag) {
227			warnx(
228		"unable to connect to devd pipe, using polling mode instead");
229		}
230	} else {
231		apm_fd = open(APMDEV, O_RDONLY);
232		if (apm_fd == -1)
233			warnx(
234		"cannot read AC line status, using default settings");
235	}
236}
237
238static int
239acline_read()
240{
241	int acline;
242	size_t len;
243#ifdef __i386__
244	struct apm_info info;
245#endif
246
247	acline = SRC_UNKNOWN;
248	len = sizeof(acline);
249
250	/*
251	 * Get state from our devd thread, the ACPI sysctl, or APM.  We
252	 * prefer sources in this order.
253	 */
254	if (devd_pipe >= 0)
255		acline = devd_acline;
256	else if (sysctl(acline_mib, 3, &acline, &len, NULL, 0) == 0)
257		acline = acline ? SRC_AC : SRC_BATTERY;
258#ifdef __i386__
259	else if (apm_fd != -1 && ioctl(apm_fd, APMIO_GETINFO, &info) == 0)
260		acline = info.ai_acline ? SRC_AC : SRC_BATTERY;
261#endif
262
263	return (acline);
264}
265
266static int
267devd_init(void)
268{
269	struct sockaddr_un devd_addr;
270	int devd_sock;
271
272	bzero(&devd_addr, sizeof(devd_addr));
273	if ((devd_sock = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) {
274		if (vflag)
275			warn("failed to create devd socket");
276		return (-1);
277	}
278
279	devd_addr.sun_family = PF_LOCAL;
280	strlcpy(devd_addr.sun_path, DEVDPIPE, sizeof(devd_addr.sun_path));
281	if (connect(devd_sock, (struct sockaddr *)&devd_addr,
282	    sizeof(devd_addr)) == -1) {
283		close(devd_sock);
284		return (-1);
285	}
286
287	return (devd_sock);
288}
289
290static void
291devd_close(void)
292{
293
294	if (devd_pipe < 0)
295		return;
296
297	pthread_kill(devd_thread, SIGTERM);
298	close(devd_pipe);
299}
300
301/*
302 * This loop runs as a separate thread.  It reads events from devd, but
303 * spends most of its time blocked in select(2).
304 */
305static void *
306devd_read(void *arg)
307{
308	char buf[DEVCTL_MAXBUF], *ptr;
309	fd_set fdset;
310	int fd, notify, rlen;
311
312	fd = *(int *)arg;
313	notify = -1;
314	FD_ZERO(&fdset);
315	while (!exit_requested) {
316		FD_SET(fd, &fdset);
317		if (select(fd + 1, &fdset, NULL, NULL, NULL) < 0)
318			break;
319		if (!FD_ISSET(fd, &fdset))
320			continue;
321
322		/* Read the notify string, devd NULL-terminates it. */
323		rlen = read(fd, buf, sizeof(buf));
324		if (rlen <= 0) {
325			close(devd_pipe);
326			devd_pipe = -1;
327			if (vflag)
328				warnx(
329			"devd disappeared, downgrading to polling mode");
330
331			/*
332			 * Keep trying to reconnect to devd but sleep in
333			 * between to avoid wasting CPU cycles.
334			 */
335			while (!exit_requested && (fd = devd_init()) < 0)
336				sleep(300);
337
338			if (fd >= 0) {
339				devd_pipe = fd;
340				if (vflag)
341					warnx(
342				"devd came back, upgrading to event mode");
343			}
344			continue;
345		}
346
347		/* Loosely match the notify string. */
348		if ((ptr = strstr(buf, "system=ACPI")) != NULL &&
349		    (ptr = strstr(ptr, "subsystem=ACAD")) != NULL &&
350		    (ptr = strstr(ptr, "notify=")) != NULL) {
351		        if (sscanf(ptr, "notify=%x", &notify) != 1) {
352				warnx("bad devd notify string");
353				continue;
354			}
355			devd_acline = notify ? SRC_AC : SRC_BATTERY;
356		}
357	}
358
359	return (NULL);
360}
361
362static void
363parse_mode(char *arg, int *mode, int ch)
364{
365
366	if (strcmp(arg, "minimum") == 0 || strcmp(arg, "min") == 0)
367		*mode = MODE_MIN;
368	else if (strcmp(arg, "maximum") == 0 || strcmp(arg, "max") == 0)
369		*mode = MODE_MAX;
370	else if (strcmp(arg, "adaptive") == 0)
371		*mode = MODE_ADAPTIVE;
372	else
373		errx(1, "bad option: -%c %s", (char)ch, optarg);
374}
375
376static void
377handle_sigs(int __unused sig)
378{
379
380	exit_requested = 1;
381}
382
383static void
384usage(void)
385{
386
387	fprintf(stderr,
388"usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-n mode] [-p ival] [-r %%] [-P pidfile]\n");
389	exit(1);
390}
391
392int
393main(int argc, char * argv[])
394{
395	struct pidfh *pfh = NULL;
396	const char *pidfile = NULL;
397	long idle, total;
398	int acline, curfreq, *freqs, i, *mwatts, numfreqs;
399	int ch, mode, mode_ac, mode_battery, mode_none;
400	uint64_t mjoules_used;
401	size_t len;
402
403	/* Default mode for all AC states is adaptive. */
404	mode_ac = mode_battery = mode_none = MODE_ADAPTIVE;
405	cpu_running_mark = DEFAULT_ACTIVE_PERCENT;
406	cpu_idle_mark = DEFAULT_IDLE_PERCENT;
407	poll_ival = DEFAULT_POLL_INTERVAL;
408	mjoules_used = 0;
409	vflag = 0;
410	apm_fd = -1;
411
412	/* User must be root to control frequencies. */
413	if (geteuid() != 0)
414		errx(1, "must be root to run");
415
416	while ((ch = getopt(argc, argv, "a:b:i:n:p:P:r:v")) != EOF)
417		switch (ch) {
418		case 'a':
419			parse_mode(optarg, &mode_ac, ch);
420			break;
421		case 'b':
422			parse_mode(optarg, &mode_battery, ch);
423			break;
424		case 'i':
425			cpu_idle_mark = atoi(optarg);
426			if (cpu_idle_mark < 0 || cpu_idle_mark > 100) {
427				warnx("%d is not a valid percent",
428				    cpu_idle_mark);
429				usage();
430			}
431			break;
432		case 'n':
433			parse_mode(optarg, &mode_none, ch);
434			break;
435		case 'p':
436			poll_ival = atoi(optarg);
437			if (poll_ival < 5) {
438				warnx("poll interval is in units of ms");
439				usage();
440			}
441			break;
442		case 'P':
443			pidfile = optarg;
444			break;
445		case 'r':
446			cpu_running_mark = atoi(optarg);
447			if (cpu_running_mark < 0 || cpu_running_mark > 100) {
448				warnx("%d is not a valid percent",
449				    cpu_running_mark);
450				usage();
451			}
452			break;
453		case 'v':
454			vflag = 1;
455			break;
456		default:
457			usage();
458		}
459
460	mode = mode_none;
461
462	/* Poll interval is in units of ms. */
463	poll_ival *= 1000;
464
465	/* Look up various sysctl MIBs. */
466	len = 2;
467	if (sysctlnametomib("kern.cp_time", cp_time_mib, &len))
468		err(1, "lookup kern.cp_time");
469	len = 4;
470	if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len))
471		err(1, "lookup freq");
472	len = 4;
473	if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len))
474		err(1, "lookup freq_levels");
475
476	/* Check if we can read the idle time and supported freqs. */
477	if (read_usage_times(NULL, NULL))
478		err(1, "read_usage_times");
479	if (read_freqs(&numfreqs, &freqs, &mwatts))
480		err(1, "error reading supported CPU frequencies");
481
482	/* Decide whether to use ACPI or APM to read the AC line status. */
483	acline_init();
484
485	/* Run in the background unless in verbose mode. */
486	if (!vflag) {
487		pid_t otherpid;
488
489		pfh = pidfile_open(pidfile, 0600, &otherpid);
490		if (pfh == NULL) {
491			if (errno == EEXIST) {
492				errx(1, "powerd already running, pid: %d",
493				    otherpid);
494			}
495			warn("cannot open pid file");
496		}
497		daemon(0, 0);
498		pidfile_write(pfh);
499	}
500	signal(SIGINT, handle_sigs);
501	signal(SIGTERM, handle_sigs);
502	signal(SIGPIPE, SIG_IGN);
503
504	/* Main loop. */
505	for (;;) {
506		/* Check status every few milliseconds. */
507		usleep(poll_ival);
508
509		/* If the user requested we quit, print some statistics. */
510		if (exit_requested) {
511			if (vflag && mjoules_used != 0)
512				printf("total joules used: %u.%03u\n",
513				    (u_int)(mjoules_used / 1000),
514				    (int)mjoules_used % 1000);
515			break;
516		}
517
518		/* Read the current AC status and record the mode. */
519		acline = acline_read();
520		switch (acline) {
521		case SRC_AC:
522			mode = mode_ac;
523			break;
524		case SRC_BATTERY:
525			mode = mode_battery;
526			break;
527		case SRC_UNKNOWN:
528			mode = mode_none;
529			break;
530		default:
531			errx(1, "invalid AC line status %d", acline);
532		}
533
534		/* Read the current frequency. */
535		len = sizeof(curfreq);
536		if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0) != 0) {
537			if (vflag)
538				warn("error reading current CPU frequency");
539			continue;
540		}
541
542		if (vflag) {
543			for (i = 0; i < numfreqs; i++) {
544				if (freqs[i] == curfreq)
545					break;
546			}
547
548			/* Keep a sum of all power actually used. */
549			if (i < numfreqs && mwatts[i] != -1)
550				mjoules_used +=
551				    (mwatts[i] * (poll_ival / 1000)) / 1000;
552		}
553
554		/* Always switch to the lowest frequency in min mode. */
555		if (mode == MODE_MIN) {
556			if (curfreq != freqs[numfreqs - 1]) {
557				if (vflag) {
558					printf("now operating on %s power; "
559					    "changing frequency to %d MHz\n",
560					    modes[acline], freqs[numfreqs - 1]);
561				}
562				if (set_freq(freqs[numfreqs - 1]) != 0) {
563					warn("error setting CPU freq %d",
564					    freqs[numfreqs - 1]);
565					continue;
566				}
567			}
568			continue;
569		}
570
571		/* Always switch to the highest frequency in max mode. */
572		if (mode == MODE_MAX) {
573			if (curfreq != freqs[0]) {
574				if (vflag) {
575					printf("now operating on %s power; "
576					    "changing frequency to %d MHz\n",
577					    modes[acline], freqs[0]);
578				}
579				if (set_freq(freqs[0]) != 0) {
580					warn("error setting CPU freq %d",
581				    	    freqs[0]);
582					continue;
583				}
584			}
585			continue;
586		}
587
588		/* Adaptive mode; get the current CPU usage times. */
589		if (read_usage_times(&idle, &total)) {
590			if (vflag)
591				warn("read_usage_times() failed");
592			continue;
593		}
594
595		/*
596		 * If we're idle less than the active mark, bump up two levels.
597		 * If we're idle more than the idle mark, drop down one level.
598		 */
599		for (i = 0; i < numfreqs - 1; i++) {
600			if (freqs[i] == curfreq)
601				break;
602		}
603		if (idle < (total * cpu_running_mark) / 100 &&
604		    curfreq < freqs[0]) {
605			i -= 2;
606			if (i < 0)
607				i = 0;
608			if (vflag) {
609				printf("idle time < %d%%, increasing clock"
610				    " speed from %d MHz to %d MHz\n",
611				    cpu_running_mark, curfreq, freqs[i]);
612			}
613			if (set_freq(freqs[i]))
614				err(1, "error setting CPU frequency %d",
615				    freqs[i]);
616		} else if (idle > (total * cpu_idle_mark) / 100 &&
617		    curfreq > freqs[numfreqs - 1]) {
618			i++;
619			if (vflag) {
620				printf("idle time > %d%%, decreasing clock"
621				    " speed from %d MHz to %d MHz\n",
622				    cpu_idle_mark, curfreq, freqs[i]);
623			}
624			if (set_freq(freqs[i]) != 0)
625				warn("error setting CPU frequency %d",
626			    	    freqs[i]);
627		}
628	}
629	free(freqs);
630	free(mwatts);
631	devd_close();
632	if (!vflag)
633		pidfile_remove(pfh);
634
635	exit(0);
636}
637