4 breezed is a fan speed control daemon for Linux computers.
6 Copyright (c) 2008, 2009, 2010 Francois Fleuret
7 Written by Francois Fleuret <francois@fleuret.org>
9 This file is part of breezed.
11 breezed is free software: you can redistribute it and/or modify it
12 under the terms of the GNU General Public License version 3 as
13 published by the Free Software Foundation.
15 breezed is distributed in the hope that it will be useful, but
16 WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with breezed. If not, see <http://www.gnu.org/licenses/>.
32 const int major_version_number = 1;
33 const int minor_version_number = 4;
35 const int buffer_size = 1024;
37 const char *default_configuration_file = "/etc/breezed.conf";
39 /* The time period to check the temperature. */
40 const int polling_delay = 5;
42 /* Minimum time between last change and a change down. */
43 const int minimum_delay_to_go_down = 30;
45 /* Gap between a threshold to go up and a threshold to go down to
46 reduce the oscillations. */
47 const int down_temperature_delta = 2;
52 int nb_rounds_since_last_change = 0;
57 int nb_temperature_thresholds;
58 int *temperature_thresholds = 0;
59 char **speed_names = 0;
61 int nb_file_thermal = 0;
62 char **file_thermal = 0;
63 int *file_thermal_fd = 0;
65 char *configuration_file;
67 /********************************************************************/
69 /* malloc with error checking. */
71 void *safe_malloc(size_t n) {
74 fprintf(stderr, "Can not allocate memory: %s\n", strerror(errno));
80 /******************************************************************/
82 char *next_word(char *buffer, char *r, int buffer_size) {
87 while((*r == ' ') || (*r == '\t') || (*r == ',')) r++;
90 while((*r != '"') && (*r != '\0') &&
91 (s<buffer+buffer_size-1))
95 while((*r != '\r') && (*r != '\n') && (*r != '\0') &&
96 (*r != '\t') && (*r != ' ') && (*r != ',')) {
97 if(s == buffer + buffer_size) {
98 fprintf(stderr, "Buffer overflow in next_word.\n");
105 while((*r == ' ') || (*r == '\t') || (*r == ',')) r++;
106 if((*r == '\0') || (*r=='\r') || (*r=='\n')) r = 0;
113 void set_fan_level(int fan_fd, int f, int max_fan_level) {
114 char buffer[buffer_size];
115 if(f < 0 || f > max_fan_level) f = max_fan_level;
116 sprintf(buffer, "level %s\n", speed_names[f]);
117 if(write(fan_fd, buffer, strlen(buffer)) < 0) {
118 fprintf(stderr, "Error in setting the fan level (%s).\n",
124 void define_thermal_files(char *definition) {
125 char token[buffer_size];
128 fprintf(stderr, "Thermal files already defined.\n");
135 s = next_word(token, s, buffer_size);
139 file_thermal = safe_malloc(nb_file_thermal * sizeof(char *));
140 file_thermal_fd = safe_malloc(nb_file_thermal * sizeof(int));
144 s = next_word(token, s, buffer_size);
145 file_thermal[k] = strdup(token);
150 void define_temperature_thresholds(char *definition) {
151 char token[buffer_size];
153 if(temperature_thresholds) {
154 fprintf(stderr, "Temperature thresholds already defined.\n");
158 nb_temperature_thresholds = 0;
164 s = next_word(token, s, buffer_size);
165 nb_temperature_thresholds++;
168 temperature_thresholds =
169 safe_malloc(nb_temperature_thresholds * sizeof(int));
172 safe_malloc(nb_temperature_thresholds * sizeof(char *));
177 s = next_word(token, s, buffer_size);
179 while(*u && *u != ':') { u++; }
182 temperature_thresholds[k] = atoi(token);
184 speed_names[k] = strdup(u);
186 temperature_thresholds[k] = atoi(token);
187 snprintf(token, buffer_size, "%d", k);
188 speed_names[k] = strdup(token);
192 temperature_thresholds[k] < temperature_thresholds[k-1]) {
193 fprintf(stderr, "The temperature thresholds have to be increasing.\n");
199 if(nb_temperature_thresholds <= 0) {
200 fprintf(stderr, "There has to be at least one temperature.\n");
204 if(temperature_thresholds[0] > 0) {
205 fprintf(stderr, "The lowest temperature has to be 0.\n");
211 void evaluate_one_configuration_line(char *line, int line_number) {
212 char token[buffer_size];
215 s = next_word(token, line, buffer_size);
217 if(strcmp(token, "thermal_files") == 0) {
219 fprintf(stderr, "Missing parameter in %s:%d\n",
220 configuration_file, line_number);
223 define_thermal_files(s);
226 else if(strcmp(token, "debug") == 0) {
230 else if(strcmp(token, "fan_file") == 0) {
232 fprintf(stderr, "Fan file already defined.\n");
236 fprintf(stderr, "Missing parameter in %s:%d\n",
237 configuration_file, line_number);
240 file_fan = strdup(s);
243 else if(strcmp(token, "temperature_thresholds") == 0) {
245 fprintf(stderr, "Missing parameter in %s:%d\n",
246 configuration_file, line_number);
249 define_temperature_thresholds(s);
252 else if(token[0] && token[0] != '#') {
253 fprintf(stderr, "Unknown keyword '%s' in %s:%d.\n",
254 token, configuration_file, line_number);
259 /******************************************************************/
261 int main(int argc, char **argv) {
262 char buffer[buffer_size];
265 configuration_file = strdup(default_configuration_file);
270 if(strcmp(argv[i], "--debug") == 0 || strcmp(argv[i], "-d") == 0) {
275 else if(strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) {
276 printf("Breezed v%d.%d. Written by Francois Fleuret (francois@fleuret.org).\n",
277 major_version_number, minor_version_number);
281 else if(strcmp(argv[i], "--no-configuration-file") == 0 ||
282 strcmp(argv[i], "-ncf") == 0) {
283 free(configuration_file);
284 configuration_file = 0;
288 else if(strcmp(argv[i], "--configuration-file") == 0 ||
289 strcmp(argv[i], "-cf") == 0) {
292 fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
296 free(configuration_file);
297 configuration_file = strdup(argv[i]);
302 else if(strcmp(argv[i], "--thermal-files") == 0 ||
303 strcmp(argv[i], "-tf") == 0) {
306 fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
309 define_thermal_files(argv[i]);
313 else if(strcmp(argv[i], "--fan-file") == 0 ||
314 strcmp(argv[i], "-ff") == 0) {
317 fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
322 fprintf(stderr, "Fan file already defined.\n");
325 file_fan = strdup(argv[i]);
330 else if(strcmp(argv[i], "--temperature-thresholds") == 0 ||
331 strcmp(argv[i], "-tt") == 0) {
335 fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
339 define_temperature_thresholds(argv[i]);
343 else if(strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
345 printf("%s [--version|-v] [--help|-h] [--debug|-d]\\\n\
346 [--configuration-file|-cf <file>]\\\n\
347 [--no-configuration-file|-ncf]\\\n\
348 [--thermal-files|-tf <thermalfile1,thermalfile2,...>] \\\n\
349 [--fanfile|-ff <fan file>] \\\n\
350 [--temperature-thresholds|-tt <t1,t2,t3,t4,...>]\n\
352 --help|-h : shows this help\n\
353 --version|-v : shows the version number\n\
354 --debug|-d : prints out additional information\n\
355 --configuration-file|-cf sets the configuration file\n\
356 --no-configuration-file|-ncf do not load a configuration file\n\
357 --thermal-files|-tf : sets where to look for temperatures\n\
358 --fanfile|-ff : sets where to control the fan level\n\
359 --temperature-thresholds|-tt : sets the temperature thresholds\n\
361 This daemon polls the temperatures every 5s, takes the max and sets\n\
362 the fan level accordingly. It uses as temperatures all the numbers it\n\
363 finds in the provided \"thermal files\". Hence you can use as well\n\
364 /proc/acpi/thermal_zone/THM*/temperature or /proc/acpi/ibm/thermal.\n\
366 The fan speed is set by echoing into the provided fan file.\n\
368 To reduce oscillations, it will not reduce the fan speed less than 30s\n\
369 after the last previous change and the thresholds actually used to\n\
370 reduce the fan speed are two degrees lower than the provided\n\
371 thresholds, which are used to increase the fan speed.\n\
373 This daemon should be started through the adequate shell script in\n\
376 Options can be set either in the configuration file, or on the \n\
377 command line. Options can not be set twice.\n\
379 Version %d.%d, November 2009.\n\
381 Written by Francois Fleuret (francois@fleuret.org).\n",
383 major_version_number, minor_version_number);
389 fprintf(stderr, "Unknown argument %s.\n", argv[i]);
395 /******************************************************************/
397 if(configuration_file) {
398 char raw_line[buffer_size];
399 int start, end, eol, k;
402 file = fopen(configuration_file, "r");
405 fprintf(stderr, "Can not open `%s' for reading.\n", configuration_file);
416 while(end > start || !feof(file)) {
419 /* Look for the end of a line in what is already in the buffer */
420 while(eol < end && raw_line[eol] != '\n') eol++;
422 /* if we did not find the of a line, move what has not been
423 processed and is in the buffer to the beginning of the buffer,
424 fill the buffer with new data from the file, and look for the
427 for(k = 0; k < end - start; k++) {
428 raw_line[k] = raw_line[k + start];
433 end += fread(raw_line + end, sizeof(char), buffer_size - end, file);
434 while(eol < end && raw_line[eol] != '\n') eol++;
437 /* The end of the line is the buffer size, which means the line is
440 if(eol == buffer_size) {
441 raw_line[buffer_size - 1] = '\0';
442 fprintf(stderr, "Selector: Line too long (max is %d characters):\n",
444 fprintf(stderr, raw_line);
445 fprintf(stderr, "\n");
449 /* If we got a line, we replace the carriage return by a \0 to
452 raw_line[eol] = '\0';
454 /* here we process the line */
459 printf("%s:%d \"%s\"\n",
460 configuration_file, line_number, raw_line + start);
463 evaluate_one_configuration_line(raw_line + start, line_number);
470 /******************************************************************/
472 if(nb_temperature_thresholds == 0) {
473 fprintf(stderr, "No temperature threshold was provided.\n");
477 if(nb_file_thermal == 0) {
478 fprintf(stderr, "No thermal file was provided.\n");
483 fprintf(stderr, "No fan file was provided.\n");
487 for(i = 0; i < nb_file_thermal; i++) {
488 file_thermal_fd[i] = open(file_thermal[i], O_RDONLY);
489 if(file_thermal_fd[i] < 0) {
490 fprintf(stderr, "Can not open %s for reading (%s).\n",
491 file_thermal[i], strerror(errno));
496 file_fan_fd = open(file_fan, O_WRONLY);
498 if(file_fan_fd < 0) {
499 fprintf(stderr, "Can not open %s for writing (%s).\n",
500 file_fan, strerror(errno));
504 /******************************************************************/
507 for(t = 0; t < nb_file_thermal; t++) {
508 printf("file_thermal[%d] %s\n", t, file_thermal[t]);
511 printf("file_fan %s\n", file_fan);
513 for(t = 0; t < nb_temperature_thresholds; t++) {
514 printf("temperature_thresholds[%d] = %d speed_names[%d] = \"%s\"\n",
515 t, temperature_thresholds[t],
520 /******************************************************************/
524 int temperature = -1;
527 printf("Temperature:");
530 for(i = 0; i < nb_file_thermal; i++) {
531 lseek(file_thermal_fd[i], 0, SEEK_SET);
532 int size = read(file_thermal_fd[i], buffer, buffer_size);
534 if(size > 0 && size < buffer_size) {
540 while(*s && *s != '-' && (*s < '0') || (*s > '9')) s++;
549 while(*s >= '0' && *s <= '9') {
550 t = t * 10 + (*s - '0');
559 /* So that we can deal with the new files where the
560 temperature are in 1/1000th of C */
561 if(t > 1000) t /= 1000;
563 if(t > temperature) temperature = t;
568 if(i < nb_file_thermal - 1)
575 fprintf(stderr, "Nothing to read in %d.\n", file_thermal[i]);
576 } else if(size < 0) {
577 fprintf(stderr, "Error while reading %s (%s).\n",
578 file_thermal[i], strerror(errno));
580 fprintf(stderr, "Error while reading %s (too large).\n",
587 if(temperature < 0) {
588 fprintf(stderr, "Could not read a meaningful temperature.\n");
592 nb_rounds_since_last_change++;
594 int new_level = last_level;
596 int new_level_up = nb_temperature_thresholds - 1;
597 while(new_level_up > 0 &&
598 temperature < temperature_thresholds[new_level_up]) {
602 if(new_level_up > last_level) {
603 new_level = new_level_up;
605 int new_level_down = nb_temperature_thresholds - 1;
606 while(new_level_down > 0 &&
608 temperature_thresholds[new_level_down] - down_temperature_delta)
610 if(new_level_down < last_level &&
611 nb_rounds_since_last_change * polling_delay >=
612 minimum_delay_to_go_down) {
613 new_level = new_level_down;
617 /* We set it every time, even when there is no change, to handle
618 when the level has been modified somewhere else (for instance
619 when coming back from suspend). */
621 set_fan_level(file_fan_fd, new_level, nb_temperature_thresholds - 1);
623 if(new_level != last_level) {
624 nb_rounds_since_last_change = 0;
625 last_level = new_level;
627 printf("Temperature is %dC setting the fan level to %d (%s).\n",
630 speed_names[new_level]);
634 sleep(polling_delay);