联想笔记本电脑在Windows下通过联想驱动实现风扇控制

概述

本文旨在解决部分联想笔记本电脑无法使用主流的风扇控制工具(如Fan Control, SpeedFan)控制风扇的问题。主流的风扇控制工具在这些电脑上会因无法找到控制风扇的EC寄存器而无法发挥作用。但这是不是就意味着没办法控制风扇了呢?答案是否定的。在出厂自带的联想电脑管家程序中有一个叫做降温除尘的工具,运行这个工具能够让风扇达到最高转速,维持几秒然后恢复正常转速。

降温除尘

查看联想电脑管家程序文件,这个降温除尘工具其实是调用了一个叫做WSHardwarePlugin.dll的链接库,该链接库位于程序安装目录中的plugins文件夹中,使用IDA反编译此dll可以找到一个名为pcm_plugin_action的导出函数,查看此函数能找到如下控制风扇的关键代码:

IDA反编译

鉴于笔者能力有限,未能进一步挖掘,有能力或感兴趣的朋友可以深入研究一下这个dll。测试发现联想电脑管家是通过Lenovo ACPI-Compliant Virtual Power Controller驱动来控制风扇的,这个驱动正是联想笔记本电脑所特有的电源控制驱动。

按照这个思路,笔者最终找到了2个相关的开源项目FanControlLenovo-IdeaPad-Z500-Fan-Controller,这2个项目通过逆向了相关驱动,找到了使用Lenovo ACPI-Compliant Virtual Power Controller驱动控制风扇的方法,不过美中不足的是只能控制风扇在最高转速和正常转速间切换,而不能设定和读取风扇的具体转速。

这2个项目还有个问题,就是只能实现让风扇断断续续地以最高转速运行,而我通过调试找到了解决这个问题的方法,详见下文。

程序下载

我参考上文提到的开源项目重写并完善了代码,解决了IdeaFan控制风扇时会短暂暂停的问题,编写了一个简单的联想笔记本电脑风扇控制程序并将源码在Github上开源,你可以直接通过以下链接下载编译好的样例程序:

https://github.com/jiarandiana0307/Lenovo-Fan-Control/releases

下载LenovoFanControl-x64.exe程序,双击运行后,程序图标会出现在系统托盘,随后风扇会以最高转速运行,在开始的几十秒的时间里风扇会有几次短暂的停顿,然后风扇就能一直以最高转速稳定运行了。

点击系统托盘里的程序图标可以打开程序菜单,程序菜单的首行会显示当前风扇运行的状态,可能是以下三种状态:

  1. 低转速:此时风扇正以最低转速运转。
  2. 高转速:此时风扇正以最高转速运转。
  3. 正常转速:此时风扇正以正常转速运转。

此时可以点击程序菜单的低转速高转速,或者按下相应的快捷键Ctrl+Alt+F10Ctrl+Alt+F11,让风扇在最低转速和最高转速之间切换。另外,点击程序菜单的正常转速或快捷键Ctrl+Alt+F12可以让风扇恢复正常转速。

最后,点击程序菜单中的退出即可终止程序,随后风扇会恢复为正常转速。

注意:请谨慎使用低转速模式,因为本程序没有温度监控功能,所以在低转速模式下容易引起硬件高温从而导致硬件损坏。

具体实现

以下代码主要参考了FanControl项目,查看该开源项目的源码,发现需要用到的核心函数就2个,一个用于控制风扇,一个用于获取风扇状态,且都是通过直接读写\\.\EnergyDrv这个设备的某些字节实现的,以下是修改项目源码得到的一个简单的样例:

#include <stdio.h>
#include <time.h>
#include <Windows.h>

enum FanMode {
    NORMAL,
    FAST
};

static int NORMAL_MODE_EXPECTED_VALUE = -1;

int fan_control(enum FanMode mode) {
    HANDLE hndl = CreateFileW(L"\\\\.\\EnergyDrv", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hndl == INVALID_HANDLE_VALUE) {
        return -1;
    }
    // lpInBuffer value: 06 00 00 00  01 00 00 00  01 00 00 00 ~ [ 6, 1, 1 ] (inv endian)
    DWORD inBuffer[3] = { 6, 1 };
    inBuffer[2] = mode;
    DWORD bytesReturned = 0;

    DeviceIoControl(hndl, 0x831020C0, inBuffer, sizeof(inBuffer), NULL, 0, &bytesReturned, NULL);
    CloseHandle(hndl);

    return 1;
}

