/** * mbpfan.c - automatically control fan for MacBook Pro * Copyright (C) 2010 Allan McRae * Modifications by Rafael Vega * Modifications (2012) by Ismail Khatib * Modifications (2012-present) by Daniel Graziotin [CURRENT MAINTAINER] * Modifications (2017-present) by Robert Musial * Modifications (2018-present) by Ati Sharma * * 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 and fans (max. 6, 16, 12 * as defined in NUM_PROCESSORS, NUM_HWMONS, NUM_TEMP_INPUTS and NUM_FANS) * It uses only the temperatures from the processors as input. * Requires coretemp and applesmc kernel modules to be loaded. * Requires root use * * Tested models: see README.md */ #include #include #include #include #include #include #include #include #include #include #include #include "mbpfan.h" #include "global.h" #include "settings.h" /* lazy min/max... */ #define min(a,b) ((a) < (b) ? (a) : (b)) #define max(a,b) ((a) > (b) ? (a) : (b)) /* 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 // maximum number of processors etc supported #define NUM_PROCESSORS 6 #define NUM_HWMONS 12 #define NUM_TEMP_INPUTS 16 #define NUM_FANS 10 #define NUM_MIN_FAN_SPEED_DEFAULT 2000 #define NUM_MAX_FAN_SPEED_DEFAULT 6200 int polling_interval = 7; 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() { struct utsname kernel; uname(&kernel); char *str_kernel_version; 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); } int counter; 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; } } } return 0; } t_sensors *retrieve_sensors() { t_sensors *sensors_head = NULL; t_sensors *s = NULL; char *path = NULL; char *path_begin = NULL; const char *path_end = "_input"; int sensors_found = 0; if (!is_modern_sensors_path()) { 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"); } } path_begin = strdup("/sys/devices/platform/coretemp.0/temp"); } else { if(verbose) { printf("Using new sensor path for kernel >= 3.15.0 or some CentOS versions with kernel 3.10.0\n"); if(daemonize) { syslog(LOG_INFO, "Using new sensor path for kernel >= 3.15.0 or some CentOS versions with kernel 3.10.0 "); } } // loop over up to 6 processors int processor; for (processor = 0; processor < NUM_PROCESSORS; processor++) { if (path_begin != NULL) { free(path_begin); } path_begin = smprintf("/sys/devices/platform/coretemp.%d/hwmon/hwmon", processor); int counter; for (counter = 0; counter < NUM_HWMONS; counter++) { char hwmon_path[strlen(path_begin)+2]; sprintf(hwmon_path, "%s%d", path_begin, counter); int res = access(hwmon_path, R_OK); if (res == 0) { free(path_begin); path_begin = smprintf("%s/temp", hwmon_path); if(verbose) { printf("Found hwmon path at %s\n", path_begin); if(daemonize) { syslog(LOG_INFO, "Found hwmon path at %s\n", path_begin); } } break; } } int core = 0; for(core = 0; corepath = strdup(path); fscanf(file, "%d", &s->temperature); if (sensors_head == NULL) { sensors_head = s; sensors_head->next = NULL; } else { t_sensors *tmp = sensors_head; while (tmp->next != NULL) { tmp = tmp->next; } tmp->next = s; tmp->next->next = NULL; } s->file = file; sensors_found++; } free(path); path = NULL; } } } if(verbose) { printf("Found %d sensors\n", sensors_found); if(daemonize) { syslog(LOG_INFO, "Found %d sensors", sensors_found); } } 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; } 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; } t_fans *retrieve_fans() { t_fans *fans_head = NULL; t_fans *fan = NULL; char *path_output = NULL; char *path_manual = NULL; char *path_fan_max = NULL; char *path_fan_min = NULL; const char *path_begin = "/sys/devices/platform/applesmc.768/fan"; const char *path_output_end = "_output"; const char *path_man_end = "_manual"; const char *path_max_speed = "_max"; const char *path_min_speed = "_min"; int counter = 0; int fans_found = 0; for(counter = 0; counterfan_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 < NUM_MIN_FAN_SPEED_DEFAULT) fan->fan_min_speed = NUM_MIN_FAN_SPEED_DEFAULT; else fan->fan_min_speed = fan_speed; fan_speed = read_value(path_fan_max); if(fan_speed == -1 || fan_speed > NUM_MAX_FAN_SPEED_DEFAULT) fan->fan_max_speed = NUM_MAX_FAN_SPEED_DEFAULT; else fan->fan_max_speed = fan_speed; fan->old_speed = 0; 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; } fan->file = file; fans_found++; } free(path_fan_min); path_fan_min = NULL; free(path_fan_max); path_fan_max = NULL; free(path_output); path_output = NULL; free(path_manual); path_manual = NULL; } if(verbose) { printf("Found %d fans\n", fans_found); if(daemonize) { syslog(LOG_INFO, "Found %d fans", fans_found); } } 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); } return fans_head; } static void set_fans_mode(t_fans *fans, int mode) { t_fans *tmp = fans; FILE *file; while(tmp != NULL) { file = fopen(tmp->fan_manual_path, "rw+"); if(file != NULL) { fprintf(file, "%d", mode); fclose(file); } tmp = tmp->next; } } void set_fans_man(t_fans *fans) { set_fans_mode(fans, 1); } void set_fans_auto(t_fans *fans) { set_fans_mode(fans, 0); } t_sensors *refresh_sensors(t_sensors *sensors) { t_sensors *tmp = sensors; while(tmp != NULL) { if(tmp->file != NULL) { char buf[16]; int len = pread(fileno(tmp->file), buf, sizeof(buf), /*offset=*/ 0); buf[len] = '\0'; sscanf(buf, "%d", &tmp->temperature); } tmp = tmp->next; } return sensors; } /* Controls the speed of a fan */ void set_fan_speed(t_fans* fan, int speed) { 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; } } void set_fan_minimum_speed(t_fans* fans) { t_fans *tmp = fans; while(tmp != NULL) { set_fan_speed(tmp,tmp->fan_min_speed); tmp = tmp->next; } } unsigned short get_temp(t_sensors* sensors) { sensors = refresh_sensors(sensors); int sum_temp = 0; unsigned short temp = 0; t_sensors* tmp = sensors; int number_sensors = 0; while(tmp != NULL) { sum_temp += tmp->temperature; tmp = tmp->next; number_sensors++; } // just to be safe if (number_sensors == 0) { number_sensors++; } temp = (unsigned short)( ceil( (float)( sum_temp ) / (number_sensors * 1000) ) ); return temp; } void retrieve_settings(const char* settings_path, t_fans* fans) { Settings *settings = NULL; int result = 0; FILE *f = NULL; 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"); if(daemonize) { syslog(LOG_INFO, "Couldn't open configfile, using defaults"); } } } else { settings = settings_open(f); fclose(f); if (settings == NULL) { /* Could not read configfile */ if(verbose) { printf("Couldn't read configfile\n"); if(daemonize) { syslog(LOG_WARNING, "Couldn't read configfile"); } } } else { t_fans *fan = fans; while(fan != NULL) { 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); if (result != 0) { fan->fan_max_speed = result; } free(config_key); fan = fan->next; } result = settings_get_int(settings, "general", "low_temp"); if (result != 0) { low_temp = result; } result = settings_get_int(settings, "general", "high_temp"); if (result != 0) { high_temp = result; } result = settings_get_int(settings, "general", "max_temp"); if (result != 0) { max_temp = result; } result = settings_get_int(settings, "general", "polling_interval"); 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); } 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; } if(verbose) { printf("Sleeping for 2 seconds to get first temp delta\n"); if(daemonize) { syslog(LOG_INFO, "Sleeping for 2 seconds to get first temp delta"); } } sleep(2); while(1) { old_temp = new_temp; new_temp = get_temp(sensors); fan = fans; while(fan != NULL) { fan_speed = fan->old_speed; if(new_temp >= max_temp && fan->old_speed != fan->fan_max_speed) { fan_speed = fan->fan_max_speed; } if(new_temp <= low_temp && fan_speed != fan->fan_min_speed) { fan_speed = fan->fan_min_speed; } temp_change = new_temp - old_temp; 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) ); } 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) ); } if(verbose) { printf("Old Temp %d: New Temp: %d, Fan Speed: %d\n", old_temp, new_temp, fan_speed); if(daemonize) { syslog(LOG_INFO, "Old Temp %d: New Temp: %d, Fan Speed: %d", old_temp, new_temp, fan_speed); } } set_fan_speed(fan, fan_speed); fan = fan->next; } if(verbose) { printf("Sleeping for %d seconds\n", polling_interval); fflush(stdout); if(daemonize) { syslog(LOG_INFO, "Sleeping for %d seconds", polling_interval); } } // 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); } }