diff --git a/usr.sbin/powerd/Makefile b/usr.sbin/powerd/Makefile new file mode 100644 index 000000000000..e1047895d88c --- /dev/null +++ b/usr.sbin/powerd/Makefile @@ -0,0 +1,8 @@ +# $FreeBSD$ + +PROG= powerd +MAN= powerd.8 +SRCS= powerd.c +WARNS?= 6 + +.include diff --git a/usr.sbin/powerd/powerd.8 b/usr.sbin/powerd/powerd.8 new file mode 100644 index 000000000000..80cf607921c0 --- /dev/null +++ b/usr.sbin/powerd/powerd.8 @@ -0,0 +1,128 @@ +.\" Copyright (c) 2005 Nate Lawson +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd February 26, 2005 +.Dt POWERD 8 +.Os +.Sh NAME +.Nm powerd +.Nd system power control utility +.Sh SYNOPSIS +.Nm +.Op Fl a mode +.Op Fl b mode +.Op Fl i percent +.Op Fl n mode +.Op Fl p ival +.Op Fl r percent +.Op Fl v +.Sh DESCRIPTION +The +.Nm +utility monitors the system state and sets various power control options +accordingly. +It offers three modes (max, min, and adaptive) that can be +individually selected while on AC power or batteries. +.Pp +Maximum mode chooses the highest performance values. +Minimum mode selects the lowest performance values to get the most power +savings. +Adaptive mode attempts to strike a balance by degrading performance when +the system appears idle and increasing it when the system is busy. +It offers a good balance between a small performance loss for greatly +increased power savings. +The default mode is +adaptive. +.Pp +The +.Nm +utility recognizes the following runtime options: +.Bl -tag -width -i_percent +.It Fl a Ar mode +Selects the +.Ar mode +to use while on AC power. +.It Fl b Ar mode +Selects the +.Ar mode +to use while on battery power. +.It Fl i Ar percent +Specifies the CPU idle percent level when +adaptive +mode should begin to degrade performance to save power. +The default is 75% or higher. +.It Fl n Ar mode +Selects the +.Ar mode +to use normally when the AC line state is unknown. +.It Fl p Ar ival +Specifies a different polling interval (in milliseconds) for AC line state +and system idle levels. +The default is 500 ms. +.It Fl r Ar percent +Specifies the CPU idle percent level where +adaptive +mode should consider the CPU running and increase performance. +The default is 50% or lower. +.It Fl v +Verbose mode. +Messages about power changes will be printed to stdout and +.Nm +will operate in the foreground. +.El +.Sh SEE ALSO +.Xr acpi 4 , +.Xr apm 4 , +.Xr cpufreq 4 +.Sh HISTORY +The +.Nm +utility first appeared in +.Fx 5.5 . +.Sh AUTHORS +.An Colin Percival +first wrote +.Pa estctrl , +the utility that +.Nm +is based on. +.An Nate Lawson +then updated it for +.Xr cpufreq 4 , +added features, and wrote this man page. +.Sh BUGS +The +.Nm +utility should also power down idle disks and other components besides the CPU. +.Pp +If +.Nm +is used with power_profile, they may override each other. +.Pp +.Nm +should probably use the +.Xr devctl 4 +interface instead of polling for AC line state. diff --git a/usr.sbin/powerd/powerd.c b/usr.sbin/powerd/powerd.c new file mode 100644 index 000000000000..932996a381e8 --- /dev/null +++ b/usr.sbin/powerd/powerd.c @@ -0,0 +1,390 @@ +/*- + * Copyright (c) 2004 Colin Percival + * Copyright (c) 2005 Nate Lawson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#define DEFAULT_ACTIVE_PERCENT 50 +#define DEFAULT_IDLE_PERCENT 75 +#define DEFAULT_POLL_INTERVAL 500 + +enum modes_t { + MODE_MIN, + MODE_ADAPTIVE, + MODE_MAX, +}; + +enum power_src_t { + SRC_AC, + SRC_BATTERY, + SRC_UNKNOWN, +}; + +const char *modes[] = { + "AC", + "battery", + "unknown" +}; + +#define ACPIAC "hw.acpi.acline" +#define APMDEV "/dev/apm" + +static int read_usage_times(long *idle, long *total); +static int read_freqs(int *numfreqs, int **freqs); +static int set_freq(int freq); +static void parse_mode(char *arg, int *mode, int ch); +static void usage(void); + +/* Sysctl data structures. */ +static int cp_time_mib[2]; +static int freq_mib[4]; +static int levels_mib[4]; +static int acline_mib[3]; + +/* Configuration */ +static int cpu_running_mark; +static int cpu_idle_mark; +static int poll_ival; + +static int +read_usage_times(long *idle, long *total) +{ + static long idle_old, total_old; + long cp_time[CPUSTATES], i, total_new; + size_t cp_time_len; + int error; + + cp_time_len = sizeof(cp_time); + error = sysctl(cp_time_mib, 2, cp_time, &cp_time_len, NULL, 0); + if (error) + return (error); + for (total_new = 0, i = 0; i < CPUSTATES; i++) + total_new += cp_time[i]; + + if (idle) + *idle = cp_time[CP_IDLE] - idle_old; + if (total) + *total = total_new - total_old; + + idle_old = cp_time[CP_IDLE]; + total_old = total_new; + + return (0); +} + +static int +read_freqs(int *numfreqs, int **freqs) +{ + char *freqstr, *p, *q; + int i; + size_t len = 0; + + if (sysctl(levels_mib, 4, NULL, &len, NULL, 0)) + return (-1); + if ((freqstr = malloc(len)) == NULL) + return (-1); + if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0)) + return (-1); + + *numfreqs = 1; + for (p = freqstr; *p != '\0'; p++) + if (*p == ' ') + (*numfreqs)++; + + if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) { + free(freqstr); + return (-1); + } + for (i = 0, p = freqstr; i < *numfreqs; i++) { + q = strchr(p, ' '); + if (q != NULL) + *q = '\0'; + if (sscanf(p, "%d/%*d", &(*freqs)[i]) != 1) { + free(freqstr); + free(*freqs); + return (-1); + } + p = q + 1; + } + + free(freqstr); + return (0); +} + +static int +set_freq(int freq) +{ + + if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq))) + return (-1); + + return (0); +} + +static void +parse_mode(char *arg, int *mode, int ch) +{ + + if (strcmp(arg, "min") == 0) + *mode = MODE_MIN; + else if (strcmp(arg, "max") == 0) + *mode = MODE_MAX; + else if (strcmp(arg, "adaptive") == 0) + *mode = MODE_ADAPTIVE; + else + errx(1, "bad option: -%c %s", (char)ch, optarg); +} + +static void +usage(void) +{ + + fprintf(stderr, +"usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-n mode] [-p ival] [-r %%]\n"); + exit(1); +} + +int +main(int argc, char * argv[]) +{ + struct apm_info info; + long idle, total; + int apm_fd, curfreq, *freqs, i, numfreqs; + int ch, mode_ac, mode_battery, mode_none, acline, mode, vflag; + size_t len; + + /* Default mode for all AC states is adaptive. */ + mode_ac = mode_battery = mode_none = MODE_ADAPTIVE; + cpu_running_mark = DEFAULT_ACTIVE_PERCENT; + cpu_idle_mark = DEFAULT_IDLE_PERCENT; + poll_ival = DEFAULT_POLL_INTERVAL; + vflag = 0; + + while ((ch = getopt(argc, argv, "a:b:i:n:p:r:v")) != EOF) + switch (ch) { + case 'a': + parse_mode(optarg, &mode_ac, ch); + break; + case 'b': + parse_mode(optarg, &mode_battery, ch); + break; + case 'i': + cpu_idle_mark = atoi(optarg); + if (cpu_idle_mark < 0 || cpu_idle_mark > 100) { + warnx("%d is not a valid percent", + cpu_idle_mark); + usage(); + } + break; + case 'n': + parse_mode(optarg, &mode_none, ch); + break; + case 'p': + poll_ival = atoi(optarg); + if (poll_ival < 5) { + warnx("poll interval is in units of ms"); + usage(); + } + break; + case 'r': + cpu_running_mark = atoi(optarg); + if (cpu_running_mark < 0 || cpu_running_mark > 100) { + warnx("%d is not a valid percent", + cpu_running_mark); + usage(); + } + break; + case 'v': + vflag = 1; + break; + default: + usage(); + } + + /* Poll interval is in units of ms. */ + poll_ival *= 1000; + + /* Look up various sysctl MIBs. */ + len = 2; + if (sysctlnametomib("kern.cp_time", cp_time_mib, &len)) + err(1, "lookup kern.cp_time"); + len = 4; + if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len)) + err(1, "lookup freq"); + len = 4; + if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len)) + err(1, "lookup freq_levels"); + + /* Check if we can read the idle time and supported freqs. */ + if (read_usage_times(NULL, NULL)) + err(1, "read_usage_times"); + if (read_freqs(&numfreqs, &freqs)) + err(1, "error reading supported CPU frequencies"); + + /* Decide whether to use ACPI or APM to read the AC line status. */ + len = sizeof(acline); + if (sysctlbyname(ACPIAC, &acline, &len, NULL, 0)) { + /* ACPI disabled, try APM */ + apm_fd = open(APMDEV, O_RDONLY); + if (apm_fd == -1) { + warnx("cannot read AC line status, " + "using default settings"); + } + } else { + len = 3; + if (sysctlnametomib(ACPIAC, acline_mib, &len)) + err(1, "lookup acline"); + apm_fd = -1; + } + + /* Run in the background unless in verbose mode. */ + if (!vflag) + daemon(0, 0); + + /* Main loop. */ + for (;;) { + /* Check status every few milliseconds. */ + usleep(poll_ival); + + /* Read the current AC status and record the mode. */ + if (apm_fd != -1) { + if (ioctl(apm_fd, APMIO_GETINFO, &info) == -1) + acline = SRC_UNKNOWN; + else + acline = info.ai_acline ? SRC_AC : SRC_BATTERY; + } else { + len = sizeof(acline); + if (sysctl(acline_mib, 3, &acline, &len, NULL, 0)) + acline = SRC_UNKNOWN; + else + acline = acline ? SRC_AC : SRC_BATTERY; + } + switch (acline) { + case SRC_AC: + mode = mode_ac; + break; + case SRC_BATTERY: + mode = mode_battery; + break; + case SRC_UNKNOWN: + mode = mode_none; + break; + default: + errx(1, "invalid AC line status %d", acline); + } + + /* Read the current frequency. */ + len = sizeof(curfreq); + if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0)) + err(1, "error reading current CPU frequency"); + + /* Always switch to the lowest frequency in min mode. */ + if (mode == MODE_MIN) { + if (curfreq != freqs[numfreqs - 1]) { + if (vflag) { + printf("now operating on %s power; " + "changing frequency to %d MHz\n", + modes[acline], freqs[numfreqs - 1]); + } + if (set_freq(freqs[numfreqs - 1])) + err(1, "error setting CPU freq %d", + freqs[numfreqs - 1]); + } + continue; + } + + /* Always switch to the highest frequency in max mode. */ + if (mode == MODE_MAX) { + if (curfreq != freqs[0]) { + if (vflag) { + printf("Now operating on %s power; " + "changing frequency to %d MHz\n", + modes[acline], freqs[0]); + } + if (set_freq(freqs[0])) + err(1, "error setting CPU freq %d", + freqs[0]); + } + continue; + } + + /* Adaptive mode; get the current CPU usage times. */ + if (read_usage_times(&idle, &total)) + err(1, "read_usage_times"); + + /* + * If we're idle less than the active mark, jump the CPU to + * its fastest speed if we're not there yet. If we're idle + * more than the idle mark, drop down to the first setting + * that is half the current speed (exponential backoff). + */ + if (idle < (total * cpu_running_mark) / 100 && + curfreq < freqs[0]) { + if (vflag) { + printf("idle time < %d%%, increasing clock" + " speed from %d MHz to %d MHz\n", + cpu_running_mark, curfreq, freqs[0]); + } + if (set_freq(freqs[0])) + err(1, "error setting CPU frequency %d", + freqs[0]); + } else if (idle > (total * cpu_idle_mark) / 100 && + curfreq > freqs[numfreqs - 1]) { + for (i = 0; i < numfreqs - 1; i++) { + if (freqs[i] <= curfreq / 2) + break; + } + if (vflag) { + printf("idle time > %d%%, decreasing clock" + " speed from %d MHz to %d MHz\n", + cpu_idle_mark, curfreq, freqs[i]); + } + if (set_freq(freqs[i])) + err(1, "error setting CPU frequency %d", + freqs[i]); + } + } + /* NOTREACHED */ + + if (apm_fd != -1) + close(apm_fd); + + exit(0); +}