enum FanMode read_state() {
    if (NORMAL_MODE_EXPECTED_VALUE == -1) {
        // Set fan spinning mode to NORMAL to get NORMAL_MODE_EXPECTED_VALUE at the first run
        fan_control(NORMAL);
    }
    HANDLE hndl = CreateFileW(L"\\\\.\\EnergyDrv", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hndl == INVALID_HANDLE_VALUE) {
        return -1;
    }
    // lpInBuffer value: 0E 00 00 00 ~ [ 14 ] (inv endian)
    DWORD inBuffer[1] = { 14 };
    DWORD outBuffer[1];
    DWORD bytesReturned = 0;

    DeviceIoControl(hndl, 0x831020C4, inBuffer, sizeof(inBuffer), outBuffer, sizeof(outBuffer), &bytesReturned, NULL);
    CloseHandle(hndl);

    if (NORMAL_MODE_EXPECTED_VALUE == -1) {
        // Set this value when the fan is in normal mode at the first run
        NORMAL_MODE_EXPECTED_VALUE = outBuffer[0];
    }
    return outBuffer[0] == NORMAL_MODE_EXPECTED_VALUE ? NORMAL : FAST;
}

int main() {
    int mode = read_state();
    switch (mode) {
        case -1:
            printf("Failed to open \\\\.\\EnergyDrv\n");
            break;
        case FAST:
            printf("NORMAL mode on\n");
            fan_control(NORMAL);
            break;
        case NORMAL:
            printf("FAST mode on\n");
            fan_control(FAST);
            break;
    }
    printf("Enter to exit...");
    getchar();
    return 0;
}

函数int fan_control(enum FanMode mode)用于控制风扇的运行状态,参数mode指定风扇运行模式,取值0控制风扇恢复正常转速,取值1控制风扇达到最高转速。 返回值-1表示没有找到联想电源管理驱动,无法实现控制,返回1表示成功实现控制。

函数enum FanMode read_state()用于获取风扇当前的运行状态,返回0表示当前风扇为正常转速模式,返回1表示当前风扇为最高速运行模式。

编译运行程序,控制台输出FAST mode on则风扇开启最高转速,程序结束运行,再次运行程序控制台输出NORMAL mode on,风扇恢复正常运行状态。如果输出Failed to open \\.\EnergyDrv则说明没有安装Lenovo ACPI-Compliant Virtual Power Controller驱动,或没有驱动正常运行。

到这里事情还没结束,上述代码控制风扇所使用的实际上是驱动所提供的一个风扇除尘功能(联想电脑管家和IdeaFan也是如此),在实际使用中风扇并非如我们所设想的那样一直保持最高转速状态运行,而是会先以最高速度转约9秒钟,暂停2秒,再以最高转速转9秒,再停顿2秒如此循环往复,直到大约2分钟后风扇恢复正常运行状态,风扇又重新交给系统进行控制。

也就是说,开源项目中的上述代码只能让风扇断断续续地运行,且只能维持2分钟,需要继续改进。

通过不断调试,我找到了能让风扇保持高速运行的方法,代码如下:

void keep_fan_running() {
    const int interval = 9000; // ms, fine-tuned, see https://www.allstone.lt/ideafan/
    while (1) {
        while (read_state() != FAST) {
            fan_control(FAST);
            Sleep(10);
        }
        Sleep(interval);
        fan_control(NORMAL); // Reset the fan to NORMAL mode, than switch to FAST mode as soon as possible.
    }
}

在函数void keep_fan_running()中,先循环判断风扇当前运行状态,如果不为高速运行状态则不断尝试控制风扇高速运行,当风扇此时为高速运行状态时,维持9秒后将风扇切换回正常运行状态以此重置计时,随后在风扇停止转动前又立即切换回高速运行状态,这样一次快速的切换就能让风扇跳过原来2秒漫长的停顿,取而代之的是0.1秒左右的短暂停顿,这样不断循环就能实现风扇一直以高速状态运行了。

void keep_fan_running()整合进样例中得到能够完美控制风扇的代码:

#include <stdio.h>
#include <time.h>
#include <Windows.h>

enum FanMode {
    NORMAL,
    FAST
};

static int NORMAL_MODE_EXPECTED_VALUE = -1;

int fan_control(enum FanMode mode) {
    HANDLE hndl = CreateFileW(L"\\\\.\\EnergyDrv", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hndl == INVALID_HANDLE_VALUE) {
        return -1;
    }
    // lpInBuffer value: 06 00 00 00  01 00 00 00  01 00 00 00 ~ [ 6, 1, 1 ] (inv endian)
    DWORD inBuffer[3] = { 6, 1 };
    inBuffer[2] = mode;
    DWORD bytesReturned = 0;

    DeviceIoControl(hndl, 0x831020C0, inBuffer, sizeof(inBuffer), NULL, 0, &bytesReturned, NULL);
    CloseHandle(hndl);

    return 1;
}

