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

概述

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

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

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

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

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

具体实现

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

c 复制代码
#include <stdio.h>
#include <Windows.h>

// NORMAL: Fan spins at normal speed.
// FAST: Fan spins at the highest speed.
enum FanMode { NORMAL, FAST };

// Control the operating mode of the fan.
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;
}

// Get the current operating mode of the fan.
int read_state() {
	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 = 14;
	DWORD outBuffer;
    DWORD bytesReturned = 0;

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

    if (outBuffer == 3) {
        return FAST;
    }
	return Normal;
}

int main() {
    int mode = read_state();
    if (mode == -1) {
        printf("Failed to open \\\\.\\EnergyDrv\n");
        return 1;
    }
    if (mode == NORMAL) {
        printf("FAST mode on\n");
        fan_control(FAST);
    } else {
        printf("NORMAL mode on\n");
        fan_control(NORMAL);
    }
    return 0;
}

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

函数int 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分钟,需要继续改进。

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

c 复制代码
void keep_fast() {
    int interval = 9000; // ms, fine-tuned, see https://www.allstone.lt/ideafan/
    while (1) {
        if (read_state() == FAST) {
            Sleep(interval);
            fan_control(NORMAL); // Reset the fan to NORMAL mode, than switch to FAST mode as soon as possible.
        } else {
            fan_control(FAST);
            Sleep(10);
        }
    }
}

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

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

c 复制代码
#include <stdio.h>
#include <Windows.h>

// NORMAL: Fan spins at normal speed.
// FAST: Fan spins at the highest speed.
enum FanMode { NORMAL, FAST };

// Control the operating mode of the fan.
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;
}

// Get the current operating mode of the fan.
int read_state() {
	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 = 14;
	DWORD outBuffer;
    DWORD bytesReturned = 0;

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

    if (outBuffer == 3) {
        return FAST;
    }
    return NORMAL;
}

// Try to keep the fan in fast mode. Blocking.
void keep_fast() {
    int interval = 9000; // ms, fine-tuned, see https://www.allstone.lt/ideafan/
    while (1) {
        if (read_state() == FAST) {
            Sleep(interval);
            fan_control(NORMAL); // Reset the fan to NORMAL mode, than switch to FAST mode as soon as possible.
        } else {
            fan_control(FAST);
            Sleep(10);
        }
    }
}

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_fast();
            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
相关推荐
cpsvps_net5 小时前
美国服务器环境下Windows容器工作负载智能弹性伸缩
windows
甄超锋5 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
cpsvps8 小时前
美国服务器环境下Windows容器工作负载基于指标的自动扩缩
windows
天波信息技术分享10 小时前
AI云电脑盒子技术分析——从“盒子”到“算力云边缘节点”的跃迁
人工智能·电脑
网硕互联的小客服11 小时前
Apache 如何支持SHTML(SSI)的配置方法
运维·服务器·网络·windows·php
etcix11 小时前
implement copy file content to clipboard on Windows
windows·stm32·单片机
许泽宇的技术分享11 小时前
Windows MCP.Net:基于.NET的Windows桌面自动化MCP服务器深度解析
windows·自动化·.net
非凡ghost12 小时前
AMS PhotoMaster:全方位提升你的照片编辑体验
windows·学习·信息可视化·软件需求
mortimer14 小时前
一次与“顽固”外部程序的艰难交锋:subprocess 调用exe踩坑实录
windows·python·ai编程
OBOO鸥柏商用液晶显示厂家15 小时前
OBOO鸥柏丨75寸/86平板企业办公会议触控一体机核心国产化品牌招投标参数
计算机外设·电脑·大屏端·信息发布系统·会议一体机