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