Fancontrol: Unterschied zwischen den Versionen

Aus wiki.frank-wulf.de
Zur Navigation springen Zur Suche springen
Zeile 61: Zeile 61:
struct s_fan {
struct s_fan {
     char name[20];
     char name[20];
     char control_by_cpu;
     char control[2];
    char control_by_hdd;
     char pwm_enable[50];
     char pwm_enable[50];
     char pwm_write[50];
     char pwm_write[50];
Zeile 68: Zeile 67:
     short stop_delay;
     short stop_delay;
     short decr_delay;
     short decr_delay;
     short interval_cpu;
     short interval[2];
    short interval_hdd;
     char interpolate[2];
    char interpolate_cpu;
     short hyst[2];
     char interpolate_hdd;
    short hyst_cpu;
     short hyst_hdd;
     short count_scan_hdd;
     short count_scan_hdd;
     short count_step_cpu;
     short count_step[2];
    short count_step_hdd;
     time_t next_check[2];
    time_t next_cpu_check;
     time_t next_hdd_check;
     char first_check;
     char first_check;
     char loglevel;
     char loglevel;
     short cpu_temp;
     short temp[2];
    short hdd_temp;
     unsigned char pwm[2];
    unsigned char cpu_pwm;
     unsigned char hdd_pwm;
     unsigned char actual_pwm;
     unsigned char actual_pwm;
     unsigned char idle_pwm;
     unsigned char idle_pwm;
     unsigned char cpu_error_pwm;
     unsigned char error_pwm[2];
    unsigned char hdd_error_pwm;
     time_t min_decr_time;
     time_t min_decr_time;
     time_t min_stop_time;
     time_t min_stop_time;
Zeile 150: Zeile 141:
     FILE *fp;
     FILE *fp;
     char *name, *value, buf[BUFSIZE];
     char *name, *value, buf[BUFSIZE];
     int i = -1, j, k;
     int i = -1, j, k, t;


     now = time(NULL);
     now = time(NULL);
Zeile 171: Zeile 162:
             value = strtok(value, "\n");
             value = strtok(value, "\n");
             if (strcmp(name, "control_by_cpu") == 0) {
             if (strcmp(name, "control_by_cpu") == 0) {
                 fan[i].control_by_cpu = atoi(value);
                 fan[i].control[CPU] = atoi(value);
                 fan[i].next_cpu_check = now;
                 fan[i].next_check[CPU] = now;
                 fan[i].cpu_error_pwm = 255;
                 fan[i].error_pwm[CPU] = 255;
             } else if (strcmp(name, "control_by_hdd") == 0) {
             } else if (strcmp(name, "control_by_hdd") == 0) {
                 fan[i].control_by_hdd = atoi(value);
                 fan[i].control[HDD] = atoi(value);
                 fan[i].next_hdd_check = now;
                 fan[i].next_check[HDD] = now;
                 fan[i].hdd_error_pwm = 255;
                 fan[i].error_pwm[HDD] = 255;
             } else if (strcmp(name, "pwm_enable") == 0) {
             } else if (strcmp(name, "pwm_enable") == 0) {
                 strcpy(fan[i].pwm_enable, value);
                 strcpy(fan[i].pwm_enable, value);
Zeile 191: Zeile 182:
                 fan[i].loglevel = atoi(value);
                 fan[i].loglevel = atoi(value);
             } else if (strcmp(name, "interval_cpu") == 0) {
             } else if (strcmp(name, "interval_cpu") == 0) {
                 fan[i].interval_cpu = atoi(value);
                 fan[i].interval[CPU] = atoi(value);
             } else if (strcmp(name, "interval_hdd") == 0) {
             } else if (strcmp(name, "interval_hdd") == 0) {
                 fan[i].interval_hdd = atoi(value);
                 fan[i].interval[HDD] = atoi(value);
             } else if (strcmp(name, "interpolate_cpu") == 0) {
             } else if (strcmp(name, "interpolate_cpu") == 0) {
                 fan[i].interpolate_cpu = atoi(value);
                 fan[i].interpolate[CPU] = atoi(value);
             } else if (strcmp(name, "interpolate_hdd") == 0) {
             } else if (strcmp(name, "interpolate_hdd") == 0) {
                 fan[i].interpolate_hdd = atoi(value);
                 fan[i].interpolate[HDD] = atoi(value);
             } else if (strcmp(name, "hyst_cpu") == 0) {
             } else if (strcmp(name, "hyst_cpu") == 0) {
                 fan[i].hyst_cpu = atoi(value);
                 fan[i].hyst[CPU] = atoi(value);
             } else if (strcmp(name, "hyst_hdd") == 0) {
             } else if (strcmp(name, "hyst_hdd") == 0) {
                 fan[i].hyst_hdd = atoi(value);
                 fan[i].hyst[HDD] = atoi(value);
             } else if (strcmp(name, "idle_pwm") == 0) {
             } else if (strcmp(name, "idle_pwm") == 0) {
                 fan[i].idle_pwm = atoi(value);
                 fan[i].idle_pwm = atoi(value);
             } else if (strcmp(name, "cpu_error_pwm") == 0) {
             } else if (strcmp(name, "cpu_error_pwm") == 0) {
                 fan[i].cpu_error_pwm = atoi(value);
                 fan[i].error_pwm[CPU] = atoi(value);
             } else if (strcmp(name, "hdd_error_pwm") == 0) {
             } else if (strcmp(name, "hdd_error_pwm") == 0) {
                 fan[i].hdd_error_pwm = atoi(value);
                 fan[i].error_pwm[HDD] = atoi(value);
             } else if (strcmp(name, "scan_hdd") == 0) {
             } else if (strcmp(name, "scan_hdd") == 0) {
                 value = strtok(value, ",");
                 value = strtok(value, ",");
Zeile 215: Zeile 206:
                 }
                 }
                 fan[i].count_scan_hdd = k;
                 fan[i].count_scan_hdd = k;
             } else if (strcmp(name, "temp_pwm_hdd") == 0) {
             } else if (strcmp(name, "temp_pwm_hdd") == 0 ||
                      strcmp(name, "temp_pwm_cpu") == 0) {
                if (strcmp(name, "temp_pwm_cpu") == 0)
                    t = CPU;
                else
                    t = HDD;
                 j = 0;
                 j = 0;
                 value = strtok(value, ",");
                 value = strtok(value, ",");
                 while (value != NULL && ++j <= MAX_STEP) {
                 while (value != NULL && ++j <= MAX_STEP) {
                     data_pwm[HDD][i][j-1].temp = atoi(value);
                     data_pwm[t][i][j-1].temp = atoi(value);
                     value = strtok(NULL, ",");
                     value = strtok(NULL, ",");
                     data_pwm[HDD][i][j-1].pwm = atoi(value);
                     data_pwm[t][i][j-1].pwm = atoi(value);
                     value = strtok(NULL, ",");
                     value = strtok(NULL, ",");
                 }
                 }
                 fan[i].count_step_hdd = j;
                 fan[i].count_step[t] = j;
            } else if (strcmp(name, "temp_pwm_cpu") == 0) {
                j = 0;
                value = strtok(value, ",");
                while (value != NULL && ++j <= MAX_STEP) {
                    data_pwm[CPU][i][j-1].temp = atoi(value);
                    value = strtok(NULL, ",");
                    data_pwm[CPU][i][j-1].pwm = atoi(value);
                    value = strtok(NULL, ",");
                }
                fan[i].count_step_cpu = j;
             }
             }
         }
         }
