/* * fan-ctrl.c - lmsensors-based fan speed control * * Copyright (c) 2009 Christian Mueller. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * fan-ctrl is a little daemon that controls the speed of one or more system or * cpu fans. It does so by monitoring temperature inputs and selecting a fan * speed from an array of available speeds based on a set of temperature thres- * holds. * * Why thresholds instead of smooth speed control? Because my CPU fan has a few * resonance frequencies which make it "sing". Unfortunately, one of those * resonance frequencies is right at the idle operating temperature when using * the BIOS automatic control, or lmsensors' fancontrol without tweaking. On * top of that, it's less annoying to have the fan change speed in steps using * a hysteresis of a few degrees to minimize the chance of the fan speed * jumping up and down, possibly going through resonance points again and again. * * A word of caution: if this program screws up, the parameters are set * incorrectly or the OS crashes badly, the fan speed might be too low and the * cpu might overheat. Modern CPUs should be able to deal with this, starting * to throttle and eventually resetting the system or going offline, but there * might still be damage. * * You have been warned... * * CVS Change Log: * --------------- * $Log: fan-ctrl.c,v $ * Revision 1.3 2009/02/12 22:11:34 cjmueller * Yet another comment change... * * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_SPEEDS 100 #define dprintf if (debug) printf /* typedefs and structures */ typedef struct FANINFO { struct FANINFO *next; char pwm_out[PATH_MAX]; char temp_in[PATH_MAX]; int divisor; int speeds[MAX_SPEEDS]; int temps[MAX_SPEEDS]; int cnt; int hysteresis; int last_temp; int last_indx; } FANINFO; /* function prototypes */ static void daemonize (void); static void logmsg (int priority, const char *format, ...); static void sig_handler (int sig); static void restore_fans (void); static int read_int (const char *path); static void write_int (const char *path, int val); /* global/static variables */ FANINFO *fi_root; int debug; volatile int exiting = 0; /* main function */ int main(int argc, char *argv[]) { FANINFO *fi; char *base_path = ""; char *tmp; char *s; int sleep_time = 10; int opt; /* process command line options */ while ((opt = getopt(argc, argv, "p:n:f:t:s:h:d")) != -1) { switch (opt) { case 'p': base_path = optarg; break; case 'n': sleep_time = atoi(optarg); break; case 'f': fi = calloc(1, sizeof(*fi)); snprintf(fi->pwm_out, sizeof(fi->pwm_out), "%s/%s", base_path, optarg); if (access(fi->pwm_out, W_OK)) { fprintf(stderr, "error: no write access to %s\n", fi->pwm_out); return(2); } fi->next = fi_root; fi_root = fi; break; case 't': if (fi == NULL) { fprintf(stderr, "error: must define fan before setting temp_in\n"); return(1); } snprintf(fi->temp_in, sizeof(fi->temp_in), "%s/%s", base_path, optarg); if (access(fi->pwm_out, R_OK)) { fprintf(stderr, "error: no read access to %s\n", fi->temp_in); return(2); } /* set divisor based on whether we're using /sys or /proc */ fi->divisor = !strncmp(fi->temp_in, "/sys", 4) ? 1000 : 1; break; case 's': if (fi == NULL) { fprintf(stderr, "error: must define fan before setting speeds\n"); return(1); } tmp = strdup(optarg); for (s = strtok(tmp, ","); s != NULL; s = strtok(NULL, ",")) { if (fi->cnt >= MAX_SPEEDS) { fprintf(stderr, "error: too many temp/speed entries\n"); return(1); } if (sscanf(s, "%d:%d", &fi->temps[fi->cnt], &fi->speeds[fi->cnt]) != 2) { fprintf(stderr, "error: format for temp/speed array is temp:speed,temp:speed,...\n"); return(1); } fi->cnt++; } free(tmp); break; case 'h': if (fi == NULL) { fprintf(stderr, "error: must define fan before setting hysteresis\n"); return(1); } fi->hysteresis = atoi(optarg); break; case 'd': debug = 1; break; case ':': fprintf(stderr, "error: option -%c requires an argument\n", optopt); return(1); case '?': fprintf(stderr, "error: unknown option -%c\n", optopt); return(1); } } if (fi_root == NULL) { printf("\n" "usage: fan-ctrl [-d] [-p ]\n" " -f -t -s \n" " [-h ]\n" "\n" "(multiple fans can be defined by providing more than one -f/-t/-s/-h block)\n" "\n" "Example:\n" " fan-ctrl -p /sys/bus/i2c/devices/9191-0228 -f pwm1 -t temp2_input \\\n" " -s 0:140,40:176,50:196,55:226,60:255 -h 5\n\n" " - base path is /sys/bus/i2c/devices/9191-0228\n" " - fan to be controlled is pwm1\n" " - temperature input is read from temp2_input\n" " - fan speeds are set as follows:\n" " 0 - 39°C -> 140\n" " 40 - 49°C -> 176\n" " 50 - 54°C -> 196\n" " 55 - 59°C -> 224\n" " 60°C+ -> 255 (max)\n" " - hysteresis is 5°C, i.e. fan won't slow down until temperature dropped\n" " 5°C below threshold\n" "\n" ); return(0); } /* verify that temperature input and at least one temp/speed pair have been set */ for (fi = fi_root; fi != NULL; fi = fi->next) { if (*fi->temp_in == '\0') {\ fprintf(stderr, "error: no temperature input defined for %s\n", fi->pwm_out); return(1); } if (fi->cnt == 0) { fprintf(stderr, "error: no temp/speed pairs defined for %s\n", fi->pwm_out); return(1); } } /* daemonize unless we're running in debug mode */ if (!debug) { daemonize(); } /* register exit handlers */ signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); signal(SIGHUP, sig_handler); signal(SIGQUIT, sig_handler); signal(SIGKILL, sig_handler); atexit(restore_fans); /* enable fan control for all fans */ for (fi = fi_root; fi != NULL; fi = fi->next) { char buf[PATH_MAX]; snprintf(buf, sizeof(buf), "%s_enable", fi->pwm_out); write_int(buf, 1); } /* main loop: probe all fan inputs and set fan speed correspondigly */ while (!exiting) { int last_temp; int temp; int indx; int cooling_down; for (fi = fi_root; fi != NULL; fi = fi->next) { /* get current temperature */ if ((temp = read_int(fi->temp_in) / fi->divisor) == fi->last_temp) { /* no temperature change; fan speed can remain where it is */ continue; } last_temp = fi->last_temp; fi->last_temp = temp; if (temp < last_temp) { /* temperature going down; add hysteresis */ temp += fi->hysteresis; cooling_down = 1; } else { cooling_down = 0; } for (indx = fi->cnt - 1; indx > 0; indx--) { if (temp >= fi->temps[indx]) { break; } } /* prevent needless speed changes due to hysteresis effects */ if (cooling_down && indx > fi->last_indx) { /* hysteresis would cause fan to speed up despite temp going down */ indx = fi->last_indx; } else if (!cooling_down && indx < fi->last_indx) { /* hysteresis caused fan speed to stay up; now don't reduce fan * speed just because temp went up a bit inside the hysteresis * zone */ indx = fi->last_indx; } dprintf("fan %s: temp = %d, indx = %d, speed = %d\n", fi->pwm_out, fi->last_temp, indx, fi->speeds[indx]); write_int(fi->pwm_out, fi->speeds[indx]); fi->last_indx = indx; } sleep(sleep_time); } return(0); } /* become a daemon */ static void daemonize(void) { int maxfd; int i; /* fork #1: exit parent process and continue in the background */ if ((i = fork()) < 0) { perror("couldn't fork"); exit(2); } else if (i > 0) { _exit(0); } /* fork #2: detach from terminal and fork again so we can never regain * access to the terminal */ setsid(); if ((i = fork()) < 0) { perror("couldn't fork #2"); exit(2); } else if (i > 0) { _exit(0); } /* change to root directory and close file descriptors */ chdir("/"); maxfd = getdtablesize(); for (i = 0; i < maxfd; i++) { close(i); } /* use /dev/null for stdin, stdout and stderr */ open("/dev/null", O_RDONLY); open("/dev/null", O_WRONLY); open("/dev/null", O_WRONLY); /* open syslog */ openlog("fan-ctrl", LOG_CONS, LOG_DAEMON); } /* write a message to syslog or stdout when in debug mode */ static void logmsg(int priority, const char *format, ...) { va_list va; va_start(va, format); if (debug) { vprintf(format, va); printf("\n"); } else { vsyslog(priority, format, va); } } /* signal handler for all exit signals */ static void sig_handler(int sig) { /* just set the flag; the main loop will exit as soon as it regains control */ exiting = 1; } /* restore fans to maximum RPM and disable pwm control */ static void restore_fans(void) { FANINFO *fi; char buf[PATH_MAX]; for (fi = fi_root; fi != NULL; fi = fi->next) { logmsg(LOG_NOTICE, "setting max speed on %s", fi->pwm_out); write_int(fi->pwm_out, 255); snprintf(buf, sizeof(buf), "%s_enable", fi->pwm_out); write_int(buf, 0); } } /* read int value from specified control file */ static int read_int(const char *path) { FILE *fp = fopen(path, "r"); char buf[1024]; if (fp == NULL) { logmsg(LOG_ERR, "couldn't open %s for reading", path); exit(3); } fgets(buf, sizeof(buf), fp); fclose(fp); return(atoi(buf)); } /* write int value to specified control file */ static void write_int(const char *path, int val) { FILE *fp = fopen(path, "w"); if (fp == NULL) { logmsg(LOG_ERR, "couldn't open %s for writing", path); exit(3); } fprintf(fp, "%d\n", val); fclose(fp); }