Initial commit
[breezed.git] / breezed.cc
1
2 /*
3
4    breezed is a fan speed control daemon for Linux computers.
5
6    Copyright (c) 2008 Francois Fleuret
7    Written by Francois Fleuret <francois.fleuret@idiap.ch>
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 = 1;
41
42 const int buffer_size = 1024;
43
44 const char *default_confguration_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 = 0;
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   char *s;
152   s = definition;
153   while(s) {
154     s = next_word(token, s, buffer_size);
155     nb_temperature_thresholds++;
156   }
157
158   temperature_thresholds = new int[nb_temperature_thresholds];
159
160   s = definition;
161   int k = 0;
162   while(s) {
163     s = next_word(token, s, buffer_size);
164     temperature_thresholds[k] = atoi(token);
165     if(k > 0 &&
166        temperature_thresholds[k] < temperature_thresholds[k-1]) {
167       cerr << "The temperature thresholds have to be increasing."
168            << endl;
169       exit(0);
170     }
171     k++;
172   }
173
174   temperature_thresholds[0] = -1;
175 }
176
177 //////////////////////////////////////////////////////////////////////
178
179 int main(int argc, char **argv) {
180
181   char buffer[buffer_size], token[buffer_size];
182
183   configuration_file = new char[strlen(default_confguration_file) + 1];
184   strcpy(configuration_file, default_confguration_file);
185
186   int i = 1;
187   while(i < argc) {
188
189     if(strcmp(argv[i], "--debug") == 0 || strcmp(argv[i], "-d") == 0) {
190       debug = 1;
191       i++;
192     }
193
194     else if(strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) {
195       cout << "Breezed, "
196            << "v" << major_version_number
197            << "." << minor_version_number
198            << ". "
199            << "Written by Francois Fleuret (francois@fleuret.org).\n";
200       exit(0);
201     }
202
203     else if(strcmp(argv[i], "--no-configuration-file") == 0 ||
204             strcmp(argv[i], "-ncf") == 0) {
205       delete[] configuration_file;
206       configuration_file = 0;
207       i++;
208     }
209
210     else if(strcmp(argv[i], "--configuration-file") == 0 ||
211             strcmp(argv[i], "-cf") == 0) {
212       i++;
213       if(i == argc) {
214         cerr << "Missing parameter for " << argv[i - 1] << endl;
215         exit(1);
216       }
217
218       delete[] configuration_file;
219       configuration_file = new char[strlen(argv[i]) + 1];
220       strcpy(configuration_file, argv[i]);
221
222       i++;
223     }
224
225     else if(strcmp(argv[i], "--thermal-files") == 0 ||
226             strcmp(argv[i], "-tf") == 0) {
227       i++;
228       if(i == argc) {
229         cerr << "Missing parameter for " << argv[i - 1] << endl;
230         exit(1);
231       }
232       define_thermal_files(argv[i]);
233       i++;
234     }
235
236     else if(strcmp(argv[i], "--fan-file") == 0 ||
237             strcmp(argv[i], "-ff") == 0) {
238       i++;
239       if(i == argc) {
240         cerr << "Missing parameter for " << argv[i - 1] << endl;
241         exit(1);
242       }
243
244       if(file_fan) {
245         cerr << "Fan file already defined." << endl;
246         exit(1);
247       }
248       file_fan = new char[strlen(argv[i]) + 1];
249       strcpy(file_fan, argv[i]);
250
251       i++;
252     }
253
254     else if(strcmp(argv[i], "--temperature-thresholds") == 0 ||
255             strcmp(argv[i], "-tt") == 0) {
256       i++;
257
258       if(i == argc) {
259         cerr << "Missing parameter for " << argv[i - 1] << endl;
260         exit(1);
261       }
262
263       define_temperature_thresholds(argv[i]);
264       i++;
265     }
266
267     else if(strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
268
269       cout << argv[0] << " [--version|-v] [--help|-h] [--debug|-d]\\\n\
270   [--configuration-file|-cf <file>]\\\n\
271   [--no-configuration-file|-ncf]\\\n\
272   [--thermal-files|-tf <thermalfile1,thermalfile2,...>] \\\n\
273   [--fanfile|-ff <fan file>] \\\n\
274   [--temperature-thresholds|-tt <t1,t2,t3,t4,...>]\n\
275 \n\
276   --help|-h :                    shows this help\n\
277   --version|-v :                 shows the version number\n\
278   --debug|-d :                   prints out additional information\n\
279   --configuration-file|-cf       sets the configuration file\n\
280   --no-configuration-file|-ncf   do not load a configuration file\n\
281   --thermal-files|-tf :          sets where to look for temperatures\n\
282   --fanfile|-ff :                sets where to control the fan level\n\
283   --temperature-thresholds|-tt : sets the temperature thresholds\n\
284 \n\
285 This daemon polls the temperatures every 5s, takes the max and sets\n\
286 the fan level accordingly. It uses as temperatures all the numbers it\n\
287 finds in the provided \"thermal files\". Hence you can use as well\n\
288 /proc/acpi/thermal_zone/THM*/temperature or /proc/acpi/ibm/thermal.\n\
289 \n\
290 The fan speed is set by echoing into the provided fan file.\n\
291 \n\
292 To reduce oscillations, it will not reduce the fan speed less than 30s\n\
293 after the last previous change and the thresholds actually used to\n\
294 reduce the fan speed are two degrees lower than the provided\n\
295 thresholds, which are used to increase the fan speed.\n\
296 \n\
297 This daemon should be started through the adequate shell script in\n\
298 /etc/init.d.\n\
299 \n\
300 Options can be set either in the configuration file, or on the \n\
301 command line. Options can not be set twice.\n\
302 \n\
303 Version " << major_version_number << "."
304            << minor_version_number << ", December 2008.\n\
305 \n\
306 Written by Francois Fleuret (francois@fleuret.org).\n";
307
308       exit(0);
309     }
310
311     else {
312       cerr << "Unknown argument " << argv[i] << "." << endl;
313       exit(1);
314     }
315   }
316
317
318   //////////////////////////////////////////////////////////////////////
319
320   if(configuration_file) {
321     ifstream cf(configuration_file);
322     if(cf.fail()) {
323       cerr << "Can not open " << configuration_file << " for reading." << endl;
324       exit(1);
325     }
326
327     char *s;
328
329     int line_number = 0;
330
331     while(!cf.eof()) {
332       cf.getline(buffer, buffer_size);
333       line_number++;
334
335       s = next_word(token, buffer, buffer_size);
336
337       if(strcmp(token, "thermal_files") == 0) {
338         if(s == 0) {
339           cerr << "Missing parameter in "
340                << configuration_file << ":" << line_number << endl;
341           exit(1);
342         }
343         define_thermal_files(s);
344       }
345
346       else if(strcmp(token, "debug") == 0) {
347         debug = 1;
348       }
349
350       else if(strcmp(token, "fan_file") == 0) {
351         if(file_fan) {
352           cerr << "Fan file already defined." << endl;
353           exit(1);
354         }
355         if(s == 0) {
356           cerr << "Missing parameter in "
357                << configuration_file << ":" << line_number << endl;
358           exit(1);
359         }
360         file_fan = new char[strlen(s) + 1];
361         strcpy(file_fan, s);
362       }
363
364       else if(strcmp(token, "temperature_thresholds") == 0) {
365         if(s == 0) {
366           cerr << "Missing parameter in "
367                << configuration_file << ":" << line_number << endl;
368           exit(1);
369         }
370         define_temperature_thresholds(s);
371       }
372
373       else if(token[0] && token[0] != '#') {
374         cerr << "Unknown keyword '" << token << "' in "
375              << configuration_file << ":" << line_number << endl;
376         exit(1);
377       }
378     }
379   }
380
381   //////////////////////////////////////////////////////////////////////
382
383   if(nb_temperature_thresholds == 0) {
384     cerr << "No temperature threshold was provided." << endl;
385     exit(1);
386   }
387
388   if(nb_file_thermal == 0) {
389     cerr << "No thermal file was provided." << endl;
390     exit(1);
391   }
392
393   if(file_fan == 0){
394     cerr << "No fan file was provided." << endl;
395     exit(1);
396   }
397
398   for(int i = 0; i < nb_file_thermal; i++) {
399     file_thermal_fd[i] = open(file_thermal[i], O_RDONLY);
400     if(file_thermal_fd[i] < 0) {
401       cerr << "Can not open " << file_thermal[i]
402            << " for reading (" << strerror(errno) << ")."
403            << endl;
404       exit(1);
405     }
406   }
407
408   file_fan_fd = open(file_fan, O_WRONLY);
409
410   if(file_fan_fd < 0) {
411     cerr << "Can not open " << file_fan
412          << " for writing (" << strerror(errno) << ")."
413          << endl;
414     exit(1);
415   }
416
417   //////////////////////////////////////////////////////////////////////
418
419   if(debug) {
420     for(int t = 0; t < nb_file_thermal; t++) {
421       cout << "file_thermal[" << t << "] " << file_thermal[t] << endl;
422     }
423
424     cout << "file_fan " << file_fan << endl;
425
426     for(int t = 0; t < nb_temperature_thresholds; t++) {
427       cout << "temperature_thresholds[" << t << "] "
428            << temperature_thresholds[t] << endl;
429     }
430   }
431
432   //////////////////////////////////////////////////////////////////////
433
434   while(1) {
435
436     int temperature = -1;
437
438     if(debug) {
439       cout << "Temperature:";
440     }
441
442     for(int i = 0; i < nb_file_thermal; i++) {
443       lseek(file_thermal_fd[i], 0, SEEK_SET);
444       int size = read(file_thermal_fd[i], buffer, buffer_size);
445
446       if(size > 0 && size < buffer_size) {
447         buffer[size] = '\0';
448
449         char *s = buffer;
450
451         while(*s) {
452           while(*s && *s != '-' && (*s < '0') || (*s > '9')) s++;
453
454           if(*s) {
455             int t = 0, sign = 1;
456             if(*s == '-') {
457               sign = -1;
458               s++;
459             }
460
461             while(*s >= '0' && *s <= '9') {
462               t = t * 10 + (*s - '0');
463               s++;
464             }
465
466             t *= sign;
467             if(debug) {
468               cout << " " << t;
469             }
470
471             if(t > temperature) temperature = t;
472           }
473         }
474
475         if(debug) {
476           if(i < nb_file_thermal - 1)
477             cout << " /";
478           else
479             cout << endl;
480         }
481       } else {
482         if(size == 0) {
483           cerr << "Nothing to read in " << file_thermal[i] << endl;
484         } else if(size < 0) {
485           cerr << "Error while reading " << file_thermal[i]
486                << " (" << strerror(errno) << ")." << endl;
487         } else {
488           cerr << "Error while reading " << file_thermal[i]
489                << " (too large)." << endl;
490         }
491         exit(1);
492       }
493     }
494
495     if(temperature < 0) {
496       cerr << "Could not read a meaningful temperature." << endl;
497       exit(1);
498     }
499
500     nb_rounds_since_last_change++;
501
502     int new_level = last_level;
503
504     int new_level_up = nb_temperature_thresholds - 1;
505     while(new_level_up > 0 &&
506           temperature < temperature_thresholds[new_level_up]) {
507       new_level_up--;
508     }
509
510     if(new_level_up > last_level) {
511       new_level = new_level_up;
512     } else {
513       int new_level_down = nb_temperature_thresholds - 1;
514       while(new_level_down > 0 &&
515             temperature <
516             temperature_thresholds[new_level_down] - down_temperature_delta)
517         new_level_down--;
518       if(new_level_down < last_level &&
519          nb_rounds_since_last_change * polling_delay >=
520          minimum_delay_to_go_down) {
521         new_level = new_level_down;
522       }
523     }
524
525     // We set it every time, even when there is no change, to handle
526     // when the level has been modified somewhere else (for instance
527     // when combing back from suspend).
528
529     set_fan_level(file_fan_fd, new_level, nb_temperature_thresholds - 1);
530
531     if(new_level != last_level) {
532       nb_rounds_since_last_change = 0;
533       last_level = new_level;
534       if(debug) {
535         cout << "Temperature is " << temperature << "C,"
536              << " setting the fan level to " << new_level
537              << endl;
538       }
539     }
540
541     sleep(polling_delay);
542   }
543 }