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