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