xref: /dflybsd-src/usr.sbin/powerd/powerd.c (revision 25ca8c794df06033ca37cb3fdf9b2992a7b5a9e6)
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 <sys/soundcard.h>
46 #include <sys/time.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <unistd.h>
50 #include <string.h>
51 #include <syslog.h>
52 
53 #include "alert1.h"
54 
55 static void usage(void);
56 static double getcputime(double);
57 static void acpi_setcpufreq(int nstate);
58 static void setupdominfo(void);
59 static int has_battery(void);
60 static int mon_battery(void);
61 
62 int DebugOpt;
63 int TurboOpt = 1;
64 int CpuLimit;		/* # of cpus at max frequency */
65 int DomLimit;		/* # of domains at max frequency */
66 int PowerFd;
67 int DomBeg;
68 int DomEnd;
69 int NCpus;
70 int CpuCount[256];	/* # of cpus in any given domain */
71 int CpuToDom[256];	/* domain a particular cpu belongs to */
72 int Hysteresis = 10;	/* percentage */
73 double TriggerUp = 0.25;/* single-cpu load to force max freq */
74 double TriggerDown; /* load per cpu to force the min freq */
75 static int BatLifeMin = 2; /* shutdown the box, if low on battery life */
76 static struct timespec BatLifePrevT;
77 static int BatLifePollIntvl = 5; /* unit: sec */
78 
79 static struct timespec BatShutdownStartT;
80 static int BatShutdownLinger = -1;
81 static int BatShutdownLingerSet = 60; /* unit: sec */
82 static int BatShutdownLingerCnt;
83 static int BatShutdownAudioAlert = 1;
84 
85 static void sigintr(int signo);
86 
87 int
88 main(int ac, char **av)
89 {
90 	double qavg;
91 	double uavg;	/* uavg - used for speeding up */
92 	double davg;	/* davg - used for slowing down */
93 	double srt;
94 	double pollrate;
95 	int ch;
96 	int ustate;
97 	int dstate;
98 	int nstate;
99 	char buf[64];
100 	int monbat;
101 
102 	srt = 8.0;	/* time for samples - 8 seconds */
103 	pollrate = 1.0;	/* polling rate in seconds */
104 
105 	while ((ch = getopt(ac, av, "dp:r:tu:B:L:P:QT:")) != -1) {
106 		switch(ch) {
107 		case 'd':
108 			DebugOpt = 1;
109 			break;
110 		case 'p':
111 			Hysteresis = (int)strtol(optarg, NULL, 10);
112 			break;
113 		case 'r':
114 			pollrate = strtod(optarg, NULL);
115 			break;
116 		case 't':
117 			TurboOpt = 0;
118 			break;
119 		case 'u':
120 			TriggerUp = (double)strtol(optarg, NULL, 10) / 100;
121 			break;
122 		case 'B':
123 			BatLifeMin = strtol(optarg, NULL, 10);
124 			break;
125 		case 'L':
126 			BatShutdownLingerSet = strtol(optarg, NULL, 10);
127 			if (BatShutdownLingerSet < 0)
128 				BatShutdownLingerSet = 0;
129 			break;
130 		case 'P':
131 			BatLifePollIntvl = strtol(optarg, NULL, 10);
132 			break;
133 		case 'Q':
134 			BatShutdownAudioAlert = 0;
135 			break;
136 		case 'T':
137 			srt = strtod(optarg, NULL);
138 			break;
139 		default:
140 			usage();
141 			/* NOT REACHED */
142 		}
143 	}
144 	ac -= optind;
145 	av += optind;
146 
147 	if (0 > Hysteresis || Hysteresis > 99) {
148 		fprintf(stderr, "Invalid hysteresis value\n");
149 		exit(1);
150 	}
151 
152 	if (0 > TriggerUp || TriggerUp > 1) {
153 		fprintf(stderr, "Invalid load limit value\n");
154 		exit(1);
155 	}
156 
157 	TriggerDown = TriggerUp - (TriggerUp * (double) Hysteresis / 100);
158 
159 	/*
160 	 * Make sure powerd is not already running.
161 	 */
162 	PowerFd = open("/var/run/powerd.pid", O_CREAT|O_RDWR, 0644);
163 	if (PowerFd < 0) {
164 		fprintf(stderr,
165 			"Cannot create /var/run/powerd.pid, "
166 			"continuing anyway\n");
167 	} else {
168 		if (flock(PowerFd, LOCK_EX|LOCK_NB) < 0) {
169 			fprintf(stderr, "powerd is already running\n");
170 			exit(1);
171 		}
172 	}
173 
174 	/*
175 	 * Demonize and set pid
176 	 */
177 	if (DebugOpt == 0) {
178 		daemon(0, 0);
179 		openlog("powerd", LOG_CONS | LOG_PID, LOG_DAEMON);
180 	}
181 
182 	if (PowerFd >= 0) {
183 		ftruncate(PowerFd, 0);
184 		snprintf(buf, sizeof(buf), "%d\n", (int)getpid());
185 		write(PowerFd, buf, strlen(buf));
186 	}
187 
188 	/* Do we need to monitor battery life? */
189 	if (BatLifePollIntvl <= 0)
190 		monbat = 0;
191 	else
192 		monbat = has_battery();
193 
194 	/*
195 	 * Wait hw.acpi.cpu.px_dom* sysctl to be created by kernel
196 	 *
197 	 * Since hw.acpi.cpu.px_dom* creation is queued into ACPI
198 	 * taskqueue and ACPI taskqueue is shared across various
199 	 * ACPI modules, any delay in other modules may cause
200 	 * hw.acpi.cpu.px_dom* to be created at quite a later time
201 	 * (e.g. cmbat module's task could take quite a lot of time).
202 	 */
203 	for (;;) {
204 		/*
205 		 * Prime delta cputime calculation, make sure at least
206 		 * dom0 exists.
207 		 */
208 		getcputime(pollrate);
209 
210 		setupdominfo();
211 		if (DomBeg >= DomEnd) {
212 			usleep((int)(pollrate * 1000000.0));
213 			continue;
214 		}
215 
216 		DomLimit = DomEnd;
217 		CpuLimit = NCpus;
218 		break;
219 	}
220 
221 	/*
222 	 * Set to maximum performance if killed.
223 	 */
224 	signal(SIGINT, sigintr);
225 	signal(SIGTERM, sigintr);
226 	uavg = 0.0;
227 	davg = 0.0;
228 
229 	srt = srt / pollrate;	/* convert to sample count */
230 
231 	if (DebugOpt)
232 		printf("samples for downgrading: %5.2f\n", srt);
233 
234 	/*
235 	 * Monitoring loop
236 	 *
237 	 * Calculate nstate, the number of cpus we wish to run at max
238 	 * frequency.  All remaining cpus will be set to their lowest
239 	 * frequency and mapped out of the user process scheduler.
240 	 */
241 	for (;;) {
242 		qavg = getcputime(pollrate);
243 		uavg = (uavg * 2.0 + qavg) / 3.0;	/* speeding up */
244 		davg = (davg * srt + qavg) / (srt + 1);	/* slowing down */
245 		if (davg < uavg)
246 			davg = uavg;
247 
248 		ustate = uavg / TriggerUp;
249 		if (ustate < CpuLimit)
250 			ustate = uavg / TriggerDown;
251 		dstate = davg / TriggerUp;
252 		if (dstate < CpuLimit)
253 			dstate = davg / TriggerDown;
254 
255 		nstate = (ustate > dstate) ? ustate : dstate;
256 		if (nstate > NCpus)
257 			nstate = NCpus;
258 
259 		if (DebugOpt) {
260 			printf("\rqavg=%5.2f uavg=%5.2f davg=%5.2f "
261 			       "%2d/%2d ncpus=%d\r",
262 				qavg, uavg, davg,
263 				CpuLimit, DomLimit, nstate);
264 			fflush(stdout);
265 		}
266 		if (nstate != CpuLimit)
267 			acpi_setcpufreq(nstate);
268 		if (monbat)
269 			monbat = mon_battery();
270 		usleep((int)(pollrate * 1000000.0));
271 	}
272 }
273 
274 static
275 void
276 sigintr(int signo __unused)
277 {
278 	syslog(LOG_INFO, "killed, setting max and exiting");
279 	acpi_setcpufreq(NCpus);
280 	exit(1);
281 }
282 
283 /*
284  * Figure out the domains and calculate the CpuCount[] and CpuToDom[]
285  * arrays.
286  */
287 static
288 void
289 setupdominfo(void)
290 {
291 	char buf[64];
292 	char members[1024];
293 	char *str;
294 	size_t msize;
295 	int i;
296 	int n;
297 
298 	for (i = 0; i < 256; ++i) {
299 		snprintf(buf, sizeof(buf),
300 			 "hw.acpi.cpu.px_dom%d.available", i);
301 		if (sysctlbyname(buf, NULL, NULL, NULL, 0) >= 0)
302 			break;
303 	}
304 	DomBeg = i;
305 
306 	for (i = 255; i >= DomBeg; --i) {
307 		snprintf(buf, sizeof(buf),
308 			 "hw.acpi.cpu.px_dom%d.available", i);
309 		if (sysctlbyname(buf, NULL, NULL, NULL, 0) >= 0) {
310 			++i;
311 			break;
312 		}
313 	}
314 	DomEnd = i;
315 
316 	for (i = DomBeg; i < DomEnd; ++i) {
317 		snprintf(buf, sizeof(buf),
318 			 "hw.acpi.cpu.px_dom%d.members", i);
319 		msize = sizeof(members);
320 		if (sysctlbyname(buf, members, &msize, NULL, 0) == 0) {
321 			members[msize] = 0;
322 			for (str = strtok(members, " "); str;
323 			     str = strtok(NULL, " ")) {
324 				n = -1;
325 				sscanf(str, "cpu%d", &n);
326 				if (n >= 0) {
327 					++NCpus;
328 					++CpuCount[i];
329 					CpuToDom[n]= i;
330 				}
331 			}
332 		}
333 	}
334 }
335 
336 /*
337  * Return the one-second cpu load.  One cpu at 100% will return a value
338  * of 1.0.  On a SMP system N cpus running at 100% will return a value of N.
339  */
340 static
341 double
342 getcputime(double pollrate)
343 {
344 	static struct kinfo_cputime ocpu_time[64];
345 	static struct kinfo_cputime ncpu_time[64];
346 	size_t slen;
347 	int ncpu;
348 	int cpu;
349 	uint64_t delta;
350 
351 	bcopy(ncpu_time, ocpu_time, sizeof(ncpu_time));
352 	slen = sizeof(ncpu_time);
353 	if (sysctlbyname("kern.cputime", &ncpu_time, &slen, NULL, 0) < 0) {
354 		fprintf(stderr, "kern.cputime sysctl not available\n");
355 		exit(1);
356 	}
357 	ncpu = slen / sizeof(ncpu_time[0]);
358 	delta = 0;
359 
360 	for (cpu = 0; cpu < ncpu; ++cpu) {
361 		delta += (ncpu_time[cpu].cp_user + ncpu_time[cpu].cp_sys +
362 			  ncpu_time[cpu].cp_nice + ncpu_time[cpu].cp_intr) -
363 			 (ocpu_time[cpu].cp_user + ocpu_time[cpu].cp_sys +
364 			  ocpu_time[cpu].cp_nice + ocpu_time[cpu].cp_intr);
365 	}
366 	return((double)delta / (pollrate * 1000000.0));
367 }
368 
369 /*
370  * nstate is the requested number of cpus that we wish to run at full
371  * frequency.  We calculate how many domains we have to adjust to reach
372  * this goal.
373  *
374  * This function also sets the user scheduler global cpu mask.
375  */
376 static
377 void
378 acpi_setcpufreq(int nstate)
379 {
380 	int ncpus = 0;
381 	int increasing = (nstate > CpuLimit);
382 	int dom;
383 	int domBeg;
384 	int domEnd;
385 	int lowest;
386 	int highest;
387 	int desired;
388 	int v;
389 	char *sysid;
390 	char *ptr;
391 	char buf[256];
392 	size_t buflen;
393 	cpumask_t global_cpumask;
394 
395 	/*
396 	 * Calculate the ending domain if the number of operating cpus
397 	 * has increased.
398 	 *
399 	 * Calculate the starting domain if the number of operating cpus
400 	 * has decreased.
401 	 */
402 	for (dom = DomBeg; dom < DomEnd; ++dom) {
403 		if (ncpus >= nstate)
404 			break;
405 		ncpus += CpuCount[dom];
406 	}
407 
408 	syslog(LOG_INFO, "using %d cpus", nstate);
409 
410 	/*
411 	 * Set the mask of cpus the userland scheduler is allowed to use.
412 	 */
413 	CPUMASK_ASSBMASK(global_cpumask, nstate);
414 	sysctlbyname("kern.usched_global_cpumask", NULL, 0,
415 		     &global_cpumask, sizeof(global_cpumask));
416 
417 	if (increasing) {
418 		domBeg = DomLimit;
419 		domEnd = dom;
420 	} else {
421 		domBeg = dom;
422 		domEnd = DomLimit;
423 	}
424 	DomLimit = dom;
425 	CpuLimit = nstate;
426 
427 	/*
428 	 * Adjust the cpu frequency
429 	 */
430 	if (DebugOpt)
431 		printf("\n");
432 	for (dom = domBeg; dom < domEnd; ++dom) {
433 		/*
434 		 * Retrieve availability list
435 		 */
436 		asprintf(&sysid, "hw.acpi.cpu.px_dom%d.available", dom);
437 		buflen = sizeof(buf) - 1;
438 		v = sysctlbyname(sysid, buf, &buflen, NULL, 0);
439 		free(sysid);
440 		if (v < 0)
441 			continue;
442 		buf[buflen] = 0;
443 
444 		/*
445 		 * Parse out the highest and lowest cpu frequencies
446 		 */
447 		ptr = buf;
448 		highest = lowest = 0;
449 		while (ptr && (v = strtol(ptr, &ptr, 10)) > 0) {
450 			if (lowest == 0 || lowest > v)
451 				lowest = v;
452 			if (highest == 0 || highest < v)
453 				highest = v;
454 			/*
455 			 * Detect turbo mode
456 			 */
457 			if ((highest - v == 1) && ! TurboOpt)
458 				highest = v;
459 
460 		}
461 
462 		/*
463 		 * Calculate the desired cpu frequency, test, and set.
464 		 */
465 		desired = increasing ? highest : lowest;
466 
467 		asprintf(&sysid, "hw.acpi.cpu.px_dom%d.select", dom);
468 		buflen = sizeof(v);
469 		v = 0;
470 		sysctlbyname(sysid, &v, &buflen, NULL, 0);
471 		{
472 			if (DebugOpt) {
473 				printf("dom%d set frequency %d\n",
474 				       dom, desired);
475 			}
476 			sysctlbyname(sysid, NULL, NULL,
477 				     &desired, sizeof(desired));
478 		}
479 		free(sysid);
480 	}
481 }
482 
483 static
484 void
485 usage(void)
486 {
487 	fprintf(stderr, "usage: powerd [-dt] [-p hysteresis] "
488 	    "[-u trigger_up] [-T sample_interval] [-r poll_interval] "
489 	    "[-B min_battery_life] [-L low_battery_linger] "
490 	    "[-P battery_poll_interval] [-Q]\n");
491 	exit(1);
492 }
493 
494 #ifndef timespecsub
495 #define timespecsub(vvp, uvp)						\
496 	do {								\
497 		(vvp)->tv_sec -= (uvp)->tv_sec;				\
498 		(vvp)->tv_nsec -= (uvp)->tv_nsec;			\
499 		if ((vvp)->tv_nsec < 0) {				\
500 			(vvp)->tv_sec--;				\
501 			(vvp)->tv_nsec += 1000000000;			\
502 		}							\
503 	} while (0)
504 #endif
505 
506 #define BAT_SYSCTL_TIME_MAX	50000000 /* unit: nanosecond */
507 
508 static int
509 has_battery(void)
510 {
511 	struct timespec s, e;
512 	size_t len;
513 	int val;
514 
515 	clock_gettime(CLOCK_MONOTONIC_FAST, &s);
516 	BatLifePrevT = s;
517 
518 	len = sizeof(val);
519 	if (sysctlbyname("hw.acpi.acline", &val, &len, NULL, 0) < 0) {
520 		/* No AC line information */
521 		return 0;
522 	}
523 	clock_gettime(CLOCK_MONOTONIC_FAST, &e);
524 
525 	timespecsub(&e, &s);
526 	if (e.tv_sec > 0 || e.tv_nsec > BAT_SYSCTL_TIME_MAX) {
527 		/* hw.acpi.acline takes to long to be useful */
528 		syslog(LOG_NOTICE, "hw.acpi.acline takes too long");
529 		return 0;
530 	}
531 
532 	clock_gettime(CLOCK_MONOTONIC_FAST, &s);
533 	len = sizeof(val);
534 	if (sysctlbyname("hw.acpi.battery.life", &val, &len, NULL, 0) < 0) {
535 		/* No battery life */
536 		return 0;
537 	}
538 	clock_gettime(CLOCK_MONOTONIC_FAST, &e);
539 
540 	timespecsub(&e, &s);
541 	if (e.tv_sec > 0 || e.tv_nsec > BAT_SYSCTL_TIME_MAX) {
542 		/* hw.acpi.battery.life takes to long to be useful */
543 		syslog(LOG_NOTICE, "hw.acpi.battery.life takes too long");
544 		return 0;
545 	}
546 	return 1;
547 }
548 
549 static void
550 low_battery_alert(int life)
551 {
552 	int fmt, stereo, freq;
553 	int fd;
554 
555 	syslog(LOG_ALERT, "low battery life %d%%, please plugin AC line, #%d",
556 	    life, BatShutdownLingerCnt);
557 	++BatShutdownLingerCnt;
558 
559 	if (!BatShutdownAudioAlert)
560 		return;
561 
562 	fd = open("/dev/dsp", O_WRONLY);
563 	if (fd < 0)
564 		return;
565 
566 	fmt = AFMT_S16_LE;
567 	if (ioctl(fd, SNDCTL_DSP_SETFMT, &fmt, sizeof(fmt)) < 0)
568 		goto done;
569 
570 	stereo = 0;
571 	if (ioctl(fd, SNDCTL_DSP_STEREO, &stereo, sizeof(stereo)) < 0)
572 		goto done;
573 
574 	freq = 44100;
575 	if (ioctl(fd, SNDCTL_DSP_SPEED, &freq, sizeof(freq)) < 0)
576 		goto done;
577 
578 	write(fd, alert1, sizeof(alert1));
579 	write(fd, alert1, sizeof(alert1));
580 
581 done:
582 	close(fd);
583 }
584 
585 static int
586 mon_battery(void)
587 {
588 	struct timespec cur, ts;
589 	int acline, life;
590 	size_t len;
591 
592 	clock_gettime(CLOCK_MONOTONIC_FAST, &cur);
593 	ts = cur;
594 	timespecsub(&ts, &BatLifePrevT);
595 	if (ts.tv_sec < BatLifePollIntvl)
596 		return 1;
597 	BatLifePrevT = cur;
598 
599 	len = sizeof(acline);
600 	if (sysctlbyname("hw.acpi.acline", &acline, &len, NULL, 0) < 0)
601 		return 1;
602 	if (acline) {
603 		BatShutdownLinger = -1;
604 		BatShutdownLingerCnt = 0;
605 		return 1;
606 	}
607 
608 	len = sizeof(life);
609 	if (sysctlbyname("hw.acpi.battery.life", &life, &len, NULL, 0) < 0)
610 		return 1;
611 
612 	if (BatShutdownLinger > 0) {
613 		ts = cur;
614 		timespecsub(&ts, &BatShutdownStartT);
615 		if (ts.tv_sec > BatShutdownLinger)
616 			BatShutdownLinger = 0;
617 	}
618 
619 	if (life <= BatLifeMin) {
620 		if (BatShutdownLinger == 0 || BatShutdownLingerSet == 0) {
621 			syslog(LOG_ALERT, "low battery life %d%%, "
622 			    "shutting down", life);
623 			if (vfork() == 0)
624 				execlp("poweroff", "poweroff", NULL);
625 			return 0;
626 		} else if (BatShutdownLinger < 0) {
627 			BatShutdownLinger = BatShutdownLingerSet;
628 			BatShutdownStartT = cur;
629 		}
630 		low_battery_alert(life);
631 	}
632 	return 1;
633 }
634