]> CyberLeo.Net >> Repos - FreeBSD/releng/7.2.git/blob - usr.sbin/powerd/powerd.c
Create releng/7.2 from stable/7 in preparation for 7.2-RELEASE.
[FreeBSD/releng/7.2.git] / usr.sbin / powerd / powerd.c
1 /*-
2  * Copyright (c) 2004 Colin Percival
3  * Copyright (c) 2005 Nate Lawson
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted providing that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR``AS IS'' AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
19  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
23  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25  * POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30
31 #include <sys/param.h>
32 #include <sys/ioctl.h>
33 #include <sys/sysctl.h>
34 #include <sys/resource.h>
35 #include <sys/socket.h>
36 #include <sys/time.h>
37 #include <sys/un.h>
38
39 #include <err.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <libutil.h>
43 #include <signal.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <unistd.h>
48
49 #ifdef USE_APM
50 #include <machine/apm_bios.h>
51 #endif
52
53 #define DEFAULT_ACTIVE_PERCENT  75
54 #define DEFAULT_IDLE_PERCENT    50
55 #define DEFAULT_POLL_INTERVAL   250     /* Poll interval in milliseconds */
56
57 typedef enum {
58         MODE_MIN,
59         MODE_ADAPTIVE,
60         MODE_HIADAPTIVE,
61         MODE_MAX,
62 } modes_t;
63
64 typedef enum {
65         SRC_AC,
66         SRC_BATTERY,
67         SRC_UNKNOWN,
68 } power_src_t;
69
70 const char *modes[] = {
71         "AC",
72         "battery",
73         "unknown"
74 };
75
76 #define ACPIAC          "hw.acpi.acline"
77 #define APMDEV          "/dev/apm"
78 #define DEVDPIPE        "/var/run/devd.pipe"
79 #define DEVCTL_MAXBUF   1024
80
81 static int      read_usage_times(int *load);
82 static int      read_freqs(int *numfreqs, int **freqs, int **power);
83 static int      set_freq(int freq);
84 static void     acline_init(void);
85 static void     acline_read(void);
86 static int      devd_init(void);
87 static void     devd_close(void);
88 static void     handle_sigs(int sig);
89 static void     parse_mode(char *arg, int *mode, int ch);
90 static void     usage(void);
91
92 /* Sysctl data structures. */
93 static int      cp_times_mib[2];
94 static int      freq_mib[4];
95 static int      levels_mib[4];
96 static int      acline_mib[3];
97
98 /* Configuration */
99 static int      cpu_running_mark;
100 static int      cpu_idle_mark;
101 static int      poll_ival;
102 static int      vflag;
103
104 static volatile sig_atomic_t exit_requested;
105 static power_src_t acline_status;
106 static enum {
107         ac_none,
108         ac_acpi_sysctl,
109         ac_acpi_devd,
110 #ifdef USE_APM
111         ac_apm,
112 #endif
113 } acline_mode;
114 #ifdef USE_APM
115 static int      apm_fd = -1;
116 #endif
117 static int      devd_pipe = -1;
118
119 #define DEVD_RETRY_INTERVAL 60 /* seconds */
120 static struct timeval tried_devd;
121
122 static int
123 read_usage_times(int *load)
124 {
125         static long *cp_times = NULL, *cp_times_old = NULL;
126         static int ncpus = 0;
127         size_t cp_times_len;
128         int error, cpu, i, total;
129
130         if (cp_times == NULL) {
131                 cp_times_len = 0;
132                 error = sysctl(cp_times_mib, 2, NULL, &cp_times_len, NULL, 0);
133                 if (error)
134                         return (error);
135                 if ((cp_times = malloc(cp_times_len)) == NULL)
136                         return (errno);
137                 if ((cp_times_old = malloc(cp_times_len)) == NULL) {
138                         free(cp_times);
139                         cp_times = NULL;
140                         return (errno);
141                 }
142                 ncpus = cp_times_len / (sizeof(long) * CPUSTATES);
143         }
144
145         cp_times_len = sizeof(long) * CPUSTATES * ncpus;
146         error = sysctl(cp_times_mib, 2, cp_times, &cp_times_len, NULL, 0);
147         if (error)
148                 return (error);
149                 
150         if (load) {
151                 *load = 0;
152                 for (cpu = 0; cpu < ncpus; cpu++) {
153                         total = 0;
154                         for (i = 0; i < CPUSTATES; i++) {
155                             total += cp_times[cpu * CPUSTATES + i] -
156                                 cp_times_old[cpu * CPUSTATES + i];
157                         }
158                         if (total == 0)
159                                 continue;
160                         *load += 100 - (cp_times[cpu * CPUSTATES + CP_IDLE] - 
161                             cp_times_old[cpu * CPUSTATES + CP_IDLE]) * 100 / total;
162                 }
163         }
164
165         memcpy(cp_times_old, cp_times, cp_times_len);
166
167         return (0);
168 }
169
170 static int
171 read_freqs(int *numfreqs, int **freqs, int **power)
172 {
173         char *freqstr, *p, *q;
174         int i;
175         size_t len = 0;
176
177         if (sysctl(levels_mib, 4, NULL, &len, NULL, 0))
178                 return (-1);
179         if ((freqstr = malloc(len)) == NULL)
180                 return (-1);
181         if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0))
182                 return (-1);
183
184         *numfreqs = 1;
185         for (p = freqstr; *p != '\0'; p++)
186                 if (*p == ' ')
187                         (*numfreqs)++;
188
189         if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) {
190                 free(freqstr);
191                 return (-1);
192         }
193         if ((*power = malloc(*numfreqs * sizeof(int))) == NULL) {
194                 free(freqstr);
195                 free(*freqs);
196                 return (-1);
197         }
198         for (i = 0, p = freqstr; i < *numfreqs; i++) {
199                 q = strchr(p, ' ');
200                 if (q != NULL)
201                         *q = '\0';
202                 if (sscanf(p, "%d/%d", &(*freqs)[i], &(*power)[i]) != 2) {
203                         free(freqstr);
204                         free(*freqs);
205                         free(*power);
206                         return (-1);
207                 }
208                 p = q + 1;
209         }
210
211         free(freqstr);
212         return (0);
213 }
214
215 static int
216 get_freq(void)
217 {
218         size_t len;
219         int curfreq;
220         
221         len = sizeof(curfreq);
222         if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0) != 0) {
223                 if (vflag)
224                         warn("error reading current CPU frequency");
225                 curfreq = 0;
226         }
227         return (curfreq);
228 }
229
230 static int
231 set_freq(int freq)
232 {
233
234         if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq))) {
235                 if (errno != EPERM)
236                         return (-1);
237         }
238
239         return (0);
240 }
241
242 static int
243 get_freq_id(int freq, int *freqs, int numfreqs)
244 {
245         int i = 1;
246         
247         while (i < numfreqs) {
248                 if (freqs[i] < freq)
249                         break;
250                 i++;
251         }
252         return (i - 1);
253 }
254
255 /*
256  * Try to use ACPI to find the AC line status.  If this fails, fall back
257  * to APM.  If nothing succeeds, we'll just run in default mode.
258  */
259 static void
260 acline_init()
261 {
262         size_t len;
263
264         len = 3;
265         if (sysctlnametomib(ACPIAC, acline_mib, &len) == 0) {
266                 acline_mode = ac_acpi_sysctl;
267                 if (vflag)
268                         warnx("using sysctl for AC line status");
269 #ifdef USE_APM
270         } else if ((apm_fd = open(APMDEV, O_RDONLY)) >= 0) {
271                 if (vflag)
272                         warnx("using APM for AC line status");
273                 acline_mode = ac_apm;
274 #endif
275         } else {
276                 warnx("unable to determine AC line status");
277                 acline_mode = ac_none;
278         }
279 }
280
281 static void
282 acline_read(void)
283 {
284         if (acline_mode == ac_acpi_devd) {
285                 char buf[DEVCTL_MAXBUF], *ptr;
286                 ssize_t rlen;
287                 int notify;
288
289                 rlen = read(devd_pipe, buf, sizeof(buf));
290                 if (rlen == 0 || (rlen < 0 && errno != EWOULDBLOCK)) {
291                         if (vflag)
292                                 warnx("lost devd connection, switching to sysctl");
293                         devd_close();
294                         acline_mode = ac_acpi_sysctl;
295                         /* FALLTHROUGH */
296                 }
297                 if (rlen > 0 &&
298                     (ptr = strstr(buf, "system=ACPI")) != NULL &&
299                     (ptr = strstr(ptr, "subsystem=ACAD")) != NULL &&
300                     (ptr = strstr(ptr, "notify=")) != NULL &&
301                     sscanf(ptr, "notify=%x", &notify) == 1)
302                         acline_status = (notify ? SRC_AC : SRC_BATTERY);
303         }
304         if (acline_mode == ac_acpi_sysctl) {
305                 int acline;
306                 size_t len;
307
308                 len = sizeof(acline);
309                 if (sysctl(acline_mib, 3, &acline, &len, NULL, 0) == 0)
310                         acline_status = (acline ? SRC_AC : SRC_BATTERY);
311                 else
312                         acline_status = SRC_UNKNOWN;
313         }
314 #ifdef USE_APM
315         if (acline_mode == ac_apm) {
316                 struct apm_info info;
317
318                 if (ioctl(apm_fd, APMIO_GETINFO, &info) == 0) {
319                         acline_status = (info.ai_acline ? SRC_AC : SRC_BATTERY);
320                 } else {
321                         close(apm_fd);
322                         apm_fd = -1;
323                         acline_mode = ac_none;
324                         acline_status = SRC_UNKNOWN;
325                 }
326         }
327 #endif
328         /* try to (re)connect to devd */
329         if (acline_mode == ac_acpi_sysctl) {
330                 struct timeval now;
331
332                 gettimeofday(&now, NULL);
333                 if (now.tv_sec > tried_devd.tv_sec + DEVD_RETRY_INTERVAL) {
334                         if (devd_init() >= 0) {
335                                 if (vflag)
336                                         warnx("using devd for AC line status");
337                                 acline_mode = ac_acpi_devd;
338                         }
339                         tried_devd = now;
340                 }
341         }
342 }
343
344 static int
345 devd_init(void)
346 {
347         struct sockaddr_un devd_addr;
348
349         bzero(&devd_addr, sizeof(devd_addr));
350         if ((devd_pipe = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) {
351                 if (vflag)
352                         warn("%s(): socket()", __func__);
353                 return (-1);
354         }
355
356         devd_addr.sun_family = PF_LOCAL;
357         strlcpy(devd_addr.sun_path, DEVDPIPE, sizeof(devd_addr.sun_path));
358         if (connect(devd_pipe, (struct sockaddr *)&devd_addr,
359             sizeof(devd_addr)) == -1) {
360                 if (vflag)
361                         warn("%s(): connect()", __func__);
362                 close(devd_pipe);
363                 devd_pipe = -1;
364                 return (-1);
365         }
366
367         if (fcntl(devd_pipe, F_SETFL, O_NONBLOCK) == -1) {
368                 if (vflag)
369                         warn("%s(): fcntl()", __func__);
370                 close(devd_pipe);
371                 return (-1);
372         }
373
374         return (devd_pipe);
375 }
376
377 static void
378 devd_close(void)
379 {
380
381         close(devd_pipe);
382         devd_pipe = -1;
383 }
384
385 static void
386 parse_mode(char *arg, int *mode, int ch)
387 {
388
389         if (strcmp(arg, "minimum") == 0 || strcmp(arg, "min") == 0)
390                 *mode = MODE_MIN;
391         else if (strcmp(arg, "maximum") == 0 || strcmp(arg, "max") == 0)
392                 *mode = MODE_MAX;
393         else if (strcmp(arg, "adaptive") == 0 || strcmp(arg, "adp") == 0)
394                 *mode = MODE_ADAPTIVE;
395         else if (strcmp(arg, "hiadaptive") == 0 || strcmp(arg, "hadp") == 0)
396                 *mode = MODE_HIADAPTIVE;
397         else
398                 errx(1, "bad option: -%c %s", (char)ch, optarg);
399 }
400
401 static void
402 handle_sigs(int __unused sig)
403 {
404
405         exit_requested = 1;
406 }
407
408 static void
409 usage(void)
410 {
411
412         fprintf(stderr,
413 "usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-n mode] [-p ival] [-r %%] [-P pidfile]\n");
414         exit(1);
415 }
416
417 int
418 main(int argc, char * argv[])
419 {
420         struct timeval timeout;
421         fd_set fdset;
422         int nfds;
423         struct pidfh *pfh = NULL;
424         const char *pidfile = NULL;
425         int freq, curfreq, initfreq, *freqs, i, j, *mwatts, numfreqs, load;
426         int ch, mode, mode_ac, mode_battery, mode_none;
427         uint64_t mjoules_used;
428         size_t len;
429
430         /* Default mode for all AC states is adaptive. */
431         mode_ac = mode_none = MODE_HIADAPTIVE;
432         mode_battery = MODE_ADAPTIVE;
433         cpu_running_mark = DEFAULT_ACTIVE_PERCENT;
434         cpu_idle_mark = DEFAULT_IDLE_PERCENT;
435         poll_ival = DEFAULT_POLL_INTERVAL;
436         mjoules_used = 0;
437         vflag = 0;
438
439         /* User must be root to control frequencies. */
440         if (geteuid() != 0)
441                 errx(1, "must be root to run");
442
443         while ((ch = getopt(argc, argv, "a:b:i:n:p:P:r:v")) != -1)
444                 switch (ch) {
445                 case 'a':
446                         parse_mode(optarg, &mode_ac, ch);
447                         break;
448                 case 'b':
449                         parse_mode(optarg, &mode_battery, ch);
450                         break;
451                 case 'i':
452                         cpu_idle_mark = atoi(optarg);
453                         if (cpu_idle_mark < 0 || cpu_idle_mark > 100) {
454                                 warnx("%d is not a valid percent",
455                                     cpu_idle_mark);
456                                 usage();
457                         }
458                         break;
459                 case 'n':
460                         parse_mode(optarg, &mode_none, ch);
461                         break;
462                 case 'p':
463                         poll_ival = atoi(optarg);
464                         if (poll_ival < 5) {
465                                 warnx("poll interval is in units of ms");
466                                 usage();
467                         }
468                         break;
469                 case 'P':
470                         pidfile = optarg;
471                         break;
472                 case 'r':
473                         cpu_running_mark = atoi(optarg);
474                         if (cpu_running_mark <= 0 || cpu_running_mark > 100) {
475                                 warnx("%d is not a valid percent",
476                                     cpu_running_mark);
477                                 usage();
478                         }
479                         break;
480                 case 'v':
481                         vflag = 1;
482                         break;
483                 default:
484                         usage();
485                 }
486
487         mode = mode_none;
488
489         /* Poll interval is in units of ms. */
490         poll_ival *= 1000;
491
492         /* Look up various sysctl MIBs. */
493         len = 2;
494         if (sysctlnametomib("kern.cp_times", cp_times_mib, &len))
495                 err(1, "lookup kern.cp_times");
496         len = 4;
497         if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len))
498                 err(1, "lookup freq");
499         len = 4;
500         if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len))
501                 err(1, "lookup freq_levels");
502
503         /* Check if we can read the load and supported freqs. */
504         if (read_usage_times(NULL))
505                 err(1, "read_usage_times");
506         if (read_freqs(&numfreqs, &freqs, &mwatts))
507                 err(1, "error reading supported CPU frequencies");
508
509         /* Run in the background unless in verbose mode. */
510         if (!vflag) {
511                 pid_t otherpid;
512
513                 pfh = pidfile_open(pidfile, 0600, &otherpid);
514                 if (pfh == NULL) {
515                         if (errno == EEXIST) {
516                                 errx(1, "powerd already running, pid: %d",
517                                     otherpid);
518                         }
519                         warn("cannot open pid file");
520                 }
521                 if (daemon(0, 0) != 0) {
522                         warn("cannot enter daemon mode, exiting");
523                         pidfile_remove(pfh);
524                         exit(EXIT_FAILURE);
525
526                 }
527                 pidfile_write(pfh);
528         }
529
530         /* Decide whether to use ACPI or APM to read the AC line status. */
531         acline_init();
532
533         /*
534          * Exit cleanly on signals.
535          */
536         signal(SIGINT, handle_sigs);
537         signal(SIGTERM, handle_sigs);
538
539         freq = initfreq = get_freq();
540         if (freq < 1)
541                 freq = 1;
542         /* Main loop. */
543         for (;;) {
544                 FD_ZERO(&fdset);
545                 if (devd_pipe >= 0) {
546                         FD_SET(devd_pipe, &fdset);
547                         nfds = devd_pipe + 1;
548                 } else {
549                         nfds = 0;
550                 }
551                 timeout.tv_sec = poll_ival / 1000000;
552                 timeout.tv_usec = poll_ival % 1000000;
553                 select(nfds, &fdset, NULL, &fdset, &timeout);
554
555                 /* If the user requested we quit, print some statistics. */
556                 if (exit_requested) {
557                         if (vflag && mjoules_used != 0)
558                                 printf("total joules used: %u.%03u\n",
559                                     (u_int)(mjoules_used / 1000),
560                                     (int)mjoules_used % 1000);
561                         break;
562                 }
563
564                 /* Read the current AC status and record the mode. */
565                 acline_read();
566                 switch (acline_status) {
567                 case SRC_AC:
568                         mode = mode_ac;
569                         break;
570                 case SRC_BATTERY:
571                         mode = mode_battery;
572                         break;
573                 case SRC_UNKNOWN:
574                         mode = mode_none;
575                         break;
576                 default:
577                         errx(1, "invalid AC line status %d", acline_status);
578                 }
579
580                 /* Read the current frequency. */
581                 if ((curfreq = get_freq()) == 0)
582                         continue;
583
584                 i = get_freq_id(curfreq, freqs, numfreqs);
585         
586                 if (vflag) {
587                         /* Keep a sum of all power actually used. */
588                         if (mwatts[i] != -1)
589                                 mjoules_used +=
590                                     (mwatts[i] * (poll_ival / 1000)) / 1000;
591                 }
592
593                 /* Always switch to the lowest frequency in min mode. */
594                 if (mode == MODE_MIN) {
595                         freq = freqs[numfreqs - 1];
596                         if (curfreq != freq) {
597                                 if (vflag) {
598                                         printf("now operating on %s power; "
599                                             "changing frequency to %d MHz\n",
600                                             modes[acline_status], freq);
601                                 }
602                                 if (set_freq(freq) != 0) {
603                                         warn("error setting CPU freq %d",
604                                             freq);
605                                         continue;
606                                 }
607                         }
608                         continue;
609                 }
610
611                 /* Always switch to the highest frequency in max mode. */
612                 if (mode == MODE_MAX) {
613                         freq = freqs[0];
614                         if (curfreq != freq) {
615                                 if (vflag) {
616                                         printf("now operating on %s power; "
617                                             "changing frequency to %d MHz\n",
618                                             modes[acline_status], freq);
619                                 }
620                                 if (set_freq(freq) != 0) {
621                                         warn("error setting CPU freq %d",
622                                             freq);
623                                         continue;
624                                 }
625                         }
626                         continue;
627                 }
628
629                 /* Adaptive mode; get the current CPU usage times. */
630                 if (read_usage_times(&load)) {
631                         if (vflag)
632                                 warn("read_usage_times() failed");
633                         continue;
634                 }
635                 
636                 if (mode == MODE_ADAPTIVE) {
637                         if (load > cpu_running_mark) {
638                                 if (load > 95 || load > cpu_running_mark * 2)
639                                         freq *= 2;
640                                 else
641                                         freq = freq * load / cpu_running_mark;
642                                 if (freq > freqs[0])
643                                         freq = freqs[0];
644                         } else if (load < cpu_idle_mark &&
645                             curfreq * load < freqs[get_freq_id(
646                             freq * 7 / 8, freqs, numfreqs)] * 
647                             cpu_running_mark) {
648                                 freq = freq * 7 / 8;
649                                 if (freq < freqs[numfreqs - 1])
650                                         freq = freqs[numfreqs - 1];
651                         }
652                 } else { /* MODE_HIADAPTIVE */
653                         if (load > cpu_running_mark / 2) {
654                                 if (load > 95 || load > cpu_running_mark)
655                                         freq *= 4;
656                                 else
657                                         freq = freq * load * 2 / cpu_running_mark;
658                                 if (freq > freqs[0] * 2)
659                                         freq = freqs[0] * 2;
660                         } else if (load < cpu_idle_mark / 2 &&
661                             curfreq * load < freqs[get_freq_id(
662                             freq * 31 / 32, freqs, numfreqs)] * 
663                             cpu_running_mark / 2) {
664                                 freq = freq * 31 / 32;
665                                 if (freq < freqs[numfreqs - 1])
666                                         freq = freqs[numfreqs - 1];
667                         }
668                 }
669                 if (vflag) {
670                     printf("load %3d%%, current freq %4d MHz (%2d), wanted freq %4d MHz\n",
671                         load, curfreq, i, freq);
672                 }
673                 j = get_freq_id(freq, freqs, numfreqs);
674                 if (i != j) {
675                         if (vflag) {
676                                 printf("changing clock"
677                                     " speed from %d MHz to %d MHz\n",
678                                     freqs[i], freqs[j]);
679                         }
680                         if (set_freq(freqs[j]))
681                                 warn("error setting CPU frequency %d",
682                                     freqs[j]);
683                 }
684         }
685         if (set_freq(initfreq))
686                 warn("error setting CPU frequency %d", initfreq);
687         free(freqs);
688         free(mwatts);
689         devd_close();
690         if (!vflag)
691                 pidfile_remove(pfh);
692
693         exit(0);
694 }