| 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 => 1; |
|---|
| 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 | |
|---|
| 58 | use constant SDIR => '/sys/bus/i2c/devices'; |
|---|
| 59 | ### End Configuration ### |
|---|
| 60 | |
|---|
| 61 | our $interval; |
|---|
| 62 | our $pwmo; |
|---|
| 63 | our @afcpwm; |
|---|
| 64 | our @afctemp; |
|---|
| 65 | our @afcfan; |
|---|
| 66 | our @afcmaxtemp; |
|---|
| 67 | our @afcmintemp; |
|---|
| 68 | our @afcminstart; |
|---|
| 69 | our @afcminstop; |
|---|
| 70 | |
|---|
| 71 | sub loadconfig($); |
|---|
| 72 | sub pwmdisable($); |
|---|
| 73 | sub pwmenable($); |
|---|
| 74 | sub restorefans(); |
|---|
| 75 | sub calc(@); |
|---|
| 76 | sub UpdateFanSpeeds(); |
|---|
| 77 | END { restorefans(); } |
|---|
| 78 | |
|---|
| 79 | our $opt_d; |
|---|
| 80 | getopts('d'); |
|---|
| 81 | |
|---|
| 82 | my $config = shift; |
|---|
| 83 | if (defined($config)) |
|---|
| 84 | { loadconfig($config); } |
|---|
| 85 | else |
|---|
| 86 | { loadconfig(CONFFILE); } |
|---|
| 87 | |
|---|
| 88 | ### Daemonize |
|---|
| 89 | if ( defined($opt_d) && ($opt_d == 1) ) |
|---|
| 90 | { |
|---|
| 91 | my $pid = fork; |
|---|
| 92 | POSIX::_exit(0) if $pid; |
|---|
| 93 | |
|---|
| 94 | unless (defined($pid)) |
|---|
| 95 | { die("Couldn't fork: $!"); } |
|---|
| 96 | |
|---|
| 97 | open(*STDERR, '>', ERRFILE); |
|---|
| 98 | IO::Handle::autoflush(*STDERR); |
|---|
| 99 | open(*STDOUT, '>>', LOGFILE); |
|---|
| 100 | IO::Handle::autoflush(*STDOUT); |
|---|
| 101 | |
|---|
| 102 | unless (POSIX::setsid()) |
|---|
| 103 | { die("Couldn't open new session: $!"); } |
|---|
| 104 | |
|---|
| 105 | } |
|---|
| 106 | |
|---|
| 107 | ### Pidfile |
|---|
| 108 | if (open(FILE, ">" . PIDFILE)) |
|---|
| 109 | { |
|---|
| 110 | print(FILE "$$\n"); |
|---|
| 111 | close(FILE); |
|---|
| 112 | } |
|---|
| 113 | else |
|---|
| 114 | { print(PIDFILE . ": $!\n"); } |
|---|
| 115 | |
|---|
| 116 | |
|---|
| 117 | ### What kind of interface? |
|---|
| 118 | our $sysfs = 0; |
|---|
| 119 | our $dir = '/proc/sys/dev/sensors'; |
|---|
| 120 | if (!(-d $dir)) |
|---|
| 121 | { |
|---|
| 122 | if (!(-d SDIR)) |
|---|
| 123 | { die("No sensors found! (are the necessary modules loaded?) : |
|---|
| 124 | $!\n"); } |
|---|
| 125 | else |
|---|
| 126 | { |
|---|
| 127 | $sysfs = 1; |
|---|
| 128 | $dir = SDIR; |
|---|
| 129 | } |
|---|
| 130 | } |
|---|
| 131 | |
|---|
| 132 | ### Trap signals |
|---|
| 133 | $SIG{TERM} = \&restorefans; |
|---|
| 134 | $SIG{HUP} = \&restorefans; |
|---|
| 135 | $SIG{INT} = \&restorefans; |
|---|
| 136 | $SIG{QUIT} = \&restorefans; |
|---|
| 137 | |
|---|
| 138 | ### Enable PWM |
|---|
| 139 | print("Enabling PWM on fans...\n"); |
|---|
| 140 | my $fcvcount = 0; |
|---|
| 141 | while ($fcvcount < $#afcpwm+1) |
|---|
| 142 | { |
|---|
| 143 | $pwmo = $afcpwm[$fcvcount]; |
|---|
| 144 | unless (pwmenable($pwmo)) |
|---|
| 145 | { die("Error enabling PWM on $dir/$pwmo : $!\n"); } |
|---|
| 146 | $fcvcount++; |
|---|
| 147 | } |
|---|
| 148 | |
|---|
| 149 | print("Starting automatic fan control...\n"); |
|---|
| 150 | |
|---|
| 151 | while(1) |
|---|
| 152 | { |
|---|
| 153 | UpdateFanSpeeds(); |
|---|
| 154 | sleep($interval); |
|---|
| 155 | } |
|---|
| 156 | |
|---|
| 157 | 1; |
|---|
| 158 | |
|---|
| 159 | ################################################################ |
|---|
| 160 | sub loadconfig($) |
|---|
| 161 | { |
|---|
| 162 | my $file = shift; |
|---|
| 163 | |
|---|
| 164 | print("Loading configuration from $file ...\n"); |
|---|
| 165 | |
|---|
| 166 | unless ( (-e $file) && (-r $file) ) |
|---|
| 167 | { die("Unable to read config file $file: $!"); } |
|---|
| 168 | |
|---|
| 169 | open(F, $file); |
|---|
| 170 | |
|---|
| 171 | our ($interval, $fctemps, $fcfans, $mintemp, $maxtemp, $minstart, |
|---|
| 172 | $minstop); |
|---|
| 173 | while($_ = <F>) |
|---|
| 174 | { |
|---|
| 175 | if ($_ =~ /^\s+$/) { next; } |
|---|
| 176 | elsif ($_ =~ /^INTERVAL=(.*)$/) { $interval = $1; next; } |
|---|
| 177 | elsif ($_ =~ /^FCTEMPS=(.*)$/) { $fctemps = $1; next; } |
|---|
| 178 | elsif ($_ =~ /^FCFANS=(.*)$/) { $fcfans = $1; next; } |
|---|
| 179 | elsif ($_ =~ /^MINTEMP=(.*)$/) { $mintemp = $1; next; } |
|---|
| 180 | elsif ($_ =~ /^MAXTEMP=(.*)$/) { $maxtemp = $1; next; } |
|---|
| 181 | elsif ($_ =~ /^MINSTART=(.*)$/) { $minstart = $1; next; } |
|---|
| 182 | elsif ($_ =~ /^MINSTOP=(.*)$/) { $minstop = $1; next; } |
|---|
| 183 | } |
|---|
| 184 | close(F); |
|---|
| 185 | |
|---|
| 186 | unless (defined($interval)) |
|---|
| 187 | { die("Some settings missing ..."); } |
|---|
| 188 | |
|---|
| 189 | print("\nCommon settings:\n"); |
|---|
| 190 | print(" INTERVAL=$interval\n"); |
|---|
| 191 | |
|---|
| 192 | my $fcvcount = 0; |
|---|
| 193 | foreach my $fcv (split(/\s+/, $fctemps)) |
|---|
| 194 | { |
|---|
| 195 | ($afcpwm[$fcvcount], $afctemp[$fcvcount]) = split(/=/, $fcv); |
|---|
| 196 | |
|---|
| 197 | $fcfans =~ s/^\S*=(\S+)\s*//; $afcfan[$fcvcount] = $1; |
|---|
| 198 | $mintemp =~ s/^\S*=(\S+)\s*//; $afcmintemp[$fcvcount] = $1; |
|---|
| 199 | $maxtemp =~ s/^\S*=(\S+)\s*//; $afcmaxtemp[$fcvcount] = $1; |
|---|
| 200 | $minstart =~ s/^\S*=(\S+)\s*//; $afcminstart[$fcvcount] = $1; |
|---|
| 201 | $minstop =~ s/^\S*=(\S+)\s*//; $afcminstop[$fcvcount] = $1; |
|---|
| 202 | |
|---|
| 203 | print("\nSettings for $afcpwm[$fcvcount]:\n"); |
|---|
| 204 | print(" Depends on $afctemp[$fcvcount]\n"); |
|---|
| 205 | print(" Controls $afcfan[$fcvcount]\n"); |
|---|
| 206 | print(" MINTEMP = $afcmintemp[$fcvcount]\n"); |
|---|
| 207 | print(" MAXTEMP = $afcmaxtemp[$fcvcount]\n"); |
|---|
| 208 | print(" MINSTART = $afcminstart[$fcvcount]\n"); |
|---|
| 209 | print(" MINSTOP = $afcminstop[$fcvcount]\n"); |
|---|
| 210 | |
|---|
| 211 | $fcvcount++; |
|---|
| 212 | } |
|---|
| 213 | } |
|---|
| 214 | |
|---|
| 215 | |
|---|
| 216 | ################################################################ |
|---|
| 217 | sub pwmdisable($) |
|---|
| 218 | { |
|---|
| 219 | my $p = shift; |
|---|
| 220 | |
|---|
| 221 | if ($sysfs == 1) |
|---|
| 222 | { |
|---|
| 223 | if (open(F, ">$dir/$p")) |
|---|
| 224 | { |
|---|
| 225 | print(F MAX . '\n'); |
|---|
| 226 | close(F); |
|---|
| 227 | } |
|---|
| 228 | else |
|---|
| 229 | { die("$dir/$p : $!"); } |
|---|
| 230 | |
|---|
| 231 | my $enable = "$dir/$p/pwm/pwm_enable"; |
|---|
| 232 | if (-f $enable) |
|---|
| 233 | { |
|---|
| 234 | if (open(F, ">$enable")) |
|---|
| 235 | { |
|---|
| 236 | print(F '0'); |
|---|
| 237 | close(F); |
|---|
| 238 | } |
|---|
| 239 | else |
|---|
| 240 | { die("$dir/$p/pwm/pwm_enable : $!"); } |
|---|
| 241 | } |
|---|
| 242 | } |
|---|
| 243 | else |
|---|
| 244 | { |
|---|
| 245 | if (open(F, ">$dir/$p")) |
|---|
| 246 | { |
|---|
| 247 | print(F MAX . ' 0'); |
|---|
| 248 | close(F); |
|---|
| 249 | } |
|---|
| 250 | else |
|---|
| 251 | { die("$dir/$p : $!"); } |
|---|
| 252 | } |
|---|
| 253 | return(1); |
|---|
| 254 | } |
|---|
| 255 | |
|---|
| 256 | |
|---|
| 257 | ################################################################# |
|---|
| 258 | sub pwmenable($) |
|---|
| 259 | { |
|---|
| 260 | my $p = shift; |
|---|
| 261 | |
|---|
| 262 | if ($sysfs == 1) |
|---|
| 263 | { |
|---|
| 264 | my $enable = "$dir/$p/pwm/pwm_enable"; |
|---|
| 265 | if (-f $enable) |
|---|
| 266 | { |
|---|
| 267 | if (open(F, ">$enable")) |
|---|
| 268 | { |
|---|
| 269 | print(F "1\n"); |
|---|
| 270 | close(F); |
|---|
| 271 | } |
|---|
| 272 | else |
|---|
| 273 | { die("$dir/$p : $!\n"); } |
|---|
| 274 | } |
|---|
| 275 | } |
|---|
| 276 | else |
|---|
| 277 | { |
|---|
| 278 | if (open(F, ">$dir/$p")) |
|---|
| 279 | { |
|---|
| 280 | print(F MAX . " 1\n"); |
|---|
| 281 | close(F); |
|---|
| 282 | } |
|---|
| 283 | else |
|---|
| 284 | { die("$dir/$p : $!\n"); } |
|---|
| 285 | } |
|---|
| 286 | return(1); |
|---|
| 287 | } |
|---|
| 288 | |
|---|
| 289 | |
|---|
| 290 | ################################################################ |
|---|
| 291 | sub restorefans() |
|---|
| 292 | { |
|---|
| 293 | $SIG{TERM} = 'IGNORE'; |
|---|
| 294 | $SIG{HUP} = 'IGNORE'; |
|---|
| 295 | $SIG{INT} = 'IGNORE'; |
|---|
| 296 | $SIG{QUIT} = 'IGNORE'; |
|---|
| 297 | |
|---|
| 298 | print("Aborting, restoring fans...\n"); |
|---|
| 299 | my $fcvcount = 0; |
|---|
| 300 | |
|---|
| 301 | while ( $fcvcount < $#afcpwm+1) |
|---|
| 302 | { |
|---|
| 303 | my $pwmo = $afcpwm[$fcvcount]; |
|---|
| 304 | &pwmdisable($afcpwm[$fcvcount]); |
|---|
| 305 | $fcvcount++; |
|---|
| 306 | } |
|---|
| 307 | print("Verify fans have returned to full speed\n"); |
|---|
| 308 | POSIX:_exit(-1); |
|---|
| 309 | } |
|---|
| 310 | |
|---|
| 311 | |
|---|
| 312 | ############################################################ |
|---|
| 313 | sub UpdateFanSpeeds() |
|---|
| 314 | { |
|---|
| 315 | my $fcvcount = 0; |
|---|
| 316 | |
|---|
| 317 | while ($fcvcount < $#afcpwm+1) |
|---|
| 318 | { |
|---|
| 319 | my $pwmo = $afcpwm[$fcvcount]; |
|---|
| 320 | my $tsens = $afctemp[$fcvcount]; |
|---|
| 321 | my $fan = $afcfan[$fcvcount]; |
|---|
| 322 | my $mint = $afcmintemp[$fcvcount]; |
|---|
| 323 | my $maxt = $afcmaxtemp[$fcvcount]; |
|---|
| 324 | my $minsa = $afcminstart[$fcvcount]; |
|---|
| 325 | my $minso = $afcminstop[$fcvcount]; |
|---|
| 326 | |
|---|
| 327 | ### tval |
|---|
| 328 | my $tval = 0; |
|---|
| 329 | if (open(F, "$dir/$tsens")) |
|---|
| 330 | { |
|---|
| 331 | $tval = <F>; |
|---|
| 332 | close(F); |
|---|
| 333 | } |
|---|
| 334 | else |
|---|
| 335 | { die("Error reading temperature from $dir/$tsens"); } |
|---|
| 336 | $tval =~ /([.\d]+)\s*$/; |
|---|
| 337 | $tval = int($1); |
|---|
| 338 | if ($sysfs == 1) |
|---|
| 339 | { $tval /= 1000; } |
|---|
| 340 | |
|---|
| 341 | ### pwmpval |
|---|
| 342 | my $pwmpval = 0; |
|---|
| 343 | if (open(F, "$dir/$pwmo")) |
|---|
| 344 | { |
|---|
| 345 | $pwmpval = <F>; |
|---|
| 346 | close(F); |
|---|
| 347 | } |
|---|
| 348 | else |
|---|
| 349 | { die("Error reading PWM value from $dir/$pwmo"); } |
|---|
| 350 | ($pwmpval) = split(/\s/, $pwmpval); |
|---|
| 351 | |
|---|
| 352 | ### fanval |
|---|
| 353 | my $fanval = 0; |
|---|
| 354 | if (open(F, "$dir/$fan")) |
|---|
| 355 | { |
|---|
| 356 | $fanval = <F>; |
|---|
| 357 | close(F); |
|---|
| 358 | } |
|---|
| 359 | else |
|---|
| 360 | { die("Error reading Fan value from $dir/$fan"); } |
|---|
| 361 | $fanval =~ /(\d+)\s$/; |
|---|
| 362 | $fanval = $1; |
|---|
| 363 | |
|---|
| 364 | ### DEBUG |
|---|
| 365 | if (DEBUG == 1) |
|---|
| 366 | { |
|---|
| 367 | print("pwmo=$pwmo\n"); |
|---|
| 368 | print("tsens=$tsens\n"); |
|---|
| 369 | print("fan=$fan\n"); |
|---|
| 370 | print("mint=$mint\n"); |
|---|
| 371 | print("maxt=$maxt\n"); |
|---|
| 372 | print("minsa=$minsa\n"); |
|---|
| 373 | print("minso=$minso\n"); |
|---|
| 374 | print("tval=$tval\n"); |
|---|
| 375 | print("pwmpval=$pwmpval\n"); |
|---|
| 376 | print("fanval=$fanval\n"); |
|---|
| 377 | print("\n"); |
|---|
| 378 | } |
|---|
| 379 | |
|---|
| 380 | my $pwmval; |
|---|
| 381 | if ($tval <= $mint) |
|---|
| 382 | { $pwmval = 0; } |
|---|
| 383 | elsif ($tval >= $maxt) |
|---|
| 384 | { $pwmval = MAX; } |
|---|
| 385 | else |
|---|
| 386 | { |
|---|
| 387 | $pwmval = eval ( ($tval - $mint) / ($maxt - $mint) )**2 ; |
|---|
| 388 | $pwmval *= (255 - $minso); |
|---|
| 389 | $pwmval += $minso; |
|---|
| 390 | $pwmval = int($pwmval); |
|---|
| 391 | if ( ($pwmval == 0) || ($fanval == 0) ) |
|---|
| 392 | { |
|---|
| 393 | if (open(F, ">$dir/$pwmo")) |
|---|
| 394 | { |
|---|
| 395 | print(F "$minsa\n"); |
|---|
| 396 | close(F); |
|---|
| 397 | } |
|---|
| 398 | else |
|---|
| 399 | { die("Error writing PWM value to $dir/$pwmo : $!\n"); } |
|---|
| 400 | sleep 1; |
|---|
| 401 | } |
|---|
| 402 | } |
|---|
| 403 | |
|---|
| 404 | if (open(F, ">$dir/$pwmo")) |
|---|
| 405 | { |
|---|
| 406 | print(F "$pwmval\n"); |
|---|
| 407 | close(F); |
|---|
| 408 | } |
|---|
| 409 | else |
|---|
| 410 | { die("Error writing PWM value to $dir/$pwmo : $!\n"); } |
|---|
| 411 | |
|---|
| 412 | if (DEBUG == 1) |
|---|
| 413 | { print("new pwmval = $pwmval\n"); } |
|---|
| 414 | |
|---|
| 415 | $fcvcount++; |
|---|
| 416 | } |
|---|
| 417 | |
|---|
| 418 | } |
|---|
| 419 | |
|---|
| 420 | 1; |
|---|
| 421 | |
|---|
| 422 | |
|---|