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/>.
33 const int major_version_number = 1;
34 const int minor_version_number = 5;
36 #define BUFFER_SIZE 1024
38 const char *default_configuration_file = "/etc/breezed.conf";
40 /* The time period to check the temperature. */
41 const int polling_delay = 5;
43 /* Minimum time between last change and a change down. */
44 const int minimum_delay_to_go_down = 10;
46 /* Gap between a threshold to go up and a threshold to go down to
47 reduce the oscillations. */
48 const int down_temperature_delta = 2;
53 int nb_rounds_since_last_change = 0;
58 int nb_temperature_thresholds;
59 int *temperature_thresholds = 0;
60 char **speed_names = 0;
62 int nb_file_thermal = 0;
63 char **file_thermal = 0;
64 int *file_thermal_fd = 0;
66 char *configuration_file;
68 /********************************************************************/
70 /* malloc with error checking. */
72 void *safe_malloc(size_t n) {
75 fprintf(stderr, "Can not allocate memory: %s\n", strerror(errno));
81 /******************************************************************/
83 char *next_word(char *buffer, char *r, int buffer_size) {
88 while((*r == ' ') || (*r == '\t') || (*r == ',')) r++;
91 while((*r != '"') && (*r != '\0') &&
92 (s<buffer+buffer_size-1))
96 while((*r != '\r') && (*r != '\n') && (*r != '\0') &&
97 (*r != '\t') && (*r != ' ') && (*r != ',')) {
98 if(s == buffer + buffer_size) {
99 fprintf(stderr, "Buffer overflow in next_word.\n");
106 while((*r == ' ') || (*r == '\t') || (*r == ',')) r++;
107 if((*r == '\0') || (*r=='\r') || (*r=='\n')) r = 0;
114 void set_fan_level(int fan_fd, int f, int max_fan_level) {
115 char buffer[BUFFER_SIZE];
116 if(f < 0 || f > max_fan_level) f = max_fan_level;
117 sprintf(buffer, "level %s\n", speed_names[f]);
118 if(write(fan_fd, buffer, strlen(buffer)) < 0) {
119 fprintf(stderr, "Error in setting the fan level (%s).\n",
125 void define_thermal_files(char *definition) {
126 char token[BUFFER_SIZE];
131 fprintf(stderr, "Thermal files already defined.\n");
137 s = next_word(token, s, BUFFER_SIZE);
141 file_thermal = safe_malloc(nb_file_thermal * sizeof(char *));
142 file_thermal_fd = safe_malloc(nb_file_thermal * sizeof(int));
146 s = next_word(token, s, BUFFER_SIZE);
147 file_thermal[k] = strdup(token);
152 void define_temperature_thresholds(char *definition) {
153 char token[BUFFER_SIZE];
157 if(temperature_thresholds) {
158 fprintf(stderr, "Temperature thresholds already defined.\n");
162 nb_temperature_thresholds = 0;
166 s = next_word(token, s, BUFFER_SIZE);
167 nb_temperature_thresholds++;
170 temperature_thresholds =
171 safe_malloc(nb_temperature_thresholds * sizeof(int));
174 safe_malloc(nb_temperature_thresholds * sizeof(char *));
179 s = next_word(token, s, BUFFER_SIZE);
181 while(*u && *u != ':') { u++; }
184 temperature_thresholds[k] = atoi(token);
186 speed_names[k] = strdup(u);
188 temperature_thresholds[k] = atoi(token);
189 snprintf(token, BUFFER_SIZE, "%d", k);
190 speed_names[k] = strdup(token);
194 temperature_thresholds[k] < temperature_thresholds[k-1]) {
195 fprintf(stderr, "The temperature thresholds have to be increasing.\n");
201 if(nb_temperature_thresholds <= 0) {
202 fprintf(stderr, "There has to be at least one temperature.\n");
206 if(temperature_thresholds[0] > 0) {
207 fprintf(stderr, "The lowest temperature has to be 0.\n");
213 void evaluate_one_configuration_line(char *line, int line_number) {
214 char token[BUFFER_SIZE];
217 s = next_word(token, line, BUFFER_SIZE);
219 if(strcmp(token, "thermal_files") == 0) {
221 fprintf(stderr, "Missing parameter in %s:%d\n",
222 configuration_file, line_number);
225 define_thermal_files(s);
228 else if(strcmp(token, "debug") == 0) {
232 else if(strcmp(token, "fan_file") == 0) {
234 fprintf(stderr, "Fan file already defined.\n");
238 fprintf(stderr, "Missing parameter in %s:%d\n",
239 configuration_file, line_number);
242 file_fan = strdup(s);
245 else if(strcmp(token, "temperature_thresholds") == 0) {
247 fprintf(stderr, "Missing parameter in %s:%d\n",
248 configuration_file, line_number);
251 define_temperature_thresholds(s);
254 else if(token[0] && token[0] != '#') {
255 fprintf(stderr, "Unknown keyword '%s' in %s:%d.\n",
256 token, configuration_file, line_number);
261 /******************************************************************/
263 int main(int argc, char **argv) {
264 char buffer[BUFFER_SIZE];
267 configuration_file = strdup(default_configuration_file);
272 if(strcmp(argv[i], "--debug") == 0 || strcmp(argv[i], "-d") == 0) {
277 else if(strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) {
278 printf("Breezed v%d.%d. Written by Francois Fleuret (francois@fleuret.org).\n",
279 major_version_number, minor_version_number);
283 else if(strcmp(argv[i], "--no-configuration-file") == 0 ||
284 strcmp(argv[i], "-ncf") == 0) {
285 free(configuration_file);
286 configuration_file = 0;
290 else if(strcmp(argv[i], "--configuration-file") == 0 ||
291 strcmp(argv[i], "-cf") == 0) {
294 fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
298 free(configuration_file);
299 configuration_file = strdup(argv[i]);
304 else if(strcmp(argv[i], "--thermal-files") == 0 ||
305 strcmp(argv[i], "-tf") == 0) {
308 fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
311 define_thermal_files(argv[i]);
315 else if(strcmp(argv[i], "--fan-file") == 0 ||
316 strcmp(argv[i], "-ff") == 0) {
319 fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
324 fprintf(stderr, "Fan file already defined.\n");
327 file_fan = strdup(argv[i]);
332 else if(strcmp(argv[i], "--temperature-thresholds") == 0 ||
333 strcmp(argv[i], "-tt") == 0) {
337 fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
341 define_temperature_thresholds(argv[i]);
345 else if(strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
347 printf("%s [--version|-v] [--help|-h] [--debug|-d] \\\n\
348 [--configuration-file|-cf <file>] \\\n\
349 [--no-configuration-file|-ncf] \\\n\
350 [--thermal-files|-tf <thermalfile1,thermalfile2,...>] \\\n\
351 [--fanfile|-ff <fan file>] \\\n\
352 [--temperature-thresholds|-tt <t1,t2,t3,t4,...>]\n\
356 printf(" --help|-h shows this help\n\
357 --version|-v shows the version number\n\
358 --debug|-d prints out additional information\n\
359 --configuration-file|-cf sets the configuration file\n\
360 --no-configuration-file|-ncf do not load a configuration file\n\
361 --thermal-files|-tf sets where to look for temperatures\n\
362 --fanfile|-ff sets where to control the fan level\n\
363 --temperature-thresholds|-tt sets the temperature thresholds\n\n");
365 printf("This daemon polls the temperatures every 5s, takes the max and sets\n\
366 the fan level accordingly. It uses as temperatures all the numbers it\n\
367 finds in the provided \"thermal files\". Hence you can use as well\n\
368 /proc/acpi/thermal_zone/THM*/temperature or /proc/acpi/ibm/thermal.\n\n");
370 printf("The fan speed is set by echoing into the provided fan file.\n\
372 To reduce oscillations, it will not reduce the fan speed less than 30s\n\
373 after the last previous change and the thresholds actually used to\n\
374 reduce the fan speed are two degrees lower than the provided\n\
375 thresholds, which are used to increase the fan speed.\n\n");
377 printf("This daemon should be started through the adequate shell script in\n\
381 Options can be set either in the configuration file, or on the \n\
382 command line. Options can not be set twice.\n\
384 Version %d.%d, November 2015\n\
385 Written by Francois Fleuret (francois@fleuret.org).\n",
386 major_version_number, minor_version_number);
392 fprintf(stderr, "Unknown argument %s.\n", argv[i]);
398 /******************************************************************/
400 if(configuration_file) {
401 char raw_line[BUFFER_SIZE];
404 char *start = raw_line;
407 file = fopen(configuration_file, "r");
410 fprintf(stderr, "Can not open `%s' for reading.\n", configuration_file);
414 while(fgets(start, raw_line + BUFFER_SIZE - start, file)) {
417 /* Go to the end of the line */
418 for(s = start; s < raw_line + BUFFER_SIZE && *s; s++);
420 if(s == raw_line + BUFFER_SIZE - 1) {
421 fprintf(stderr, "Line too long at %s:%d.\n", configuration_file, line_number);
426 if(s > start && *(s-1) == '\n') { s--; *s = '\0'; }
428 /* Allows line continuation */
429 if(s == start || *(s-1) != '\\') {
430 /* printf("%d: \"%s\"\n", line_number, raw_line); */
431 evaluate_one_configuration_line(raw_line, line_number);
439 /******************************************************************/
441 if(nb_temperature_thresholds == 0) {
442 fprintf(stderr, "No temperature threshold was provided.\n");
446 if(nb_file_thermal == 0) {
447 fprintf(stderr, "No thermal file was provided.\n");
452 fprintf(stderr, "No fan file was provided.\n");
456 for(i = 0; i < nb_file_thermal; i++) {
457 file_thermal_fd[i] = open(file_thermal[i], O_RDONLY);
458 if(file_thermal_fd[i] < 0) {
459 fprintf(stderr, "Can not open %s for reading (%s).\n",
460 file_thermal[i], strerror(errno));
465 file_fan_fd = open(file_fan, O_WRONLY);
467 if(file_fan_fd < 0) {
468 fprintf(stderr, "Can not open %s for writing (%s).\n",
469 file_fan, strerror(errno));
473 /******************************************************************/
476 for(t = 0; t < nb_file_thermal; t++) {
477 printf("file_thermal[%d] %s\n", t, file_thermal[t]);
480 printf("file_fan %s\n", file_fan);
482 for(t = 0; t < nb_temperature_thresholds; t++) {
483 printf("temperature_thresholds[%d] = %d speed_names[%d] = \"%s\"\n",
484 t, temperature_thresholds[t],
489 /******************************************************************/
495 int size, temperature;
501 printf("Temperature:");
504 for(i = 0; i < nb_file_thermal; i++) {
505 lseek(file_thermal_fd[i], 0, SEEK_SET);
506 size = read(file_thermal_fd[i], buffer, BUFFER_SIZE);
508 if(size > 0 && size < BUFFER_SIZE) {
514 while(*s && (*s != '-') && ((*s < '0') || (*s > '9'))) s++;
523 while(*s >= '0' && *s <= '9') {
524 t = t * 10 + (*s - '0');
533 /* So that we can deal with the new files where the
534 temperature are in 1/1000th of C */
535 if(t > 1000) t /= 1000;
537 if(t > temperature) temperature = t;
542 if(i < nb_file_thermal - 1)
549 fprintf(stderr, "Nothing to read in %s.\n", file_thermal[i]);
550 } else if(size < 0) {
551 fprintf(stderr, "Error while reading %s (%s).\n",
552 file_thermal[i], strerror(errno));
554 fprintf(stderr, "Error while reading %s (too large).\n",
561 if(temperature < 0) {
562 fprintf(stderr, "Could not read a meaningful temperature.\n");
566 nb_rounds_since_last_change++;
568 new_level = last_level;
569 new_level_up = nb_temperature_thresholds - 1;
570 while(new_level_up > 0 &&
571 temperature < temperature_thresholds[new_level_up]) {
575 if(new_level_up > last_level) {
576 new_level = new_level_up;
578 int new_level_down = nb_temperature_thresholds - 1;
579 while(new_level_down > 0 &&
581 temperature_thresholds[new_level_down] - down_temperature_delta)
583 if(new_level_down < last_level &&
584 nb_rounds_since_last_change * polling_delay >=
585 minimum_delay_to_go_down) {
586 new_level = new_level_down;
590 /* We set it every time, even when there is no change, to handle
591 when the level has been modified somewhere else (for instance
592 when coming back from suspend). */
594 set_fan_level(file_fan_fd, new_level, nb_temperature_thresholds - 1);
596 if(new_level != last_level) {
597 nb_rounds_since_last_change = 0;
598 last_level = new_level;
600 printf("Temperature is %dC setting the fan level to %d (%s).\n",
603 speed_names[new_level]);
607 sleep(polling_delay);