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