xref: /freebsd/usr.sbin/powerd/powerd.c (revision 5883360b2f2acfd5f707e8daacf180147250e6fd)
15883360bSNate Lawson /*-
25883360bSNate Lawson  * Copyright (c) 2004 Colin Percival
35883360bSNate Lawson  * Copyright (c) 2005 Nate Lawson
45883360bSNate Lawson  * All rights reserved.
55883360bSNate Lawson  *
65883360bSNate Lawson  * Redistribution and use in source and binary forms, with or without
75883360bSNate Lawson  * modification, are permitted providing that the following conditions
85883360bSNate Lawson  * are met:
95883360bSNate Lawson  * 1. Redistributions of source code must retain the above copyright
105883360bSNate Lawson  *    notice, this list of conditions and the following disclaimer.
115883360bSNate Lawson  * 2. Redistributions in binary form must reproduce the above copyright
125883360bSNate Lawson  *    notice, this list of conditions and the following disclaimer in the
135883360bSNate Lawson  *    documentation and/or other materials provided with the distribution.
145883360bSNate Lawson  *
155883360bSNate Lawson  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR``AS IS'' AND ANY EXPRESS OR
165883360bSNate Lawson  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
175883360bSNate Lawson  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
185883360bSNate Lawson  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
195883360bSNate Lawson  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
205883360bSNate Lawson  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
215883360bSNate Lawson  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
225883360bSNate Lawson  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
235883360bSNate Lawson  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
245883360bSNate Lawson  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
255883360bSNate Lawson  * POSSIBILITY OF SUCH DAMAGE.
265883360bSNate Lawson  */
275883360bSNate Lawson 
285883360bSNate Lawson #include <sys/cdefs.h>
295883360bSNate Lawson __FBSDID("$FreeBSD$");
305883360bSNate Lawson 
315883360bSNate Lawson #include <err.h>
325883360bSNate Lawson #include <fcntl.h>
335883360bSNate Lawson #include <stdio.h>
345883360bSNate Lawson #include <stdlib.h>
355883360bSNate Lawson #include <string.h>
365883360bSNate Lawson #include <unistd.h>
375883360bSNate Lawson 
385883360bSNate Lawson #include <machine/apm_bios.h>
395883360bSNate Lawson 
405883360bSNate Lawson #include <sys/ioctl.h>
415883360bSNate Lawson #include <sys/sysctl.h>
425883360bSNate Lawson #include <sys/resource.h>
435883360bSNate Lawson 
445883360bSNate Lawson #define DEFAULT_ACTIVE_PERCENT	50
455883360bSNate Lawson #define DEFAULT_IDLE_PERCENT	75
465883360bSNate Lawson #define DEFAULT_POLL_INTERVAL	500
475883360bSNate Lawson 
485883360bSNate Lawson enum modes_t {
495883360bSNate Lawson 	MODE_MIN,
505883360bSNate Lawson 	MODE_ADAPTIVE,
515883360bSNate Lawson 	MODE_MAX,
525883360bSNate Lawson };
535883360bSNate Lawson 
545883360bSNate Lawson enum power_src_t {
555883360bSNate Lawson 	SRC_AC,
565883360bSNate Lawson 	SRC_BATTERY,
575883360bSNate Lawson 	SRC_UNKNOWN,
585883360bSNate Lawson };
595883360bSNate Lawson 
605883360bSNate Lawson const char *modes[] = {
615883360bSNate Lawson 	"AC",
625883360bSNate Lawson 	"battery",
635883360bSNate Lawson 	"unknown"
645883360bSNate Lawson };
655883360bSNate Lawson 
665883360bSNate Lawson #define ACPIAC		"hw.acpi.acline"
675883360bSNate Lawson #define APMDEV		"/dev/apm"
685883360bSNate Lawson 
695883360bSNate Lawson static int	read_usage_times(long *idle, long *total);
705883360bSNate Lawson static int	read_freqs(int *numfreqs, int **freqs);
715883360bSNate Lawson static int	set_freq(int freq);
725883360bSNate Lawson static void	parse_mode(char *arg, int *mode, int ch);
735883360bSNate Lawson static void	usage(void);
745883360bSNate Lawson 
755883360bSNate Lawson /* Sysctl data structures. */
765883360bSNate Lawson static int	cp_time_mib[2];
775883360bSNate Lawson static int	freq_mib[4];
785883360bSNate Lawson static int	levels_mib[4];
795883360bSNate Lawson static int	acline_mib[3];
805883360bSNate Lawson 
815883360bSNate Lawson /* Configuration */
825883360bSNate Lawson static int	cpu_running_mark;
835883360bSNate Lawson static int	cpu_idle_mark;
845883360bSNate Lawson static int	poll_ival;
855883360bSNate Lawson 
865883360bSNate Lawson static int
875883360bSNate Lawson read_usage_times(long *idle, long *total)
885883360bSNate Lawson {
895883360bSNate Lawson 	static long idle_old, total_old;
905883360bSNate Lawson 	long cp_time[CPUSTATES], i, total_new;
915883360bSNate Lawson 	size_t cp_time_len;
925883360bSNate Lawson 	int error;
935883360bSNate Lawson 
945883360bSNate Lawson 	cp_time_len = sizeof(cp_time);
955883360bSNate Lawson 	error = sysctl(cp_time_mib, 2, cp_time, &cp_time_len, NULL, 0);
965883360bSNate Lawson 	if (error)
975883360bSNate Lawson 		return (error);
985883360bSNate Lawson 	for (total_new = 0, i = 0; i < CPUSTATES; i++)
995883360bSNate Lawson 		total_new += cp_time[i];
1005883360bSNate Lawson 
1015883360bSNate Lawson 	if (idle)
1025883360bSNate Lawson 		*idle = cp_time[CP_IDLE] - idle_old;
1035883360bSNate Lawson 	if (total)
1045883360bSNate Lawson 		*total = total_new - total_old;
1055883360bSNate Lawson 
1065883360bSNate Lawson 	idle_old = cp_time[CP_IDLE];
1075883360bSNate Lawson 	total_old = total_new;
1085883360bSNate Lawson 
1095883360bSNate Lawson 	return (0);
1105883360bSNate Lawson }
1115883360bSNate Lawson 
1125883360bSNate Lawson static int
1135883360bSNate Lawson read_freqs(int *numfreqs, int **freqs)
1145883360bSNate Lawson {
1155883360bSNate Lawson 	char *freqstr, *p, *q;
1165883360bSNate Lawson 	int i;
1175883360bSNate Lawson 	size_t len = 0;
1185883360bSNate Lawson 
1195883360bSNate Lawson 	if (sysctl(levels_mib, 4, NULL, &len, NULL, 0))
1205883360bSNate Lawson 		return (-1);
1215883360bSNate Lawson 	if ((freqstr = malloc(len)) == NULL)
1225883360bSNate Lawson 		return (-1);
1235883360bSNate Lawson 	if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0))
1245883360bSNate Lawson 		return (-1);
1255883360bSNate Lawson 
1265883360bSNate Lawson 	*numfreqs = 1;
1275883360bSNate Lawson 	for (p = freqstr; *p != '\0'; p++)
1285883360bSNate Lawson 		if (*p == ' ')
1295883360bSNate Lawson 			(*numfreqs)++;
1305883360bSNate Lawson 
1315883360bSNate Lawson 	if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) {
1325883360bSNate Lawson 		free(freqstr);
1335883360bSNate Lawson 		return (-1);
1345883360bSNate Lawson 	}
1355883360bSNate Lawson 	for (i = 0, p = freqstr; i < *numfreqs; i++) {
1365883360bSNate Lawson 		q = strchr(p, ' ');
1375883360bSNate Lawson 		if (q != NULL)
1385883360bSNate Lawson 			*q = '\0';
1395883360bSNate Lawson 		if (sscanf(p, "%d/%*d", &(*freqs)[i]) != 1) {
1405883360bSNate Lawson 			free(freqstr);
1415883360bSNate Lawson 			free(*freqs);
1425883360bSNate Lawson 			return (-1);
1435883360bSNate Lawson 		}
1445883360bSNate Lawson 		p = q + 1;
1455883360bSNate Lawson 	}
1465883360bSNate Lawson 
1475883360bSNate Lawson 	free(freqstr);
1485883360bSNate Lawson 	return (0);
1495883360bSNate Lawson }
1505883360bSNate Lawson 
1515883360bSNate Lawson static int
1525883360bSNate Lawson set_freq(int freq)
1535883360bSNate Lawson {
1545883360bSNate Lawson 
1555883360bSNate Lawson 	if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq)))
1565883360bSNate Lawson 		return (-1);
1575883360bSNate Lawson 
1585883360bSNate Lawson 	return (0);
1595883360bSNate Lawson }
1605883360bSNate Lawson 
1615883360bSNate Lawson static void
1625883360bSNate Lawson parse_mode(char *arg, int *mode, int ch)
1635883360bSNate Lawson {
1645883360bSNate Lawson 
1655883360bSNate Lawson 	if (strcmp(arg, "min") == 0)
1665883360bSNate Lawson 		*mode = MODE_MIN;
1675883360bSNate Lawson 	else if (strcmp(arg, "max") == 0)
1685883360bSNate Lawson 		*mode = MODE_MAX;
1695883360bSNate Lawson 	else if (strcmp(arg, "adaptive") == 0)
1705883360bSNate Lawson 		*mode = MODE_ADAPTIVE;
1715883360bSNate Lawson 	else
1725883360bSNate Lawson 		errx(1, "bad option: -%c %s", (char)ch, optarg);
1735883360bSNate Lawson }
1745883360bSNate Lawson 
1755883360bSNate Lawson static void
1765883360bSNate Lawson usage(void)
1775883360bSNate Lawson {
1785883360bSNate Lawson 
1795883360bSNate Lawson 	fprintf(stderr,
1805883360bSNate Lawson "usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-n mode] [-p ival] [-r %%]\n");
1815883360bSNate Lawson 	exit(1);
1825883360bSNate Lawson }
1835883360bSNate Lawson 
1845883360bSNate Lawson int
1855883360bSNate Lawson main(int argc, char * argv[])
1865883360bSNate Lawson {
1875883360bSNate Lawson 	struct apm_info info;
1885883360bSNate Lawson 	long idle, total;
1895883360bSNate Lawson 	int apm_fd, curfreq, *freqs, i, numfreqs;
1905883360bSNate Lawson 	int ch, mode_ac, mode_battery, mode_none, acline, mode, vflag;
1915883360bSNate Lawson 	size_t len;
1925883360bSNate Lawson 
1935883360bSNate Lawson 	/* Default mode for all AC states is adaptive. */
1945883360bSNate Lawson 	mode_ac = mode_battery = mode_none = MODE_ADAPTIVE;
1955883360bSNate Lawson 	cpu_running_mark = DEFAULT_ACTIVE_PERCENT;
1965883360bSNate Lawson 	cpu_idle_mark = DEFAULT_IDLE_PERCENT;
1975883360bSNate Lawson 	poll_ival = DEFAULT_POLL_INTERVAL;
1985883360bSNate Lawson 	vflag = 0;
1995883360bSNate Lawson 
2005883360bSNate Lawson 	while ((ch = getopt(argc, argv, "a:b:i:n:p:r:v")) != EOF)
2015883360bSNate Lawson 		switch (ch) {
2025883360bSNate Lawson 		case 'a':
2035883360bSNate Lawson 			parse_mode(optarg, &mode_ac, ch);
2045883360bSNate Lawson 			break;
2055883360bSNate Lawson 		case 'b':
2065883360bSNate Lawson 			parse_mode(optarg, &mode_battery, ch);
2075883360bSNate Lawson 			break;
2085883360bSNate Lawson 		case 'i':
2095883360bSNate Lawson 			cpu_idle_mark = atoi(optarg);
2105883360bSNate Lawson 			if (cpu_idle_mark < 0 || cpu_idle_mark > 100) {
2115883360bSNate Lawson 				warnx("%d is not a valid percent",
2125883360bSNate Lawson 				    cpu_idle_mark);
2135883360bSNate Lawson 				usage();
2145883360bSNate Lawson 			}
2155883360bSNate Lawson 			break;
2165883360bSNate Lawson 		case 'n':
2175883360bSNate Lawson 			parse_mode(optarg, &mode_none, ch);
2185883360bSNate Lawson 			break;
2195883360bSNate Lawson 		case 'p':
2205883360bSNate Lawson 			poll_ival = atoi(optarg);
2215883360bSNate Lawson 			if (poll_ival < 5) {
2225883360bSNate Lawson 				warnx("poll interval is in units of ms");
2235883360bSNate Lawson 				usage();
2245883360bSNate Lawson 			}
2255883360bSNate Lawson 			break;
2265883360bSNate Lawson 		case 'r':
2275883360bSNate Lawson 			cpu_running_mark = atoi(optarg);
2285883360bSNate Lawson 			if (cpu_running_mark < 0 || cpu_running_mark > 100) {
2295883360bSNate Lawson 				warnx("%d is not a valid percent",
2305883360bSNate Lawson 				    cpu_running_mark);
2315883360bSNate Lawson 				usage();
2325883360bSNate Lawson 			}
2335883360bSNate Lawson 			break;
2345883360bSNate Lawson 		case 'v':
2355883360bSNate Lawson 			vflag = 1;
2365883360bSNate Lawson 			break;
2375883360bSNate Lawson 		default:
2385883360bSNate Lawson 			usage();
2395883360bSNate Lawson 		}
2405883360bSNate Lawson 
2415883360bSNate Lawson 	/* Poll interval is in units of ms. */
2425883360bSNate Lawson 	poll_ival *= 1000;
2435883360bSNate Lawson 
2445883360bSNate Lawson 	/* Look up various sysctl MIBs. */
2455883360bSNate Lawson 	len = 2;
2465883360bSNate Lawson 	if (sysctlnametomib("kern.cp_time", cp_time_mib, &len))
2475883360bSNate Lawson 		err(1, "lookup kern.cp_time");
2485883360bSNate Lawson 	len = 4;
2495883360bSNate Lawson 	if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len))
2505883360bSNate Lawson 		err(1, "lookup freq");
2515883360bSNate Lawson 	len = 4;
2525883360bSNate Lawson 	if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len))
2535883360bSNate Lawson 		err(1, "lookup freq_levels");
2545883360bSNate Lawson 
2555883360bSNate Lawson 	/* Check if we can read the idle time and supported freqs. */
2565883360bSNate Lawson 	if (read_usage_times(NULL, NULL))
2575883360bSNate Lawson 		err(1, "read_usage_times");
2585883360bSNate Lawson 	if (read_freqs(&numfreqs, &freqs))
2595883360bSNate Lawson 		err(1, "error reading supported CPU frequencies");
2605883360bSNate Lawson 
2615883360bSNate Lawson 	/* Decide whether to use ACPI or APM to read the AC line status. */
2625883360bSNate Lawson 	len = sizeof(acline);
2635883360bSNate Lawson 	if (sysctlbyname(ACPIAC, &acline, &len, NULL, 0)) {
2645883360bSNate Lawson 		/* ACPI disabled, try APM */
2655883360bSNate Lawson 		apm_fd = open(APMDEV, O_RDONLY);
2665883360bSNate Lawson 		if (apm_fd == -1) {
2675883360bSNate Lawson 			warnx("cannot read AC line status, "
2685883360bSNate Lawson 			    "using default settings");
2695883360bSNate Lawson 		}
2705883360bSNate Lawson 	} else {
2715883360bSNate Lawson 		len = 3;
2725883360bSNate Lawson 		if (sysctlnametomib(ACPIAC, acline_mib, &len))
2735883360bSNate Lawson 			err(1, "lookup acline");
2745883360bSNate Lawson 		apm_fd = -1;
2755883360bSNate Lawson 	}
2765883360bSNate Lawson 
2775883360bSNate Lawson 	/* Run in the background unless in verbose mode. */
2785883360bSNate Lawson 	if (!vflag)
2795883360bSNate Lawson 		daemon(0, 0);
2805883360bSNate Lawson 
2815883360bSNate Lawson 	/* Main loop. */
2825883360bSNate Lawson 	for (;;) {
2835883360bSNate Lawson 		/* Check status every few milliseconds. */
2845883360bSNate Lawson 		usleep(poll_ival);
2855883360bSNate Lawson 
2865883360bSNate Lawson 		/* Read the current AC status and record the mode. */
2875883360bSNate Lawson 		if (apm_fd != -1) {
2885883360bSNate Lawson 			if (ioctl(apm_fd, APMIO_GETINFO, &info) == -1)
2895883360bSNate Lawson 				acline = SRC_UNKNOWN;
2905883360bSNate Lawson 			else
2915883360bSNate Lawson 				acline = info.ai_acline ? SRC_AC : SRC_BATTERY;
2925883360bSNate Lawson 		} else {
2935883360bSNate Lawson 			len = sizeof(acline);
2945883360bSNate Lawson 			if (sysctl(acline_mib, 3, &acline, &len, NULL, 0))
2955883360bSNate Lawson 				acline = SRC_UNKNOWN;
2965883360bSNate Lawson 			else
2975883360bSNate Lawson 				acline = acline ? SRC_AC : SRC_BATTERY;
2985883360bSNate Lawson 		}
2995883360bSNate Lawson 		switch (acline) {
3005883360bSNate Lawson 		case SRC_AC:
3015883360bSNate Lawson 			mode = mode_ac;
3025883360bSNate Lawson 			break;
3035883360bSNate Lawson 		case SRC_BATTERY:
3045883360bSNate Lawson 			mode = mode_battery;
3055883360bSNate Lawson 			break;
3065883360bSNate Lawson 		case SRC_UNKNOWN:
3075883360bSNate Lawson 			mode = mode_none;
3085883360bSNate Lawson 			break;
3095883360bSNate Lawson 		default:
3105883360bSNate Lawson 			errx(1, "invalid AC line status %d", acline);
3115883360bSNate Lawson 		}
3125883360bSNate Lawson 
3135883360bSNate Lawson 		/* Read the current frequency. */
3145883360bSNate Lawson 		len = sizeof(curfreq);
3155883360bSNate Lawson 		if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0))
3165883360bSNate Lawson 			err(1, "error reading current CPU frequency");
3175883360bSNate Lawson 
3185883360bSNate Lawson 		/* Always switch to the lowest frequency in min mode. */
3195883360bSNate Lawson 		if (mode == MODE_MIN) {
3205883360bSNate Lawson 			if (curfreq != freqs[numfreqs - 1]) {
3215883360bSNate Lawson 				if (vflag) {
3225883360bSNate Lawson 					printf("now operating on %s power; "
3235883360bSNate Lawson 					    "changing frequency to %d MHz\n",
3245883360bSNate Lawson 					    modes[acline], freqs[numfreqs - 1]);
3255883360bSNate Lawson 				}
3265883360bSNate Lawson 				if (set_freq(freqs[numfreqs - 1]))
3275883360bSNate Lawson 					err(1, "error setting CPU freq %d",
3285883360bSNate Lawson 					    freqs[numfreqs - 1]);
3295883360bSNate Lawson 			}
3305883360bSNate Lawson 			continue;
3315883360bSNate Lawson 		}
3325883360bSNate Lawson 
3335883360bSNate Lawson 		/* Always switch to the highest frequency in max mode. */
3345883360bSNate Lawson 		if (mode == MODE_MAX) {
3355883360bSNate Lawson 			if (curfreq != freqs[0]) {
3365883360bSNate Lawson 				if (vflag) {
3375883360bSNate Lawson 					printf("Now operating on %s power; "
3385883360bSNate Lawson 					    "changing frequency to %d MHz\n",
3395883360bSNate Lawson 					    modes[acline], freqs[0]);
3405883360bSNate Lawson 				}
3415883360bSNate Lawson 				if (set_freq(freqs[0]))
3425883360bSNate Lawson 					err(1, "error setting CPU freq %d",
3435883360bSNate Lawson 					    freqs[0]);
3445883360bSNate Lawson 			}
3455883360bSNate Lawson 			continue;
3465883360bSNate Lawson 		}
3475883360bSNate Lawson 
3485883360bSNate Lawson 		/* Adaptive mode; get the current CPU usage times. */
3495883360bSNate Lawson 		if (read_usage_times(&idle, &total))
3505883360bSNate Lawson 			err(1, "read_usage_times");
3515883360bSNate Lawson 
3525883360bSNate Lawson 		/*
3535883360bSNate Lawson 		 * If we're idle less than the active mark, jump the CPU to
3545883360bSNate Lawson 		 * its fastest speed if we're not there yet.  If we're idle
3555883360bSNate Lawson 		 * more than the idle mark, drop down to the first setting
3565883360bSNate Lawson 		 * that is half the current speed (exponential backoff).
3575883360bSNate Lawson 		 */
3585883360bSNate Lawson 		if (idle < (total * cpu_running_mark) / 100 &&
3595883360bSNate Lawson 		    curfreq < freqs[0]) {
3605883360bSNate Lawson 			if (vflag) {
3615883360bSNate Lawson 				printf("idle time < %d%%, increasing clock"
3625883360bSNate Lawson 				    " speed from %d MHz to %d MHz\n",
3635883360bSNate Lawson 				    cpu_running_mark, curfreq, freqs[0]);
3645883360bSNate Lawson 			}
3655883360bSNate Lawson 			if (set_freq(freqs[0]))
3665883360bSNate Lawson 				err(1, "error setting CPU frequency %d",
3675883360bSNate Lawson 				    freqs[0]);
3685883360bSNate Lawson 		} else if (idle > (total * cpu_idle_mark) / 100 &&
3695883360bSNate Lawson 		    curfreq > freqs[numfreqs - 1]) {
3705883360bSNate Lawson 			for (i = 0; i < numfreqs - 1; i++) {
3715883360bSNate Lawson 				if (freqs[i] <= curfreq / 2)
3725883360bSNate Lawson 					break;
3735883360bSNate Lawson 			}
3745883360bSNate Lawson 			if (vflag) {
3755883360bSNate Lawson 				printf("idle time > %d%%, decreasing clock"
3765883360bSNate Lawson 				    " speed from %d MHz to %d MHz\n",
3775883360bSNate Lawson 				    cpu_idle_mark, curfreq, freqs[i]);
3785883360bSNate Lawson 			}
3795883360bSNate Lawson 			if (set_freq(freqs[i]))
3805883360bSNate Lawson 				err(1, "error setting CPU frequency %d",
3815883360bSNate Lawson 				    freqs[i]);
3825883360bSNate Lawson 		}
3835883360bSNate Lawson 	}
3845883360bSNate Lawson 	/* NOTREACHED */
3855883360bSNate Lawson 
3865883360bSNate Lawson 	if (apm_fd != -1)
3875883360bSNate Lawson 		close(apm_fd);
3885883360bSNate Lawson 
3895883360bSNate Lawson 	exit(0);
3905883360bSNate Lawson }
391