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