Log frequency changes via syslog(3) from powerd(8) instead from acpi(4).
[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 #include <sys/types.h>
41 #include <sys/sysctl.h>
42 #include <sys/kinfo.h>
43 #include <sys/file.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <unistd.h>
47 #include <string.h>
48 #include <syslog.h>
49
50 #define STATE_UNKNOWN   0
51 #define STATE_LOW       1
52 #define STATE_HIGH      2
53
54 static void usage(void);
55 static double getcputime(void);
56 static void acpi_setcpufreq(int ostate, int nstate);
57
58 int DebugOpt;
59 int PowerState = STATE_UNKNOWN;
60 int PowerFd;
61 double Trigger = 0.25;
62
63 int
64 main(int ac, char **av)
65 {
66         double qavg;
67         double savg;
68         int ch;
69         int nstate;
70         char buf[32];
71
72         while ((ch = getopt(ac, av, "d")) != -1) {
73                 switch(ch) {
74                 case 'd':
75                         DebugOpt = 1;
76                         break;
77                 default:
78                         usage();
79                         /* NOT REACHED */
80                 }
81         }
82         ac -= optind;
83         av -= optind;
84
85         /*
86          * Prime delta cputime calculation, make sure at least dom0 exists,
87          * and make sure powerd is not already running.
88          */
89         getcputime();
90         savg = 0.0;
91
92         if (sysctlbyname("hw.acpi.cpu.px_dom0.available", NULL, NULL,
93                          NULL, 0) < 0) {
94                 fprintf(stderr, "hw.acpi.cpu.px_dom* sysctl not available\n");
95                 exit(1);
96         }
97
98         PowerFd = open("/var/run/powerd.pid", O_CREAT|O_RDWR, 0644);
99         if (PowerFd < 0) {
100                 fprintf(stderr,
101                         "Cannot create /var/run/powerd.pid, "
102                         "continuing anyway\n");
103         } else {
104                 if (flock(PowerFd, LOCK_EX|LOCK_NB) < 0) {
105                         fprintf(stderr, "powerd is already running\n");
106                         exit(1);
107                 }
108         }
109
110         /*
111          * Demonize and set pid
112          */
113         if (DebugOpt == 0) {
114                 daemon(0, 0);
115                 openlog("powerd", LOG_CONS | LOG_PID, LOG_DAEMON);
116         }
117
118         if (PowerFd >= 0) {
119                 ftruncate(PowerFd, 0);
120                 snprintf(buf, sizeof(buf), "%d\n", (int)getpid());
121                 write(PowerFd, buf, strlen(buf));
122         }
123
124         /*
125          * Monitoring loop
126          */
127         for (;;) {
128                 qavg = getcputime();
129                 savg = (savg * 7.0 + qavg) / 8.0;
130
131                 if (DebugOpt) {
132                         printf("\rqavg=%5.2f savg=%5.2f\r", qavg, savg);
133                         fflush(stdout);
134                 }
135
136                 nstate = PowerState;
137                 if (nstate == STATE_UNKNOWN) {
138                         if (savg >= Trigger)
139                                 nstate = STATE_HIGH;
140                         else
141                                 nstate = STATE_LOW;
142                 } else if (nstate == STATE_LOW) {
143                         if (savg >= Trigger || qavg >= 0.9)
144                                 nstate = STATE_HIGH;
145                 } else {
146                         if (savg < Trigger / 2.0 && qavg < Trigger / 2.0)
147                                 nstate = STATE_LOW;
148                 }
149                 if (PowerState != nstate) {
150                         acpi_setcpufreq(PowerState, nstate);
151                         PowerState = nstate;
152                 }
153                 sleep(1);
154         }
155 }
156
157 /*
158  * Return the one-second cpu load.  One cpu at 100% will return a value
159  * of 1.0.  On a SMP system N cpus running at 100% will return a value of N.
160  */
161 static
162 double
163 getcputime(void)
164 {
165         static struct kinfo_cputime ocpu_time[64];
166         static struct kinfo_cputime ncpu_time[64];
167         size_t slen;
168         int ncpu;
169         int cpu;
170         uint64_t delta;
171
172         bcopy(ncpu_time, ocpu_time, sizeof(ncpu_time));
173         slen = sizeof(ncpu_time);
174         if (sysctlbyname("kern.cputime", &ncpu_time, &slen, NULL, 0) < 0) {
175                 fprintf(stderr, "kern.cputime sysctl not available\n");
176                 exit(1);
177         }
178         ncpu = slen / sizeof(ncpu_time[0]);
179         delta = 0;
180
181         for (cpu = 0; cpu < ncpu; ++cpu) {
182                 delta += (ncpu_time[cpu].cp_user + ncpu_time[cpu].cp_sys +
183                           ncpu_time[cpu].cp_intr) -
184                          (ocpu_time[cpu].cp_user + ocpu_time[cpu].cp_sys +
185                           ocpu_time[cpu].cp_intr);
186         }
187         return((double)delta / 1000000.0);
188 }
189
190
191 static
192 void
193 acpi_setcpufreq(int ostate, int nstate)
194 {
195         int dom;
196         int lowest;
197         int highest;
198         int desired;
199         int v;
200         char *sysid;
201         char *ptr;
202         char buf[256];
203         size_t buflen;
204
205         dom = 0;
206         for (;;) {
207                 /*
208                  * Retrieve availability list
209                  */
210                 asprintf(&sysid, "hw.acpi.cpu.px_dom%d.available", dom);
211                 buflen = sizeof(buf) - 1;
212                 v = sysctlbyname(sysid, buf, &buflen, NULL, 0);
213                 free(sysid);
214                 if (v < 0)
215                         break;
216                 buf[buflen] = 0;
217
218                 /*
219                  * Parse out the highest and lowest cpu frequencies
220                  */
221                 ptr = buf;
222                 highest = lowest = 0;
223                 while (ptr && (v = strtol(ptr, &ptr, 10)) > 0) {
224                         if (lowest == 0 || lowest > v)
225                                 lowest = v;
226                         if (highest == 0 || highest < v)
227                                 highest = v;
228                 }
229
230                 /*
231                  * Calculate the desired cpu frequency, test, and set.
232                  */
233                 desired = (nstate == STATE_LOW) ? lowest : highest;
234
235                 asprintf(&sysid, "hw.acpi.cpu.px_dom%d.select", dom);
236                 buflen = sizeof(v);
237                 v = 0;
238                 sysctlbyname(sysid, &v, &buflen, NULL, 0);
239                 if (v != desired || ostate == STATE_UNKNOWN) {
240                         if (DebugOpt) {
241                                 printf("dom%d set frequency %d\n",
242                                        dom, desired);
243                         } else {
244                                 syslog(LOG_INFO, "dom%d set frequency %d\n",
245                                     dom, desired);
246                         }
247                         sysctlbyname(sysid, NULL, NULL,
248                                      &desired, sizeof(desired));
249                 }
250                 free(sysid);
251                 ++dom;
252         }
253 }
254
255 static
256 void
257 usage(void)
258 {
259         fprintf(stderr, "usage: powerd [-d]\n");
260         exit(1);
261 }