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