X-Git-Url: https://fleuret.org/cgi-bin/gitweb/gitweb.cgi?a=blobdiff_plain;f=breezed.c;fp=breezed.c;h=054179e05137684bba58494a82ceedae29271cc4;hb=5e61d0ea33e99a3b61d90fc3c8efe1c1b371a4c3;hp=0000000000000000000000000000000000000000;hpb=074a731c1d1e849790ae45fdf58492be6d54d016;p=breezed.git diff --git a/breezed.c b/breezed.c new file mode 100644 index 0000000..054179e --- /dev/null +++ b/breezed.c @@ -0,0 +1,594 @@ + +/* + + breezed is a fan speed control daemon for Linux computers. + + Copyright (c) 2008, 2009 Francois Fleuret + Written by Francois Fleuret + + This file is part of breezed. + + breezed is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License version 3 as + published by the Free Software Foundation. + + breezed is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with breezed. If not, see . + +*/ + +#include +#include +#include +#include +#include +#include + +const int major_version_number = 1; +const int minor_version_number = 2; + +const int buffer_size = 1024; + +const char *default_configuration_file = "/etc/breezed.conf"; + +/* The time period to check the temperature. */ +const int polling_delay = 5; + +/* Minimum time between last change and a change down. */ +const int minimum_delay_to_go_down = 30; + +/* Gap between a threshold to go up and a threshold to go down to + reduce the oscillations. */ +const int down_temperature_delta = 2; + +char *file_fan = 0; + +int debug = 0; +int nb_rounds_since_last_change = 0; +int last_level = -1; + +int file_fan_fd; + +int nb_temperature_thresholds; +int *temperature_thresholds = 0; + +int nb_file_thermal = 0; +char **file_thermal = 0; +int *file_thermal_fd = 0; + +char *configuration_file; + +/******************************************************************/ + +char *next_word(char *buffer, char *r, int buffer_size) { + char *s; + s = buffer; + + if(r != 0) { + while((*r == ' ') || (*r == '\t') || (*r == ',')) r++; + if(*r == '"') { + r++; + while((*r != '"') && (*r != '\0') && + (s max_fan_level) f = max_fan_level; + sprintf(buffer, "level %d\n", f); + if(write(fan_fd, buffer, strlen(buffer)) < 0) { + fprintf(stderr, "Error in setting the fan level (%s).\n", + strerror(errno)); + exit(1); + } +} + +void define_thermal_files(char *definition) { + char token[buffer_size]; + + if(file_thermal) { + fprintf(stderr, "Thermal files already defined.\n"); + exit(1); + } + + char *s; + s = definition; + while(s) { + s = next_word(token, s, buffer_size); + nb_file_thermal++; + } + + file_thermal = (char **) malloc(nb_file_thermal * sizeof(char *)); + file_thermal_fd = (int *) malloc(nb_file_thermal * sizeof(int)); + s = definition; + int k = 0; + while(s) { + s = next_word(token, s, buffer_size); + file_thermal[k] = strdup(token); + k++; + } +} + +void define_temperature_thresholds(char *definition) { + char token[buffer_size]; + + if(temperature_thresholds) { + fprintf(stderr, "Temperature thresholds already defined.\n"); + exit(1); + } + + nb_temperature_thresholds = 1; + + char *s; + s = definition; + while(s) { + s = next_word(token, s, buffer_size); + nb_temperature_thresholds++; + } + + temperature_thresholds = + (int *) malloc(nb_temperature_thresholds * sizeof(int)); + + temperature_thresholds[0] = -1; + + s = definition; + int k = 1; + while(s) { + s = next_word(token, s, buffer_size); + temperature_thresholds[k] = atoi(token); + if(k > 0 && + temperature_thresholds[k] < temperature_thresholds[k-1]) { + fprintf(stderr, "The temperature thresholds have to be increasing.\n"); + exit(0); + } + k++; + } +} + +void evaluate_one_configuration_line(char *line, int line_number) { + char token[buffer_size]; + char *s; + + s = next_word(token, line, buffer_size); + + if(strcmp(token, "thermal_files") == 0) { + if(s == 0) { + fprintf(stderr, "Missing parameter in %s:%d\n", + configuration_file, line_number); + exit(1); + } + define_thermal_files(s); + } + + else if(strcmp(token, "debug") == 0) { + debug = 1; + } + + else if(strcmp(token, "fan_file") == 0) { + if(file_fan) { + fprintf(stderr, "Fan file already defined.\n"); + exit(1); + } + if(s == 0) { + fprintf(stderr, "Missing parameter in %s:%d\n", + configuration_file, line_number); + exit(1); + } + file_fan = strdup(s); + } + + else if(strcmp(token, "temperature_thresholds") == 0) { + if(s == 0) { + fprintf(stderr, "Missing parameter in %s:%d\n", + configuration_file, line_number); + exit(1); + } + define_temperature_thresholds(s); + } + + else if(token[0] && token[0] != '#') { + fprintf(stderr, "Unknown keyword '%s' in %s:%d.\n", + token, configuration_file, line_number); + exit(1); + } +} + +/******************************************************************/ + +int main(int argc, char **argv) { + char buffer[buffer_size]; + int i, t; + + configuration_file = strdup(default_configuration_file); + + i = 1; + while(i < argc) { + + if(strcmp(argv[i], "--debug") == 0 || strcmp(argv[i], "-d") == 0) { + debug = 1; + i++; + } + + else if(strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) { + printf("Breezed v%d.%d. Written by Francois Fleuret (francois@fleuret.org).\n", + major_version_number, minor_version_number); + exit(0); + } + + else if(strcmp(argv[i], "--no-configuration-file") == 0 || + strcmp(argv[i], "-ncf") == 0) { + free(configuration_file); + configuration_file = 0; + i++; + } + + else if(strcmp(argv[i], "--configuration-file") == 0 || + strcmp(argv[i], "-cf") == 0) { + i++; + if(i == argc) { + fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]); + exit(1); + } + + free(configuration_file); + configuration_file = strdup(argv[i]); + + i++; + } + + else if(strcmp(argv[i], "--thermal-files") == 0 || + strcmp(argv[i], "-tf") == 0) { + i++; + if(i == argc) { + fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]); + exit(1); + } + define_thermal_files(argv[i]); + i++; + } + + else if(strcmp(argv[i], "--fan-file") == 0 || + strcmp(argv[i], "-ff") == 0) { + i++; + if(i == argc) { + fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]); + exit(1); + } + + if(file_fan) { + fprintf(stderr, "Fan file already defined.\n"); + exit(1); + } + file_fan = strdup(argv[i]); + + i++; + } + + else if(strcmp(argv[i], "--temperature-thresholds") == 0 || + strcmp(argv[i], "-tt") == 0) { + i++; + + if(i == argc) { + fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]); + exit(1); + } + + define_temperature_thresholds(argv[i]); + i++; + } + + else if(strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + + printf("%s [--version|-v] [--help|-h] [--debug|-d]\\\n\ + [--configuration-file|-cf ]\\\n\ + [--no-configuration-file|-ncf]\\\n\ + [--thermal-files|-tf ] \\\n\ + [--fanfile|-ff ] \\\n\ + [--temperature-thresholds|-tt ]\n\ +\n\ + --help|-h : shows this help\n\ + --version|-v : shows the version number\n\ + --debug|-d : prints out additional information\n\ + --configuration-file|-cf sets the configuration file\n\ + --no-configuration-file|-ncf do not load a configuration file\n\ + --thermal-files|-tf : sets where to look for temperatures\n\ + --fanfile|-ff : sets where to control the fan level\n\ + --temperature-thresholds|-tt : sets the temperature thresholds\n\ +\n\ +This daemon polls the temperatures every 5s, takes the max and sets\n\ +the fan level accordingly. It uses as temperatures all the numbers it\n\ +finds in the provided \"thermal files\". Hence you can use as well\n\ +/proc/acpi/thermal_zone/THM*/temperature or /proc/acpi/ibm/thermal.\n\ +\n\ +The fan speed is set by echoing into the provided fan file.\n\ +\n\ +To reduce oscillations, it will not reduce the fan speed less than 30s\n\ +after the last previous change and the thresholds actually used to\n\ +reduce the fan speed are two degrees lower than the provided\n\ +thresholds, which are used to increase the fan speed.\n\ +\n\ +This daemon should be started through the adequate shell script in\n\ +/etc/init.d.\n\ +\n\ +Options can be set either in the configuration file, or on the \n\ +command line. Options can not be set twice.\n\ +\n\ +Version %d.%d, November 2009.\n\ +\n\ +Written by Francois Fleuret (francois@fleuret.org).\n", + argv[0], + major_version_number, minor_version_number); + + exit(0); + } + + else { + fprintf(stderr, "Unknown argument %s.\n", argv[i]); + exit(1); + } + } + + + /******************************************************************/ + + if(configuration_file) { + char raw_line[buffer_size]; + int start, end, eol, k; + FILE *file; + + file = fopen(configuration_file, "r"); + + if(!file) { + fprintf(stderr, "Can not open `%s' for reading.\n", configuration_file); + exit(1); + } + + start = 0; + end = 0; + + char *s; + + int line_number = 0; + + while(end > start || !feof(file)) { + eol = start; + + /* Look for the end of a line in what is already in the buffer */ + while(eol < end && raw_line[eol] != '\n') eol++; + + /* if we did not find the of a line, move what has not been + processed and is in the buffer to the beginning of the buffer, + fill the buffer with new data from the file, and look for the + end of a line */ + if(eol == end) { + for(k = 0; k < end - start; k++) { + raw_line[k] = raw_line[k + start]; + } + end -= start; + eol -= start; + start = 0; + end += fread(raw_line + end, sizeof(char), buffer_size - end, file); + while(eol < end && raw_line[eol] != '\n') eol++; + } + + /* The end of the line is the buffer size, which means the line is + too long */ + + if(eol == buffer_size) { + raw_line[buffer_size - 1] = '\0'; + fprintf(stderr, "Selector: Line too long (max is %d characters):\n", + buffer_size); + fprintf(stderr, raw_line); + fprintf(stderr, "\n"); + exit(1); + } + + /* If we got a line, we replace the carriage return by a \0 to + finish the string */ + + raw_line[eol] = '\0'; + + /* here we process the line */ + + line_number++; + + if(debug) { + printf("%s:%d \"%s\"\n", + configuration_file, line_number, raw_line + start); + } + + evaluate_one_configuration_line(raw_line + start, line_number); + + start = eol + 1; + } + + } + + /******************************************************************/ + + if(nb_temperature_thresholds == 0) { + fprintf(stderr, "No temperature threshold was provided.\n"); + exit(1); + } + + if(nb_file_thermal == 0) { + fprintf(stderr, "No thermal file was provided.\n"); + exit(1); + } + + if(file_fan == 0){ + fprintf(stderr, "No fan file was provided.\n"); + exit(1); + } + + for(i = 0; i < nb_file_thermal; i++) { + file_thermal_fd[i] = open(file_thermal[i], O_RDONLY); + if(file_thermal_fd[i] < 0) { + fprintf(stderr, "Can not open %s for reading (%s).\n", + file_thermal[i], strerror(errno)); + exit(1); + } + } + + file_fan_fd = open(file_fan, O_WRONLY); + + if(file_fan_fd < 0) { + fprintf(stderr, "Can not open %s for writing (%s).\n", + file_fan, strerror(errno)); + exit(1); + } + + /******************************************************************/ + + if(debug) { + for(t = 0; t < nb_file_thermal; t++) { + printf("file_thermal[%d] %s\n", t, file_thermal[t]); + } + + printf("file_fan %s\n", file_fan); + + for(t = 0; t < nb_temperature_thresholds; t++) { + printf("temperature_thresholds[%d] %d", + t, temperature_thresholds[t]); + } + } + + /******************************************************************/ + + while(1) { + + int temperature = -1; + + if(debug) { + printf("Temperature:"); + } + + for(i = 0; i < nb_file_thermal; i++) { + lseek(file_thermal_fd[i], 0, SEEK_SET); + int size = read(file_thermal_fd[i], buffer, buffer_size); + + if(size > 0 && size < buffer_size) { + buffer[size] = '\0'; + + char *s = buffer; + + while(*s) { + while(*s && *s != '-' && (*s < '0') || (*s > '9')) s++; + + if(*s) { + int t = 0, sign = 1; + if(*s == '-') { + sign = -1; + s++; + } + + while(*s >= '0' && *s <= '9') { + t = t * 10 + (*s - '0'); + s++; + } + + t *= sign; + if(debug) { + printf(" %d", t); + } + + /* So that we can deal with the new files where the + temperature are in 1/1000th of C */ + if(t > 1000) t /= 1000; + + if(t > temperature) temperature = t; + } + } + + if(debug) { + if(i < nb_file_thermal - 1) + printf(" /"); + else + printf("\n"); + } + } else { + if(size == 0) { + fprintf(stderr, "Nothing to read in %d.\n", file_thermal[i]); + } else if(size < 0) { + fprintf(stderr, "Error while reading %s (%s).\n", + file_thermal[i], strerror(errno)); + } else { + fprintf(stderr, "Error while reading %s (too large).\n", + file_thermal[i]); + } + exit(1); + } + } + + if(temperature < 0) { + fprintf(stderr, "Could not read a meaningful temperature.\n"); + exit(1); + } + + nb_rounds_since_last_change++; + + int new_level = last_level; + + int new_level_up = nb_temperature_thresholds - 1; + while(new_level_up > 0 && + temperature < temperature_thresholds[new_level_up]) { + new_level_up--; + } + + if(new_level_up > last_level) { + new_level = new_level_up; + } else { + int new_level_down = nb_temperature_thresholds - 1; + while(new_level_down > 0 && + temperature < + temperature_thresholds[new_level_down] - down_temperature_delta) + new_level_down--; + if(new_level_down < last_level && + nb_rounds_since_last_change * polling_delay >= + minimum_delay_to_go_down) { + new_level = new_level_down; + } + } + + /* We set it every time, even when there is no change, to handle + when the level has been modified somewhere else (for instance + when combing back from suspend). */ + + set_fan_level(file_fan_fd, new_level, nb_temperature_thresholds - 1); + + if(new_level != last_level) { + nb_rounds_since_last_change = 0; + last_level = new_level; + if(debug) { + printf("Temperature is %dC setting the fan level to %d.", + temperature, new_level); + } + } + + sleep(polling_delay); + } +}