powerd.c revision 148139
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 148139 2005-07-18 20:15:31Z ume $");
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	while ((ch = getopt(argc, argv, "a:b:i:n:p:r:v")) != EOF)
273		switch (ch) {
274		case 'a':
275			parse_mode(optarg, &mode_ac, ch);
276			break;
277		case 'b':
278			parse_mode(optarg, &mode_battery, ch);
279			break;
280		case 'i':
281			cpu_idle_mark = atoi(optarg);
282			if (cpu_idle_mark < 0 || cpu_idle_mark > 100) {
283				warnx("%d is not a valid percent",
284				    cpu_idle_mark);
285				usage();
286			}
287			break;
288		case 'n':
289			parse_mode(optarg, &mode_none, ch);
290			break;
291		case 'p':
292			poll_ival = atoi(optarg);
293			if (poll_ival < 5) {
294				warnx("poll interval is in units of ms");
295				usage();
296			}
297			break;
298		case 'r':
299			cpu_running_mark = atoi(optarg);
300			if (cpu_running_mark < 0 || cpu_running_mark > 100) {
301				warnx("%d is not a valid percent",
302				    cpu_running_mark);
303				usage();
304			}
305			break;
306		case 'v':
307			vflag = 1;
308			break;
309		default:
310			usage();
311		}
312
313	/* Poll interval is in units of ms. */
314	poll_ival *= 1000;
315
316	/* Look up various sysctl MIBs. */
317	len = 2;
318	if (sysctlnametomib("kern.cp_time", cp_time_mib, &len))
319		err(1, "lookup kern.cp_time");
320	len = 4;
321	if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len))
322		err(1, "lookup freq");
323	len = 4;
324	if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len))
325		err(1, "lookup freq_levels");
326
327	/* Check if we can read the idle time and supported freqs. */
328	if (read_usage_times(NULL, NULL))
329		err(1, "read_usage_times");
330	if (read_freqs(&numfreqs, &freqs, &mwatts))
331		err(1, "error reading supported CPU frequencies");
332
333	/* Decide whether to use ACPI or APM to read the AC line status. */
334	acline_init();
335
336	/* Run in the background unless in verbose mode. */
337	if (!vflag)
338		daemon(0, 0);
339	signal(SIGINT, handle_sigs);
340	signal(SIGTERM, handle_sigs);
341
342	/* Main loop. */
343	for (;;) {
344		/* Check status every few milliseconds. */
345		usleep(poll_ival);
346
347		/* If the user requested we quit, print some statistics. */
348		if (exit_requested) {
349			if (vflag && mjoules_used != 0)
350				printf("total joules used: %u.%03u\n",
351				    (u_int)(mjoules_used / 1000),
352				    (int)mjoules_used % 1000);
353			break;
354		}
355
356		/* Read the current AC status and record the mode. */
357		acline = acline_read();
358		switch (acline) {
359		case SRC_AC:
360			mode = mode_ac;
361			break;
362		case SRC_BATTERY:
363			mode = mode_battery;
364			break;
365		case SRC_UNKNOWN:
366			mode = mode_none;
367			break;
368		default:
369			errx(1, "invalid AC line status %d", acline);
370		}
371
372		/* Read the current frequency. */
373		len = sizeof(curfreq);
374		if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0))
375			err(1, "error reading current CPU frequency");
376
377		if (vflag) {
378			for (i = 0; i < numfreqs; i++) {
379				if (freqs[i] == curfreq)
380					break;
381			}
382
383			/* Keep a sum of all power actually used. */
384			if (i < numfreqs && mwatts[i] != -1)
385				mjoules_used +=
386				    (mwatts[i] * (poll_ival / 1000)) / 1000;
387		}
388
389		/* Always switch to the lowest frequency in min mode. */
390		if (mode == MODE_MIN) {
391			if (curfreq != freqs[numfreqs - 1]) {
392				if (vflag) {
393					printf("now operating on %s power; "
394					    "changing frequency to %d MHz\n",
395					    modes[acline], freqs[numfreqs - 1]);
396				}
397				if (set_freq(freqs[numfreqs - 1]))
398					err(1, "error setting CPU freq %d",
399					    freqs[numfreqs - 1]);
400			}
401			continue;
402		}
403
404		/* Always switch to the highest frequency in max mode. */
405		if (mode == MODE_MAX) {
406			if (curfreq != freqs[0]) {
407				if (vflag) {
408					printf("now operating on %s power; "
409					    "changing frequency to %d MHz\n",
410					    modes[acline], freqs[0]);
411				}
412				if (set_freq(freqs[0]))
413					err(1, "error setting CPU freq %d",
414					    freqs[0]);
415			}
416			continue;
417		}
418
419		/* Adaptive mode; get the current CPU usage times. */
420		if (read_usage_times(&idle, &total))
421			err(1, "read_usage_times");
422
423		/*
424		 * If we're idle less than the active mark, jump the CPU to
425		 * its fastest speed if we're not there yet.  If we're idle
426		 * more than the idle mark, drop down to the first setting
427		 * that is half the current speed (exponential backoff).
428		 */
429		if (idle < (total * cpu_running_mark) / 100 &&
430		    curfreq < freqs[0]) {
431			if (vflag) {
432				printf("idle time < %d%%, increasing clock"
433				    " speed from %d MHz to %d MHz\n",
434				    cpu_running_mark, curfreq, freqs[0]);
435			}
436			if (set_freq(freqs[0]))
437				err(1, "error setting CPU frequency %d",
438				    freqs[0]);
439		} else if (idle > (total * cpu_idle_mark) / 100 &&
440		    curfreq > freqs[numfreqs - 1]) {
441			for (i = 0; i < numfreqs - 1; i++) {
442				if (freqs[i] <= curfreq / 2)
443					break;
444			}
445			if (vflag) {
446				printf("idle time > %d%%, decreasing clock"
447				    " speed from %d MHz to %d MHz\n",
448				    cpu_idle_mark, curfreq, freqs[i]);
449			}
450			if (set_freq(freqs[i]))
451				err(1, "error setting CPU frequency %d",
452				    freqs[i]);
453		}
454	}
455	free(freqs);
456	free(mwatts);
457
458	exit(0);
459}
460