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