Еще один способ управления вентилятором в Linux (на примере Acer S3-391)

в 16:25, , рубрики: embedded controller, linux, open source, метки: ,

Понадобился мне однажды для работы ноутбук. Уж не помню почему, но выбор пал на 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js