14809acccb35dc6a89ec3551b67cb6ddeb478d25
[breezed.git] / breezed.cc
1
2 /*
3
4    breezed is a fan speed control daemon for Linux computers.
5
6    Copyright (c) 2008, 2009 Francois Fleuret
7    Written by Francois Fleuret <francois@fleuret.org>
8
9    This file is part of breezed.
10
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.
14
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.
19
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/>.
22
23 */
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <fcntl.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <string.h>
31
32 #define BUFFER_SIZE 4096
33
34 using namespace std;
35
36 const int major_version_number = 1;
37 const int minor_version_number = 2;
38
39 const int buffer_size = 1024;
40
41 const char *default_configuration_file = "/etc/breezed.conf";
42
43 // The time period to check the temperature.
44 const int polling_delay = 5;
45
46 // Minimum time between last change and a change down.
47 const int minimum_delay_to_go_down = 30;
48
49 // Gap between a threshold to go up and a threshold to go down to
50 // reduce the oscillations.
51 const int down_temperature_delta = 2;
52
53 char *file_fan = 0;
54
55 int debug = 0;
56 int nb_rounds_since_last_change = 0;
57 int last_level = -1;
58
59 int file_fan_fd;
60
61 int nb_temperature_thresholds;
62 int *temperature_thresholds = 0;
63
64 int nb_file_thermal = 0;
65 char **file_thermal = 0;
66 int *file_thermal_fd = 0;
67
68 char *configuration_file;
69
70 //////////////////////////////////////////////////////////////////////
71
72 char *next_word(char *buffer, char *r, int buffer_size) {
73   char *s;
74   s = buffer;
75
76   if(r != 0) {
77     while((*r == ' ') || (*r == '\t') || (*r == ',')) r++;
78     if(*r == '"') {
79       r++;
80       while((*r != '"') && (*r != '\0') &&
81             (s<buffer+buffer_size-1))
82         *s++ = *r++;
83       if(*r == '"') r++;
84     } else {
85       while((*r != '\r') && (*r != '\n') && (*r != '\0') &&
86             (*r != '\t') && (*r != ' ') && (*r != ',')) {
87         if(s == buffer + buffer_size) {
88           fprintf(stderr, "Buffer overflow in next_word.\n");
89           exit(1);
90         }
91         *s++ = *r++;
92       }
93     }
94
95     while((*r == ' ') || (*r == '\t') || (*r == ',')) r++;
96     if((*r == '\0') || (*r=='\r') || (*r=='\n')) r = 0;
97   }
98   *s = '\0';
99
100   return r;
101 }
102
103 void set_fan_level(int fan_fd, int f, int max_fan_level) {
104   char buffer[buffer_size];
105   if(f < 0 || f > max_fan_level) f = max_fan_level;
106   sprintf(buffer, "level %d\n", f);
107   if(write(fan_fd, buffer, strlen(buffer)) < 0) {
108     fprintf(stderr, "Error in setting the fan level (%s).\n",
109             strerror(errno));
110     exit(1);
111   }
112 }
113
114 void define_thermal_files(char *definition) {
115   char token[buffer_size];
116
117   if(file_thermal) {
118     fprintf(stderr, "Thermal files already defined.\n");
119     exit(1);
120   }
121
122   char *s;
123   s = definition;
124   while(s) {
125     s = next_word(token, s, buffer_size);
126     nb_file_thermal++;
127   }
128   file_thermal = new char *[nb_file_thermal];
129   file_thermal_fd = new int[nb_file_thermal];
130   s = definition;
131   int k = 0;
132   while(s) {
133     s = next_word(token, s, buffer_size);
134     file_thermal[k] = new char[strlen(token) + 1];
135     strcpy(file_thermal[k], token);
136     k++;
137   }
138 }
139
140 void define_temperature_thresholds(char *definition) {
141   char token[buffer_size];
142
143   if(temperature_thresholds) {
144     fprintf(stderr, "Temperature thresholds already defined.\n");
145     exit(1);
146   }
147
148   nb_temperature_thresholds = 1;
149
150   char *s;
151   s = definition;
152   while(s) {
153     s = next_word(token, s, buffer_size);
154     nb_temperature_thresholds++;
155   }
156
157   temperature_thresholds = new int[nb_temperature_thresholds];
158
159   temperature_thresholds[0] = -1;
160
161   s = definition;
162   int k = 1;
163   while(s) {
164     s = next_word(token, s, buffer_size);
165     temperature_thresholds[k] = atoi(token);
166     if(k > 0 &&
167        temperature_thresholds[k] < temperature_thresholds[k-1]) {
168       fprintf(stderr, "The temperature thresholds have to be increasing.\n");
169       exit(0);
170     }
171     k++;
172   }
173 }
174
175 //////////////////////////////////////////////////////////////////////
176
177 int main(int argc, char **argv) {
178
179   char buffer[buffer_size], token[buffer_size];
180
181   configuration_file = new char[strlen(default_configuration_file) + 1];
182   strcpy(configuration_file, default_configuration_file);
183
184   int i = 1;
185   while(i < argc) {
186
187     if(strcmp(argv[i], "--debug") == 0 || strcmp(argv[i], "-d") == 0) {
188       debug = 1;
189       i++;
190     }
191
192     else if(strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) {
193       printf("Breezed v%d.%d. Written by Francois Fleuret (francois@fleuret.org).\n",
194              major_version_number, minor_version_number);
195       exit(0);
196     }
197
198     else if(strcmp(argv[i], "--no-configuration-file") == 0 ||
199             strcmp(argv[i], "-ncf") == 0) {
200       delete[] configuration_file;
201       configuration_file = 0;
202       i++;
203     }
204
205     else if(strcmp(argv[i], "--configuration-file") == 0 ||
206             strcmp(argv[i], "-cf") == 0) {
207       i++;
208       if(i == argc) {
209         fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
210         exit(1);
211       }
212
213       delete[] configuration_file;
214       configuration_file = new char[strlen(argv[i]) + 1];
215       strcpy(configuration_file, argv[i]);
216
217       i++;
218     }
219
220     else if(strcmp(argv[i], "--thermal-files") == 0 ||
221             strcmp(argv[i], "-tf") == 0) {
222       i++;
223       if(i == argc) {
224         fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
225         exit(1);
226       }
227       define_thermal_files(argv[i]);
228       i++;
229     }
230
231     else if(strcmp(argv[i], "--fan-file") == 0 ||
232             strcmp(argv[i], "-ff") == 0) {
233       i++;
234       if(i == argc) {
235         fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
236         exit(1);
237       }
238
239       if(file_fan) {
240         fprintf(stderr, "Fan file already defined.\n");
241         exit(1);
242       }
243       file_fan = new char[strlen(argv[i]) + 1];
244       strcpy(file_fan, argv[i]);
245
246       i++;
247     }
248
249     else if(strcmp(argv[i], "--temperature-thresholds") == 0 ||
250             strcmp(argv[i], "-tt") == 0) {
251       i++;
252
253       if(i == argc) {
254         fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
255         exit(1);
256       }
257
258       define_temperature_thresholds(argv[i]);
259       i++;
260     }
261
262     else if(strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
263
264       printf("%s [--version|-v] [--help|-h] [--debug|-d]\\\n\
265   [--configuration-file|-cf <file>]\\\n\
266   [--no-configuration-file|-ncf]\\\n\
267   [--thermal-files|-tf <thermalfile1,thermalfile2,...>] \\\n\
268   [--fanfile|-ff <fan file>] \\\n\
269   [--temperature-thresholds|-tt <t1,t2,t3,t4,...>]\n\
270 \n\
271   --help|-h :                    shows this help\n\
272   --version|-v :                 shows the version number\n\
273   --debug|-d :                   prints out additional information\n\
274   --configuration-file|-cf       sets the configuration file\n\
275   --no-configuration-file|-ncf   do not load a configuration file\n\
276   --thermal-files|-tf :          sets where to look for temperatures\n\
277   --fanfile|-ff :                sets where to control the fan level\n\
278   --temperature-thresholds|-tt : sets the temperature thresholds\n\
279 \n\
280 This daemon polls the temperatures every 5s, takes the max and sets\n\
281 the fan level accordingly. It uses as temperatures all the numbers it\n\
282 finds in the provided \"thermal files\". Hence you can use as well\n\
283 /proc/acpi/thermal_zone/THM*/temperature or /proc/acpi/ibm/thermal.\n\
284 \n\
285 The fan speed is set by echoing into the provided fan file.\n\
286 \n\
287 To reduce oscillations, it will not reduce the fan speed less than 30s\n\
288 after the last previous change and the thresholds actually used to\n\
289 reduce the fan speed are two degrees lower than the provided\n\
290 thresholds, which are used to increase the fan speed.\n\
291 \n\
292 This daemon should be started through the adequate shell script in\n\
293 /etc/init.d.\n\
294 \n\
295 Options can be set either in the configuration file, or on the \n\
296 command line. Options can not be set twice.\n\
297 \n\
298 Version %d.%d, November 2009.\n\
299 \n\
300 Written by Francois Fleuret (francois@fleuret.org).\n",
301              argv[0],
302              major_version_number, minor_version_number);
303
304       exit(0);
305     }
306
307     else {
308       fprintf(stderr, "Unknown argument %s.\n", argv[i]);
309       exit(1);
310     }
311   }
312
313
314   //////////////////////////////////////////////////////////////////////
315
316   if(configuration_file) {
317     char raw_line[BUFFER_SIZE];
318     int start, end, eol, k;
319     FILE *file;
320
321     file = fopen(configuration_file, "r");
322
323     if(!file) {
324       fprintf(stderr, "Can not open `%s' for reading.\n", configuration_file);
325       exit(1);
326     }
327
328     start = 0;
329     end = 0;
330
331     char *s;
332
333     int line_number = 0;
334
335     while(end > start || !feof(file)) {
336       eol = start;
337
338       /* Look for the end of a line in what is already in the buffer */
339       while(eol < end && raw_line[eol] != '\n') eol++;
340
341       /* if we did not find the of a line, move what has not been
342          processed and is in the buffer to the beginning of the buffer,
343          fill the buffer with new data from the file, and look for the
344          end of a line */
345       if(eol == end) {
346         for(k = 0; k < end - start; k++) {
347           raw_line[k] = raw_line[k + start];
348         }
349         end -= start;
350         eol -= start;
351         start = 0;
352         end += fread(raw_line + end, sizeof(char), BUFFER_SIZE - end, file);
353         while(eol < end && raw_line[eol] != '\n') eol++;
354       }
355
356       /* The end of the line is the buffer size, which means the line is
357          too long */
358
359       if(eol == BUFFER_SIZE) {
360         raw_line[BUFFER_SIZE - 1] = '\0';
361         fprintf(stderr, "Selector: Line too long (max is %d characters):\n",
362                 BUFFER_SIZE);
363         fprintf(stderr, raw_line);
364         fprintf(stderr, "\n");
365         exit(1);
366       }
367
368       /* If we got a line, we replace the carriage return by a \0 to
369          finish the string */
370
371       raw_line[eol] = '\0';
372
373       /* here we process the line */
374
375       line_number++;
376
377       if(debug) {
378         printf("%s:%d \"%s\"\n",
379                configuration_file, line_number, raw_line + start);
380       }
381
382       s = next_word(token, raw_line + start, buffer_size);
383
384       if(strcmp(token, "thermal_files") == 0) {
385         if(s == 0) {
386           fprintf(stderr, "Missing parameter in %s:%d\n",
387                   configuration_file, line_number);
388           exit(1);
389         }
390         define_thermal_files(s);
391       }
392
393       else if(strcmp(token, "debug") == 0) {
394         debug = 1;
395       }
396
397       else if(strcmp(token, "fan_file") == 0) {
398         if(file_fan) {
399           fprintf(stderr, "Fan file already defined.\n");
400           exit(1);
401         }
402         if(s == 0) {
403           fprintf(stderr, "Missing parameter in %s:%d\n",
404                   configuration_file, line_number);
405           exit(1);
406         }
407         file_fan = new char[strlen(s) + 1];
408         strcpy(file_fan, s);
409       }
410
411       else if(strcmp(token, "temperature_thresholds") == 0) {
412         if(s == 0) {
413           fprintf(stderr, "Missing parameter in %s:%d\n",
414                   configuration_file, line_number);
415           exit(1);
416         }
417         define_temperature_thresholds(s);
418       }
419
420       else if(token[0] && token[0] != '#') {
421         fprintf(stderr, "Unknown keyword '%s' in %s:%d.\n",
422                 token, configuration_file, line_number);
423         exit(1);
424       }
425
426       start = eol + 1;
427     }
428
429   }
430
431   //////////////////////////////////////////////////////////////////////
432
433   if(nb_temperature_thresholds == 0) {
434     fprintf(stderr, "No temperature threshold was provided.\n");
435     exit(1);
436   }
437
438   if(nb_file_thermal == 0) {
439     fprintf(stderr, "No thermal file was provided.\n");
440     exit(1);
441   }
442
443   if(file_fan == 0){
444     fprintf(stderr, "No fan file was provided.\n");
445     exit(1);
446   }
447
448   for(int i = 0; i < nb_file_thermal; i++) {
449     file_thermal_fd[i] = open(file_thermal[i], O_RDONLY);
450     if(file_thermal_fd[i] < 0) {
451       fprintf(stderr, "Can not open %s for reading (%s).\n",
452               file_thermal[i], strerror(errno));
453       exit(1);
454     }
455   }
456
457   file_fan_fd = open(file_fan, O_WRONLY);
458
459   if(file_fan_fd < 0) {
460     fprintf(stderr, "Can not open %s for writing (%s).\n",
461             file_fan, strerror(errno));
462     exit(1);
463   }
464
465   //////////////////////////////////////////////////////////////////////
466
467   if(debug) {
468     for(int t = 0; t < nb_file_thermal; t++) {
469       printf("file_thermal[%d] %s\n", t, file_thermal[t]);
470     }
471
472     printf("file_fan %s\n", file_fan);
473
474     for(int t = 0; t < nb_temperature_thresholds; t++) {
475       printf("temperature_thresholds[%d] %d",
476              t, temperature_thresholds[t]);
477     }
478   }
479
480   //////////////////////////////////////////////////////////////////////
481
482   while(1) {
483
484     int temperature = -1;
485
486     if(debug) {
487       printf("Temperature:");
488     }
489
490     for(int i = 0; i < nb_file_thermal; i++) {
491       lseek(file_thermal_fd[i], 0, SEEK_SET);
492       int size = read(file_thermal_fd[i], buffer, buffer_size);
493
494       if(size > 0 && size < buffer_size) {
495         buffer[size] = '\0';
496
497         char *s = buffer;
498
499         while(*s) {
500           while(*s && *s != '-' && (*s < '0') || (*s > '9')) s++;
501
502           if(*s) {
503             int t = 0, sign = 1;
504             if(*s == '-') {
505               sign = -1;
506               s++;
507             }
508
509             while(*s >= '0' && *s <= '9') {
510               t = t * 10 + (*s - '0');
511               s++;
512             }
513
514             t *= sign;
515             if(debug) {
516               printf(" %d", t);
517             }
518
519             // So that we can deal with the new files where the
520             // temperature are in 1/1000th of C
521             if(t > 1000) t /= 1000;
522
523             if(t > temperature) temperature = t;
524           }
525         }
526
527         if(debug) {
528           if(i < nb_file_thermal - 1)
529             printf(" /");
530           else
531             printf("\n");
532         }
533       } else {
534         if(size == 0) {
535           fprintf(stderr, "Nothing to read in %d.\n", file_thermal[i]);
536         } else if(size < 0) {
537           fprintf(stderr, "Error while reading %s (%s).\n",
538                   file_thermal[i], strerror(errno));
539         } else {
540           fprintf(stderr, "Error while reading %s (too large).\n",
541                   file_thermal[i]);
542         }
543         exit(1);
544       }
545     }
546
547     if(temperature < 0) {
548       fprintf(stderr, "Could not read a meaningful temperature.\n");
549       exit(1);
550     }
551
552     nb_rounds_since_last_change++;
553
554     int new_level = last_level;
555
556     int new_level_up = nb_temperature_thresholds - 1;
557     while(new_level_up > 0 &&
558           temperature < temperature_thresholds[new_level_up]) {
559       new_level_up--;
560     }
561
562     if(new_level_up > last_level) {
563       new_level = new_level_up;
564     } else {
565       int new_level_down = nb_temperature_thresholds - 1;
566       while(new_level_down > 0 &&
567             temperature <
568             temperature_thresholds[new_level_down] - down_temperature_delta)
569         new_level_down--;
570       if(new_level_down < last_level &&
571          nb_rounds_since_last_change * polling_delay >=
572          minimum_delay_to_go_down) {
573         new_level = new_level_down;
574       }
575     }
576
577     // We set it every time, even when there is no change, to handle
578     // when the level has been modified somewhere else (for instance
579     // when combing back from suspend).
580
581     set_fan_level(file_fan_fd, new_level, nb_temperature_thresholds - 1);
582
583     if(new_level != last_level) {
584       nb_rounds_since_last_change = 0;
585       last_level = new_level;
586       if(debug) {
587         printf("Temperature is %dC setting the fan level to %d.",
588                temperature, new_level);
589       }
590     }
591
592     sleep(polling_delay);
593   }
594 }