powerd: Linger a little bit if battery life is low
[dragonfly.git] / usr.sbin / powerd / powerd.c
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 }