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