Fancontrol: Unterschied zwischen den Versionen
Zur Navigation springen
Zur Suche springen
Wulf (Diskussion | Beiträge) |
Wulf (Diskussion | Beiträge) |
||
| Zeile 61: | Zeile 61: | ||
struct s_fan { | struct s_fan { | ||
char name[20]; | char name[20]; | ||
char | char control[2]; | ||
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 | short interval[2]; | ||
char interpolate[2]; | |||
short hyst[2]; | |||
char | |||
short | |||
short count_scan_hdd; | short count_scan_hdd; | ||
short | short count_step[2]; | ||
time_t next_check[2]; | |||
time_t | |||
char first_check; | char first_check; | ||
char loglevel; | char loglevel; | ||
short | short temp[2]; | ||
unsigned char pwm[2]; | |||
unsigned char | |||
unsigned char actual_pwm; | unsigned char actual_pwm; | ||
unsigned char idle_pwm; | unsigned char idle_pwm; | ||
unsigned char | unsigned char error_pwm[2]; | ||
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]. | fan[i].control[CPU] = atoi(value); | ||
fan[i]. | fan[i].next_check[CPU] = now; | ||
fan[i]. | fan[i].error_pwm[CPU] = 255; | ||
} else if (strcmp(name, "control_by_hdd") == 0) { | } else if (strcmp(name, "control_by_hdd") == 0) { | ||
fan[i]. | fan[i].control[HDD] = atoi(value); | ||
fan[i]. | fan[i].next_check[HDD] = now; | ||
fan[i]. | 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]. | fan[i].interval[CPU] = atoi(value); | ||
} else if (strcmp(name, "interval_hdd") == 0) { | } else if (strcmp(name, "interval_hdd") == 0) { | ||
fan[i]. | fan[i].interval[HDD] = atoi(value); | ||
} else if (strcmp(name, "interpolate_cpu") == 0) { | } else if (strcmp(name, "interpolate_cpu") == 0) { | ||
fan[i]. | fan[i].interpolate[CPU] = atoi(value); | ||
} else if (strcmp(name, "interpolate_hdd") == 0) { | } else if (strcmp(name, "interpolate_hdd") == 0) { | ||
fan[i]. | fan[i].interpolate[HDD] = atoi(value); | ||
} else if (strcmp(name, "hyst_cpu") == 0) { | } else if (strcmp(name, "hyst_cpu") == 0) { | ||
fan[i]. | fan[i].hyst[CPU] = atoi(value); | ||
} else if (strcmp(name, "hyst_hdd") == 0) { | } else if (strcmp(name, "hyst_hdd") == 0) { | ||
fan[i]. | 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]. | 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]. | 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[ | data_pwm[t][i][j-1].temp = atoi(value); | ||
value = strtok(NULL, ","); | value = strtok(NULL, ","); | ||
data_pwm[ | data_pwm[t][i][j-1].pwm = atoi(value); | ||
value = strtok(NULL, ","); | value = strtok(NULL, ","); | ||
} | } | ||
fan[i]. | fan[i].count_step[t] = j; | ||
} | } | ||
} | } | ||
| Zeile 397: | Zeile 383: | ||
} | } | ||
static inline unsigned char calc_pwm(char type, int | static inline unsigned char calc_pwm(char type, int i, short temp) { | ||
unsigned char pwm = 0; | unsigned char pwm = 0; | ||
short j = 0; | short j = 0; | ||
while (temp >= data_pwm[type][ | while (temp >= data_pwm[type][i][j].temp) { | ||
pwm = data_pwm[type][ | 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][ | 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) * | |||
(data_pwm[type][ | (temp - data_pwm[type][i][j-1].temp) + data_pwm[type][i][j-1].pwm | ||
(temp - data_pwm[type][ | + 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 | char last_cpu_input[50], sw_hdd_buffered, sw_checked, sw_read_error[2]; | ||
short temp[2], dev_temp, count_hdd_buffer = 0; | |||
short | |||
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 = 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 ( | if (sw_read_error[t] == 1 && (t != HDD || | ||
if ( | 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 ( | 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; | 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 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]. | fan[i].name, fan[i].actual_pwm, new_pwm, fan[i].temp[CPU], | ||
fan[i]. | 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
}