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 static void usage(void); 51 static double getcputime(void); 52 static void acpi_setcpufreq(int nstate); 53 static void setupdominfo(void); 54 55 int DebugOpt; 56 int CpuLimit; /* # of cpus at max frequency */ 57 int DomLimit; /* # of domains at max frequency */ 58 int PowerFd; 59 int DomBeg; 60 int DomEnd; 61 int NCpus; 62 int CpuCount[256]; /* # of cpus in any given domain */ 63 int CpuToDom[256]; /* domain a particular cpu belongs to */ 64 double Trigger = 0.25; /* load per cpu to force max freq */ 65 66 int 67 main(int ac, char **av) 68 { 69 double qavg; 70 double savg; 71 int ch; 72 int nstate; 73 char buf[64]; 74 75 while ((ch = getopt(ac, av, "d")) != -1) { 76 switch(ch) { 77 case 'd': 78 DebugOpt = 1; 79 break; 80 default: 81 usage(); 82 /* NOT REACHED */ 83 } 84 } 85 ac -= optind; 86 av += optind; 87 88 /* 89 * Make sure powerd is not already running. 90 */ 91 PowerFd = open("/var/run/powerd.pid", O_CREAT|O_RDWR, 0644); 92 if (PowerFd < 0) { 93 fprintf(stderr, 94 "Cannot create /var/run/powerd.pid, " 95 "continuing anyway\n"); 96 } else { 97 if (flock(PowerFd, LOCK_EX|LOCK_NB) < 0) { 98 fprintf(stderr, "powerd is already running\n"); 99 exit(1); 100 } 101 } 102 103 /* 104 * Demonize and set pid 105 */ 106 if (DebugOpt == 0) { 107 daemon(0, 0); 108 openlog("powerd", LOG_CONS | LOG_PID, LOG_DAEMON); 109 } 110 111 if (PowerFd >= 0) { 112 ftruncate(PowerFd, 0); 113 snprintf(buf, sizeof(buf), "%d\n", (int)getpid()); 114 write(PowerFd, buf, strlen(buf)); 115 } 116 117 /* 118 * Wait hw.acpi.cpu.px_dom* sysctl to be created by kernel 119 * 120 * Since hw.acpi.cpu.px_dom* creation is queued into ACPI 121 * taskqueue and ACPI taskqueue is shared across various 122 * ACPI modules, any delay in other modules may cause 123 * hw.acpi.cpu.px_dom* to be created at quite a later time 124 * (e.g. cmbat module's task could take quite a lot of time). 125 */ 126 for (;;) { 127 /* 128 * Prime delta cputime calculation, make sure at least 129 * dom0 exists. 130 */ 131 getcputime(); 132 savg = 0.0; 133 134 setupdominfo(); 135 if (DomBeg >= DomEnd) { 136 sleep(1); 137 continue; 138 } 139 140 DomLimit = DomEnd; 141 CpuLimit = NCpus; 142 break; 143 } 144 145 /* 146 * Monitoring loop 147 * 148 * Calculate nstate, the number of cpus we wish to run at max 149 * frequency. All remaining cpus will be set to their lowest 150 * frequency and mapped out of the user process scheduler. 151 */ 152 for (;;) { 153 qavg = getcputime(); 154 savg = (savg * 7.0 + qavg) / 8.0; 155 156 nstate = savg / Trigger; 157 if (nstate > NCpus) 158 nstate = NCpus; 159 if (DebugOpt) { 160 printf("\rqavg=%5.2f savg=%5.2f %2d/%2d ncpus=%d\r", 161 qavg, savg, CpuLimit, DomLimit, nstate); 162 fflush(stdout); 163 } 164 if (nstate != CpuLimit) 165 acpi_setcpufreq(nstate); 166 sleep(1); 167 } 168 } 169 170 /* 171 * Figure out the domains and calculate the CpuCount[] and CpuToDom[] 172 * arrays. 173 */ 174 static 175 void 176 setupdominfo(void) 177 { 178 char buf[64]; 179 char members[1024]; 180 char *str; 181 size_t msize; 182 int i; 183 int n; 184 185 for (i = 0; i < 256; ++i) { 186 snprintf(buf, sizeof(buf), 187 "hw.acpi.cpu.px_dom%d.available", i); 188 if (sysctlbyname(buf, NULL, NULL, NULL, 0) >= 0) 189 break; 190 } 191 DomBeg = i; 192 193 for (i = 255; i >= DomBeg; --i) { 194 snprintf(buf, sizeof(buf), 195 "hw.acpi.cpu.px_dom%d.available", i); 196 if (sysctlbyname(buf, NULL, NULL, NULL, 0) >= 0) { 197 ++i; 198 break; 199 } 200 } 201 DomEnd = i; 202 203 for (i = DomBeg; i < DomEnd; ++i) { 204 snprintf(buf, sizeof(buf), 205 "hw.acpi.cpu.px_dom%d.members", i); 206 msize = sizeof(members); 207 if (sysctlbyname(buf, members, &msize, NULL, 0) == 0) { 208 members[msize] = 0; 209 for (str = strtok(members, " "); str; 210 str = strtok(NULL, " ")) { 211 n = -1; 212 sscanf(str, "cpu%d", &n); 213 if (n >= 0) { 214 ++NCpus; 215 ++CpuCount[i]; 216 CpuToDom[n]= i; 217 } 218 } 219 } 220 } 221 } 222 223 /* 224 * Return the one-second cpu load. One cpu at 100% will return a value 225 * of 1.0. On a SMP system N cpus running at 100% will return a value of N. 226 */ 227 static 228 double 229 getcputime(void) 230 { 231 static struct kinfo_cputime ocpu_time[64]; 232 static struct kinfo_cputime ncpu_time[64]; 233 size_t slen; 234 int ncpu; 235 int cpu; 236 uint64_t delta; 237 238 bcopy(ncpu_time, ocpu_time, sizeof(ncpu_time)); 239 slen = sizeof(ncpu_time); 240 if (sysctlbyname("kern.cputime", &ncpu_time, &slen, NULL, 0) < 0) { 241 fprintf(stderr, "kern.cputime sysctl not available\n"); 242 exit(1); 243 } 244 ncpu = slen / sizeof(ncpu_time[0]); 245 delta = 0; 246 247 for (cpu = 0; cpu < ncpu; ++cpu) { 248 delta += (ncpu_time[cpu].cp_user + ncpu_time[cpu].cp_sys + 249 ncpu_time[cpu].cp_nice + ncpu_time[cpu].cp_intr) - 250 (ocpu_time[cpu].cp_user + ocpu_time[cpu].cp_sys + 251 ocpu_time[cpu].cp_nice + ocpu_time[cpu].cp_intr); 252 } 253 return((double)delta / 1000000.0); 254 } 255 256 /* 257 * nstate is the requested number of cpus that we wish to run at full 258 * frequency. We calculate how many domains we have to adjust to reach 259 * this goal. 260 * 261 * This function also sets the user scheduler global cpu mask. 262 */ 263 static 264 void 265 acpi_setcpufreq(int nstate) 266 { 267 int ncpus = 0; 268 int increasing = (nstate > CpuLimit); 269 int dom; 270 int domBeg; 271 int domEnd; 272 int lowest; 273 int highest; 274 int desired; 275 int v; 276 char *sysid; 277 char *ptr; 278 char buf[256]; 279 size_t buflen; 280 cpumask_t global_cpumask; 281 282 /* 283 * Calculate the ending domain if the number of operating cpus 284 * has increased. 285 * 286 * Calculate the starting domain if the number of operating cpus 287 * has decreased. 288 */ 289 for (dom = DomBeg; dom < DomEnd; ++dom) { 290 if (ncpus >= nstate) 291 break; 292 ncpus += CpuCount[dom]; 293 } 294 295 syslog(LOG_INFO, "using %d cpus", nstate); 296 297 /* 298 * Set the mask of cpus the userland scheduler is allowed to use. 299 */ 300 global_cpumask = (1L << nstate) - 1; 301 sysctlbyname("kern.usched_global_cpumask", NULL, 0, 302 &global_cpumask, sizeof(global_cpumask)); 303 304 if (increasing) { 305 domBeg = DomLimit; 306 domEnd = dom; 307 } else { 308 domBeg = dom; 309 domEnd = DomLimit; 310 } 311 DomLimit = dom; 312 CpuLimit = nstate; 313 314 /* 315 * Adjust the cpu frequency 316 */ 317 if (DebugOpt) 318 printf("\n"); 319 for (dom = domBeg; dom < domEnd; ++dom) { 320 /* 321 * Retrieve availability list 322 */ 323 asprintf(&sysid, "hw.acpi.cpu.px_dom%d.available", dom); 324 buflen = sizeof(buf) - 1; 325 v = sysctlbyname(sysid, buf, &buflen, NULL, 0); 326 free(sysid); 327 if (v < 0) 328 continue; 329 buf[buflen] = 0; 330 331 /* 332 * Parse out the highest and lowest cpu frequencies 333 */ 334 ptr = buf; 335 highest = lowest = 0; 336 while (ptr && (v = strtol(ptr, &ptr, 10)) > 0) { 337 if (lowest == 0 || lowest > v) 338 lowest = v; 339 if (highest == 0 || highest < v) 340 highest = v; 341 } 342 343 /* 344 * Calculate the desired cpu frequency, test, and set. 345 */ 346 desired = increasing ? highest : lowest; 347 348 asprintf(&sysid, "hw.acpi.cpu.px_dom%d.select", dom); 349 buflen = sizeof(v); 350 v = 0; 351 sysctlbyname(sysid, &v, &buflen, NULL, 0); 352 { 353 if (DebugOpt) { 354 printf("dom%d set frequency %d\n", 355 dom, desired); 356 } 357 sysctlbyname(sysid, NULL, NULL, 358 &desired, sizeof(desired)); 359 } 360 free(sysid); 361 } 362 } 363 364 static 365 void 366 usage(void) 367 { 368 fprintf(stderr, "usage: powerd [-d]\n"); 369 exit(1); 370 } 371