1223 字
4 分钟
Lenovo Legion Q7CN 风扇控制协议 — 完整逆向记录
2026-06-16
2026-06-29

Lenovo Legion Q7CN 风扇控制协议 — 完整逆向记录#

目标:将 Legion Zone 的风扇控制功能移植到 Linux 结论:协议已完整破解,可在 Linux 上实现


背景#

硬件信息#

项目
型号Lenovo Legion Pro 7 (MTM: 83F4)
BIOSQ7CN40WW (Gen 10, 2025 款)
EC 芯片ITE IT5508 (0x5508)
风扇数量3 (CPU/GPU/VRM)
操作系统Windows 11 + Ubuntu 26.04 双系统

目标#

Legion Zone 是 Lenovo 官方的 Windows 风扇/性能控制软件。Linux 下缺少等效工具,已有的开源项目(LenovoLegionLinux、lenovo-legion-linux-toolkit)不兼容 Q7CN 固件:

  • LenovoLegionLinux (johnfanv2):风扇曲线读取全零,写入无效
  • lenovo-legion-linux-toolkit (bolekjar):虽声称支持 16IRX9H,但风扇曲线实际控不住
  • ChaoticSi1ence 的 fork:Custom 模式下会硬关机

必须从 Windows 端逆向出真实协议。


逆向过程#

第一阶段:尝试抓 USB HID 包(失败)#

最初的假设是风扇控制走 USB HID Feature Report 0xCC,尝试用 Wireshark + USBPcap 抓包。但随后发现 EC(嵌入式控制器)走的是 LPC/eSPI 总线,不经过 USB 控制器,USBPcap 抓不到任何数据。

第二阶段:WMI 静态探测#

使用 Python WMI 库 dump 了所有 Lenovo 相关的 WMI 类:

LENOVO_FAN_METHOD ← 风扇方法
LENOVO_FAN_TABLE_DATA ← 风扇表数据(只读结构)
LENOVO_OTHER_METHOD ← WMAE 通用接口
LENOVO_GAMEZONE_DATA ← 游戏区数据
LENOVO_CAPABILITY_DATA_00 ~ 02 ← 功能能力表
LENOVO_DISCRETE_DATA ← 离散值

从静态 dump 中确认了 3 组风扇配置:

风扇Fan IDSensor ID最大转速
CPU115100 RPM
GPU255100 RPM
VRM446500 RPM

但静态 WMI 属性读取无法捕获动态变化——两次 dump 完全一致。

第三阶段:发现真实 WMI 方法#

通过 PowerShell 的 Get-CimInstance 查看 LENOVO_FAN_METHOD 的实际方法签名:

Fan_Get_Table (FanID:UInt8, SensorID:UInt8, → FanTable:UInt32Array, SensorTable:UInt32Array)
Fan_Set_Table (FanTable:UInt8Array)

这与 LenovoLegionToolkit 源码中的 WMI.LenovoFanMethod.FanSetTableAsync() 一致。

第四阶段:缓冲区格式破解#

首先基于 LenovoLegionToolkit 源码尝试 4 字节头部格式:

byte[0]=FSTM=1, byte[1]=FSID=0, byte[2-3]=FSTL=0, byte[4-23]=10×ushort

结果:写入 RPM 值(1600-3500)触发 EC 保护断电!

发现 Fan_Get_Table 返回的不是 RPM,而是档位值 0-10。

改用档位值后写入不败但验证失败——数据偏移了 1 位。通过系统性测试不同偏移量:

测试头部结果
offset=1 (8-bit, no header)仅 FSTM[6,8,10,0,0,…] ← 偏移
offset=2 (16-bit, FSTM+FSID)2 字节头[3,4,5,6,7,8,9,10,0,0] ← 偏移
offset=6 (16-bit interlaced)6 字节头[1,2,3,4,5,6,7,8,9,10] ← 完美!
offset=1 interlaced2 字节头[6,8,10,0,…]
reverse test6 字节头[10,9,8,7,6,5,4,3,2,1] ← 完美!

关键发现:Q7CN 风扇表缓冲区头部是 6 字节,不是 4 字节,且数据是 8 位值交替排列在 16 位字段的低字节中。

第五阶段:Process Monitor 追踪(辅助验证)#

用 ProcMon 捕获 Legion Zone 的文件和注册表操作,发现 Legion Zone 内部日志路径。但 DLL(SystemThermalPlugin.dll、WSHardwarePlugin.dll 等)中没有直接暴露 WMI GUID 字符串,说明 WMI 方法是动态调用的。

ETW 追踪(Microsoft-Windows-WMI-Activity provider)也未捕获到有效事件——该 provider 可能需要在特定配置下才能记录方法调用。


