--- /dev/null
+
+/*
+
+ breezed is a fan speed control daemon for Linux computers.
+
+ Copyright (c) 2008, 2009 Francois Fleuret
+ Written by Francois Fleuret <francois@fleuret.org>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+
+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<buffer+buffer_size-1))
+ *s++ = *r++;
+ if(*r == '"') r++;
+ } else {
+ while((*r != '\r') && (*r != '\n') && (*r != '\0') &&
+ (*r != '\t') && (*r != ' ') && (*r != ',')) {
+ if(s == buffer + buffer_size) {
+ fprintf(stderr, "Buffer overflow in next_word.\n");
+ exit(1);
+ }
+ *s++ = *r++;
+ }
+ }
+
+ while((*r == ' ') || (*r == '\t') || (*r == ',')) r++;
+ if((*r == '\0') || (*r=='\r') || (*r=='\n')) r = 0;
+ }
+ *s = '\0';
+
+ return r;
+}
+
+void set_fan_level(int fan_fd, int f, int max_fan_level) {
+ char buffer[buffer_size];
+ if(f < 0 || f > 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 <file>]\\\n\
+ [--no-configuration-file|-ncf]\\\n\
+ [--thermal-files|-tf <thermalfile1,thermalfile2,...>] \\\n\
+ [--fanfile|-ff <fan file>] \\\n\
+ [--temperature-thresholds|-tt <t1,t2,t3,t4,...>]\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);
+ }
+}