Another way to control a fan in Linux (using the Acer S3-391 as an example)

I once needed a laptop to work. I don’t remember why, but the choice fell on the Acer S3-391, thin, light, fast, but not without flaws. In addition to a bad screen (which by the way is not so easy to replace - it has a special connector, and maybe it is glued to the frame), the fan noise especially annoyed me.
I will try to highlight the ways to solve this problem in this article.

After reading the article Managing a laptop fan via DSDT on Linux and not only like the author, I began to google hard towards ACPI and DSDT, even recompiled and connected my table, but I could not find the “same” line of code responsible for the fan .



Meanwhile, the noise of the fan, demoralized me more and more. Moreover, if at work the noise of the system engineers and the air conditioner still somehow interrupted, then at home, alone with my cockroaches, the fan methodically destroyed my psyche.
It was decided to return to Win7 for a while.

How things are in Win

A lot of programs for controlling the fan have been written for the Microsoft operating system; all of them are for the most part tailored, at best, to the same manufacturer. Which led to unpleasant thoughts.
Nevertheless, a relatively universal NBFC program was found , which immediately worked, all that was needed was to set the speed switching triggers.

For some time the decision suited me, but my heart was still somehow restless.

Homecoming

After a couple of weeks of use, the udy realized that it was inconvenient. A solution was needed for the recaptured penguin.
Then I decided to figure out how the above program works.

The decision was not just on the surface, but definitely not deep. More precisely in the manual attached to the software.

A “correct word” was found on which to google: Embedded Controller (EC) .

as written on rom.by
The Embedded Contoller is a built-in controller of the Hitachi H8 type (aka Renesas), Winbond W83L950D, designed to control the platform (usually mobile) both on and off, and for processing ACPI events. The tasks of the EC controller include maintenance of the battery of the mobile platform: the choice of its charge mode, discharge control. As a rule, on mobile platforms with the help of an EC controller, a keyboard controller is also implemented.

It turned out that the state of the fan is also recorded in the registers of this controller.
It was left to solve 2 problems:
1) What registers are responsible for the state of the fan
2) How to change their value

Decision

The NBFC program also helped to cope with the first task. All that was needed was to look at the values ​​in the config for your laptop (ultrabook?)
And the six-year-old script on the pearl helped to cope with the task “How?” , Which earned immediately and without corrections.

In general, everything could have been calmed down, but I wanted to automate the process a bit, as a result of which as many as 3 scripts appeared, maybe it was possible to solve everything by one, but my knowledge in programming is extremely limited, and I never wrote on the pearl, If someone tells me how to simplify this and make it so that the control script automatically restarts after sleep / waking up the device, I will be grateful.

Actually the scripts themselves:

Redesigned management script for my needs
!/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.require5.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 powermy $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;
subinitialize_ioports{
  sysopen (IOPORTS, "/dev/port", O_RDWR)
    ordie"/dev/port: $!\n";
  binmode IOPORTS;
}
subclose_ioports{
  close (IOPORTS)
    orprint"Warning: $!\n";
}
subinb{
  my ($res,$nrchars);
  sysseek IOPORTS, $_[0], 0orreturn -1;
  $nrchars = sysread IOPORTS, $res, 1;
  return -1ifnotdefined $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.suboutb{
  if ($_[0] > 0xff)
  {
    my ($package, $filename, $line, $sub) = caller(1);
    print"\n*** Called outb with value=$_[1] from line $line\n",
          "*** (in $sub). PLEASE REPORT!\n",
          "*** Terminating.\n";
    exit(-1);
  }
  my $towrite = pack"C", $_[0];
  sysseek IOPORTS, $_[1], 0orreturn -1;
  my $nrchars = syswrite IOPORTS, $towrite, 1;
  return -1ifnotdefined $nrchars or $nrchars != 1;
  return0;
}
subwait_write{
	my $i = 0;
	while ((inb($_[0]) & 0x02) && ($i < 10000)) {
		sleep(0.01);
		$i++;
	}
	return -($i == 10000);
}
subwait_read{
	my $i = 0;
	while (!(inb($_[0]) & 0x01) && ($i < 10000)) {
		sleep(0.01);
		$i++;
	}
	return -($i == 10000);
}
subwait_write_ec{
	wait_write(0x66);
}
subwait_read_ec{
	wait_read(0x66);
}
subsend_ec{
	if (!wait_write_ec()) { outb($_[0], 0x66); }
	if (!wait_write_ec()) { outb($_[1], 0x62); }
}
subwrite_ec{
	if (!wait_write_ec()) { outb(0x81, 0x66 ); }
	if (!wait_write_ec()) { outb($_[0], 0x62); }
	if (!wait_write_ec()) { outb($_[1], 0x62); }
}
subread_ec{
	if (!wait_write_ec()) { outb(0x80, 0x66 ); }
	if (!wait_write_ec()) { outb($_[0], 0x62); }
	if (!wait_read_ec())  { inb(0x62); }
}
subprint_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  \t00\t01\t02\t03\t04\t05\t06\t07\t|\t08\t09\t0A\t0B\t0C\t0D\t0E\t0F\n";
	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\' \t\t\t\tdumps all ec registers\n";
	print"\'fan_control ?= <reg>\' \t\tQuery register's value\n";
	print"\'fan_control := <reg> <val>\' \tSet register's value\n";
	print"\'fan_control 10|20|40|50|60|80\' \tSet fan speed value in percents\n";
	print"\'fan_control auto|manual\' \tSet fan policy\n";
	print"\'fan_control getspeed\' \tGet current speed fan value in dec format (255-0) lesser is louder\n";
} elsif ($ARGV[0] eq "regs") {
	print_regs();
} elsif ($ARGV[0] eq "?=") {
	initialize_ioports();
	my $r = hex($ARGV[1]);
	printf("REG[0x%02x] == 0x%02x\n", $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%02x\n", $r, $val);
	printf("REG[0x%02x] := 0x%02x\n", $r, $f);
        write_ec( $r, $f);
	printf("REG[0x%02x] == 0x%02x\n", $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 == %d\n", $dec_speed);
	close_ioports();
} else {
	print"wrong arguments!\n";
}	



The logic of the program
#!/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 /usr/local/bin/fan_control.pl 20");
} elsif ($temp < $half) {
	system("/usr/bin/perl -w /usr/local/bin/fan_control.pl 40");
} elsif ($temp < $full) {
	system("/usr/bin/perl -w /usr/local/bin/fan_control.pl 80");
} else {
       	system("/usr/bin/perl -w /usr/local/bin/fan_control.pl auto");
}



Launcher
#!/usr/bin/bash

/usr/local/bin/fan_control.pl manual
while [ true ]
do
	/usr/local/bin/fan_control_logic.pl
	sleep 5
done



Just copy it to / usr / local / bin / and give it permission to execute.

The values ​​of revolutions and threshold temperatures are described as convenient for me, you can play with them, choose the ones that are more suitable for you.

If you have another laptop, with the same problem, you most likely will need to change the values ​​of the register to be written.
The configs written for the already mentioned NBFC will help us
if you can’t find anything, then you can try to find out the values ​​by running:

watch -n 1 sudo fan_control.pl regs


If the registers, and their values ​​are matched correctly, just execute in the console:

sudo fan_control

the fan must change speed.
Profit!

Thank you for your attention, I hope the material will be useful to someone.
Criticism, additions and improvements are welcome.

Also popular now: