xref: /dragonfly/usr.sbin/powerd/powerd.c (revision bcb3e04d)
1 /*
2  * Copyright (c) 2010 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 /*
36  * The powerd daemon monitors the cpu load and adjusts cpu frequencies
37  * via hw.acpi.cpu.px_dom*.
38  */
39 
40 #include <sys/types.h>
41 #include <sys/sysctl.h>
42 #include <sys/kinfo.h>
43 #include <sys/file.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <unistd.h>
47 #include <string.h>
48 #include <syslog.h>
49 
50 #define STATE_UNKNOWN	0
51 #define STATE_LOW	1
52 #define STATE_HIGH	2
53 
54 static void usage(void);
55 static double getcputime(void);
56 static void acpi_setcpufreq(int ostate, int nstate);
57 
58 int DebugOpt;
59 int PowerState = STATE_UNKNOWN;
60 int PowerFd;
61 double Trigger = 0.25;
62 
63 int
64 main(int ac, char **av)
65 {
66 	double qavg;
67 	double savg;
68 	int ch;
69 	int nstate;
70 	char buf[32];
71 
72 	while ((ch = getopt(ac, av, "d")) != -1) {
73 		switch(ch) {
74 		case 'd':
75 			DebugOpt = 1;
76 			break;
77 		default:
78 			usage();
79 			/* NOT REACHED */
80 		}
81 	}
82 	ac -= optind;
83 	av += optind;
84 
85 	/*
86 	 * Prime delta cputime calculation, make sure at least dom0 exists,
87 	 * and make sure powerd is not already running.
88 	 */
89 	getcputime();
90 	savg = 0.0;
91 
92 	if (sysctlbyname("hw.acpi.cpu.px_dom0.available", NULL, NULL,
93 			 NULL, 0) < 0) {
94 		fprintf(stderr, "hw.acpi.cpu.px_dom* sysctl not available\n");
95 		exit(1);
96 	}
97 
98 	PowerFd = open("/var/run/powerd.pid", O_CREAT|O_RDWR, 0644);
99 	if (PowerFd < 0) {
100 		fprintf(stderr,
101 			"Cannot create /var/run/powerd.pid, "
102 			"continuing anyway\n");
103 	} else {
104 		if (flock(PowerFd, LOCK_EX|LOCK_NB) < 0) {
105 			fprintf(stderr, "powerd is already running\n");
106 			exit(1);
107 		}
108 	}
109 
110 	/*
111 	 * Demonize and set pid
112 	 */
113 	if (DebugOpt == 0) {
114 		daemon(0, 0);
115 		openlog("powerd", LOG_CONS | LOG_PID, LOG_DAEMON);
116 	}
117 
118 	if (PowerFd >= 0) {
119 		ftruncate(PowerFd, 0);
120 		snprintf(buf, sizeof(buf), "%d\n", (int)getpid());
121 		write(PowerFd, buf, strlen(buf));
122 	}
123 
124 	/*
125 	 * Monitoring loop
126 	 */
127 	for (;;) {
128 		qavg = getcputime();
129 		savg = (savg * 7.0 + qavg) / 8.0;
130 
131 		if (DebugOpt) {
132 			printf("\rqavg=%5.2f savg=%5.2f\r", qavg, savg);
133 			fflush(stdout);
134 		}
135 
136 		nstate = PowerState;
137 		if (nstate == STATE_UNKNOWN) {
138 			if (savg >= Trigger)
139 				nstate = STATE_HIGH;
140 			else
141 				nstate = STATE_LOW;
142 		} else if (nstate == STATE_LOW) {
143 			if (savg >= Trigger || qavg >= 0.9)
144 				nstate = STATE_HIGH;
145 		} else {
146 			if (savg < Trigger / 2.0 && qavg < Trigger / 2.0)
147 				nstate = STATE_LOW;
148 		}
149 		if (PowerState != nstate) {
150 			acpi_setcpufreq(PowerState, nstate);
151 			PowerState = nstate;
152 		}
153 		sleep(1);
154 	}
155 }
156 
157 /*
158  * Return the one-second cpu load.  One cpu at 100% will return a value
159  * of 1.0.  On a SMP system N cpus running at 100% will return a value of N.
160  */
161 static
162 double
163 getcputime(void)
164 {
165 	static struct kinfo_cputime ocpu_time[64];
166 	static struct kinfo_cputime ncpu_time[64];
167 	size_t slen;
168 	int ncpu;
169 	int cpu;
170 	uint64_t delta;
171 
172 	bcopy(ncpu_time, ocpu_time, sizeof(ncpu_time));
173 	slen = sizeof(ncpu_time);
174 	if (sysctlbyname("kern.cputime", &ncpu_time, &slen, NULL, 0) < 0) {
175 		fprintf(stderr, "kern.cputime sysctl not available\n");
176 		exit(1);
177 	}
178 	ncpu = slen / sizeof(ncpu_time[0]);
179 	delta = 0;
180 
181 	for (cpu = 0; cpu < ncpu; ++cpu) {
182 		delta += (ncpu_time[cpu].cp_user + ncpu_time[cpu].cp_sys +
183 			  ncpu_time[cpu].cp_intr) -
184 			 (ocpu_time[cpu].cp_user + ocpu_time[cpu].cp_sys +
185 			  ocpu_time[cpu].cp_intr);
186 	}
187 	return((double)delta / 1000000.0);
188 }
189 
190 
191 static
192 void
193 acpi_setcpufreq(int ostate, int nstate)
194 {
195 	int dom;
196 	int lowest;
197 	int highest;
198 	int desired;
199 	int v;
200 	char *sysid;
201 	char *ptr;
202 	char buf[256];
203 	size_t buflen;
204 
205 	dom = 0;
206 	for (;;) {
207 		/*
208 		 * Retrieve availability list
209 		 */
210 		asprintf(&sysid, "hw.acpi.cpu.px_dom%d.available", dom);
211 		buflen = sizeof(buf) - 1;
212 		v = sysctlbyname(sysid, buf, &buflen, NULL, 0);
213 		free(sysid);
214 		if (v < 0)
215 			break;
216 		buf[buflen] = 0;
217 
218 		/*
219 		 * Parse out the highest and lowest cpu frequencies
220 		 */
221 		ptr = buf;
222 		highest = lowest = 0;
223 		while (ptr && (v = strtol(ptr, &ptr, 10)) > 0) {
224 			if (lowest == 0 || lowest > v)
225 				lowest = v;
226 			if (highest == 0 || highest < v)
227 				highest = v;
228 		}
229 
230 		/*
231 		 * Calculate the desired cpu frequency, test, and set.
232 		 */
233 		desired = (nstate == STATE_LOW) ? lowest : highest;
234 
235 		asprintf(&sysid, "hw.acpi.cpu.px_dom%d.select", dom);
236 		buflen = sizeof(v);
237 		v = 0;
238 		sysctlbyname(sysid, &v, &buflen, NULL, 0);
239 		if (v != desired || ostate == STATE_UNKNOWN) {
240 			if (DebugOpt) {
241 				printf("dom%d set frequency %d\n",
242 				       dom, desired);
243 			} else {
244 				syslog(LOG_INFO, "dom%d set frequency %d\n",
245 				    dom, desired);
246 			}
247 			sysctlbyname(sysid, NULL, NULL,
248 				     &desired, sizeof(desired));
249 		}
250 		free(sysid);
251 		++dom;
252 	}
253 }
254 
255 static
256 void
257 usage(void)
258 {
259 	fprintf(stderr, "usage: powerd [-d]\n");
260 	exit(1);
261 }
262