powerd.c revision 149428
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 149428 2005-08-24 17:32:41Z pjd $");
30
31#include <sys/param.h>
32#include <sys/ioctl.h>
33#include <sys/sysctl.h>
34#include <sys/resource.h>
35
36#include <err.h>
37#include <errno.h>
38#include <fcntl.h>
39#include <libutil.h>
40#include <signal.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <unistd.h>
45
46#ifdef __i386__
47#include <machine/apm_bios.h>
48#endif
49
50#define DEFAULT_ACTIVE_PERCENT	65
51#define DEFAULT_IDLE_PERCENT	90
52#define DEFAULT_POLL_INTERVAL	500	/* Poll interval in milliseconds */
53
54enum modes_t {
55	MODE_MIN,
56	MODE_ADAPTIVE,
57	MODE_MAX,
58};
59
60enum power_src_t {
61	SRC_AC,
62	SRC_BATTERY,
63	SRC_UNKNOWN,
64};
65
66const char *modes[] = {
67	"AC",
68	"battery",
69	"unknown"
70};
71
72#define ACPIAC		"hw.acpi.acline"
73#define APMDEV		"/dev/apm"
74
75static int	read_usage_times(long *idle, long *total);
76static int	read_freqs(int *numfreqs, int **freqs, int **power);
77static int	set_freq(int freq);
78static void	acline_init(void);
79static int	acline_read(void);
80static void	handle_sigs(int sig);
81static void	parse_mode(char *arg, int *mode, int ch);
82static void	usage(void);
83
84/* Sysctl data structures. */
85static int	cp_time_mib[2];
86static int	freq_mib[4];
87static int	levels_mib[4];
88static int	acline_mib[3];
89
90/* Configuration */
91static int	cpu_running_mark;
92static int	cpu_idle_mark;
93static int	poll_ival;
94
95static int	apm_fd;
96static int	exit_requested;
97
98static int
99read_usage_times(long *idle, long *total)
100{
101	static long idle_old, total_old;
102	long cp_time[CPUSTATES], i, total_new;
103	size_t cp_time_len;
104	int error;
105
106	cp_time_len = sizeof(cp_time);
107	error = sysctl(cp_time_mib, 2, cp_time, &cp_time_len, NULL, 0);
108	if (error)
109		return (error);
110	for (total_new = 0, i = 0; i < CPUSTATES; i++)
111		total_new += cp_time[i];
112
113	if (idle)
114		*idle = cp_time[CP_IDLE] - idle_old;
115	if (total)
116		*total = total_new - total_old;
117
118	idle_old = cp_time[CP_IDLE];
119	total_old = total_new;
120
121	return (0);
122}
123
124static int
125read_freqs(int *numfreqs, int **freqs, int **power)
126{
127	char *freqstr, *p, *q;
128	int i;
129	size_t len = 0;
130
131	if (sysctl(levels_mib, 4, NULL, &len, NULL, 0))
132		return (-1);
133	if ((freqstr = malloc(len)) == NULL)
134		return (-1);
135	if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0))
136		return (-1);
137
138	*numfreqs = 1;
139	for (p = freqstr; *p != '\0'; p++)
140		if (*p == ' ')
141			(*numfreqs)++;
142
143	if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) {
144		free(freqstr);
145		return (-1);
146	}
147	if ((*power = malloc(*numfreqs * sizeof(int))) == NULL) {
148		free(freqstr);
149		free(*freqs);
150		return (-1);
151	}
152	for (i = 0, p = freqstr; i < *numfreqs; i++) {
153		q = strchr(p, ' ');
154		if (q != NULL)
155			*q = '\0';
156		if (sscanf(p, "%d/%d", &(*freqs)[i], &(*power)[i]) != 2) {
157			free(freqstr);
158			free(*freqs);
159			free(*power);
160			return (-1);
161		}
162		p = q + 1;
163	}
164
165	free(freqstr);
166	return (0);
167}
168
169static int
170set_freq(int freq)
171{
172
173	if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq))) {
174		if (errno != EPERM)
175			return (-1);
176	}
177
178	return (0);
179}
180
181/*
182 * Try to use ACPI to find the AC line status.  If this fails, fall back
183 * to APM.  If nothing succeeds, we'll just run in default mode.
184 */
185static void
186acline_init()
187{
188	int acline;
189	size_t len;
190
191	apm_fd = -1;
192	len = sizeof(acline);
193	if (sysctlbyname(ACPIAC, &acline, &len, NULL, 0) == 0) {
194		len = 3;
195		if (sysctlnametomib(ACPIAC, acline_mib, &len))
196			err(1, "lookup acline");
197	} else {
198		apm_fd = open(APMDEV, O_RDONLY);
199		if (apm_fd == -1)
200			warnx(
201		"cannot read AC line status, using default settings");
202	}
203}
204
205static int
206acline_read()
207{
208	int acline = SRC_UNKNOWN;
209	size_t len;
210
211#ifdef __i386__
212	struct apm_info info;
213
214	if (apm_fd != -1 && ioctl(apm_fd, APMIO_GETINFO, &info) == 0)
215		acline = info.ai_acline ? SRC_AC : SRC_BATTERY;
216#endif
217
218	if (acline == SRC_UNKNOWN) {
219		len = sizeof(acline);
220		if (sysctl(acline_mib, 3, &acline, &len, NULL, 0) == 0)
221			acline = acline ? SRC_AC : SRC_BATTERY;
222	}
223
224	return (acline);
225}
226
227static void
228parse_mode(char *arg, int *mode, int ch)
229{
230
231	if (strcmp(arg, "min") == 0)
232		*mode = MODE_MIN;
233	else if (strcmp(arg, "max") == 0)
234		*mode = MODE_MAX;
235	else if (strcmp(arg, "adaptive") == 0)
236		*mode = MODE_ADAPTIVE;
237	else
238		errx(1, "bad option: -%c %s", (char)ch, optarg);
239}
240
241static void
242handle_sigs(int __unused sig)
243{
244	exit_requested = 1;
245}
246
247static void
248usage(void)
249{
250
251	fprintf(stderr,
252"usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-n mode] [-p ival] [-r %%] [-P pidfile]\n");
253	exit(1);
254}
255
256int
257main(int argc, char * argv[])
258{
259	struct pidfh *pfh;
260	const char *pidfile = NULL;
261	long idle, total;
262	int curfreq, *freqs, i, *mwatts, numfreqs;
263	int ch, mode_ac, mode_battery, mode_none, acline, mode, vflag;
264	uint64_t mjoules_used;
265	size_t len;
266
267	/* Default mode for all AC states is adaptive. */
268	mode_ac = mode_battery = mode_none = MODE_ADAPTIVE;
269	cpu_running_mark = DEFAULT_ACTIVE_PERCENT;
270	cpu_idle_mark = DEFAULT_IDLE_PERCENT;
271	poll_ival = DEFAULT_POLL_INTERVAL;
272	mjoules_used = 0;
273	vflag = 0;
274	apm_fd = -1;
275
276	/* User must be root to control frequencies. */
277	if (geteuid() != 0)
278		errx(1, "must be root to run");
279
280	while ((ch = getopt(argc, argv, "a:b:i:n:p:P:r:v")) != EOF)
281		switch (ch) {
282		case 'a':
283			parse_mode(optarg, &mode_ac, ch);
284			break;
285		case 'b':
286			parse_mode(optarg, &mode_battery, ch);
287			break;
288		case 'i':
289			cpu_idle_mark = atoi(optarg);
290			if (cpu_idle_mark < 0 || cpu_idle_mark > 100) {
291				warnx("%d is not a valid percent",
292				    cpu_idle_mark);
293				usage();
294			}
295			break;
296		case 'n':
297			parse_mode(optarg, &mode_none, ch);
298			break;
299		case 'p':
300			poll_ival = atoi(optarg);
301			if (poll_ival < 5) {
302				warnx("poll interval is in units of ms");
303				usage();
304			}
305			break;
306		case 'P':
307			pidfile = optarg;
308			break;
309		case 'r':
310			cpu_running_mark = atoi(optarg);
311			if (cpu_running_mark < 0 || cpu_running_mark > 100) {
312				warnx("%d is not a valid percent",
313				    cpu_running_mark);
314				usage();
315			}
316			break;
317		case 'v':
318			vflag = 1;
319			break;
320		default:
321			usage();
322		}
323
324	/* Poll interval is in units of ms. */
325	poll_ival *= 1000;
326
327	/* Look up various sysctl MIBs. */
328	len = 2;
329	if (sysctlnametomib("kern.cp_time", cp_time_mib, &len))
330		err(1, "lookup kern.cp_time");
331	len = 4;
332	if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len))
333		err(1, "lookup freq");
334	len = 4;
335	if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len))
336		err(1, "lookup freq_levels");
337
338	/* Check if we can read the idle time and supported freqs. */
339	if (read_usage_times(NULL, NULL))
340		err(1, "read_usage_times");
341	if (read_freqs(&numfreqs, &freqs, &mwatts))
342		err(1, "error reading supported CPU frequencies");
343
344	/* Decide whether to use ACPI or APM to read the AC line status. */
345	acline_init();
346
347	/* Run in the background unless in verbose mode. */
348	if (!vflag) {
349		pid_t otherpid;
350
351		pfh = pidfile_open(pidfile, 0600, &otherpid);
352		if (pfh == NULL) {
353			if (errno == EEXIST) {
354				errx(1, "powerd already running, pid: %d",
355				    otherpid);
356			}
357			warn("cannot open pid file");
358		}
359		daemon(0, 0);
360		pidfile_write(pfh);
361	}
362	signal(SIGINT, handle_sigs);
363	signal(SIGTERM, handle_sigs);
364
365	/* Main loop. */
366	for (;;) {
367		/* Check status every few milliseconds. */
368		usleep(poll_ival);
369
370		/* If the user requested we quit, print some statistics. */
371		if (exit_requested) {
372			if (vflag && mjoules_used != 0)
373				printf("total joules used: %u.%03u\n",
374				    (u_int)(mjoules_used / 1000),
375				    (int)mjoules_used % 1000);
376			break;
377		}
378
379		/* Read the current AC status and record the mode. */
380		acline = acline_read();
381		switch (acline) {
382		case SRC_AC:
383			mode = mode_ac;
384			break;
385		case SRC_BATTERY:
386			mode = mode_battery;
387			break;
388		case SRC_UNKNOWN:
389			mode = mode_none;
390			break;
391		default:
392			errx(1, "invalid AC line status %d", acline);
393		}
394
395		/* Read the current frequency. */
396		len = sizeof(curfreq);
397		if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0))
398			err(1, "error reading current CPU frequency");
399
400		if (vflag) {
401			for (i = 0; i < numfreqs; i++) {
402				if (freqs[i] == curfreq)
403					break;
404			}
405
406			/* Keep a sum of all power actually used. */
407			if (i < numfreqs && mwatts[i] != -1)
408				mjoules_used +=
409				    (mwatts[i] * (poll_ival / 1000)) / 1000;
410		}
411
412		/* Always switch to the lowest frequency in min mode. */
413		if (mode == MODE_MIN) {
414			if (curfreq != freqs[numfreqs - 1]) {
415				if (vflag) {
416					printf("now operating on %s power; "
417					    "changing frequency to %d MHz\n",
418					    modes[acline], freqs[numfreqs - 1]);
419				}
420				if (set_freq(freqs[numfreqs - 1]))
421					err(1, "error setting CPU freq %d",
422					    freqs[numfreqs - 1]);
423			}
424			continue;
425		}
426
427		/* Always switch to the highest frequency in max mode. */
428		if (mode == MODE_MAX) {
429			if (curfreq != freqs[0]) {
430				if (vflag) {
431					printf("now operating on %s power; "
432					    "changing frequency to %d MHz\n",
433					    modes[acline], freqs[0]);
434				}
435				if (set_freq(freqs[0]))
436					err(1, "error setting CPU freq %d",
437					    freqs[0]);
438			}
439			continue;
440		}
441
442		/* Adaptive mode; get the current CPU usage times. */
443		if (read_usage_times(&idle, &total))
444			err(1, "read_usage_times");
445
446		/*
447		 * If we're idle less than the active mark, bump up two levels.
448		 * If we're idle more than the idle mark, drop down one level.
449		 */
450		for (i = 0; i < numfreqs - 1; i++) {
451			if (freqs[i] == curfreq)
452				break;
453		}
454		if (idle < (total * cpu_running_mark) / 100 &&
455		    curfreq < freqs[0]) {
456			i -= 2;
457			if (i < 0)
458				i = 0;
459			if (vflag) {
460				printf("idle time < %d%%, increasing clock"
461				    " speed from %d MHz to %d MHz\n",
462				    cpu_running_mark, curfreq, freqs[i]);
463			}
464			if (set_freq(freqs[i]))
465				err(1, "error setting CPU frequency %d",
466				    freqs[i]);
467		} else if (idle > (total * cpu_idle_mark) / 100 &&
468		    curfreq > freqs[numfreqs - 1]) {
469			i++;
470			if (vflag) {
471				printf("idle time > %d%%, decreasing clock"
472				    " speed from %d MHz to %d MHz\n",
473				    cpu_idle_mark, curfreq, freqs[i]);
474			}
475			if (set_freq(freqs[i]))
476				err(1, "error setting CPU frequency %d",
477				    freqs[i]);
478		}
479	}
480	free(freqs);
481	free(mwatts);
482	if (!vflag)
483		pidfile_remove(pfh);
484
485	exit(0);
486}
487