xref: /dflybsd-src/usr.sbin/powerd/powerd.c (revision e90a7c45c3303ed54c0fde732b2ba32dc80ffd9b)
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 static void usage(void);
51 static double getcputime(void);
52 static void acpi_setcpufreq(int nstate);
53 static void setupdominfo(void);
54 
55 int DebugOpt;
56 int CpuLimit;		/* # of cpus at max frequency */
57 int DomLimit;		/* # of domains at max frequency */
58 int PowerFd;
59 int DomBeg;
60 int DomEnd;
61 int NCpus;
62 int CpuCount[256];	/* # of cpus in any given domain */
63 int CpuToDom[256];	/* domain a particular cpu belongs to */
64 double Trigger = 0.25;	/* load per cpu to force max freq */
65 
66 int
67 main(int ac, char **av)
68 {
69 	double qavg;
70 	double savg;
71 	int ch;
72 	int nstate;
73 	char buf[64];
74 
75 	while ((ch = getopt(ac, av, "d")) != -1) {
76 		switch(ch) {
77 		case 'd':
78 			DebugOpt = 1;
79 			break;
80 		default:
81 			usage();
82 			/* NOT REACHED */
83 		}
84 	}
85 	ac -= optind;
86 	av += optind;
87 
88 	/*
89 	 * Prime delta cputime calculation, make sure at least dom0 exists,
90 	 * and make sure powerd is not already running.
91 	 */
92 	getcputime();
93 	savg = 0.0;
94 
95 	setupdominfo();
96 	if (DomBeg >= DomEnd) {
97 		fprintf(stderr, "hw.acpi.cpu.px_dom* sysctl not available\n");
98 		exit(1);
99 	}
100 	DomLimit = DomEnd;
101 	CpuLimit = NCpus;
102 
103 	PowerFd = open("/var/run/powerd.pid", O_CREAT|O_RDWR, 0644);
104 	if (PowerFd < 0) {
105 		fprintf(stderr,
106 			"Cannot create /var/run/powerd.pid, "
107 			"continuing anyway\n");
108 	} else {
109 		if (flock(PowerFd, LOCK_EX|LOCK_NB) < 0) {
110 			fprintf(stderr, "powerd is already running\n");
111 			exit(1);
112 		}
113 	}
114 
115 	/*
116 	 * Demonize and set pid
117 	 */
118 	if (DebugOpt == 0) {
119 		daemon(0, 0);
120 		openlog("powerd", LOG_CONS | LOG_PID, LOG_DAEMON);
121 	}
122 
123 	if (PowerFd >= 0) {
124 		ftruncate(PowerFd, 0);
125 		snprintf(buf, sizeof(buf), "%d\n", (int)getpid());
126 		write(PowerFd, buf, strlen(buf));
127 	}
128 
129 	/*
130 	 * Monitoring loop
131 	 *
132 	 * Calculate nstate, the number of cpus we wish to run at max
133 	 * frequency.  All remaining cpus will be set to their lowest
134 	 * frequency and mapped out of the user process scheduler.
135 	 */
136 	for (;;) {
137 		qavg = getcputime();
138 		savg = (savg * 7.0 + qavg) / 8.0;
139 
140 		nstate = savg / Trigger;
141 		if (nstate > NCpus)
142 			nstate = NCpus;
143 		if (DebugOpt) {
144 			printf("\rqavg=%5.2f savg=%5.2f %2d/%2d ncpus=%d\r",
145 				qavg, savg, CpuLimit, DomLimit, nstate);
146 			fflush(stdout);
147 		}
148 		if (nstate != CpuLimit)
149 			acpi_setcpufreq(nstate);
150 		sleep(1);
151 	}
152 }
153 
154 /*
155  * Figure out the domains and calculate the CpuCount[] and CpuToDom[]
156  * arrays.
157  */
158 static
159 void
160 setupdominfo(void)
161 {
162 	char buf[64];
163 	char members[1024];
164 	char *str;
165 	size_t msize;
166 	int i;
167 	int n;
168 
169 	for (i = 0; i < 256; ++i) {
170 		snprintf(buf, sizeof(buf),
171 			 "hw.acpi.cpu.px_dom%d.available", i);
172 		if (sysctlbyname(buf, NULL, NULL, NULL, 0) >= 0)
173 			break;
174 	}
175 	DomBeg = i;
176 
177 	for (i = 255; i >= DomBeg; --i) {
178 		snprintf(buf, sizeof(buf),
179 			 "hw.acpi.cpu.px_dom%d.available", i);
180 		if (sysctlbyname(buf, NULL, NULL, NULL, 0) >= 0) {
181 			++i;
182 			break;
183 		}
184 	}
185 	DomEnd = i;
186 
187 	for (i = DomBeg; i < DomEnd; ++i) {
188 		snprintf(buf, sizeof(buf),
189 			 "hw.acpi.cpu.px_dom%d.members", i);
190 		msize = sizeof(members);
191 		if (sysctlbyname(buf, members, &msize, NULL, 0) == 0) {
192 			members[msize] = 0;
193 			for (str = strtok(members, " "); str;
194 			     str = strtok(NULL, " ")) {
195 				n = -1;
196 				sscanf(str, "cpu%d", &n);
197 				if (n >= 0) {
198 					++NCpus;
199 					++CpuCount[i];
200 					CpuToDom[n]= i;
201 				}
202 			}
203 		}
204 	}
205 }
206 
207 /*
208  * Return the one-second cpu load.  One cpu at 100% will return a value
209  * of 1.0.  On a SMP system N cpus running at 100% will return a value of N.
210  */
211 static
212 double
213 getcputime(void)
214 {
215 	static struct kinfo_cputime ocpu_time[64];
216 	static struct kinfo_cputime ncpu_time[64];
217 	size_t slen;
218 	int ncpu;
219 	int cpu;
220 	uint64_t delta;
221 
222 	bcopy(ncpu_time, ocpu_time, sizeof(ncpu_time));
223 	slen = sizeof(ncpu_time);
224 	if (sysctlbyname("kern.cputime", &ncpu_time, &slen, NULL, 0) < 0) {
225 		fprintf(stderr, "kern.cputime sysctl not available\n");
226 		exit(1);
227 	}
228 	ncpu = slen / sizeof(ncpu_time[0]);
229 	delta = 0;
230 
231 	for (cpu = 0; cpu < ncpu; ++cpu) {
232 		delta += (ncpu_time[cpu].cp_user + ncpu_time[cpu].cp_sys +
233 			  ncpu_time[cpu].cp_intr) -
234 			 (ocpu_time[cpu].cp_user + ocpu_time[cpu].cp_sys +
235 			  ocpu_time[cpu].cp_intr);
236 	}
237 	return((double)delta / 1000000.0);
238 }
239 
240 /*
241  * nstate is the requested number of cpus that we wish to run at full
242  * frequency.  We calculate how many domains we have to adjust to reach
243  * this goal.
244  *
245  * This function also sets the user scheduler global cpu mask.
246  */
247 static
248 void
249 acpi_setcpufreq(int nstate)
250 {
251 	int ncpus = 0;
252 	int increasing = (nstate > CpuLimit);
253 	int dom;
254 	int domBeg;
255 	int domEnd;
256 	int lowest;
257 	int highest;
258 	int desired;
259 	int v;
260 	char *sysid;
261 	char *ptr;
262 	char buf[256];
263 	size_t buflen;
264 	cpumask_t global_cpumask;
265 
266 	/*
267 	 * Calculate the ending domain if the number of operating cpus
268 	 * has increased.
269 	 *
270 	 * Calculate the starting domain if the number of operating cpus
271 	 * has decreased.
272 	 */
273 	for (dom = DomBeg; dom < DomEnd; ++dom) {
274 		if (ncpus >= nstate)
275 			break;
276 		ncpus += CpuCount[dom];
277 	}
278 
279 	syslog(LOG_INFO, "using %d cpus", nstate);
280 
281 	/*
282 	 * Set the mask of cpus the userland scheduler is allowed to use.
283 	 */
284 	global_cpumask = (1L << nstate) - 1;
285 	sysctlbyname("kern.usched_global_cpumask", NULL, 0,
286 		     &global_cpumask, sizeof(global_cpumask));
287 
288 	if (increasing) {
289 		domBeg = DomLimit;
290 		domEnd = dom;
291 	} else {
292 		domBeg = dom;
293 		domEnd = DomLimit;
294 	}
295 	DomLimit = dom;
296 	CpuLimit = nstate;
297 
298 	/*
299 	 * Adjust the cpu frequency
300 	 */
301 	if (DebugOpt)
302 		printf("\n");
303 	for (dom = domBeg; dom < domEnd; ++dom) {
304 		/*
305 		 * Retrieve availability list
306 		 */
307 		asprintf(&sysid, "hw.acpi.cpu.px_dom%d.available", dom);
308 		buflen = sizeof(buf) - 1;
309 		v = sysctlbyname(sysid, buf, &buflen, NULL, 0);
310 		free(sysid);
311 		if (v < 0)
312 			continue;
313 		buf[buflen] = 0;
314 
315 		/*
316 		 * Parse out the highest and lowest cpu frequencies
317 		 */
318 		ptr = buf;
319 		highest = lowest = 0;
320 		while (ptr && (v = strtol(ptr, &ptr, 10)) > 0) {
321 			if (lowest == 0 || lowest > v)
322 				lowest = v;
323 			if (highest == 0 || highest < v)
324 				highest = v;
325 		}
326 
327 		/*
328 		 * Calculate the desired cpu frequency, test, and set.
329 		 */
330 		desired = increasing ? highest : lowest;
331 
332 		asprintf(&sysid, "hw.acpi.cpu.px_dom%d.select", dom);
333 		buflen = sizeof(v);
334 		v = 0;
335 		sysctlbyname(sysid, &v, &buflen, NULL, 0);
336 		{
337 			if (DebugOpt) {
338 				printf("dom%d set frequency %d\n",
339 				       dom, desired);
340 			}
341 			sysctlbyname(sysid, NULL, NULL,
342 				     &desired, sizeof(desired));
343 		}
344 		free(sysid);
345 	}
346 }
347 
348 static
349 void
350 usage(void)
351 {
352 	fprintf(stderr, "usage: powerd [-d]\n");
353 	exit(1);
354 }
355