使用 thinkfan 管理 T420s 风扇

小黑在升级到 Kubuntu 18.04 后出现了一个比较诡异的问题:挂起唤醒之后有比较大概率会出现主屏分辨率错误,只能在重启后重新设置主副屏才会恢复正常。由于暂时未找到问题的原因,只能让小黑保持24小时待机的方式来规避问题。

24小时待机又引出了另一个问题,T420s 的风扇管理基本等于没有,在待机状态下仍然会保持4000转速,风扇噪音在晚上格外刺耳。于是只能祭出 thinkfan,手动设置风扇的转速。

安装 thinkfan

$ sudo apt install thinkfan lm-sensors
$ sensors

coretemp-isa-0000
Adapter: ISA adapter
Package id 0:  +49.0°C  (high = +86.0°C, crit = +100.0°C)
Core 0:        +48.0°C  (high = +86.0°C, crit = +100.0°C)
Core 1:        +49.0°C  (high = +86.0°C, crit = +100.0°C)

查找 sensors 文件

$ find /sys -type f -name "temp*_input"

/sys/devices/platform/coretemp.0/hwmon/hwmon2/temp3_input
/sys/devices/platform/coretemp.0/hwmon/hwmon2/temp1_input
/sys/devices/platform/coretemp.0/hwmon/hwmon2/temp2_input
/sys/devices/virtual/hwmon/hwmon0/temp1_input

列表里 coretemp.0 目录下的 input 文件对应了上面 sensors 输出的 CPU 以及各个核心的温度来源,也是 thinkfan 需要监控的目标。

配置 thinkfan

$ cat /etc/thinkfan.conf

tp_fan /proc/acpi/ibm/fan
hwmon /sys/devices/platform/coretemp.0/hwmon/hwmon2/temp1_input

(0,     0,      42)
(1,     40,     47)
(2,     45,     52)
(3,     50,     57)
(4,     55,     62)
(5,     60,     77)
(7,     73,     32767)

我在设置里选择了 thinkfan 的简单模式,tp_fan 参数是需要控制的风扇,hwmon 则是需要监控的温度,(0, 0, 42) 表示 (风扇转速等级, 最低温度, 最高温度)。假如当前温度为55度,那么 thinkfan 会将风扇等级设置为 3,温度下降到 41度后,thinkfan 会将风扇等级自动设置为 1。

如果需要为多个 sensor 设置不同的监控参数,可以选择复杂模式:

tp_fan /proc/acpi/ibm/fan

hwmon /sys/devices/platform/coretemp.0/hwmon/hwmon2/temp1_input
hwmon /sys/devices/platform/coretemp.0/hwmon/hwmon2/temp2_input
hwmon /sys/devices/platform/coretemp.0/hwmon/hwmon2/temp3_input

{ "level 0"   # the fan level
#    1  2  3  
#    =======
    (0  0  0 )      # LOWER limit
    (43 42 42)      # UPPER limit
}

{ "level 1"   # the fan level
#    1  2  3  
#    =======
    (40 40 40)      # LOWER limit
    (48 47 46)      # UPPER limit
}

完整的复杂模式设置方法可以参考:thinkfan.conf.complex

设置 thinkpad_acpi modprobe

$ sudo echo "options thinkpad_acpi fan_control=1" | sudo tee /etc/modprobe.d/thinkfan.conf

重启后开启 thinkfan 服务

$ systemctl start thinkfan.service

到这里小黑的风扇终于消停,在待机状态下基本听不到噪音了。

简单模式和复杂模式的区别

首先要明确一点,不论简单模式还是复杂模式,thinkfan 都只能控制一个风扇的转速,如果需要控制多个风扇,需要启动不同的进程并设置对应的配置文件。

可以通过查看源码中了解 thinkfan 在调速时的基本逻辑:

//@see https://github.com/vmatare/thinkfan/blob/master/src/thinkfan.cpp#L114
    while (likely(!interrupted)) {
        std::this_thread::sleep_for(sleeptime);

        temp_state.restart();

        // 读取所有 sensors 的当前温度
        for (const SensorDriver *sensor : config.sensors())
            sensor->read_temps();
        if (unlikely(!temp_state.complete()))
            throw SystemError(MSG_SENSOR_LOST);

        // 当前转速等级不是最高且温度超过当前等级温度上限(UPPER limit),增加转速等级
        if (unlikely(cur_lvl != --config.levels().end() && (*cur_lvl)->up())) {
            while (cur_lvl != --config.levels().end() && (*cur_lvl)->up())
                cur_lvl++;
            log(TF_INF) << temp_state << " -> " <<
                    (*cur_lvl)->str() << flush;
            config.fan()->set_speed(*cur_lvl);
        }
        // 当前转速等级不是最低且温度低于当前等级温度下限(LOWER limit),降低转速等级
        else if (unlikely(cur_lvl != config.levels().begin() && (*cur_lvl)->down())) {
            while (cur_lvl != config.levels().begin() && (*cur_lvl)->down())
                cur_lvl--;
            log(TF_INF) << temp_state << " -> " <<
                    (*cur_lvl)->str() << flush;
            config.fan()->set_speed(*cur_lvl);
            tmp_sleeptime = sleeptime;
        }
        else {
            config.fan()->ping_watchdog_and_depulse(*cur_lvl);
#ifdef DEBUG
            log(TF_DBG) << temp_state << flush;
#endif
        }

    }

简单模式和复杂模式的最大区别就在于 (*cur_lvl)->up()(*cur_lvl)->down() 这两个温度上下限判断逻辑:

//@see https://github.com/vmatare/thinkfan/blob/master/src/config.cpp#L282
bool SimpleLevel::up() const
{ return *temp_state.tmax >= upper_limit().front(); }
bool SimpleLevel::down() const
{ return *temp_state.tmax < lower_limit().front(); }

//@see https://github.com/vmatare/thinkfan/blob/master/src/config.cpp#L300
bool ComplexLevel::up() const
{
    std::vector<int>::const_iterator temp_it = temp_state.biased_temps().begin();
    const int *upper_idx = upper_limit().data();

    while (temp_it != temp_state.biased_temps().end())
        if (*temp_it++ >= *upper_idx++) return true;

    return false;
}
bool ComplexLevel::down() const
{
    std::vector<int>::const_iterator temp_it = temp_state.biased_temps().begin();
    const int *lower_idx = lower_limit().data();

    while (temp_it != temp_state.biased_temps().end() && *temp_it < *lower_idx) {
        temp_it++;
        lower_idx++;
    }

    return temp_it == temp_state.biased_temps().end();
}

复杂模式下和上下限温度相比较的是每个 sensor 的 biased_temp_,而非 tmax。只要 temp_state.biased_temps() 中任意一个值高于其对应 UPPER limit 都会调高风扇转速,对应的,需要 temp_state.biased_temps() 的所有值都低于其对应 LOWER limit 时才会降低转速。

参考资料