Attachment 'smcfancontrol.c'

Download

   1 /*
   2  *  smcfancontrol.c : fan control daemon for Apple Intel Mac Pro 
   3  *  based on cmp-daemon 0.21 from http://aur.archlinux.org/packages.php?ID=21391
   4  *
   5  *  Copyright (C) 2009 Alexey Anikeenko
   6  *
   7  *  This program is free software; you can redistribute it and/or modify
   8  *  it under the terms of the GNU General Public License as published by
   9  *  the Free Software Foundation; either version 2 of the License, or
  10  *  (at your option) any later version.
  11  *
  12  *  This program is distributed in the hope that it will be useful, 
  13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15  *  GNU General Public License for more details.
  16  *
  17  *  You should have received a copy of the GNU General Public License
  18  *  along with this program; if not, write to the Free Software
  19  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  20  */
  21 
  22 #include <stdio.h>
  23 #include <stdlib.h>
  24 #include <unistd.h>
  25 #include <sys/types.h>
  26 #include <sys/stat.h>
  27 #include <errno.h>
  28 #include <fcntl.h>
  29 #include <time.h>
  30 #include <signal.h>
  31 #include <string.h>
  32 #include <syslog.h>
  33 
  34 #define SYSFS_PATH_MAX 256
  35 #define SPEED_STEP_MAX 20
  36 
  37 static const char *PIDFILE = "/var/run/smcfancontrol.pid";
  38 static const char *SMCDIR = "/sys/devices/platform/applesmc.768";
  39 static const char *CORETEMP_PREFIX = "/sys/devices/platform/coretemp";
  40 
  41 static const struct timespec smc_write_delay = { 0, 5000000 }; /* 5 ms delay between writes to smc controller */ 
  42 
  43 /* Reference data for dual-Xeon_X5482 Mac Pro:
  44 const int FANS_EFI_MIN[] = { 500, 800, 600, 600 };
  45 const int FANS_EFI_MAX[] = { 2900, 2900, 2900, 2800 };
  46 const char *FANS_LABEL[] = { "CPU_MEM", "IO", "EXHAUST", "PS" };
  47 */ 
  48 
  49 const int FANS[] = { 1, 2, 3 };  /* suffixes of fans under control */
  50 const int FANS_MIN[] = { 800, 800, 600 };
  51 const int FANS_MAX[] = { 2200, 2200, 2200 };
  52 const int FANS_NUM = sizeof(FANS)/sizeof(FANS[0]);
  53 
  54 const int SENSORS[] = { 0, 1, 2, 3, 4, 5, 6, 7 };  /* suffixes of used coretemp sensors */
  55 const int SENSORS_NUM = sizeof(SENSORS)/sizeof(SENSORS[0]);
  56 
  57 static int get_sensors_temp(void);
  58 static void set_fans_manual(int);
  59 static void set_fans_min(int);
  60 
  61 
  62 /*
  63  * Fan speed depends linearly on the speed step and changes 
  64  * between FAN_MIN and FAN_MAX for steps in range [0, SPEED_STEP_MAX]. 
  65  * This function converts current temperature to speed step 
  66  * and should be calibrated for your box! 
  67  */
  68 int temperature_to_speed_step(int t)
  69 {
  70     int step = ((t-45)*SPEED_STEP_MAX)/(75-45);  
  71 
  72     if (step < 0 ) step = 0;
  73     if (step > SPEED_STEP_MAX ) step = SPEED_STEP_MAX;
  74 
  75     return step;
  76 }
  77 
  78 
  79 /* clean up and exit */
  80 static void clean_exit_with_status(int status)
  81 {
  82     set_fans_manual(0); /* set fans to automatic control */
  83     syslog(LOG_NOTICE, "exiting\n");
  84     unlink(PIDFILE);    /* remove pidfile */
  85     exit(status);
  86 }
  87 
  88 /* signal handler for clean exit */
  89 static void clean_exit(int sig __attribute__((unused)))
  90 {
  91     clean_exit_with_status(EXIT_SUCCESS);
  92 }
  93 
  94 
  95 /* set fanX_min (min rotation speed) according to current speed step */
  96 static void set_fans_min(int speed_step)
  97 {
  98     int i;
  99     FILE *file;
 100     char fan_path[SYSFS_PATH_MAX];
 101 
 102     /* for fans under control write rpm to fanX_min */
 103     for(i=0; i < FANS_NUM; i++ )
 104     {
 105         /* fan speed depends linearly on the speed step and changes 
 106          * between FAN_MIN and FAN_MAX for steps in range [0, SPEED_STEP_MAX] */
 107         int speed = FANS_MIN[i] + speed_step*(FANS_MAX[i]-FANS_MIN[i])/SPEED_STEP_MAX;
 108 
 109         sprintf(fan_path, "%s/fan%d_min", SMCDIR, FANS[i]);
 110         nanosleep(&smc_write_delay, NULL);
 111         if ((file=fopen(fan_path, "w")) != NULL) {
 112             fprintf(file, "%d", speed);
 113             fclose(file);
 114         }
 115         else {
 116             syslog(LOG_WARNING, "Error writing to %s, check if applesmc module loaded", fan_path);
 117         }
 118     }
 119 }
 120 
 121 
 122 /* write value to fanX_manual */
 123 static void set_fans_manual(int value)
 124 {
 125     int i;
 126     FILE *file;
 127     char fan_path[SYSFS_PATH_MAX];
 128 
 129     /* for fans under control write value to fanX_manual */
 130     for(i=0; i < FANS_NUM; i++ )
 131     {
 132         sprintf(fan_path, "%s/fan%d_manual", SMCDIR, FANS[i]);
 133         nanosleep(&smc_write_delay, NULL);
 134         if ((file=fopen(fan_path, "w")) != NULL) {
 135             fprintf(file, "%d", value);
 136             fclose(file);
 137         }
 138         else {
 139             syslog(LOG_WARNING, "Error writing to %s, check if applesmc module loaded", fan_path);
 140         }
 141     }
 142 }
 143 
 144 
 145 /* sort function for qsort: integers descending order */
 146 static int compare_int_desc(const void *a, const void *b) {
 147     int *da, *db;
 148     da = (int *)a;
 149     db = (int *)b;
 150 
 151     return *db - *da;
 152 }
 153 
 154 
 155 /* return average of two largest temperatures, degrees  */
 156 static int get_sensors_temp(void)
 157 {
 158     int i;
 159     int t[SENSORS_NUM];
 160     char sensor_path[SYSFS_PATH_MAX];
 161     FILE *file;
 162 
 163     /* for all sensors read temp1_input */
 164     for(i=0; i < SENSORS_NUM; i++ )
 165     {
 166         sprintf(sensor_path, "%s.%d/temp1_input", CORETEMP_PREFIX, SENSORS[i]);
 167         if ((file=fopen(sensor_path, "r")) != NULL) {
 168             fscanf(file, "%d", &(t[i])); /* read temperature in millidegrees */ 
 169             fclose(file);
 170         }
 171         else {
 172             syslog(LOG_ERR, "Error reading %s, check if coretemp module loaded and number of sensors", sensor_path);
 173             clean_exit_with_status(EXIT_FAILURE);
 174         }
 175     }
 176 
 177     /* sort temperature values, descending order */
 178     qsort(t, SENSORS_NUM, sizeof(t[0]), compare_int_desc);
 179 
 180     /* calculate average of two largest temps (if have more than one sensor) */
 181     int avg = 0, cnt = 0;
 182     for(i=0; (i < 2) && (i < SENSORS_NUM); i++) {
 183         avg += t[i];
 184         cnt ++;
 185     }
 186     avg = avg/cnt;
 187 
 188     /* return temperature in degrees */
 189     return avg/1000;
 190 }
 191 
 192 
 193 /* lock entire file */
 194 static int lock_fd(int fd)
 195 {
 196     struct flock lock;
 197 
 198     lock.l_type = F_WRLCK;
 199     lock.l_start = 0;
 200     lock.l_whence = SEEK_SET;
 201     lock.l_len = 0;
 202 
 203     return fcntl(fd, F_SETLK, &lock);
 204 }
 205 
 206 
 207 /* creates and locks pidfile */
 208 static void create_pidfile(void)
 209 {
 210     int fd;
 211     FILE *f;
 212 
 213     /* open the pidfile */
 214     fd = open(PIDFILE, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
 215     if (fd < 0) {
 216         syslog(LOG_ERR, "can't open %s: %s\n", PIDFILE, strerror(errno));
 217         exit(EXIT_FAILURE);
 218     }
 219     /* lock pid file */
 220     if (lock_fd(fd) < 0) {
 221         if (errno == EACCES || errno == EAGAIN) {
 222             syslog(LOG_ERR, "daemon already running");
 223         } else {
 224             syslog(LOG_ERR, "can't lock %s: %s", PIDFILE, strerror(errno));
 225         }
 226         exit(EXIT_FAILURE);
 227     }
 228 
 229     /* write pid */
 230     ftruncate(fd, 0);
 231     f = fdopen(fd, "w");
 232     fprintf(f, "%d\n", getpid());
 233     fflush(f);
 234     /* keep file open and locked */
 235 }
 236 
 237 
 238 /* become a daemon and open log */
 239 void daemonize(void)
 240 {
 241     int i, maxfd;
 242     int fd0, fd1, fd2;
 243     pid_t pid;
 244 
 245     /* clear file creation mask */
 246     umask(0);
 247 
 248     /* fork and become a session leader */
 249     if ((pid=fork()) < 0) { 
 250         fprintf(stderr, "fork failed\n");
 251         exit(EXIT_FAILURE);
 252     }
 253     else if (pid != 0) { /* parent */
 254         exit(EXIT_SUCCESS);
 255     }
 256     setsid();
 257 
 258     /* change current working directory to the root */
 259     if (chdir("/") < 0) {
 260         fprintf(stderr, "can't change directory to /");
 261         exit(EXIT_FAILURE);
 262     }
 263 
 264     /* close all open file descriptors */
 265     maxfd = sysconf(_SC_OPEN_MAX);
 266     for (i=0; i < maxfd; i++)
 267         close(i); 
 268 
 269     /* set up stdin, stdout, stderr to /dev/null */
 270     fd0 = open("/dev/null", O_RDWR);
 271     fd1 = dup(fd0);
 272     fd2 = dup(fd0);
 273 
 274     /* open log */
 275     openlog("smcfancontrol", LOG_PID, LOG_DAEMON);
 276     if (fd0 != STDIN_FILENO || fd1 != STDOUT_FILENO || fd2 != STDERR_FILENO) {
 277         syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
 278         exit(EXIT_FAILURE);
 279     }
 280 }
 281 
 282 
 283 int main(int argc, char *argv[])
 284 {
 285     struct timespec sleep_period;   /* delay between sensor polls */
 286     int t, t_old;                   /* current and previous temperature */
 287     int speed_step, old_speed_step; /* current and old speed step */
 288     int cold, hot;                  /* number of consecutive temperature (de/in)creases */
 289 
 290     /* become a daemon */
 291     daemonize();
 292 
 293     syslog(LOG_INFO, "starting up\n");
 294 
 295     /* trap key signals */
 296     signal(SIGTERM, clean_exit);
 297     signal(SIGQUIT, clean_exit);
 298     signal(SIGINT,  clean_exit);
 299     signal(SIGHUP,  SIG_IGN);
 300 
 301     /* create pidfile and lock it */
 302     create_pidfile();
 303 
 304     /* set fans to automatic control */
 305     set_fans_manual(0);
 306 
 307     /* initial temperatures */
 308     t = t_old = get_sensors_temp();
 309     cold = hot = 1;
 310 
 311     /* init speed step and set fans */
 312     speed_step = temperature_to_speed_step(t);
 313     set_fans_min(speed_step);
 314     old_speed_step = speed_step;
 315 
 316     /* set sleep timer to 0.5 sec */
 317     sleep_period.tv_sec = 0;
 318     sleep_period.tv_nsec = 500000000;
 319     
 320     /* main loop */
 321     while(1)
 322     {
 323         t = get_sensors_temp();
 324 
 325         if ( t < t_old ) { /* it's getting colder */
 326             cold++;
 327             hot = 0;
 328         }
 329         if ( t > t_old ) { /* it's getting hotter */
 330             hot++;
 331             cold = 0;
 332         }
 333 
 334         if ( (cold==2) || (hot==2) ) 
 335         {
 336             speed_step = temperature_to_speed_step(t);
 337 
 338             if ( speed_step != old_speed_step) {
 339                 set_fans_min(speed_step);
 340                 syslog(LOG_INFO, "changed to speed step %d (of %d) at temperature %d", speed_step, SPEED_STEP_MAX, t);
 341                 old_speed_step = speed_step;
 342             }
 343 
 344             cold = 0;
 345             hot = 0;
 346         }
 347 
 348         if( nanosleep(&sleep_period, NULL) < 0 && errno != EINTR )
 349         {
 350             clean_exit_with_status(EXIT_FAILURE);
 351         }
 352 
 353         t_old = t;
 354     }
 355 
 356     clean_exit_with_status(EXIT_SUCCESS);
 357 
 358     return 0;
 359 }

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.

You are not allowed to attach a file to this page.