Zeile 397: Zeile 383:
}
}


static inline unsigned char calc_pwm(char type, int fan, short temp,
static inline unsigned char calc_pwm(char type, int i, short temp) {
char interpolate, short count_step) {
     unsigned char pwm = 0;
     unsigned char pwm = 0;
     short j = 0;
     short j = 0;


     while (temp >= data_pwm[type][fan][j].temp) {
     while (temp >= data_pwm[type][i][j].temp) {
         pwm = data_pwm[type][fan][j].pwm;
         pwm = data_pwm[type][i][j].pwm;
         if (++j >= count_step)
         if (++j >= fan[i].count_step[type])
             break;
             break;
     }
     }


     if (interpolate == 1 && j > 0 && j < count_step)
     if (fan[i].interpolate[type] == 1 && j > 0 && j < fan[i].count_step[type])
         pwm = ((float)data_pwm[type][fan][j].pwm -
         pwm = ((float)data_pwm[type][i][j].pwm - data_pwm[type][i][j-1].pwm) /
              data_pwm[type][fan][j-1].pwm) /
               (data_pwm[type][i][j].temp - data_pwm[type][i][j-1].temp) *
               (data_pwm[type][fan][j].temp - data_pwm[type][fan][j-1].temp) *
               (temp - data_pwm[type][i][j-1].temp) + data_pwm[type][i][j-1].pwm
               (temp - data_pwm[type][fan][j-1].temp) +
              + 0.5;
              data_pwm[type][fan][j-1].pwm + 0.5;


     return pwm;
     return pwm;
Zeile 421: Zeile 405:
     struct stat device;
     struct stat device;
     unsigned char new_pwm;
     unsigned char new_pwm;
     char sw_checked_cpu, sw_checked_hdd, sw_hdd_buffered;
     char last_cpu_input[50], sw_hdd_buffered, sw_checked, sw_read_error[2];
    char sw_cpu_read_error, sw_hdd_read_error;
     short temp[2], dev_temp, count_hdd_buffer = 0;
    char last_cpu_input[50];
     short cpu_temp, hdd_temp, dev_temp, count_hdd_buffer = 0;
     time_t last_cpu_check = 0;
     time_t last_cpu_check = 0;


Zeile 430: Zeile 412:
     next_check = 0;
     next_check = 0;
     for (int i = 0; i < count_fans; i++) {
     for (int i = 0; i < count_fans; i++) {
         sw_checked_cpu = 0;
         sw_checked = 0;
         sw_checked_hdd = 0;
         for (int t = 0; t < 2; t++) {
        if (fan[i].control_by_cpu == 1 && now >= fan[i].next_cpu_check) {
            if (fan[i].control[t] == 1 && now >= fan[i].next_check[t]) {
            if (last_cpu_check != now ||
                if (t == CPU && (last_cpu_check != now ||
              strcmp(last_cpu_input, fan[i].cpu_input) != 0) {
                  strcmp(last_cpu_input, fan[i].cpu_input) != 0)) {
                // Get CPU temperature
                    // Get CPU temperature
                if ((cpu_temp = get_cpu_temp(fan[i].cpu_input)) >= 0)
                    if ((temp[t] = get_cpu_temp(fan[i].cpu_input)) >= 0)
                    sw_cpu_read_error = 0;
                        sw_read_error[t] = 0;
                else
                    else
                    sw_cpu_read_error = 1;
                        sw_read_error[t] = 1;
                last_cpu_check = now;
                    last_cpu_check = now;
                strcpy(last_cpu_input, fan[i].cpu_input);
                    strcpy(last_cpu_input, fan[i].cpu_input);
            }
                } else if (t == HDD) {
                    temp[t] = 0;
                    sw_read_error[t] = 0;
                    for (int j = 0; j < fan[i].count_scan_hdd; j++) {
                        dev_temp = 0;
                        sw_hdd_buffered = 0;
                        for (int k = 0; k < count_hdd_buffer; k++)
                            if (strcmp(hdd_buffer[k].name,
                              scan_hdd[i][j].name) == 0) {
                                dev_temp = hdd_buffer[k].temp;
                                sw_hdd_buffered = 1;
                                break;
                            }


            if (sw_cpu_read_error == 1)
                        if (sw_hdd_buffered == 0) {
                fan[i].cpu_pwm = fan[i].cpu_error_pwm;
                            if (stat(scan_hdd[i][j].name, &device) == 0 &&
            else if ((((fan[i].cpu_temp - cpu_temp >= fan[i].hyst_cpu) &&
                              (device.st_mode & S_IFMT) == S_IFBLK &&
              now >= fan[i].min_decr_time) || fan[i].cpu_temp < cpu_temp) &&
                              get_hdd_status(scan_hdd[i][j].name) == 1) {
              ((new_pwm = calc_pwm(CPU, i, cpu_temp, fan[i].interpolate_cpu,
                                // Get temperature from hard drive
              fan[i].count_step_cpu)) != fan[i].cpu_pwm || new_pwm == 0 ||
                                dev_temp = get_hdd_temp(scan_hdd[i][j].name);
              fan[i].first_check == 1)) {
                                if (dev_temp < 0)
                fan[i].cpu_pwm = new_pwm;
                                    sw_read_error[t] = 1;
                fan[i].cpu_temp = cpu_temp;
                            }
            }


            while ((fan[i].next_cpu_check += fan[i].interval_cpu) <= now);
                            strcpy(hdd_buffer[count_hdd_buffer].name,
            sw_checked_cpu = 1;
                                  scan_hdd[i][j].name);
        }
                            hdd_buffer[count_hdd_buffer++].temp = dev_temp;
 
                        }
        if (fan[i].control_by_hdd == 1 && now >= fan[i].next_hdd_check) {
                        if (dev_temp > temp[t])
            hdd_temp = 0;
                            temp[t] = dev_temp;
            sw_hdd_read_error = 0;
            for (int j = 0; j < fan[i].count_scan_hdd; j++) {
                dev_temp = 0;
                sw_hdd_buffered = 0;
                for (int k = 0; k < count_hdd_buffer; k++)
                    if (strcmp(hdd_buffer[k].name, scan_hdd[i][j].name) == 0) {
                        dev_temp = hdd_buffer[k].temp;
                        sw_hdd_buffered = 1;
                        break;
                     }
                     }
                }


                 if (sw_hdd_buffered == 0) {
                 if (sw_read_error[t] == 1 && (t != HDD ||
                     if (stat(scan_hdd[i][j].name, &device) == 0 &&
                  calc_pwm(HDD, i, temp[HDD]) < fan[i].error_pwm[HDD]))
                      (device.st_mode & S_IFMT) == S_IFBLK &&
                     fan[i].pwm[t] = fan[i].error_pwm[t];
                      get_hdd_status(scan_hdd[i][j].name) == 1)
                else if ((((fan[i].temp[t] - temp[t] >= fan[i].hyst[t]) &&
                        // Get temperature from hard drive
                  now >= fan[i].min_decr_time) || fan[i].temp[t] < temp[t]) &&
                        if ((dev_temp = get_hdd_temp(scan_hdd[i][j].name)) < 0)
                  ((new_pwm = calc_pwm(t, i, temp[t])) != fan[i].pwm[t] ||
                            sw_hdd_read_error = 1;
                  new_pwm == 0 || fan[i].first_check == 1)) {
 
                    fan[i].pwm[t] = new_pwm;
                    strcpy(hdd_buffer[count_hdd_buffer].name,
                     fan[i].temp[t] = temp[t];
                          scan_hdd[i][j].name);
                     hdd_buffer[count_hdd_buffer++].temp = dev_temp;
                 }
                 }
                if (dev_temp > hdd_temp)
                    hdd_temp = dev_temp;
            }


            if (sw_hdd_read_error == 1 && calc_pwm(HDD, i, hdd_temp,
                while ((fan[i].next_check[t] += fan[i].interval[t]) <= now);
              fan[i].interpolate_hdd, fan[i].count_step_hdd) <
                 sw_checked = 1;
              fan[i].hdd_error_pwm)
                fan[i].hdd_pwm = fan[i].hdd_error_pwm;
            else if ((((fan[i].hdd_temp - hdd_temp >= fan[i].hyst_hdd) &&
              now >= fan[i].min_decr_time) || fan[i].hdd_temp < hdd_temp) &&
              ((new_pwm = calc_pwm(HDD, i, hdd_temp, fan[i].interpolate_hdd,
              fan[i].count_step_hdd)) != fan[i].hdd_pwm || new_pwm == 0 ||
              fan[i].first_check == 1)) {
                fan[i].hdd_pwm = new_pwm;
                 fan[i].hdd_temp = hdd_temp;
             }
             }


             while ((fan[i].next_hdd_check += fan[i].interval_hdd) <= now);
             if (fan[i].control[t] == 1 && (next_check == 0 ||
            sw_checked_hdd = 1;
              next_check > fan[i].next_check[t]))
                next_check = fan[i].next_check[t];
         }
         }


         if (fan[i].control_by_cpu == 1 && (next_check == 0 ||
         if (sw_checked == 0)
          next_check > fan[i].next_cpu_check))
             continue;
             next_check = fan[i].next_cpu_check;
        if (fan[i].control_by_hdd == 1 && (next_check == 0 ||
          next_check > fan[i].next_hdd_check))
            next_check = fan[i].next_hdd_check;


         if (sw_checked_cpu == 1 || sw_checked_hdd == 1) {
         if (fan[i].pwm[CPU] > fan[i].pwm[HDD])
            if (fan[i].cpu_pwm > fan[i].hdd_pwm)
            new_pwm = fan[i].pwm[CPU];
                new_pwm = fan[i].cpu_pwm;
        else
            else
            new_pwm = fan[i].pwm[HDD];
                new_pwm = fan[i].hdd_pwm;
 
            if (fan[i].first_check == 1)
        if (fan[i].first_check == 1)
                // First check since program start
            // First check since program start
                fan[i].first_check = 0;
            fan[i].first_check = 0;
            else if (new_pwm == fan[i].actual_pwm)
        else if (new_pwm == fan[i].actual_pwm)
            continue;
        else if (new_pwm < fan[i].actual_pwm)
            // Check if decrease delay time has passed
            if (now < fan[i].min_decr_time)
                 continue;
                 continue;
             else if (new_pwm < fan[i].actual_pwm)
             else if (new_pwm == 0)
                // Check if decrease delay time has passed
                //  Check if stop delay time has passed
                if (now < fan[i].min_decr_time)
                if (now < fan[i].min_stop_time)
                    continue;
                    if (fan[i].idle_pwm != 0 && fan[i].idle_pwm !=
                else if (new_pwm == 0)
                      fan[i].actual_pwm)
                    //  Check if stop delay time has passed
                        // Set fan speed to idle
                    if (now < fan[i].min_stop_time)
                        new_pwm = fan[i].idle_pwm;
                        if (fan[i].idle_pwm != 0 &&
                    else
                          fan[i].idle_pwm != fan[i].actual_pwm)
                        continue;
                            // Set fan speed to idle
                            new_pwm = fan[i].idle_pwm;
                        else
                            continue;
        } else
            continue;


         // Set new fan speed
         // Set new fan speed
Zeile 543: Zeile 509:
             syslog(LOG_NOTICE,
             syslog(LOG_NOTICE,
               "%s: PWM changed from %d to %d (CPU: %d°C, HDD: %d°C)\n",
               "%s: PWM changed from %d to %d (CPU: %d°C, HDD: %d°C)\n",
               fan[i].name, fan[i].actual_pwm, new_pwm, fan[i].cpu_temp,
               fan[i].name, fan[i].actual_pwm, new_pwm, fan[i].temp[CPU],
               fan[i].hdd_temp);
               fan[i].temp[HDD]);
         if (fan[i].actual_pwm < new_pwm)
         if (fan[i].actual_pwm < new_pwm)
             fan[i].min_decr_time = now + fan[i].decr_delay;
             fan[i].min_decr_time = now + fan[i].decr_delay;

Version vom 15. August 2018, 00:21 Uhr

This program monitors CPU and hard drive temperatures and changes fan speeds accordingly.

C program fwcontrol.c

// Author:  Frank Wulf
// Version: 1.5 (2018-08-14)
//
// This program can be used to control fan speeds automatically. It runs as
// a daemon and sets the PWM values of the fans based on CPU temperature
// and/or hard drive temperatures. The behaviour of this program can be
// configured in file /etc/fwcontrol.conf.
//
// Version history:
// 1.0   2017-11-17   Initial release
// 1.1   2017-12-28   Code optimized
// 1.2   2018-02-17   Fixed issue with hysteresis logic
// 1.3   2018-07-20   Code optimized
// 1.4   2018-08-12   Enhanced error handling
// 1.5   2018-08-14   Fixed issue with logfile messages

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <scsi/sg.h>

#define BUFSIZE 256
#define MAX_FANS 10
#define MAX_STEP 10
#define MAX_HDD 32

#define SMART_BUFFER_SIZE 512
#define SMART_SENSE_BUFFER_SIZE 32
#define SMART_CMD_LENGTH 12

enum {
    ATA_OP_CHECKPOWERMODE1 = 0xe5,
    ATA_OP_CHECKPOWERMODE2 = 0x98,
    ATA_USING_LBA = (1 << 6),
    ATA_STAT_DRQ = (1 << 3),
    ATA_STAT_ERR = (1 << 0),
};

enum {
    SG_ATA_16 = 0x85,
    SG_ATA_16_LEN = 16,
    SG_ATA_PROTO_NON_DATA = (3 << 1)
};

enum {SG_CDB2_CHECK_COND = (1 << 5)};

enum {CPU, HDD};

struct s_fan {
    char name[20];
    char control[2];
    char pwm_enable[50];
    char pwm_write[50];
    char cpu_input[50];
    short stop_delay;
    short decr_delay;
    short interval[2];
    char interpolate[2];
    short hyst[2];
    short count_scan_hdd;
    short count_step[2];
    time_t next_check[2];
    char first_check;
    char loglevel;
    short temp[2];
    unsigned char pwm[2];
    unsigned char actual_pwm;
    unsigned char idle_pwm;
    unsigned char error_pwm[2];
    time_t min_decr_time;
    time_t min_stop_time;
} fan[MAX_FANS];

struct s_temp_to_pwm {
    short temp;
    unsigned char pwm;
} data_pwm[2][MAX_FANS][MAX_STEP];

struct s_scan_hdd {
    char name[10];
} scan_hdd[MAX_FANS][MAX_HDD];

struct s_hdd_temp {
    char name[10];
    short temp;
} hdd_buffer[MAX_HDD];

short count_fans = 0;
time_t now;
time_t next_check;

typedef void (*sighandler_t)(int);

static sighandler_t handle_signal(int sig_nr, sighandler_t signalhandler) {
    struct sigaction new_sig, old_sig;
    new_sig.sa_handler = signalhandler;
    sigemptyset(&new_sig.sa_mask);
    new_sig.sa_flags = SA_RESTART;
    if (sigaction(sig_nr, &new_sig, &old_sig) < 0)
        return SIG_ERR;
    return old_sig.sa_handler;
}

static void start_daemon(const char *log_name, int facility) {
    int i;
    pid_t pid;
    // Fork off parent process
    if ((pid = fork()) != 0)
        exit(EXIT_FAILURE);
    // Create new SID for child process
    if (setsid() < 0)
        exit(EXIT_FAILURE);
    // Ignore signal SIGHUP
    handle_signal(SIGHUP, SIG_IGN);
    // Fork off child process
    if ((pid = fork()) != 0)
        exit(EXIT_FAILURE);
    // Change working directory
    chdir("/");
    // Change file mode mask
    umask(0);
    // Close all file descriptors
    for (i = sysconf(_SC_OPEN_MAX); i > 0; i--)
        close(i);
    openlog(log_name, LOG_PID | LOG_CONS | LOG_NDELAY, facility);
}

int read_conf(void) {
    FILE *fp;
    char *name, *value, buf[BUFSIZE];
    int i = -1, j, k, t;

    now = time(NULL);
    if ((fp = fopen("/etc/fwcontrol.conf", "r")) == NULL) {
        syslog(LOG_ERR, "Error opening configuration file\n");
        return -1;
    }

    while (fgets(buf, BUFSIZE, fp) != NULL) {
        if (buf[0] == '[') {
            if (++i >= MAX_FANS)
                break;
            count_fans = i + 1;
            strcpy(fan[i].name, strtok(buf, "[]\n"));
            fan[i].first_check = 1;
            k = 0;
        } else if (i >= 0) {
            name = strtok(buf, "=");
            value = strtok(NULL, "=");
            value = strtok(value, "\n");
            if (strcmp(name, "control_by_cpu") == 0) {
                fan[i].control[CPU] = atoi(value);
                fan[i].next_check[CPU] = now;
                fan[i].error_pwm[CPU] = 255;
            } else if (strcmp(name, "control_by_hdd") == 0) {
                fan[i].control[HDD] = atoi(value);
                fan[i].next_check[HDD] = now;
                fan[i].error_pwm[HDD] = 255;
            } else if (strcmp(name, "pwm_enable") == 0) {
                strcpy(fan[i].pwm_enable, value);
            } else if (strcmp(name, "pwm_write") == 0) {
                strcpy(fan[i].pwm_write, value);
            } else if (strcmp(name, "cpu_input") == 0) {
                strcpy(fan[i].cpu_input, value);
            } else if (strcmp(name, "stop_delay") == 0) {
                fan[i].stop_delay = atoi(value);
            } else if (strcmp(name, "decrease_delay") == 0) {
                fan[i].decr_delay = atoi(value);
            } else if (strcmp(name, "loglevel") == 0) {
                fan[i].loglevel = atoi(value);
            } else if (strcmp(name, "interval_cpu") == 0) {
                fan[i].interval[CPU] = atoi(value);
            } else if (strcmp(name, "interval_hdd") == 0) {
                fan[i].interval[HDD] = atoi(value);
            } else if (strcmp(name, "interpolate_cpu") == 0) {
                fan[i].interpolate[CPU] = atoi(value);
            } else if (strcmp(name, "interpolate_hdd") == 0) {
                fan[i].interpolate[HDD] = atoi(value);
            } else if (strcmp(name, "hyst_cpu") == 0) {
                fan[i].hyst[CPU] = atoi(value);
            } else if (strcmp(name, "hyst_hdd") == 0) {
                fan[i].hyst[HDD] = atoi(value);
            } else if (strcmp(name, "idle_pwm") == 0) {
                fan[i].idle_pwm = atoi(value);
            } else if (strcmp(name, "cpu_error_pwm") == 0) {
                fan[i].error_pwm[CPU] = atoi(value);
            } else if (strcmp(name, "hdd_error_pwm") == 0) {
                fan[i].error_pwm[HDD] = atoi(value);
            } else if (strcmp(name, "scan_hdd") == 0) {
                value = strtok(value, ",");
                while (value != NULL && ++k <= MAX_HDD) {
                    strcpy(scan_hdd[i][k-1].name, value);
                    value = strtok(NULL, ",");
                }
                fan[i].count_scan_hdd = k;
            } else if (strcmp(name, "temp_pwm_hdd") == 0 ||
                      strcmp(name, "temp_pwm_cpu") == 0) {
                if (strcmp(name, "temp_pwm_cpu") == 0)
                    t = CPU;
                else
                    t = HDD;
                j = 0;
                value = strtok(value, ",");
                while (value != NULL && ++j <= MAX_STEP) {
                    data_pwm[t][i][j-1].temp = atoi(value);
                    value = strtok(NULL, ",");
                    data_pwm[t][i][j-1].pwm = atoi(value);
                    value = strtok(NULL, ",");
                }
                fan[i].count_step[t] = j;
            }
        }
    }
    fclose(fp);

    return 0;
}

static inline int sgio_send(int fd, unsigned char cmd, unsigned char *rv) {
    unsigned char cdb[SG_ATA_16_LEN], sb[32], *desc, status, error;
    sg_io_hdr_t io_hdr;

    memset(&cdb, 0, sizeof(cdb));
    memset(&sb, 0, sizeof(sb));
    memset(&io_hdr, 0, sizeof(io_hdr));

    cdb[0] = SG_ATA_16;
    cdb[1] = SG_ATA_PROTO_NON_DATA;
    cdb[2] = SG_CDB2_CHECK_COND;
    cdb[13] = ATA_USING_LBA;
    cdb[14] = cmd;

    io_hdr.cmd_len = SG_ATA_16_LEN;
    io_hdr.interface_id = 'S';
    io_hdr.mx_sb_len = sizeof(sb);
    io_hdr.dxfer_direction = SG_DXFER_NONE;
    io_hdr.dxfer_len = 0;
    io_hdr.dxferp = NULL;
    io_hdr.cmdp = cdb;
    io_hdr.sbp = sb;
    io_hdr.pack_id = 0;
    io_hdr.timeout = 500; // Milliseconds

    if (ioctl(fd, SG_IO, &io_hdr) == -1) {
        syslog(LOG_ERR, "ioctl() failed (cmd %u, %s)\n", cmd, strerror(errno));
        return -1;
    }

    desc = sb + 8;
    status = desc[13];
    error = desc[3];
    if (rv)
        *rv = desc[5];

    if (status & (ATA_STAT_ERR | ATA_STAT_DRQ)) {
        syslog(LOG_ERR, "SG_IO command %u failed (status %u, error %u)\n",
          cmd, status, error);
        return -1;
    }

    return 0;
}

static inline char get_hdd_status(char *name) {
    int fd, ret;
    unsigned char state;

    if ((fd = open(name, O_RDONLY)) < 0) {
        syslog(LOG_ERR, "Error opening file %s!\n", name);
        return -1;
    }

    ret = 1;
    if (sgio_send(fd, ATA_OP_CHECKPOWERMODE1, &state) &&
        sgio_send(fd, ATA_OP_CHECKPOWERMODE2, &state))
        ret = 0;
    close(fd);

    return (state == 0) ? 0 : 1; // 0 = Sleeping, 1 = Running
}

static inline short get_hdd_temp(char *name) {
    short hdd_temp;
    int fd;
    unsigned char buffer[SMART_BUFFER_SIZE] = "";
    unsigned char sense_buffer[SMART_SENSE_BUFFER_SIZE];
    unsigned char smart_read_cdb[SMART_CMD_LENGTH] =
                  {0xa1, 0x0c, 0x0e, 0xd0, 1, 0, 0x4f, 0xc2, 0, 0xb0, 0, 0};

    sg_io_hdr_t io_hdr;

    if ((fd = open(name, O_RDONLY)) < 0) {
        syslog(LOG_ERR, "Error opening file %s!\n", name);
        return -1;
    }

    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
    io_hdr.interface_id = 'S';
    io_hdr.cmd_len = SMART_CMD_LENGTH;
    io_hdr.mx_sb_len = SMART_SENSE_BUFFER_SIZE;
    io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
    io_hdr.dxfer_len = SMART_BUFFER_SIZE;
    io_hdr.dxferp = buffer;
    io_hdr.cmdp = smart_read_cdb;
    io_hdr.sbp = sense_buffer;
    io_hdr.timeout = 500; // Milliseconds

    if (ioctl(fd, SG_IO, &io_hdr) < 0) {
        syslog(LOG_ERR, "ioctl() call for reading temperature failed\n");
        close(fd);
        return -1;
    }

    for (register int i = 2; i < 361; i += 12)
        if ((int)buffer[i] == 194) {
            hdd_temp = ((long long int)((buffer[i+5])|
                       (buffer[i+6]<<8)|
                       (buffer[i+7]<<16)|
                       (buffer[i+8]<<24)|
                       ((long long int)buffer[i+9]<<32)|
                       ((long long int)buffer[i+10]<<40))) & 0xFF;
            break;
        }
    close(fd);

    return hdd_temp;
}

static inline short get_cpu_temp(char *name) {
    FILE *fp;
    char buf[BUFSIZE];
    short cpu_temp;

    if ((fp = fopen(name, "r")) == NULL) {
        syslog(LOG_ERR, "Error opening file %s!\n", name);
        return -1;
    }
    if (fgets(buf, BUFSIZE, fp) != NULL)
        cpu_temp = atoi(buf) / 1000;
    fclose(fp);

    return cpu_temp;
}

int set_fan_mode(void) {
    FILE *fp;

    for (int i = 0; i < count_fans; i++) {
        if ((fp = fopen(fan[i].pwm_enable, "w")) == NULL) {
            syslog(LOG_ERR, "Error opening file %s!\n", fan[i].pwm_enable);
            return -1;
        }
        fprintf(fp, "1");
        fclose(fp);
    }

    return 0;
}

static inline int set_fan_speed(char *name, unsigned char value) {
    FILE *fp;

    if ((fp = fopen(name, "w")) == NULL) {
        syslog(LOG_ERR, "Error opening file %s!\n", name);
        return -1;
    }
    fprintf(fp, "%d", value);
    fclose(fp);

    return 0;
}

static inline unsigned char calc_pwm(char type, int i, short temp) {
    unsigned char pwm = 0;
    short j = 0;

    while (temp >= data_pwm[type][i][j].temp) {
        pwm = data_pwm[type][i][j].pwm;
        if (++j >= fan[i].count_step[type])
            break;
    }

    if (fan[i].interpolate[type] == 1 && j > 0 && j < fan[i].count_step[type])
        pwm = ((float)data_pwm[type][i][j].pwm - data_pwm[type][i][j-1].pwm) /
              (data_pwm[type][i][j].temp - data_pwm[type][i][j-1].temp) *
              (temp - data_pwm[type][i][j-1].temp) + data_pwm[type][i][j-1].pwm
              + 0.5;

    return pwm;
}

static inline int control_fan_speed(void) {
    struct stat device;
    unsigned char new_pwm;
    char last_cpu_input[50], sw_hdd_buffered, sw_checked, sw_read_error[2];
    short temp[2], dev_temp, count_hdd_buffer = 0;
    time_t last_cpu_check = 0;

    now = time(NULL);
    next_check = 0;
    for (int i = 0; i < count_fans; i++) {
        sw_checked = 0;
        for (int t = 0; t < 2; t++) {
            if (fan[i].control[t] == 1 && now >= fan[i].next_check[t]) {
                if (t == CPU && (last_cpu_check != now ||
                   strcmp(last_cpu_input, fan[i].cpu_input) != 0)) {
                    // Get CPU temperature
                    if ((temp[t] = get_cpu_temp(fan[i].cpu_input)) >= 0)
                        sw_read_error[t] = 0;
                    else
                        sw_read_error[t] = 1;
                    last_cpu_check = now;
                    strcpy(last_cpu_input, fan[i].cpu_input);
                } else if (t == HDD) {
                    temp[t] = 0;
                    sw_read_error[t] = 0;
                    for (int j = 0; j < fan[i].count_scan_hdd; j++) {
                        dev_temp = 0;
                        sw_hdd_buffered = 0;
                        for (int k = 0; k < count_hdd_buffer; k++)
                            if (strcmp(hdd_buffer[k].name,
                               scan_hdd[i][j].name) == 0) {
                                dev_temp = hdd_buffer[k].temp;
                                sw_hdd_buffered = 1;
                                break;
                            }

                        if (sw_hdd_buffered == 0) {
                            if (stat(scan_hdd[i][j].name, &device) == 0 &&
                               (device.st_mode & S_IFMT) == S_IFBLK &&
                               get_hdd_status(scan_hdd[i][j].name) == 1) {
                                // Get temperature from hard drive
                                dev_temp = get_hdd_temp(scan_hdd[i][j].name);
                                if (dev_temp < 0)
                                    sw_read_error[t] = 1;
                            }

                            strcpy(hdd_buffer[count_hdd_buffer].name,
                                  scan_hdd[i][j].name);
                            hdd_buffer[count_hdd_buffer++].temp = dev_temp;
                        }
                        if (dev_temp > temp[t])
                            temp[t] = dev_temp;
                    }
                }

                if (sw_read_error[t] == 1 && (t != HDD ||
                   calc_pwm(HDD, i, temp[HDD]) < fan[i].error_pwm[HDD]))
                    fan[i].pwm[t] = fan[i].error_pwm[t];
                else if ((((fan[i].temp[t] - temp[t] >= fan[i].hyst[t]) &&
                   now >= fan[i].min_decr_time) || fan[i].temp[t] < temp[t]) &&
                   ((new_pwm = calc_pwm(t, i, temp[t])) != fan[i].pwm[t] ||
                   new_pwm == 0 || fan[i].first_check == 1)) {
                    fan[i].pwm[t] = new_pwm;
                    fan[i].temp[t] = temp[t];
                }

                while ((fan[i].next_check[t] += fan[i].interval[t]) <= now);
                sw_checked = 1;
            }

            if (fan[i].control[t] == 1 && (next_check == 0 ||
               next_check > fan[i].next_check[t]))
                next_check = fan[i].next_check[t];
        }

        if (sw_checked == 0)
            continue;

        if (fan[i].pwm[CPU] > fan[i].pwm[HDD])
            new_pwm = fan[i].pwm[CPU];
        else
            new_pwm = fan[i].pwm[HDD];

        if (fan[i].first_check == 1)
            // First check since program start
            fan[i].first_check = 0;
        else if (new_pwm == fan[i].actual_pwm)
            continue;
        else if (new_pwm < fan[i].actual_pwm)
            // Check if decrease delay time has passed
            if (now < fan[i].min_decr_time)
                continue;
            else if (new_pwm == 0)
                //  Check if stop delay time has passed
                if (now < fan[i].min_stop_time)
                    if (fan[i].idle_pwm != 0 && fan[i].idle_pwm !=
                       fan[i].actual_pwm)
                        // Set fan speed to idle
                        new_pwm = fan[i].idle_pwm;
                    else
                        continue;

        // Set new fan speed
        set_fan_speed(fan[i].pwm_write, new_pwm);
        if (fan[i].loglevel >= 1)
            syslog(LOG_NOTICE,
              "%s: PWM changed from %d to %d (CPU: %d°C, HDD: %d°C)\n",
              fan[i].name, fan[i].actual_pwm, new_pwm, fan[i].temp[CPU],
              fan[i].temp[HDD]);
        if (fan[i].actual_pwm < new_pwm)
            fan[i].min_decr_time = now + fan[i].decr_delay;
        if (fan[i].actual_pwm == 0 && new_pwm != 0)
            fan[i].min_stop_time = now + fan[i].stop_delay;
        fan[i].actual_pwm = new_pwm;
    }

    return 0;
}

int main (int argc, char **argv) {
    start_daemon ("fwcontrol", LOG_LOCAL0);
    syslog(LOG_NOTICE, "fwcontrol started ...\n");
    read_conf();
    set_fan_mode();
    while (1) {
        control_fan_speed();
        sleep(next_check - now);
    }
    syslog(LOG_NOTICE, "fwcontrol terminated.\n");
    closelog();
    return EXIT_SUCCESS;
}

Configuration file /etc/fwcontrol.conf

# Configuration file for fwcontrol
#
# Parameter         Value       Description
# ================+===========+=========================================
# control_by_cpu  | 0 or 1    | If set to 1 the fan is controlled by
#                 |           | CPU temperature.
# control_by_hdd  | 0 or 1    | If set to 1 the fan is controlled by
#                 |           | hard drive temperature.
# pwm_enable      | filename  | Used to set the fan mode.
# pwm_write       | filename  | Used to change the fan speed.
# cpu_input       | filename  | Used to read the CPU temperature sensor.
# temp_pwm_cpu    | temp,pwm  | Defines the PWM value for a given CPU
#                 |           | temperature. Up to 10 data pairs are
#                 |           | possible: temp1,pwm1,temp2,pwm2,...
# interpolate_cpu | 0 or 1    | If set to 1 the program calculates
#                 |           | intermediate values between the data
#                 |           | points defined in "temp_pwm_cpu".
# hyst_cpu        | temp      | CPU temperature must be decreased by at
#                 |           | least this value until fan speed can be
#                 |           | reduced.
# scan_hdd        | device(s) | Defines which hard drives should be
#                 |           | checked. Up to 32 hard drives possible:
#                 |           | hdd1,hdd2,hdd3,...
# temp_pwm_hdd    | temp,pwm  | Defines the PWM value for a given hard
#                 |           | drive temperature. Up to 10 data pairs
#                 |           | are possible: temp1,pwm1,temp2,pwm2,...
# interpolate_hdd | 0 or 1    | If set to 1 the program calculates
#                 |           | intermediate values between the data
#                 |           | points defined in "temp_pwm_hdd".
# hyst_hdd        | temp      | Hard drive temperature must be decreased
#                 |           | by at least this value until fan speed
#                 |           | can be reduced.
# decrease_delay  | seconds   | Defines how many seconds must have
#                 |           | passed since last speed increase until
#                 |           | the speed can be decreased.
# stop_delay      | seconds   | Defines how many seconds must have
#                 |           | passed since start of the fan until the
#                 |           | fan can be stopped.
# idle_pwm        | 0-255     | If the fan should be stopped but stop
#                 |           | delay time has not yet passed, then the
#                 |           | fan speed is set to this value.
# cpu_error_pwm   | 0-255     | If something goes wrong when reading
#                 |           | the CPU temperature then the fan speed
#                 |           | is set to this value (default is 255).
# hdd_error_pwm   | 0-255     | If something goes wrong when reading the
#                 |           | hard drive temperatures then the fan
#                 |           | speed is set to this value (default is
#                 |           | 255).
# loglevel        | 0 or 1    | If set to 1 then every speed change will
#                 |           | be written to the logfile.
# interval_cpu    | seconds   | Check CPU temperature every n seconds.
# interval_hdd    | seconds   | Check hard drive temperatures every
#                 |           | n seconds.
# ================+===========+=========================================
[CPU Cooler]
control_by_cpu=1
control_by_hdd=0
pwm_enable=/sys/class/hwmon/hwmon2/pwm2_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm2
cpu_input=/sys/class/hwmon/hwmon1/temp1_input
temp_pwm_cpu=0,20,29,20,30,40,41,40,90,255
interpolate_cpu=1
hyst_cpu=4
decrease_delay=30
stop_delay=120
idle_pwm=20
interval_cpu=3
loglevel=1

[Front left]
control_by_cpu=1
control_by_hdd=1
pwm_enable=/sys/class/hwmon/hwmon2/pwm3_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm3
cpu_input=/sys/class/hwmon/hwmon1/temp1_input
scan_hdd=/dev/sdb
temp_pwm_cpu=0,78,30,104,41,104,90,255
temp_pwm_hdd=35,104,39,255
interpolate_cpu=1
interpolate_hdd=1
hyst_cpu=4
hyst_hdd=2
decrease_delay=30
stop_delay=120
idle_pwm=78
interval_cpu=15
interval_hdd=60
loglevel=1

[Front right]
control_by_cpu=1
control_by_hdd=1
pwm_enable=/sys/class/hwmon/hwmon2/pwm4_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm4
cpu_input=/sys/class/hwmon/hwmon1/temp1_input
scan_hdd=/dev/sdc,/dev/sdd,/dev/sde,/dev/sdf
temp_pwm_cpu=55,110,90,255
temp_pwm_hdd=33,78,37,255
interpolate_cpu=1
interpolate_hdd=1
hyst_cpu=4
hyst_hdd=2
decrease_delay=30
stop_delay=120
idle_pwm=78
interval_cpu=15
interval_hdd=60
loglevel=1

[Rear left]
control_by_cpu=1
control_by_hdd=1
pwm_enable=/sys/class/hwmon/hwmon2/pwm1_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm1
cpu_input=/sys/class/hwmon/hwmon1/temp1_input
scan_hdd=/dev/sdb,/dev/sdc,/dev/sdd,/dev/sde,/dev/sdf
temp_pwm_cpu=0,80,41,80,90,255
temp_pwm_hdd=35,104,39,255
interpolate_cpu=1
interpolate_hdd=1
hyst_cpu=4
hyst_hdd=2
decrease_delay=30
stop_delay=120
idle_pwm=80
interval_cpu=15
interval_hdd=60
loglevel=1

[Rear right]
control_by_cpu=1
control_by_hdd=1
pwm_enable=/sys/class/hwmon/hwmon2/pwm5_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm5
cpu_input=/sys/class/hwmon/hwmon1/temp1_input
scan_hdd=/dev/sdb,/dev/sdc,/dev/sdd,/dev/sde,/dev/sdf
temp_pwm_cpu=0,80,41,80,90,255
temp_pwm_hdd=35,104,39,255
interpolate_cpu=1
interpolate_hdd=1
hyst_cpu=4
hyst_hdd=2
decrease_delay=30
stop_delay=120
idle_pwm=80
interval_cpu=15
interval_hdd=60
loglevel=1

Start fwcontrol during boot with script /etc/init.d/fwcontrol

#! /bin/sh
#

### BEGIN INIT INFO
# Provides:             fwcontrol
# Required-Start:       $syslog $remote_fs
# Required-Stop:        $syslog $remote_fs
# Default-Start:        2 3 4 5
# Default-Stop:
# Short-Description:    fwcontrol
# Description:          Control of fan speeds
### END INIT INFO


NAME="fwcontrol"
DESC="Control of fan speeds"
DAEMON=/usr/sbin/${NAME}
FWUSER=root

PATH=/sbin:/bin:/usr/sbin:/usr/bin

test -f $DAEMON || exit 0

if [ -f /etc/default/$NAME ]; then
   . /etc/default/$NAME
fi

mkdir -p /var/run
PIDFILE=/var/run/${NAME}.pid

RETVAL=0
case "$1" in
   start)
      echo -n "Starting ${DESC}: "
      start-stop-daemon --start --quiet --make-pidfile --pidfile ${PIDFILE} --chuid ${FWUSER} --exec ${DAEMON} -- ${BOPTIONS}
      RETVAL=$?
      echo "${NAME}"
      ;;
   stop)
      echo -n "Stopping ${DESC}: "
      start-stop-daemon --oknodo --stop --quiet --chuid ${FWUSER} --exec ${DAEMON} -- ${BOPTIONS}
      RETVAL=$?
      echo "${NAME}"
      ;;
   restart|force-reload)
      $0 stop
      sleep 5
      $0 start
      RETVAL=$?
      ;;
   *)
      echo "Usage: /etc/init.d/${NAME} {start|stop|restart|force-reload}" >&2
      exit 1
      ;;
esac
exit $RETVAL

Logfile Configuration

File /etc/rsyslog.d/10-fwcontrol.conf:

local0.*                        /var/log/fwcontrol.log
& stop

Logfile rotation configured in file /etc/logrotate.d/fwcontrol:

/var/log/fwcontrol.log {
        weekly
        rotate 4
        compress
        delaycompress
        notifempty
        missingok
}

Fancontrol Documentation