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