最终协议#

Fan_Set_Table 缓冲区格式#

总长度:64 字节
偏移 长度 内容
0x00 1 FSTM = 0x01(必须为 1,否则 EC 忽略。Linux 驱动曾发 0,是 bug 根因)
0x01 5 保留(全 0)
0x06 2 FSS0(位 0: 档位值, 位 1: 始终为 0)
0x08 2 FSS1
0x0A 2 FSS2
0x0C 2 FSS3
0x0E 2 FSS4
0x10 2 FSS5
0x12 2 FSS6
0x14 2 FSS7
0x16 2 FSS8
0x18 2 FSS9
0x1A-0x3F 填充 0

档位-转速对照#

档位转速(约)
0停转
1~1600 RPM
2~1800 RPM
3~2000 RPM
4~2200 RPM
5~2400 RPM
6~2800 RPM
7~3200 RPM
8~3600 RPM
9~4000 RPM
10~5100 RPM (CPU/GPU) / ~6500 RPM (VRM)

Fan_Get_Table 调用#

输入:FanID (UInt8), SensorID (UInt8)
输出:FanTable (UInt32[10]) — 10 个档位值 0-10
SensorTable (UInt32[10]) — 10 个温度阈值

限制#

  • 风扇表仅在自定义模式下生效:安静/均衡/性能模式使用 EC 内部固话曲线,写入的风扇表被忽略
  • 必须用 Fn+Q 切换到自定义模式:模式切换后 EC 重新加载风扇表
  • 写入超范围值(>10 或 RPM 值)可能触发保护断电

Windows 实现#

Terminal window
# 读取 3 个风扇曲线
$fan = Get-CimInstance -Namespace root/WMI -ClassName LENOVO_FAN_METHOD
$cpu = Invoke-CimMethod $fan -MethodName Fan_Get_Table -Args @{FanID=1;SensorID=1}
$gpu = Invoke-CimMethod $fan -MethodName Fan_Get_Table -Args @{FanID=2;SensorID=5}
$vrm = Invoke-CimMethod $fan -MethodName Fan_Get_Table -Args @{FanID=4;SensorID=4}
# 写入
function Set-FanTable($vals) {
$buf = [byte[]]::new(64)
$buf[0] = 1 # FSTM
for ($i=0; $i -lt 10; $i++) {
$buf[6 + 2*$i] = [byte][Math]::Min(10, [Math]::Max(0, $vals[$i]))
}
Invoke-CimMethod $fan -MethodName Fan_Set_Table -Args @{FanTable=$buf}
}
# 拉满
Set-FanTable @(10,10,10,10,10,10,10,10,10,10)
# 静音
Set-FanTable @(1,1,1,1,1,1,1,1,1,1)

Linux 移植方案#

方案 A:修补 ChaoticSi1ence fork#

git clone https://github.com/ChaoticSi1ence/legion-pro-7-16iax10h-linux.git
cd legion-pro-7-16iax10h-linux
sudo bash kernel_module/build-legion-module.sh

需修补的点:

  1. Fan_Set_Table 缓冲区头部从 4 字节改为 6 字节
  2. Fan_Get_Table 的档位值从 UInt32 正确解析(已是 0-10)

方案 B:直接用 WMI char 设备#

# /dev/wmi/ 下找到 GUID 92549549-4BDE-4F06-AC04-CE8BF898DBAA
# 使用 ioctl(WMI_CMD) 发送 Fan_Set_Table 缓冲区

方案 C:通过 firmware-attributes sysfs(如果内核暴露)#

cat /sys/class/firmware-attributes/lenovo/attributes/fan_curve/current_value

待办:在 Ubuntu 26.04 上实际测试方案 A 并修补


经验教训#

  1. 不要假设 USB:笔记本 EC 通常走 LPC/eSPI,USB 抓包工具无效
  2. WMI 方法调用 vs 属性读取Get-CimInstance 读的是静态类属性,真正的方法要 Invoke-CimMethod
  3. 缓冲区偏移要系统性测试:4 字节头是 LLT 的标准但 Q7CN 用了 6 字节,差 2 字节一切全乱
  4. 值域很重要:写 RPM 值而非档位值导致保护断电,差点以为协议完全错了
  5. Process Monitor 抓不到 WMI:ProcMon 追踪的是文件/注册表/进程,WMI 调用需要 ETW 或专门的 WMI 追踪工具

参考#

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Lenovo Legion Q7CN 风扇控制协议 — 完整逆向记录
https://caoyue.xin/posts/lenovo-fan/
作者
Colton/曹越
发布于
2026-06-16
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录