]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/powerd/powerd.c
add -n option to suppress clearing the build tree and add -DNO_CLEAN
[FreeBSD/FreeBSD.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  65
54 #define DEFAULT_IDLE_PERCENT    90
55 #define DEFAULT_POLL_INTERVAL   500     /* Poll interval in milliseconds */
56
57 typedef enum {
58         MODE_MIN,
59         MODE_ADAPTIVE,
60         MODE_MAX,
61 } modes_t;
62
63 typedef enum {
64         SRC_AC,
65         SRC_BATTERY,
66         SRC_UNKNOWN,
67 } power_src_t;
68
69 const char *modes[] = {
70         "AC",
71         "battery",
72         "unknown"
73 };
74
75 #define ACPIAC          "hw.acpi.acline"
76 #define APMDEV          "/dev/apm"
77 #define DEVDPIPE        "/var/run/devd.pipe"
78 #define DEVCTL_MAXBUF   1024
79
80 static int      read_usage_times(long *idle, long *total);
81 static int      read_freqs(int *numfreqs, int **freqs, int **power);
82 static int      set_freq(int freq);
83 static void     acline_init(void);
84 static void     acline_read(void);
85 static int      devd_init(void);
86 static void     devd_close(void);
87 static void     handle_sigs(int sig);
88 static void     parse_mode(char *arg, int *mode, int ch);
89 static void     usage(void);
90
91 /* Sysctl data structures. */
92 static int      cp_time_mib[2];
93 static int      freq_mib[4];
94 static int      levels_mib[4];
95 static int      acline_mib[3];
96
97 /* Configuration */
98 static int      cpu_running_mark;
99 static int      cpu_idle_mark;
100 static int      poll_ival;
101 static int      vflag;
102
103 static volatile sig_atomic_t exit_requested;
104 static power_src_t acline_status;
105 static enum {
106         ac_none,
107         ac_acpi_sysctl,
108         ac_acpi_devd,
109 #ifdef USE_APM
110         ac_apm,
111 #endif
112 } acline_mode;
113 #ifdef USE_APM
114 static int      apm_fd = -1;
115 #endif
116 static int      devd_pipe = -1;
117
118 #define DEVD_RETRY_INTERVAL 60 /* seconds */
119 static struct timeval tried_devd;
120
121 static int
122 read_usage_times(long *idle, long *total)
123 {
124         static long idle_old, total_old;
125         long cp_time[CPUSTATES], i, total_new;
126         size_t cp_time_len;
127         int error;
128
129         cp_time_len = sizeof(cp_time);
130         error = sysctl(cp_time_mib, 2, cp_time, &cp_time_len, NULL, 0);
131         if (error)
132                 return (error);
133         for (total_new = 0, i = 0; i < CPUSTATES; i++)
134                 total_new += cp_time[i];
135
136         if (idle)
137                 *idle = cp_time[CP_IDLE] - idle_old;
138         if (total)
139                 *total = total_new - total_old;
140
141         idle_old = cp_time[CP_IDLE];
142         total_old = total_new;
143
144         return (0);
145 }
146
147 static int
148 read_freqs(int *numfreqs, int **freqs, int **power)
149 {
150         char *freqstr, *p, *q;
151         int i;
152         size_t len = 0;
153
154         if (sysctl(levels_mib, 4, NULL, &len, NULL, 0))
155                 return (-1);
156         if ((freqstr = malloc(len)) == NULL)
157                 return (-1);
158         if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0))
159                 return (-1);
160
161         *numfreqs = 1;
162         for (p = freqstr; *p != '\0'; p++)
163                 if (*p == ' ')
164                         (*numfreqs)++;
165
166         if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) {
167                 free(freqstr);
168                 return (-1);
169         }
170         if ((*power = malloc(*numfreqs * sizeof(int))) == NULL) {
171                 free(freqstr);
172                 free(*freqs);
173                 return (-1);
174         }
175         for (i = 0, p = freqstr; i < *numfreqs; i++) {
176                 q = strchr(p, ' ');
177                 if (q != NULL)
178                         *q = '\0';
179                 if (sscanf(p, "%d/%d", &(*freqs)[i], &(*power)[i]) != 2) {
180                         free(freqstr);
181                         free(*freqs);
182                         free(*power);
183                         return (-1);
184                 }
185                 p = q + 1;
186         }
187
188         free(freqstr);
189         return (0);
190 }
191
192 static int
193 set_freq(int freq)
194 {
195
196         if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq))) {
197                 if (errno != EPERM)
198                         return (-1);
199         }
200
201         return (0);
202 }
203
204 /*
205  * Try to use ACPI to find the AC line status.  If this fails, fall back
206  * to APM.  If nothing succeeds, we'll just run in default mode.
207  */
208 static void
209 acline_init()
210 {
211         size_t len;
212
213         len = 3;
214         if (sysctlnametomib(ACPIAC, acline_mib, &len) == 0) {
215                 acline_mode = ac_acpi_sysctl;
216                 if (vflag)
217                         warnx("using sysctl for AC line status");
218 #ifdef USE_APM
219         } else if ((apm_fd = open(APMDEV, O_RDONLY)) >= 0) {
220                 if (vflag)
221                         warnx("using APM for AC line status");
222                 acline_mode = ac_apm;
223 #endif
224         } else {
225                 warnx("unable to determine AC line status");
226                 acline_mode = ac_none;
227         }
228 }
229
230 static void
231 acline_read(void)
232 {
233         if (acline_mode == ac_acpi_devd) {
234                 char buf[DEVCTL_MAXBUF], *ptr;
235                 ssize_t rlen;
236                 int notify;
237
238                 rlen = read(devd_pipe, buf, sizeof(buf));
239                 if (rlen == 0 || (rlen < 0 && errno != EWOULDBLOCK)) {
240                         if (vflag)
241                                 warnx("lost devd connection, switching to sysctl");
242                         devd_close();
243                         acline_mode = ac_acpi_sysctl;
244                         /* FALLTHROUGH */
245                 }
246                 if (rlen > 0 &&
247                     (ptr = strstr(buf, "system=ACPI")) != NULL &&
248                     (ptr = strstr(ptr, "subsystem=ACAD")) != NULL &&
249                     (ptr = strstr(ptr, "notify=")) != NULL &&
250                     sscanf(ptr, "notify=%x", &notify) == 1)
251                         acline_status = (notify ? SRC_AC : SRC_BATTERY);
252         }
253         if (acline_mode == ac_acpi_sysctl) {
254                 int acline;
255                 size_t len;
256
257                 len = sizeof(acline);
258                 if (sysctl(acline_mib, 3, &acline, &len, NULL, 0) == 0)
259                         acline_status = (acline ? SRC_AC : SRC_BATTERY);
260                 else
261                         acline_status = SRC_UNKNOWN;
262         }
263 #ifdef USE_APM
264         if (acline_mode == ac_apm) {
265                 struct apm_info info;
266
267                 if (ioctl(apm_fd, APMIO_GETINFO, &info) == 0) {
268                         acline_status = (info.ai_acline ? SRC_AC : SRC_BATTERY);
269                 } else {
270                         close(apm_fd);
271                         apm_fd = -1;
272                         acline_mode = ac_none;
273                         acline_status = SRC_UNKNOWN;
274                 }
275         }
276 #endif
277         /* try to (re)connect to devd */
278         if (acline_mode == ac_acpi_sysctl) {
279                 struct timeval now;
280
281                 gettimeofday(&now, NULL);
282                 if (now.tv_sec > tried_devd.tv_sec + DEVD_RETRY_INTERVAL) {
283                         if (devd_init() >= 0) {
284                                 if (vflag)
285                                         warnx("using devd for AC line status");
286                                 acline_mode = ac_acpi_devd;
287                         }
288                         tried_devd = now;
289                 }
290         }
291 }
292
293 static int
294 devd_init(void)
295 {
296         struct sockaddr_un devd_addr;
297
298         bzero(&devd_addr, sizeof(devd_addr));
299         if ((devd_pipe = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) {
300                 if (vflag)
301                         warn("%s(): socket()", __func__);
302                 return (-1);
303         }
304
305         devd_addr.sun_family = PF_LOCAL;
306         strlcpy(devd_addr.sun_path, DEVDPIPE, sizeof(devd_addr.sun_path));
307         if (connect(devd_pipe, (struct sockaddr *)&devd_addr,
308             sizeof(devd_addr)) == -1) {
309                 if (vflag)
310                         warn("%s(): connect()", __func__);
311                 close(devd_pipe);
312                 devd_pipe = -1;
313                 return (-1);
314         }
315
316         if (fcntl(devd_pipe, F_SETFL, O_NONBLOCK) == -1) {
317                 if (vflag)
318                         warn("%s(): fcntl()", __func__);
319                 close(devd_pipe);
320                 return (-1);
321         }
322
323         return (devd_pipe);
324 }
325
326 static void
327 devd_close(void)
328 {
329
330         close(devd_pipe);
331         devd_pipe = -1;
332 }
333
334 static void
335 parse_mode(char *arg, int *mode, int ch)
336 {
337
338         if (strcmp(arg, "minimum") == 0 || strcmp(arg, "min") == 0)
339                 *mode = MODE_MIN;
340         else if (strcmp(arg, "maximum") == 0 || strcmp(arg, "max") == 0)
341                 *mode = MODE_MAX;
342         else if (strcmp(arg, "adaptive") == 0 || strcmp(arg, "adp") == 0)
343                 *mode = MODE_ADAPTIVE;
344         else
345                 errx(1, "bad option: -%c %s", (char)ch, optarg);
346 }
347
348 static void
349 handle_sigs(int __unused sig)
350 {
351
352         exit_requested = 1;
353 }
354
355 static void
356 usage(void)
357 {
358
359         fprintf(stderr,
360 "usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-n mode] [-p ival] [-r %%] [-P pidfile]\n");
361         exit(1);
362 }
363
364 int
365 main(int argc, char * argv[])
366 {
367         struct timeval timeout;
368         fd_set fdset;
369         int nfds;
370         struct pidfh *pfh = NULL;
371         const char *pidfile = NULL;
372         long idle, total;
373         int curfreq, *freqs, i, *mwatts, numfreqs;
374         int ch, mode, mode_ac, mode_battery, mode_none;
375         uint64_t mjoules_used;
376         size_t len;
377
378         /* Default mode for all AC states is adaptive. */
379         mode_ac = mode_battery = mode_none = MODE_ADAPTIVE;
380         cpu_running_mark = DEFAULT_ACTIVE_PERCENT;
381         cpu_idle_mark = DEFAULT_IDLE_PERCENT;
382         poll_ival = DEFAULT_POLL_INTERVAL;
383         mjoules_used = 0;
384         vflag = 0;
385
386         /* User must be root to control frequencies. */
387         if (geteuid() != 0)
388                 errx(1, "must be root to run");
389
390         while ((ch = getopt(argc, argv, "a:b:i:n:p:P:r:v")) != -1)
391                 switch (ch) {
392                 case 'a':
393                         parse_mode(optarg, &mode_ac, ch);
394                         break;
395                 case 'b':
396                         parse_mode(optarg, &mode_battery, ch);
397                         break;
398                 case 'i':
399                         cpu_idle_mark = atoi(optarg);
400                         if (cpu_idle_mark < 0 || cpu_idle_mark > 100) {
401                                 warnx("%d is not a valid percent",
402                                     cpu_idle_mark);
403                                 usage();
404                         }
405                         break;
406                 case 'n':
407                         parse_mode(optarg, &mode_none, ch);
408                         break;
409                 case 'p':
410                         poll_ival = atoi(optarg);
411                         if (poll_ival < 5) {
412                                 warnx("poll interval is in units of ms");
413                                 usage();
414                         }
415                         break;
416                 case 'P':
417                         pidfile = optarg;
418                         break;
419                 case 'r':
420                         cpu_running_mark = atoi(optarg);
421                         if (cpu_running_mark < 0 || cpu_running_mark > 100) {
422                                 warnx("%d is not a valid percent",
423                                     cpu_running_mark);
424                                 usage();
425                         }
426                         break;
427                 case 'v':
428                         vflag = 1;
429                         break;
430                 default:
431                         usage();
432                 }
433
434         mode = mode_none;
435
436         /* Poll interval is in units of ms. */
437         poll_ival *= 1000;
438
439         /* Look up various sysctl MIBs. */
440         len = 2;
441         if (sysctlnametomib("kern.cp_time", cp_time_mib, &len))
442                 err(1, "lookup kern.cp_time");
443         len = 4;
444         if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len))
445                 err(1, "lookup freq");
446         len = 4;
447         if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len))
448                 err(1, "lookup freq_levels");
449
450         /* Check if we can read the idle time and supported freqs. */
451         if (read_usage_times(NULL, NULL))
452                 err(1, "read_usage_times");
453         if (read_freqs(&numfreqs, &freqs, &mwatts))
454                 err(1, "error reading supported CPU frequencies");
455
456         /* Run in the background unless in verbose mode. */
457         if (!vflag) {
458                 pid_t otherpid;
459
460                 pfh = pidfile_open(pidfile, 0600, &otherpid);
461                 if (pfh == NULL) {
462                         if (errno == EEXIST) {
463                                 errx(1, "powerd already running, pid: %d",
464                                     otherpid);
465                         }
466                         warn("cannot open pid file");
467                 }
468                 if (daemon(0, 0) != 0) {
469                         warn("cannot enter daemon mode, exiting");
470                         pidfile_remove(pfh);
471                         exit(EXIT_FAILURE);
472
473                 }
474                 pidfile_write(pfh);
475         }
476
477         /* Decide whether to use ACPI or APM to read the AC line status. */
478         acline_init();
479
480         /*
481          * Exit cleanly on signals.
482          */
483         signal(SIGINT, handle_sigs);
484         signal(SIGTERM, handle_sigs);
485
486         /* Main loop. */
487         for (;;) {
488                 FD_ZERO(&fdset);
489                 if (devd_pipe >= 0) {
490                         FD_SET(devd_pipe, &fdset);
491                         nfds = devd_pipe + 1;
492                 } else {
493                         nfds = 0;
494                 }
495                 timeout.tv_sec = poll_ival / 1000000;
496                 timeout.tv_usec = poll_ival % 1000000;
497                 select(nfds, &fdset, NULL, &fdset, &timeout);
498
499                 /* If the user requested we quit, print some statistics. */
500                 if (exit_requested) {
501                         if (vflag && mjoules_used != 0)
502                                 printf("total joules used: %u.%03u\n",
503                                     (u_int)(mjoules_used / 1000),
504                                     (int)mjoules_used % 1000);
505                         break;
506                 }
507
508                 /* Read the current AC status and record the mode. */
509                 acline_read();
510                 switch (acline_status) {
511                 case SRC_AC:
512                         mode = mode_ac;
513                         break;
514                 case SRC_BATTERY:
515                         mode = mode_battery;
516                         break;
517                 case SRC_UNKNOWN:
518                         mode = mode_none;
519                         break;
520                 default:
521                         errx(1, "invalid AC line status %d", acline_status);
522                 }
523
524                 /* Read the current frequency. */
525                 len = sizeof(curfreq);
526                 if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0) != 0) {
527                         if (vflag)
528                                 warn("error reading current CPU frequency");
529                         continue;
530                 }
531
532                 if (vflag) {
533                         for (i = 0; i < numfreqs; i++) {
534                                 if (freqs[i] == curfreq)
535                                         break;
536                         }
537
538                         /* Keep a sum of all power actually used. */
539                         if (i < numfreqs && mwatts[i] != -1)
540                                 mjoules_used +=
541                                     (mwatts[i] * (poll_ival / 1000)) / 1000;
542                 }
543
544                 /* Always switch to the lowest frequency in min mode. */
545                 if (mode == MODE_MIN) {
546                         if (curfreq != freqs[numfreqs - 1]) {
547                                 if (vflag) {
548                                         printf("now operating on %s power; "
549                                             "changing frequency to %d MHz\n",
550                                             modes[acline_status],
551                                             freqs[numfreqs - 1]);
552                                 }
553                                 if (set_freq(freqs[numfreqs - 1]) != 0) {
554                                         warn("error setting CPU freq %d",
555                                             freqs[numfreqs - 1]);
556                                         continue;
557                                 }
558                         }
559                         continue;
560                 }
561
562                 /* Always switch to the highest frequency in max mode. */
563                 if (mode == MODE_MAX) {
564                         if (curfreq != freqs[0]) {
565                                 if (vflag) {
566                                         printf("now operating on %s power; "
567                                             "changing frequency to %d MHz\n",
568                                             modes[acline_status],
569                                             freqs[0]);
570                                 }
571                                 if (set_freq(freqs[0]) != 0) {
572                                         warn("error setting CPU freq %d",
573                                             freqs[0]);
574                                         continue;
575                                 }
576                         }
577                         continue;
578                 }
579
580                 /* Adaptive mode; get the current CPU usage times. */
581                 if (read_usage_times(&idle, &total)) {
582                         if (vflag)
583                                 warn("read_usage_times() failed");
584                         continue;
585                 }
586
587                 /*
588                  * If we're idle less than the active mark, bump up two levels.
589                  * If we're idle more than the idle mark, drop down one level.
590                  */
591                 for (i = 0; i < numfreqs - 1; i++) {
592                         if (freqs[i] == curfreq)
593                                 break;
594                 }
595                 if (idle < (total * cpu_running_mark) / 100 &&
596                     curfreq < freqs[0]) {
597                         i -= 2;
598                         if (i < 0)
599                                 i = 0;
600                         if (vflag) {
601                                 printf("idle time < %d%%, increasing clock"
602                                     " speed from %d MHz to %d MHz\n",
603                                     cpu_running_mark, curfreq, freqs[i]);
604                         }
605                         if (set_freq(freqs[i]))
606                                 warn("error setting CPU frequency %d",
607                                     freqs[i]);
608                 } else if (idle > (total * cpu_idle_mark) / 100 &&
609                     curfreq > freqs[numfreqs - 1]) {
610                         i++;
611                         if (vflag) {
612                                 printf("idle time > %d%%, decreasing clock"
613                                     " speed from %d MHz to %d MHz\n",
614                                     cpu_idle_mark, curfreq, freqs[i]);
615                         }
616                         if (set_freq(freqs[i]) != 0)
617                                 warn("error setting CPU frequency %d",
618                                     freqs[i]);
619                 }
620         }
621         free(freqs);
622         free(mwatts);
623         devd_close();
624         if (!vflag)
625                 pidfile_remove(pfh);
626
627         exit(0);
628 }