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