Понадобился мне однажды для работы ноутбук. Уж не помню почему, но выбор пал на Acer S3-391, тонкий, легкий, быстрый, но не лишенный недостатков. Кроме плохого экрана (который кстати не так просто заменить — у него особый коннектор, и возможно он приклеен к рамке), особенно меня раздражал шум вентилятора.
Пути решения этой проблемы я и постараюсь осветить в этой статье.
Прочитав статью Управляем вентилятором ноутбука через DSDT в Linux и не только, как и автор, я начал усердно гуглить в сторону ACPI и DSDT, даже перекомпилировал и подключил свою таблицу, но найти «ту самую» строчку кода отвечающую за работу вентилятора так и не удалось.
Тем временем шум вентилятора, меня все больше деморализировал. При чем, если на работе шум системников и кондиционера еще как-то перебивал, то дома, наедине со своими тараканами, вентилятор методично разрушал мою психику.
Решено было на время вернуться в Win7.
Как обстоят дела в Win
Для ОС от Майкрософт написано очень много программ для управлени вентилятором, все он по большей части заточены в лучшем случае под одного производителя. Что наводило на неприятные мысли.
Но тем не менее была найдена относительно универсальная программа NBFC, которая сразу заработала, требовалось лишь выставить тригеры переключения оборотов.
Какое-то время решение меня устраивало, но на душе все равно было как-то неспокойно.
Возвращение домой
После пары недель использования вынды понял что неудобно. Нужно было решение для непокоренного пингвина.
Тогда я решил все таки разобраться как же работает вышеупомянутая программа.
Решение было не то что бы совсем на поверхноости, но точно не глубоко. Точнее в мануале приложенном к софтине.
Было найдено «правильное слово» по которому нужно гуглить: Embedded Controller (EC).
как написано на rom.by
Embedded Contoller — это встроенный контроллер типа Hitachi H8 (он же — Renesas), Winbond W83L950D, предназначенный для управления платформой (как правило — мобильной) как на уровне включения и выключения, так и для обработки ACPI-событий. В задачи EC-контроллера входит обслуживание аккумулятора мобильной платформы: выбор режима его заряда, контроль разрядки. Как правило, на мобильных платформах с помощью EC-контроллера реализуется и контроллер клавиатуры.
Оказалось что состояние вентилятора так же записывается в регистры этого контроллера.
Отавалось решить 2 задачи:
1) Какие регистры отвечают за состояние вентилятора
2) Как изменять их значение
Решение
С первой задачей помогла справится все также программка NBFC. Всего-то и нужно было посмотреть значения в конфиге для своего ноутбука (ультрабука?)
А с задачей «Как?» помог справится скрипт на перле шестилетней давности, который заработал сразу и без правок.
В общем-то все можно было бы и успокоиться, но хотелось немного увтоматизировать процесс, в результате чего появилось целых 3 скрипта, возможно и можно было все решить одним, но мои познания в программировании крайне ограничены, а на перле я вообще не писал никогда, если кто подскажет как это все упростить и сделать так что бы управляющий скрипт автоматически перезапускался после сна/пробуждения устройства — буду благодарен.
Собственно сами скрипты:
!/usr/bin/perl -w
# Copyright (C) 2013 George Butskivsky butskivsky (at) gmail.com
#
# Version 0.1 (09-aug-2013)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 5.004;
use strict;
use Fcntl;
use POSIX;
use File::Basename;
my $fan_control_reg = 0x93;
my $fan_manual_mode = 0x14;
my $fan_auto_mode = 0x04;
my $fan_speed_reg = 0x94;
my $fan_speed_val_10 = 0xe6; # 10% of power
my $fan_speed_val_20 = 0xc8;
my $fan_speed_val_40 = 0x96;
my $fan_speed_val_50 = 0x7e;
my $fan_speed_val_60 = 0x64;
my $fan_speed_val_80 = 0x32;
sub initialize_ioports
{
sysopen (IOPORTS, "/dev/port", O_RDWR)
or die "/dev/port: $!n";
binmode IOPORTS;
}
sub close_ioports
{
close (IOPORTS)
or print "Warning: $!n";
}
sub inb
{
my ($res,$nrchars);
sysseek IOPORTS, $_[0], 0 or return -1;
$nrchars = sysread IOPORTS, $res, 1;
return -1 if not defined $nrchars or $nrchars != 1;
$res = unpack "C",$res ;
return $res;
}
# $_[0]: value to write
# $_[1]: port to write
# Returns: -1 on failure, 0 on success.
sub outb
{
if ($_[0] > 0xff)
{
my ($package, $filename, $line, $sub) = caller(1);
print "n*** Called outb with value=$_[1] from line $linen",
"*** (in $sub). PLEASE REPORT!n",
"*** Terminating.n";
exit(-1);
}
my $towrite = pack "C", $_[0];
sysseek IOPORTS, $_[1], 0 or return -1;
my $nrchars = syswrite IOPORTS, $towrite, 1;
return -1 if not defined $nrchars or $nrchars != 1;
return 0;
}
sub wait_write
{
my $i = 0;
while ((inb($_[0]) & 0x02) && ($i < 10000)) {
sleep(0.01);
$i++;
}
return -($i == 10000);
}
sub wait_read
{
my $i = 0;
while (!(inb($_[0]) & 0x01) && ($i < 10000)) {
sleep(0.01);
$i++;
}
return -($i == 10000);
}
sub wait_write_ec
{
wait_write(0x66);
}
sub wait_read_ec
{
wait_read(0x66);
}
sub send_ec
{
if (!wait_write_ec()) { outb($_[0], 0x66); }
if (!wait_write_ec()) { outb($_[1], 0x62); }
}
sub write_ec
{
if (!wait_write_ec()) { outb(0x81, 0x66 ); }
if (!wait_write_ec()) { outb($_[0], 0x62); }
if (!wait_write_ec()) { outb($_[1], 0x62); }
}
sub read_ec
{
if (!wait_write_ec()) { outb(0x80, 0x66 ); }
if (!wait_write_ec()) { outb($_[0], 0x62); }
if (!wait_read_ec()) { inb(0x62); }
}
sub print_regs
{
initialize_ioports();
my @arr = ("00","10","20","30","40","50","60","70","80","90","A0","B0","C0","D0","E0","F0", "");
my $i = 0;
my $t = 0;
print "n t00t01t02t03t04t05t06t07t|t08t09t0At0Bt0Ct0Dt0Et0Fn";
print " t__t__t__t__t__t__t__t__t|t__t__t__t__t__t__t__t__n";
print "00 |t";
for ($i = 0; $i < 256; $i++)
{
$t = read_ec($i);
print $t;
print "t";
if ((($i + 1) % 8) == 0){
if ((($i + 1) % 16) == 0) {
if ($i != 255) { print "n$arr[(($i-(($i + 1) % 16)) / 16) + 1] |t"; }
} else {
print "|t";
}
}
}
print "n";
close_ioports();
}
if (!$ARGV[0]){
print "wrong arguments!n";
print "usage:n";
print "'fan_control regs' ttttdumps all ec registersn";
print "'fan_control ?= <reg>' ttQuery register's valuen";
print "'fan_control := <reg> <val>' tSet register's valuen";
print "'fan_control 10|20|40|50|60|80' tSet fan speed value in percentsn";
print "'fan_control auto|manual' tSet fan policyn";
print "'fan_control getspeed' tGet current speed fan value in dec format (255-0) lesser is loudern";
} elsif ($ARGV[0] eq "regs") {
print_regs();
} elsif ($ARGV[0] eq "?=") {
initialize_ioports();
my $r = hex($ARGV[1]);
printf("REG[0x%02x] == 0x%02xn", $r, read_ec($r));
close_ioports();
} elsif ($ARGV[0] eq ":=") {
initialize_ioports();
my $r = hex($ARGV[1]);
my $f = hex($ARGV[2]);
my $val = read_ec($r);
printf("REG[0x%02x] == 0x%02xn", $r, $val);
printf("REG[0x%02x] := 0x%02xn", $r, $f);
write_ec( $r, $f);
printf("REG[0x%02x] == 0x%02xn", $r, read_ec($r));
close_ioports();
} elsif ($ARGV[0] eq "10") {
initialize_ioports();
write_ec( $fan_speed_reg, $fan_speed_val_10);
close_ioports();
} elsif ($ARGV[0] eq "20") {
initialize_ioports();
write_ec( $fan_speed_reg, $fan_speed_val_20);
close_ioports();
} elsif ($ARGV[0] eq "40") {
initialize_ioports();
write_ec( $fan_speed_reg, $fan_speed_val_40);
close_ioports();
} elsif ($ARGV[0] eq "50") {
initialize_ioports();
write_ec( $fan_speed_reg, $fan_speed_val_50);
close_ioports();
} elsif ($ARGV[0] eq "60") {
initialize_ioports();
write_ec( $fan_speed_reg, $fan_speed_val_60);
close_ioports();
} elsif ($ARGV[0] eq "80") {
initialize_ioports();
write_ec( $fan_speed_reg, $fan_speed_val_80);
close_ioports();
} elsif ($ARGV[0] eq "manual") {
initialize_ioports();
write_ec( $fan_control_reg, $fan_manual_mode);
close_ioports();
} elsif ($ARGV[0] eq "auto") {
initialize_ioports();
#write_ec( 0x93, 0x04);
write_ec( $fan_control_reg, $fan_auto_mode);
close_ioports();
} elsif ($ARGV[0] eq "getspeed") {
initialize_ioports();
my $speed = read_ec($fan_speed_reg);
my $dec_speed = sprintf("%d", $speed);
printf("fan speed == %dn", $dec_speed);
close_ioports();
} else {
print "wrong arguments!n";
}
#!/usr/bin/perl -w
$temp = `cat /sys/class/thermal/thermal_zone0/temp`;
$silent = int(60000);
$half = int(65000);
$full = int(75000);
if ($temp < $silent) {
system("/usr/bin/perl -w /home/user/fan_control.pl 20");
} elsif ($temp < $half) {
system("/usr/bin/perl -w /home/user/fan_control.pl 40");
} elsif ($temp < $full) {
system("/usr/bin/perl -w /home/user/fan_control.pl 80");
} else {
system("/usr/bin/perl -w /home/user/fan_control.pl auto");
}
#!/usr/bin/bash
/usr/local/bin/fan_control.pl manual
while [ true ]
do
/usr/local/bin/fan_control_logic.pl
sleep 5
done
Просто скопируйте в /usr/local/bin/ и дайте права на выполнение
Значения оборотов и пороговых тепмератур описаны такие как удобно мне, вы можете с ними поиграться, подобрать более подходящие для вас.
Если у вас другой ноутбук, с той же проблемой вам скорее всего потребуется изменить значения записываемого регистра
В этом нам помогут конфиги написанные для уже неоднократно упоминавшейся NBFC
Если ничего найти не удалось то можно попробовать узнать значения запустив:
watch -n 1 sudo fan_control.pl regs
Если регистры, и их значения подобраны верно просто выполняем в консоли:
sudo fan_control
вентилятор должен изменить обороты.
Profit!
Спасибо за внимание, надеюсь материал будет кому-нибудь полезен.
Критика, дополнения и улучшения приветствуются.
Автор: fun666