| 1 | #!/usr/bin/perl -wT |
|---|
| 2 | # $Id$ |
|---|
| 3 | # |
|---|
| 4 | # Perl script for temperature dependent fan speed control. |
|---|
| 5 | # |
|---|
| 6 | # Copyright 2004 dean takemori <deant@hawaii.rr.com> |
|---|
| 7 | # |
|---|
| 8 | # This is a reimplementation in perl of Marius Reiner's bash script for |
|---|
| 9 | # fan speed control. It has advantages in that it can daemonize itself |
|---|
| 10 | # and needn't spawn subprocesses for grep, sleep etc. Much of the structure |
|---|
| 11 | # of the bash script is preserved to make mirroring changes easier, so |
|---|
| 12 | # this is seriously non-idiomatic perl but at the same time it should not |
|---|
| 13 | # be considered a a direct bash to perl translation. |
|---|
| 14 | # |
|---|
| 15 | # Usage: fancontrol [CONFIGFILE] |
|---|
| 16 | # |
|---|
| 17 | # For configuration instructions and warnings please see fancontrol.txt, |
|---|
| 18 | # which can be found in the doc/ directory or at the website mentioned |
|---|
| 19 | # elsewhere. |
|---|
| 20 | # |
|---|
| 21 | # This script is derived from Marius Reiner's bash version, so it is |
|---|
| 22 | # hereby placed under the GPL. |
|---|
| 23 | # |
|---|
| 24 | # Copyright 2003 Marius Reiner <marius.reiner@hdev.de> |
|---|
| 25 | # |
|---|
| 26 | # This program is free software; you can redistribute it and/or modify |
|---|
| 27 | # it under the terms of the GNU General Public License as published by |
|---|
| 28 | # the Free Software Foundation; either version 2 of the License, or |
|---|
| 29 | # (at your option) any later version. |
|---|
| 30 | # |
|---|
| 31 | # This program is distributed in the hope that it will be useful, |
|---|
| 32 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 33 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 34 | # GNU General Public License for more details. |
|---|
| 35 | # |
|---|
| 36 | # You should have received a copy of the GNU General Public License |
|---|
| 37 | # along with this program; if not, write to the Free Software |
|---|
| 38 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
|---|
| 39 | # |
|---|
| 40 | |
|---|
| 41 | use warnings; |
|---|
| 42 | use strict; |
|---|
| 43 | use IO::Handle; |
|---|
| 44 | use Getopt::Std; |
|---|
| 45 | use POSIX; |
|---|
| 46 | |
|---|
| 47 | $ENV{PATH} = "/bin:/usr/bin"; |
|---|
| 48 | |
|---|
| 49 | ##### Configuration ##### |
|---|
| 50 | use constant DEBUG => 0; |
|---|
| 51 | use constant MAX => 255; |
|---|
| 52 | |
|---|
| 53 | use constant PIDFILE => '/var/run/fancontrol.pid'; |
|---|
| 54 | use constant CONFFILE => '/etc/fancontrol'; |
|---|
| 55 | use constant LOGFILE => '/var/log/fancontrol/fancontrol.log'; |
|---|
| 56 | use constant ERRFILE => '/var/log/fancontrol/fancontrol.err'; |
|---|
| 57 | ### End Configuration ### |
|---|
| 58 | |
|---|
| 59 | our $interval; |
|---|
| 60 | our $pwmo; |
|---|
| 61 | our @afcpwm; |
|---|
| 62 | our @afctemp; |
|---|
| 63 | our @afcfan; |
|---|
| 64 | our @afcmaxtemp; |
|---|
| 65 | our @afcmintemp; |
|---|
| 66 | our @afcminstart; |
|---|
| 67 | our @afcminstop; |
|---|
| 68 | |
|---|
| 69 | sub loadconfig($); |
|---|
| 70 | sub pwmdisable($); |
|---|
| 71 | sub pwmenable($); |
|---|
| 72 | sub restorefans(); |
|---|
| 73 | sub calc(@); |
|---|
| 74 | sub UpdateFanSpeeds(); |
|---|
| 75 | END { restorefans(); } |
|---|
| 76 | |
|---|
| 77 | our $opt_d; |
|---|
| 78 | getopts('d'); |
|---|
| 79 | |
|---|
| 80 | my $config = shift; |
|---|
| 81 | if (defined($config)) |
|---|
| 82 | { loadconfig($config); } |
|---|
| 83 | else |
|---|
| 84 | { loadconfig(CONFFILE); } |
|---|
| 85 | |
|---|
| 86 | ### Daemonize |
|---|
| 87 | if ( defined($opt_d) && ($opt_d == 1) ) |
|---|
| 88 | { |
|---|
| 89 | my $pid = fork; |
|---|
| 90 | POSIX::_exit(0) if $pid; |
|---|
| 91 | |
|---|
| 92 | unless (defined($pid)) |
|---|
| 93 | { die("Couldn't fork: $!"); } |
|---|
| 94 | |
|---|
| 95 | open(*STDERR, '>', ERRFILE); |
|---|
| 96 | IO::Handle::autoflush(*STDERR); |
|---|
| 97 | open(*STDOUT, '>>', LOGFILE); |
|---|
| 98 | IO::Handle::autoflush(*STDOUT); |
|---|
| 99 | |
|---|
| 100 | unless (POSIX::setsid()) |
|---|
| 101 | { die("Couldn't open new session: $!"); } |
|---|
| 102 | |
|---|
| 103 | } |
|---|
| 104 | |
|---|
| 105 | ### Pidfile |
|---|
| 106 | if (open(FILE, ">" . PIDFILE)) |
|---|
| 107 | { |
|---|
| 108 | print(FILE "$$\n"); |
|---|
| 109 | close(FILE); |
|---|
| 110 | } |
|---|
| 111 | else |
|---|
| 112 | { print(PIDFILE . ": $!\n"); } |
|---|
| 113 | |
|---|
| 114 | |
|---|
| 115 | ### What kind of interface? |
|---|
| 116 | our $sysfs = 0; |
|---|
| 117 | our $dir = '/proc/sys/dev/sensors'; |
|---|
| 118 | if (!(-d $dir)) |
|---|
| 119 | { |
|---|
| 120 | if ($afcpwm[0] =~ m/^hwmon\d/) |
|---|
| 121 | { |
|---|
| 122 | $dir = '/sys/class/hwmon'; |
|---|
| 123 | } |
|---|
| 124 | else |
|---|
| 125 | { |
|---|
| 126 | $dir = '/sys/bus/i2c/devices'; |
|---|
| 127 | } |
|---|
| 128 | |
|---|
| 129 | if (!(-d $dir)) |
|---|
| 130 | { die("No sensors found! (are the necessary modules loaded?) : |
|---|
| 131 | $!\n"); } |
|---|
| 132 | else |
|---|
| 133 | { |
|---|
| 134 | $sysfs = 1; |
|---|
| 135 | } |
|---|
| 136 | } |
|---|
| 137 | |
|---|
| 138 | ### Trap signals |
|---|
| 139 | $SIG{TERM} = \&restorefans; |
|---|
| 140 | $SIG{HUP} = \&restorefans; |
|---|
| 141 | $SIG{INT} = \&restorefans; |
|---|
| 142 | $SIG{QUIT} = \&restorefans; |
|---|
| 143 | |
|---|
| 144 | ### Enable PWM |
|---|
| 145 | print("Enabling PWM on fans...\n"); |
|---|
| 146 | my $fcvcount = 0; |
|---|
| 147 | while ($fcvcount < $#afcpwm+1) |
|---|
| 148 | { |
|---|
| 149 | $pwmo = $afcpwm[$fcvcount]; |
|---|
| 150 | unless (pwmenable($pwmo)) |
|---|
| 151 | { die("Error enabling PWM on $dir/$pwmo : $!\n"); } |
|---|
| 152 | $fcvcount++; |
|---|
| 153 | } |
|---|
| 154 | |
|---|
| 155 | print("Starting automatic fan control...\n"); |
|---|
| 156 | |
|---|
| 157 | while(1) |
|---|
| 158 | { |
|---|
| 159 | UpdateFanSpeeds(); |
|---|
| 160 | sleep($interval); |
|---|
| 161 | } |
|---|
| 162 | |
|---|
| 163 | 1; |
|---|
| 164 | |
|---|
| 165 | ################################################################ |
|---|
| 166 | sub loadconfig($) |
|---|
| 167 | { |
|---|
| 168 | my $file = shift; |
|---|
| 169 | |
|---|
| 170 | print("Loading configuration from $file ...\n"); |
|---|
| 171 | |
|---|
| 172 | unless ( (-e $file) && (-r $file) ) |
|---|
| 173 | { die("Unable to read config file $file: $!"); } |
|---|
| 174 | |
|---|
| 175 | open(F, $file); |
|---|
| 176 | |
|---|
| 177 | our ($interval, $fctemps, $fcfans, $mintemp, $maxtemp, $minstart, |
|---|
| 178 | $minstop); |
|---|
| 179 | while($_ = <F>) |
|---|
| 180 | { |
|---|
| 181 | if ($_ =~ /^\s+$/) { next; } |
|---|
| 182 | elsif ($_ =~ /^INTERVAL=\s*(.*)$/) { $interval = $1; next; } |
|---|
| 183 | elsif ($_ =~ /^FCTEMPS=\s*(.*)$/) { $fctemps = $1; next; } |
|---|
| 184 | elsif ($_ =~ /^FCFANS=\s*(.*)$/) { $fcfans = $1; next; } |
|---|
| 185 | elsif ($_ =~ /^MINTEMP=\s*(.*)$/) { $mintemp = $1; next; } |
|---|
| 186 | elsif ($_ =~ /^MAXTEMP=\s*(.*)$/) { $maxtemp = $1; next; } |
|---|
| 187 | elsif ($_ =~ /^MINSTART=\s*(.*)$/) { $minstart = $1; next; } |
|---|
| 188 | elsif ($_ =~ /^MINSTOP=\s*(.*)$/) { $minstop = $1; next; } |
|---|
| 189 | } |
|---|
| 190 | close(F); |
|---|
| 191 | |
|---|
| 192 | unless (defined($interval)) |
|---|
| 193 | { die("Some settings missing ..."); } |
|---|
| 194 | |
|---|
| 195 | print("\nCommon settings:\n"); |
|---|
| 196 | print(" INTERVAL=$interval\n"); |
|---|
| 197 | |
|---|
| 198 | my $fcvcount = 0; |
|---|
| 199 | foreach my $fcv (split(/\s+/, $fctemps)) |
|---|
| 200 | { |
|---|
| 201 | ($afcpwm[$fcvcount], $afctemp[$fcvcount]) = split(/=/, $fcv); |
|---|
| 202 | |
|---|
| 203 | $fcfans =~ s/^\S*=(\S+)\s*//; $afcfan[$fcvcount] = $1; |
|---|
| 204 | $mintemp =~ s/^\S*=(\S+)\s*//; $afcmintemp[$fcvcount] = $1; |
|---|
| 205 | $maxtemp =~ s/^\S*=(\S+)\s*//; $afcmaxtemp[$fcvcount] = $1; |
|---|
| 206 | $minstart =~ s/^\S*=(\S+)\s*//; $afcminstart[$fcvcount] = $1; |
|---|
| 207 | $minstop =~ s/^\S*=(\S+)\s*//; $afcminstop[$fcvcount] = $1; |
|---|
| 208 | |
|---|
| 209 | print("\nSettings for $afcpwm[$fcvcount]:\n"); |
|---|
| 210 | print(" Depends on $afctemp[$fcvcount]\n"); |
|---|
| 211 | print(" Controls $afcfan[$fcvcount]\n"); |
|---|
| 212 | print(" MINTEMP = $afcmintemp[$fcvcount]\n"); |
|---|
| 213 | print(" MAXTEMP = $afcmaxtemp[$fcvcount]\n"); |
|---|
| 214 | print(" MINSTART = $afcminstart[$fcvcount]\n"); |
|---|
| 215 | print(" MINSTOP = $afcminstop[$fcvcount]\n"); |
|---|
| 216 | |
|---|
| 217 | $fcvcount++; |
|---|
| 218 | } |
|---|
| 219 | } |
|---|
| 220 | |
|---|
| 221 | |
|---|
| 222 | ################################################################ |
|---|
| 223 | sub pwmdisable($) |
|---|
| 224 | { |
|---|
| 225 | my $p = shift; |
|---|
| 226 | |
|---|
| 227 | if ($sysfs == 1) |
|---|
| 228 | { |
|---|
| 229 | if (open(F, ">$dir/$p")) |
|---|
| 230 | { |
|---|
| 231 | print(F MAX . '\n'); |
|---|
| 232 | close(F); |
|---|
| 233 | } |
|---|
| 234 | else |
|---|
| 235 | { die("$dir/$p : $!"); } |
|---|
| 236 | |
|---|
| 237 | my $enable = "$dir/$p/pwm/pwm_enable"; |
|---|
| 238 | if (-f $enable) |
|---|
| 239 | { |
|---|
| 240 | if (open(F, ">$enable")) |
|---|
| 241 | { |
|---|
| 242 | print(F '0'); |
|---|
| 243 | close(F); |
|---|
| 244 | } |
|---|
| 245 | else |
|---|
| 246 | { die("$dir/$p/pwm/pwm_enable : $!"); } |
|---|
| 247 | } |
|---|
| 248 | } |
|---|
| 249 | else |
|---|
| 250 | { |
|---|
| 251 | if (open(F, ">$dir/$p")) |
|---|
| 252 | { |
|---|
| 253 | print(F MAX . ' 0'); |
|---|
| 254 | close(F); |
|---|
| 255 | } |
|---|
| 256 | else |
|---|
| 257 | { die("$dir/$p : $!"); } |
|---|
| 258 | } |
|---|
| 259 | return(1); |
|---|
| 260 | } |
|---|
| 261 | |
|---|
| 262 | |
|---|
| 263 | ################################################################# |
|---|
| 264 | sub pwmenable($) |
|---|
| 265 | { |
|---|
| 266 | my $p = shift; |
|---|
| 267 | |
|---|
| 268 | if ($sysfs == 1) |
|---|
| 269 | { |
|---|
| 270 | my $enable = "$dir/$p/pwm/pwm_enable"; |
|---|
| 271 | if (-f $enable) |
|---|
| 272 | { |
|---|
| 273 | if (open(F, ">$enable")) |
|---|
| 274 | { |
|---|
| 275 | print(F "1\n"); |
|---|
| 276 | close(F); |
|---|
| 277 | } |
|---|
| 278 | else |
|---|
| 279 | { die("$dir/$p : $!\n"); } |
|---|
| 280 | } |
|---|
| 281 | } |
|---|
| 282 | else |
|---|
| 283 | { |
|---|
| 284 | if (open(F, ">$dir/$p")) |
|---|
| 285 | { |
|---|
| 286 | print(F MAX . " 1\n"); |
|---|
| 287 | close(F); |
|---|
| 288 | } |
|---|
| 289 | else |
|---|
| 290 | { die("$dir/$p : $!\n"); } |
|---|
| 291 | } |
|---|
| 292 | return(1); |
|---|
| 293 | } |
|---|
| 294 | |
|---|
| 295 | |
|---|
| 296 | ################################################################ |
|---|
| 297 | sub restorefans() |
|---|
| 298 | { |
|---|
| 299 | $SIG{TERM} = 'IGNORE'; |
|---|
| 300 | $SIG{HUP} = 'IGNORE'; |
|---|
| 301 | $SIG{INT} = 'IGNORE'; |
|---|
| 302 | $SIG{QUIT} = 'IGNORE'; |
|---|
| 303 | |
|---|
| 304 | print("Aborting, restoring fans...\n"); |
|---|
| 305 | my $fcvcount = 0; |
|---|
| 306 | |
|---|
| 307 | while ( $fcvcount < $#afcpwm+1) |
|---|
| 308 | { |
|---|
| 309 | my $pwmo = $afcpwm[$fcvcount]; |
|---|
| 310 | &pwmdisable($afcpwm[$fcvcount]); |
|---|
| 311 | $fcvcount++; |
|---|
| 312 | } |
|---|
| 313 | print("Verify fans have returned to full speed\n"); |
|---|
| 314 | POSIX:_exit(-1); |
|---|
| 315 | } |
|---|
| 316 | |
|---|
| 317 | |
|---|
| 318 | ############################################################ |
|---|
| 319 | sub UpdateFanSpeeds() |
|---|
| 320 | { |
|---|
| 321 | my $fcvcount = 0; |
|---|
| 322 | |
|---|
| 323 | while ($fcvcount < $#afcpwm+1) |
|---|
| 324 | { |
|---|
| 325 | my $pwmo = $afcpwm[$fcvcount]; |
|---|
| 326 | my $tsens = $afctemp[$fcvcount]; |
|---|
| 327 | my $fan = $afcfan[$fcvcount]; |
|---|
| 328 | my $mint = $afcmintemp[$fcvcount]; |
|---|
| 329 | my $maxt = $afcmaxtemp[$fcvcount]; |
|---|
| 330 | my $minsa = $afcminstart[$fcvcount]; |
|---|
| 331 | my $minso = $afcminstop[$fcvcount]; |
|---|
| 332 | |
|---|
| 333 | ### tval |
|---|
| 334 | my $tval = 0; |
|---|
| 335 | if (open(F, "$dir/$tsens")) |
|---|
| 336 | { |
|---|
| 337 | $tval = <F>; |
|---|
| 338 | close(F); |
|---|
| 339 | } |
|---|
| 340 | else |
|---|
| 341 | { die("Error reading temperature from $dir/$tsens"); } |
|---|
| 342 | $tval =~ /([.\d]+)\s*$/; |
|---|
| 343 | $tval = int($1); |
|---|
| 344 | if ($sysfs == 1) |
|---|
| 345 | { $tval /= 1000; } |
|---|
| 346 | |
|---|
| 347 | ### pwmpval |
|---|
| 348 | my $pwmpval = 0; |
|---|
| 349 | if (open(F, "$dir/$pwmo")) |
|---|
| 350 | { |
|---|
| 351 | $pwmpval = <F>; |
|---|
| 352 | close(F); |
|---|
| 353 | } |
|---|
| 354 | else |
|---|
| 355 | { die("Error reading PWM value from $dir/$pwmo"); } |
|---|
| 356 | ($pwmpval) = split(/\s/, $pwmpval); |
|---|
| 357 | |
|---|
| 358 | ### fanval |
|---|
| 359 | my $fanval = 0; |
|---|
| 360 | if (open(F, "$dir/$fan")) |
|---|
| 361 | { |
|---|
| 362 | $fanval = <F>; |
|---|
| 363 | close(F); |
|---|
| 364 | } |
|---|
| 365 | else |
|---|
| 366 | { die("Error reading Fan value from $dir/$fan"); } |
|---|
| 367 | $fanval =~ /(\d+)\s$/; |
|---|
| 368 | $fanval = $1; |
|---|
| 369 | |
|---|
| 370 | ### DEBUG |
|---|
| 371 | if (DEBUG == 1) |
|---|
| 372 | { |
|---|
| 373 | print("pwmo=$pwmo\n"); |
|---|
| 374 | print("tsens=$tsens\n"); |
|---|
| 375 | print("fan=$fan\n"); |
|---|
| 376 | print("mint=$mint\n"); |
|---|
| 377 | print("maxt=$maxt\n"); |
|---|
| 378 | print("minsa=$minsa\n"); |
|---|
| 379 | print("minso=$minso\n"); |
|---|
| 380 | print("tval=$tval\n"); |
|---|
| 381 | print("pwmpval=$pwmpval\n"); |
|---|
| 382 | print("fanval=$fanval\n"); |
|---|
| 383 | print("\n"); |
|---|
| 384 | } |
|---|
| 385 | |
|---|
| 386 | my $pwmval; |
|---|
| 387 | if ($tval <= $mint) |
|---|
| 388 | { $pwmval = 0; } |
|---|
| 389 | elsif ($tval >= $maxt) |
|---|
| 390 | { $pwmval = MAX; } |
|---|
| 391 | else |
|---|
| 392 | { |
|---|
| 393 | $pwmval = eval ( ($tval - $mint) / ($maxt - $mint) )**2 ; |
|---|
| 394 | $pwmval *= (255 - $minso); |
|---|
| 395 | $pwmval += $minso; |
|---|
| 396 | $pwmval = int($pwmval); |
|---|
| 397 | if ( ($pwmval == 0) || ($fanval == 0) ) |
|---|
| 398 | { |
|---|
| 399 | if (open(F, ">$dir/$pwmo")) |
|---|
| 400 | { |
|---|
| 401 | print(F "$minsa\n"); |
|---|
| 402 | close(F); |
|---|
| 403 | } |
|---|
| 404 | else |
|---|
| 405 | { die("Error writing PWM value to $dir/$pwmo : $!\n"); } |
|---|
| 406 | sleep 1; |
|---|
| 407 | } |
|---|
| 408 | } |
|---|
| 409 | |
|---|
| 410 | if (open(F, ">$dir/$pwmo")) |
|---|
| 411 | { |
|---|
| 412 | print(F "$pwmval\n"); |
|---|
| 413 | close(F); |
|---|
| 414 | } |
|---|
| 415 | else |
|---|
| 416 | { die("Error writing PWM value to $dir/$pwmo : $!\n"); } |
|---|
| 417 | |
|---|
| 418 | if (DEBUG == 1) |
|---|
| 419 | { print("new pwmval = $pwmval\n"); } |
|---|
| 420 | |
|---|
| 421 | $fcvcount++; |
|---|
| 422 | } |
|---|
| 423 | |
|---|
| 424 | } |
|---|
| 425 | |
|---|
| 426 | 1; |
|---|
| 427 | |
|---|
| 428 | |
|---|