联想笔记本电脑在Windows下通过联想驱动实现风扇控制
概述
本文旨在解决部分联想笔记本电脑无法使用主流的风扇控制工具(如Fan Control, SpeedFan)控制风扇的问题。主流的风扇控制工具在这些电脑上会因无法找到控制风扇的EC寄存器而无法发挥作用。但这是不是就意味着没办法控制风扇了呢?答案是否定的。在出厂自带的联想电脑管家程序中有一个叫做降温除尘的工具,运行这个工具能够让风扇达到最高转速,维持几秒然后恢复正常转速。
查看联想电脑管家程序文件,这个降温除尘工具其实是调用了一个叫做IdeaFanPlugin.dll的链接库,根据这个dll的名称猜测这可能是一个叫做IdeaFan程序的插件,通过搜索引擎得知IdeaFan是一个用于控制联想电脑风扇的程序。测试发现无论是联想电脑管家还是IdeaFan,都是通过Lenovo ACPI-Compliant Virtual Power Controller驱动来控制风扇的,这个驱动正是联想笔记本电脑所特有的电源控制驱动。
按照这个思路,笔者最终找到了2个相关的开源项目FanControl和Lenovo-IdeaPad-Z500-Fan-Controller,这2个项目通过逆向了相关驱动,找到了使用Lenovo ACPI-Compliant Virtual Power Controller驱动控制风扇的方法,不过美中不足的是只能控制风扇在最高转速和正常转速间切换,而不能设定和读取风扇的具体转速。
这2个项目还有个问题,就是只能实现让风扇断断续续地以最高转速运行,而我通过调试找到了解决这个问题的方法,详见下文。
具体实现
以下代码主要参考了FanControl项目,查看该开源项目的源码,发现需要用到的核心函数就2个,一个用于控制风扇,一个用于获取风扇状态,且都是通过直接读写\\.\EnergyDrv
这个设备的某些字节实现的,以下是修改项目源码得到的一个简单的样例:
#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分钟,需要继续改进。
通过不断调试,笔者找到了能让风扇保持高速运行的方法,代码如下:
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()
整合进样例中得到能够完美控制风扇的代码:
#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;
default:
printf("FAST mode on\n");
keep_fast();
}
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
但是碍于身边没有其他的联想笔记本电脑,无法进行更多的测试,无法确定其他机型是否使用。