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