summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--usr.sbin/powerd/Makefile8
-rw-r--r--usr.sbin/powerd/powerd.8128
-rw-r--r--usr.sbin/powerd/powerd.c390
3 files changed, 526 insertions, 0 deletions
diff --git a/usr.sbin/powerd/Makefile b/usr.sbin/powerd/Makefile
new file mode 100644
index 0000000..e104789
--- /dev/null
+++ b/usr.sbin/powerd/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= powerd
+MAN= powerd.8
+SRCS= powerd.c
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/powerd/powerd.8 b/usr.sbin/powerd/powerd.8
new file mode 100644
index 0000000..80cf607
--- /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 0000000..932996a
--- /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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <machine/apm_bios.h>
+
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+#include <sys/resource.h>
+
+#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);
+}
OpenPOWER on IntegriCloud