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