mbpfan/src/mbpfan.c

685 lines
18 KiB
C
Raw Normal View History

2012-06-09 16:25:28 +02:00
/**
* mbpfan.c - automatically control fan for MacBook Pro
* Copyright (C) 2010 Allan McRae <allan@archlinux.org>
* Modifications by Rafael Vega <rvega@elsoftwarehamuerto.org>
* Modifications (2012) by Ismail Khatib <ikhatib@gmail.com>
* Modifications (2012-present) by Daniel Graziotin <daniel@ineed.coffee> [CURRENT MAINTAINER]
2017-03-27 11:44:34 -04:00
* Modifications (2017-present) by Robert Musial <rmusial@fastmail.com>
* Modifications (2018-present) by Ati Sharma <ati.sharma@gmail.com>
2012-06-09 16:25:28 +02:00
*
* 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 3 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.
*
*
* Notes:
* Assumes any number of processors, cores, sensors and fans
* (as defined in NUM_PROCESSORS, NUM_HWMONS, NUM_TEMP_INPUTS and NUM_FANS)
2012-06-15 21:07:11 +02:00
* It uses only the temperatures from the processors as input.
2012-06-09 16:25:28 +02:00
* Requires coretemp and applesmc kernel modules to be loaded.
* Requires root use
*
* Tested models: see README.md
2012-06-09 16:25:28 +02:00
*/
#include <stdarg.h>
2012-06-09 16:25:28 +02:00
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
2012-06-11 21:43:53 +02:00
#include <math.h>
#include <syslog.h>
#include <stdbool.h>
2014-06-22 16:48:44 +02:00
#include <sys/utsname.h>
#include <sys/errno.h>
2012-06-09 16:25:28 +02:00
#include "mbpfan.h"
2012-06-11 21:43:53 +02:00
#include "global.h"
#include "settings.h"
2012-06-09 16:25:28 +02:00
/* lazy min/max... */
2017-06-21 11:26:32 +02:00
#define min(a,b) ((a) < (b) ? (a) : (b))
#define max(a,b) ((a) > (b) ? (a) : (b))
2012-06-09 16:25:28 +02:00
/* temperature thresholds
* low_temp - temperature below which fan speed will be at minimum
* high_temp - fan will increase speed when higher than this temperature
* max_temp - fan will run at full speed above this temperature */
int low_temp = 63; // try ranges 55-63
int high_temp = 66; // try ranges 58-66
int max_temp = 86; // do not set it > 90
2012-06-09 16:25:28 +02:00
// maximum number of processors etc supported
#define NUM_PROCESSORS 6
#define NUM_HWMONS 12
#define NUM_TEMP_INPUTS 64
#define NUM_FANS 10
// sane defaults when user provides unexpected values
#define MIN_FAN_SPEED_DEFAULT 500
#define MAX_FAN_SPEED_DEFAULT 6500
int polling_interval = 1;
2012-06-09 16:25:28 +02:00
2014-07-21 23:43:39 +02:00
t_sensors* sensors = NULL;
t_fans* fans = NULL;
char *smprintf(const char *fmt, ...)
{
char *buf;
int cnt;
va_list ap;
// find buffer length
va_start(ap, fmt);
cnt = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);
if (cnt < 0) {
return NULL;
}
// create and write to buffer
buf = malloc(cnt + 1);
va_start(ap, fmt);
vsnprintf(buf, cnt + 1, fmt, ap);
va_end(ap);
return buf;
}
bool is_modern_sensors_path()
2014-06-22 16:48:44 +02:00
{
struct utsname kernel;
uname(&kernel);
char *str_kernel_version;
2014-06-23 10:24:03 +02:00
str_kernel_version = strtok(kernel.release, ".");
if (atoi(str_kernel_version) < 3){
syslog(LOG_INFO, "mbpfan detected a pre-3.x.x linux kernel. Detected version: %s. Exiting.\n", kernel.release);
printf("mbpfan detected a pre-3.x.x linux kernel. Detected version: %s. Exiting.\n", kernel.release);
exit(EXIT_FAILURE);
2014-06-23 10:24:03 +02:00
}
int counter;
2014-06-22 16:48:44 +02:00
for (counter = 0; counter < NUM_HWMONS; counter++) {
int temp;
for (temp = 1; temp < NUM_TEMP_INPUTS; ++temp) {
char *path = smprintf("/sys/devices/platform/coretemp.0/hwmon/hwmon%d/temp%d_input", counter, temp);
int res = access(path, R_OK);
free(path);
if (res == 0) {
return 1;
}
}
}
2014-06-22 16:48:44 +02:00
return 0;
2014-06-22 16:48:44 +02:00
}
2014-06-22 17:07:24 +02:00
2012-06-11 21:43:53 +02:00
t_sensors *retrieve_sensors()
2012-06-09 16:25:28 +02:00
{
t_sensors *sensors_head = NULL;
t_sensors *s = NULL;
char *path = NULL;
2014-06-22 16:48:44 +02:00
char *path_begin = NULL;
const char *path_end = "_input";
int sensors_found = 0;
if (!is_modern_sensors_path()) {
2014-06-22 16:48:44 +02:00
if(verbose) {
printf("Using legacy sensor path for kernel < 3.15.0\n");
if(daemonize) {
syslog(LOG_INFO, "Using legacy path for kernel < 3.15.0");
}
}
2017-07-20 03:49:31 -07:00
path_begin = strdup("/sys/devices/platform/coretemp.0/temp");
2014-06-22 16:48:44 +02:00
} else {
if(verbose) {
printf("Using new sensor path for kernel >= 3.15.0 or some CentOS versions with kernel 3.10.0\n");
2014-06-22 16:48:44 +02:00
if(daemonize) {
syslog(LOG_INFO, "Using new sensor path for kernel >= 3.15.0 or some CentOS versions with kernel 3.10.0 ");
2014-06-22 16:48:44 +02:00
}
}
// loop over up to 6 processors
int processor;
for (processor = 0; processor < NUM_PROCESSORS; processor++) {
2014-06-22 16:48:44 +02:00
2019-01-15 15:44:54 -08:00
if (path_begin != NULL) {
free(path_begin);
}
path_begin = smprintf("/sys/devices/platform/coretemp.%d/hwmon/hwmon", processor);
2014-06-22 16:48:44 +02:00
int counter;
for (counter = 0; counter < NUM_HWMONS; counter++) {
2014-06-22 16:48:44 +02:00
char *hwmon_path = smprintf("%s%d", path_begin, counter);
2014-06-22 16:48:44 +02:00
int res = access(hwmon_path, R_OK);
if (res == 0) {
2014-06-22 16:48:44 +02:00
free(path_begin);
path_begin = smprintf("%s/temp", hwmon_path);
2014-06-22 16:48:44 +02:00
if(verbose) {
printf("Found hwmon path at %s\n", path_begin);
2014-06-22 16:48:44 +02:00
if(daemonize) {
syslog(LOG_INFO, "Found hwmon path at %s\n", path_begin);
}
2014-06-22 16:48:44 +02:00
}
free(hwmon_path);
break;
}
free(hwmon_path);
}
2012-11-12 11:48:05 +01:00
int core = 0;
for(core = 0; core<NUM_TEMP_INPUTS; core++) {
path = smprintf("%s%d%s", path_begin, core, path_end);
FILE *file = fopen(path, "r");
if(file != NULL) {
s = (t_sensors *) malloc( sizeof( t_sensors ) );
s->path = strdup(path);
fscanf(file, "%d", &s->temperature);
2012-08-23 22:32:49 +02:00
if (sensors_head == NULL) {
sensors_head = s;
sensors_head->next = NULL;
2012-08-23 22:32:49 +02:00
} else {
t_sensors *tmp = sensors_head;
2012-08-23 22:32:49 +02:00
while (tmp->next != NULL) {
tmp = tmp->next;
}
2012-08-23 22:32:49 +02:00
tmp->next = s;
tmp->next->next = NULL;
}
2012-08-23 22:32:49 +02:00
s->file = file;
sensors_found++;
}
2012-08-23 22:32:49 +02:00
free(path);
path = NULL;
}
}
2012-06-16 11:02:48 +02:00
}
2012-08-23 22:32:49 +02:00
2012-10-24 11:13:25 +02:00
if(verbose) {
printf("Found %d sensors\n", sensors_found);
if(daemonize) {
syslog(LOG_INFO, "Found %d sensors", sensors_found);
}
2012-08-23 22:32:49 +02:00
}
2017-07-13 17:04:03 -07:00
if (sensors_found == 0){
syslog(LOG_CRIT, "mbpfan could not detect any temp sensor. Please contact the developer.\n");
printf("mbpfan could not detect any temp sensor. Please contact the developer.\n");
exit(EXIT_FAILURE);
}
free(path_begin);
path_begin = NULL;
return sensors_head;
2012-06-09 16:25:28 +02:00
}
static int read_value(const char *path)
{
int value = -1;
FILE *file = fopen(path, "r");
if (file != NULL) {
fscanf(file, "%d", &value);
fclose(file);
}
return value;
}
2014-06-22 17:07:24 +02:00
static void read_value_str(const char *path, char *str, size_t len)
{
FILE *file = fopen(path, "r");
if (file != NULL) {
fgets(str, len, file);
fclose(file);
}
}
static void trim_trailing_whitespace(char *str)
{
for (ssize_t i = strlen(str) - 1; i >= 0; --i) {
if (isspace(str[i]) || str[i] == '\n') {
str[i] = '\0';
}
}
}
2012-10-24 11:13:25 +02:00
t_fans *retrieve_fans()
{
2012-10-24 11:13:25 +02:00
t_fans *fans_head = NULL;
t_fans *fan = NULL;
2012-06-16 11:02:48 +02:00
char *path_output = NULL;
char *path_label = NULL;
char *path_manual = NULL;
char *path_fan_max = NULL;
char *path_fan_min = NULL;
2012-06-16 11:02:48 +02:00
const char *path_begin = "/sys/devices/platform/applesmc.768/fan";
const char *path_output_end = "_output";
const char *path_label_end = "_label";
const char *path_man_end = "_manual";
const char *path_max_speed = "_max";
const char *path_min_speed = "_min";
2012-08-22 16:02:03 +02:00
int counter = 0;
2012-10-24 11:13:25 +02:00
int fans_found = 0;
2012-06-16 11:02:48 +02:00
for(counter = 0; counter<NUM_FANS; counter++) {
2012-11-12 11:48:05 +01:00
path_output = smprintf("%s%d%s", path_begin, counter, path_output_end);
path_label = smprintf("%s%d%s", path_begin, counter, path_label_end);
path_manual = smprintf("%s%d%s", path_begin, counter, path_man_end);
path_fan_min = smprintf("%s%d%s",path_begin, counter, path_min_speed);
path_fan_max = smprintf("%s%d%s",path_begin, counter, path_max_speed);
2012-06-16 11:02:48 +02:00
FILE *file = fopen(path_output, "w");
2012-06-16 11:02:48 +02:00
if(file != NULL) {
2012-10-24 11:13:25 +02:00
fan = (t_fans *) malloc( sizeof( t_fans ) );
fan->fan_output_path = strdup(path_output);
fan->fan_manual_path = strdup(path_manual);
fan->fan_id = counter;
int fan_speed = read_value(path_fan_min);
if(fan_speed == -1 || fan_speed < MIN_FAN_SPEED_DEFAULT)
fan->fan_min_speed = MIN_FAN_SPEED_DEFAULT;
else
fan->fan_min_speed = fan_speed;
fan_speed = read_value(path_fan_max);
if(fan_speed == -1 || fan_speed > MAX_FAN_SPEED_DEFAULT)
fan->fan_max_speed = MAX_FAN_SPEED_DEFAULT;
else
fan->fan_max_speed = fan_speed;
size_t max_label_len = 64;
fan->label = malloc(max_label_len);
read_value_str(path_label, fan->label, max_label_len);
trim_trailing_whitespace(fan->label);
fan->old_speed = 0;
2012-10-24 11:13:25 +02:00
if (fans_head == NULL) {
fans_head = fan;
fans_head->next = NULL;
} else {
t_fans *tmp = fans_head;
while (tmp->next != NULL) {
tmp = tmp->next;
}
tmp->next = fan;
tmp->next->next = NULL;
2012-06-16 11:02:48 +02:00
}
2012-08-23 22:32:49 +02:00
fan->file = file;
2012-10-24 11:13:25 +02:00
fans_found++;
}
free(path_fan_min);
path_fan_min = NULL;
free(path_label);
path_label = NULL;
free(path_fan_max);
path_fan_max = NULL;
2012-10-24 11:13:25 +02:00
free(path_output);
path_output = NULL;
free(path_manual);
path_manual = NULL;
2012-06-16 11:02:48 +02:00
}
if(verbose) {
2012-10-24 11:13:25 +02:00
printf("Found %d fans\n", fans_found);
2012-11-12 11:48:05 +01:00
if(daemonize) {
2012-10-24 11:13:25 +02:00
syslog(LOG_INFO, "Found %d fans", fans_found);
2012-06-11 21:43:53 +02:00
}
2012-06-16 11:02:48 +02:00
}
2012-06-11 21:43:53 +02:00
2018-08-20 15:19:26 -07:00
if (fans_found == 0){
syslog(LOG_CRIT, "mbpfan could not detect any fan. Please contact the developer.\n");
printf("mbpfan could not detect any fan. Please contact the developer.\n");
exit(EXIT_FAILURE);
}
2012-10-24 11:13:25 +02:00
return fans_head;
}
static void set_fans_mode(t_fans *fans, int mode)
{
2012-10-24 11:13:25 +02:00
t_fans *tmp = fans;
FILE *file;
2012-08-23 22:32:49 +02:00
while(tmp != NULL) {
file = fopen(tmp->fan_manual_path, "rw+");
2012-08-23 22:32:49 +02:00
if(file != NULL) {
fprintf(file, "%d", mode);
fclose(file);
}
2012-08-23 22:32:49 +02:00
tmp = tmp->next;
2012-06-16 11:02:48 +02:00
}
}
void set_fans_man(t_fans *fans)
{
set_fans_mode(fans, 1);
}
void set_fans_auto(t_fans *fans)
{
set_fans_mode(fans, 0);
}
2012-06-09 16:25:28 +02:00
t_sensors *refresh_sensors(t_sensors *sensors)
{
t_sensors *tmp = sensors;
2012-06-09 16:25:28 +02:00
while(tmp != NULL) {
if(tmp->file != NULL) {
char buf[16];
int len = pread(fileno(tmp->file), buf, sizeof(buf), /*offset=*/ 0);
buf[len] = '\0';
2019-10-28 23:21:19 -07:00
tmp->temperature = strtod(buf, NULL);
2012-06-09 16:25:28 +02:00
}
2012-06-16 11:02:48 +02:00
tmp = tmp->next;
2012-06-16 11:02:48 +02:00
}
2012-08-23 22:32:49 +02:00
return sensors;
2012-06-09 16:25:28 +02:00
}
/* Controls the speed of a fan */
void set_fan_speed(t_fans* fan, int speed)
2012-06-09 16:25:28 +02:00
{
if(fan != NULL && fan->file != NULL && fan->old_speed != speed) {
char buf[16];
int len = snprintf(buf, sizeof(buf), "%d", speed);
int res = pwrite(fileno(fan->file), buf, len, /*offset=*/ 0);
if (res == -1) {
perror("Could not set fan speed");
}
fan->old_speed = speed;
2012-06-16 11:02:48 +02:00
}
2012-06-09 16:25:28 +02:00
}
void set_fan_minimum_speed(t_fans* fans)
{
t_fans *tmp = fans;
2012-06-09 16:25:28 +02:00
while(tmp != NULL) {
set_fan_speed(tmp,tmp->fan_min_speed);
tmp = tmp->next;
}
}
2012-06-09 16:25:28 +02:00
unsigned short get_temp(t_sensors* sensors)
{
sensors = refresh_sensors(sensors);
unsigned int temp = 0;
t_sensors* tmp = sensors;
2012-08-23 22:32:49 +02:00
while(tmp != NULL) {
temp = max(temp, tmp->temperature);
tmp = tmp->next;
2012-11-12 11:48:05 +01:00
}
return temp / 1000;
2012-06-09 16:25:28 +02:00
}
void retrieve_settings(const char* settings_path, t_fans* fans)
2012-06-09 16:25:28 +02:00
{
Settings *settings = NULL;
int result = 0;
FILE *f = NULL;
2012-11-18 22:16:49 +01:00
if (settings_path == NULL) {
f = fopen("/etc/mbpfan.conf", "r");
} else {
f = fopen(settings_path, "r");
}
if (f == NULL) {
/* Could not open configfile */
if(verbose) {
printf("Couldn't open configfile, using defaults\n");
2012-08-23 22:32:49 +02:00
if(daemonize) {
syslog(LOG_INFO, "Couldn't open configfile, using defaults");
}
}
2012-08-23 22:32:49 +02:00
} else {
settings = settings_open(f);
fclose(f);
2012-08-23 22:32:49 +02:00
if (settings == NULL) {
/* Could not read configfile */
if(verbose) {
printf("Couldn't read configfile\n");
2012-08-23 22:32:49 +02:00
if(daemonize) {
syslog(LOG_WARNING, "Couldn't read configfile");
}
}
2012-08-23 22:32:49 +02:00
} else {
t_fans *fan = fans;
2012-08-23 22:32:49 +02:00
while(fan != NULL) {
2012-08-23 22:32:49 +02:00
char* config_key;
config_key = smprintf("min_fan%d_speed", fan->fan_id);
/* Read configfile values */
result = settings_get_int(settings, "general", config_key);
if (result != 0) {
fan->fan_min_speed = result;
}
free(config_key);
config_key = smprintf("max_fan%d_speed", fan->fan_id);
result = settings_get_int(settings, "general", config_key);
2012-08-23 22:32:49 +02:00
if (result != 0) {
fan->fan_max_speed = result;
}
free(config_key);
fan = fan->next;
}
result = settings_get_int(settings, "general", "low_temp");
2012-08-23 22:32:49 +02:00
if (result != 0) {
low_temp = result;
}
result = settings_get_int(settings, "general", "high_temp");
2012-08-23 22:32:49 +02:00
if (result != 0) {
high_temp = result;
}
result = settings_get_int(settings, "general", "max_temp");
2012-08-23 22:32:49 +02:00
if (result != 0) {
max_temp = result;
}
result = settings_get_int(settings, "general", "polling_interval");
2012-08-23 22:32:49 +02:00
if (result != 0) {
polling_interval = result;
}
/* Destroy the settings object */
settings_delete(settings);
}
}
}
void mbpfan()
{
int old_temp, new_temp, fan_speed, steps;
int temp_change;
sensors = retrieve_sensors();
fans = retrieve_fans();
retrieve_settings(NULL, fans);
t_fans* fan = fans;
while(fan != NULL) {
if (fan->fan_min_speed > fan->fan_max_speed) {
syslog(LOG_INFO, "Invalid fan speeds: %d %d", fan->fan_min_speed, fan->fan_max_speed);
printf("Invalid fan speeds: %d %d\n", fan->fan_min_speed, fan->fan_max_speed);
exit(EXIT_FAILURE);
}
fan = fan->next;
}
if (low_temp > high_temp || high_temp > max_temp) {
syslog(LOG_INFO, "Invalid temperatures: %d %d %d", low_temp, high_temp, max_temp);
printf("Invalid temperatures: %d %d %d\n", low_temp, high_temp, max_temp);
exit(EXIT_FAILURE);
}
2012-10-24 11:13:25 +02:00
set_fans_man(fans);
new_temp = get_temp(sensors);
set_fan_minimum_speed(fans);
fan = fans;
while(fan != NULL) {
fan->step_up = (float)( fan->fan_max_speed - fan->fan_min_speed ) /
(float)( ( max_temp - high_temp ) * ( max_temp - high_temp + 1 ) / 2 );
fan->step_down = (float)( fan->fan_max_speed - fan->fan_min_speed ) /
(float)( ( max_temp - low_temp ) * ( max_temp - low_temp + 1 ) / 2 );
fan = fan->next;
}
recalibrate:
if(verbose) {
printf("Sleeping for 2 seconds to get first temp delta\n");
2012-08-23 22:32:49 +02:00
if(daemonize) {
syslog(LOG_INFO, "Sleeping for 2 seconds to get first temp delta");
2012-06-11 21:43:53 +02:00
}
2012-06-16 11:02:48 +02:00
}
sleep(2);
2012-08-23 22:32:49 +02:00
while(1) {
old_temp = new_temp;
new_temp = get_temp(sensors);
2012-06-09 16:25:28 +02:00
fan = fans;
2012-06-09 16:25:28 +02:00
while(fan != NULL) {
fan_speed = fan->old_speed;
2012-06-09 16:25:28 +02:00
if(new_temp >= max_temp && fan->old_speed != fan->fan_max_speed) {
fan_speed = fan->fan_max_speed;
}
2012-06-09 16:25:28 +02:00
if(new_temp <= low_temp && fan_speed != fan->fan_min_speed) {
fan_speed = fan->fan_min_speed;
}
2012-06-09 16:25:28 +02:00
temp_change = new_temp - old_temp;
2012-06-09 16:25:28 +02:00
if(temp_change > 0 && new_temp > high_temp && new_temp < max_temp) {
steps = ( new_temp - high_temp ) * ( new_temp - high_temp + 1 ) / 2;
fan_speed = max( fan_speed, ceil(fan->fan_min_speed + steps * fan->step_up) );
}
2012-08-23 22:32:49 +02:00
if(temp_change < 0 && new_temp > low_temp && new_temp < max_temp) {
steps = ( max_temp - new_temp ) * ( max_temp - new_temp + 1 ) / 2;
fan_speed = min( fan_speed, floor(fan->fan_max_speed - steps * fan->step_down) );
2012-06-16 11:02:48 +02:00
}
2012-06-11 21:43:53 +02:00
if(verbose) {
2019-10-16 10:03:12 -07:00
printf("Old Temp: %d New Temp: %d Fan: %s Speed: %d\n", old_temp, new_temp, fan->label, fan_speed);
if(daemonize) {
2019-10-16 10:03:12 -07:00
syslog(LOG_INFO, "Old Temp: %d New Temp: %d Fan: %s Speed: %d", old_temp, new_temp, fan->label, fan_speed);
}
}
set_fan_speed(fan, fan_speed);
fan = fan->next;
}
2012-06-09 16:25:28 +02:00
if(verbose) {
printf("Sleeping for %d seconds\n", polling_interval);
fflush(stdout);
2012-08-23 22:32:49 +02:00
if(daemonize) {
syslog(LOG_INFO, "Sleeping for %d seconds", polling_interval);
2012-06-16 11:02:48 +02:00
}
2012-06-09 16:25:28 +02:00
}
time_t before_sleep = time(NULL);
// call nanosleep instead of sleep to avoid rt_sigprocmask and
// rt_sigaction
struct timespec ts;
ts.tv_sec = polling_interval;
ts.tv_nsec = 0;
nanosleep(&ts, NULL);
time_t after_sleep = time(NULL);
if(after_sleep - before_sleep > 2 * polling_interval) {
printf("Clock skew detected - slept for %ld seconds but expected %d\n", after_sleep - before_sleep, polling_interval);
fflush(stdout);
if(daemonize) {
syslog(LOG_INFO, "Clock skew detected - slept for %ld seconds but expected %d", after_sleep - before_sleep, polling_interval);
}
set_fans_man(fans);
goto recalibrate;
}
2012-06-16 11:02:48 +02:00
}
2012-06-09 16:25:28 +02:00
}