enum FanMode read_state() {
    if (NORMAL_MODE_EXPECTED_VALUE == -1) {
        // Set fan spinning mode to NORMAL to get NORMAL_MODE_EXPECTED_VALUE at the first run
        fan_control(NORMAL);
    }
    HANDLE hndl = CreateFileW(L"\\\\.\\EnergyDrv", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hndl == INVALID_HANDLE_VALUE) {
        return -1;
    }
    // lpInBuffer value: 0E 00 00 00 ~ [ 14 ] (inv endian)
    DWORD inBuffer[1] = { 14 };
    DWORD outBuffer[1];
    DWORD bytesReturned = 0;

    DeviceIoControl(hndl, 0x831020C4, inBuffer, sizeof(inBuffer), outBuffer, sizeof(outBuffer), &bytesReturned, NULL);
    CloseHandle(hndl);

    if (NORMAL_MODE_EXPECTED_VALUE == -1) {
        // Set this value when the fan is in normal mode at the first run
        NORMAL_MODE_EXPECTED_VALUE = outBuffer[0];
    }
    return outBuffer[0] == NORMAL_MODE_EXPECTED_VALUE ? NORMAL : FAST;
}

void keep_fan_running() {
    const int interval = 9000; // ms, fine-tuned, see https://www.allstone.lt/ideafan/
    while (1) {
        while (read_state() != FAST) {
            fan_control(FAST);
            Sleep(10);
        }
        Sleep(interval);
        fan_control(NORMAL); // Reset the fan to NORMAL mode, than switch to FAST mode as soon as possible.
    }
}

int main() {
    int mode = read_state();
    switch (mode) {
        case -1:
            printf("Failed to open \\\\.\\EnergyDrv\n");
            break;
        case FAST:
            printf("NORMAL mode on\n");
            fan_control(NORMAL);
            break;
        case NORMAL:
            printf("FAST mode on\n");
            keep_fan_running();
            break;
    }
    printf("Enter to exit...");
    getchar();
    return 0;
}

相关进一步完善的代码及样例程序我已上传至Github:

https://github.com/jiarandiana0307/Lenovo-Fan-Control

原理探讨

那么这样控制风扇的原理是什么呢?归根结底,笔记本电脑的风扇一般是由嵌入式控制器(Embedded Controller, EC)控制的,我们可以通过改变EC中寄存器的值来控制风扇。

但是,有些电脑的数据手册并没有说明EC用于控制风扇转速的是哪个寄存器,所以,前文提到的开源项目的作者逆向了Lenovo Energy Manager和相关驱动,最终发现可以通过Lenovo ACPI-Compliant Virtual Power Controller驱动控制EC,进而实现控制风扇的目的。

前文代码中出现的\\.\EnergyDrv就是由Lenovo ACPI-Compliant Virtual Power Controller驱动所生成的设备,也是该驱动向外提供的用于控制和访问的接口,上文的代码中直接读写\\.\EnergyDrv这个设备其实就是在与驱动交互,进而实现控制风扇、获取风扇状态的目的。进一步实验也证实了这一点,当这个驱动正常工作时,可以正常访问\\.\EnergyDrv设备,而当卸载了驱动并重启后这个设备就不存在且无法访问了。

不妨大胆假设,只要是安装了Lenovo ACPI-Compliant Virtual Power Controller驱动的联想笔记本电脑,应该都可以实现风扇控制。根据网上的信息和我自己的测试,以下联想机型是可行的:

  • Lenovo G500
  • Lenovo G580
  • Lenovo Ideapad 3 15ALC6 82KU
  • Lenovo Ideapad 3 15ITL6
  • Lenovo IdeaPad 330
  • Lenovo IdeaPad Y510
  • Lenovo Ideapad Z580
  • Lenovo Xiaoxin 15IIL 2020
  • Lenovo Y410P
  • Lenovo Y500
  • Lenovo Y50-70
  • Lenovo Y510p
  • Lenovo Y580
  • Lenovo Z580

但是碍于身边没有其他的联想笔记本电脑,无法进行更多的测试,无法确定其他机型是否使用。

参考

  1. Lenovo-Fan-Control
  2. IdeaFan
  3. FanControl
  4. Lenovo-IdeaPad-Z500-Fan-Controller
  5. Windows Drivers Reverse Engineering Methodology
posted @ 2024-10-31 19:42  KiraDiana  阅读(2498)  评论(0)    收藏  举报