背景:一台「高烧不退」的 NAS
如同之前的文章所说,我有一台天钡 WTR MAX AMD(Ryzen 7 PRO 8845HS)当 NAS 用,跑的是飞牛 OS(fnOS)。本质是 Debian 12 + 定制的 trim 内核:
PLAINTEXT
$ uname -r
6.18.18.c1032-trim
NAS系统里确实已经安装了三个风扇控制相关的服务:
服务 | 状态 | 原因 |
|---|---|---|
| 空转 | 依赖 |
| 未启动 | 缺少 |
| 常驻 | 端口 11987,但没有风扇显示 |
鉴于在/sys/class/hwmon/ 却没用可以控制的风扇节点,很显然是驱动出了点问题。机器上的风扇全靠 BIOS 兜底,用出厂定义的策略运行。为了能有效更改风扇策略,通过PWN控制风扇转速,我开始了一场从「没有驱动」到「原生驱动 + 开机自愈」的探索。
现场勘查:温度源很丰富,唯独没有风扇
先检查一下机器上的温度传感器:
PLAINTEXT
/sys/class/hwmon/hwmon6/name → k10temp (Tctl ≈ 81°C)
/sys/class/hwmon/hwmon5/name → amdgpu (edge)
... 还有 5 块 NVMe、spd5118 (DDR5 温度)
唯独没有任何 fan*_input / pwm* 节点,说明内核压根没认读这颗 Super I/O 芯片。
通过查询硬件资料得知:主板用的是 ITE IT8613E。这就说明了问题:
主线内核的
it87驱动支持 IT8613E 之后的芯片(IT8620E/IT8625E/IT8728F 等),但不认 IT8613E 这个非标 ID;fnOS 自己带了一个开箱即用的方案:
pwm-fancontrol.service要从/boot/board.json读机型配置——本机没有这个文件,于是服务启动即空转,什么都没干。
换成大白话:系统里有个专门管风扇的驱动,但它不知道该不该管这台机器,就放弃了。
第一回合:主线驱动 + force_id 强上(能用,但憋屈)
主线 it87 虽然不认识 0x8613,但认识长相几乎一样的 IT8620E (0x8620)。那么先用 force_id 映射一下:
BASH
sudo modprobe it87 force_id=0x8620
成功!hwmon 里立刻冒出一堆节点。但没有转速计的那一路(pwm1)和被认成空挡的 fan4 之外,还真有几个通道能转。
接下来需要映射风扇通道,把某一路 pwm_enable 写成 0(该通道被芯片接管、全速转)再比较各 fan 转速变化,就能定位物理风扇:
通道(模拟 IT8620E) | 实测定位 | 证据 |
|---|---|---|
| CPU 风扇 | 写占空比 +896 RPM |
| 机箱风扇 ① | 写占空比 +1196 RPM |
| 机箱风扇 ② |
|
| 无转速计 | 只有 PWM 没有 tach |
| 空头 | 写什么都无变化 |
但用着用着发现问题:pwm4 和 pwm5 被芯片的自动模式锁死了。
写占空比 → 内核报
EBUSY(芯片自动控制模式下占空比寄存器是只读的);把
pwm_enable写成1想切手动 → 秒弹回2(自动)。
这是这颗 Super I/O 的内部机制:pwm_ctrl 寄存器的某些位(bit7)把通道钉死在自动控制里,软件改不动。五路里只有两路真正可控,另一半是摆设。
第二回合:使用飞牛树外原生驱动
首先先在飞牛应用中心安装ite-it87驱动,但是由于没有预购建产物,没有直接生效:
那么我们就需要手动构建一下,先找到地址:
BASH
$ ls /usr/src/
it87-1.0.63
看它的 DEVID 表,有0x8613,说明支持这颗芯片。
构建并安装到当前内核:
BASH
sudo dkms build it87/1.0.63 -k 6.18.18.c1032-trim
sudo dkms install it87/1.0.63 -k 6.18.18.c1032-trim
# 产物落在
# /lib/modules/6.18.18.c1032-trim/updates/dkms/it87.ko.xz
顺手做两件清理:
BASH
# 1. force_id 不再需要,删掉模拟配置
sudo rm /etc/modprobe.d/it87.conf
# 2. 保留开机加载(只写模块名,不用参数)
echo 'it87' | sudo tee /etc/modules-load.d/it87.conf
然后加载查看状态:
PLAINTEXT
$ cat /sys/class/hwmon/hwmon9/name
it8613
原生 IT8613E 的状态
这颗芯片只有 3 个转速计输入(tach),不是 4 个
sysfs 里只有:
PLAINTEXT
fan2_input fan3_input fan4_input
没有 fan1、没有 fan5_input、也没有 pwm1。之前模拟版看到的 fan1/fan5 是 IT8620E 的幻影,不是真实存在的接线。
但 PWM 输出是 4 路,其中一路注定开环。
最终物理映射是:
通道 | 物理风扇 | 转速反馈 | 控制方式 |
|---|---|---|---|
| CPU 风扇 | ✅ | 闭环(可做 RPM 校准) |
| 机箱风扇 ① | ✅ | 闭环 |
| 空头(未接线) | — | — |
| 机箱风扇 ② | ❌ 无 tach | 只能开环(固定 % 或盲调曲线) |
机箱第二把风扇能调速,但永远看不到它的转速,即芯片第 4 个 tach 输入不存在。想要转速反馈只能另接转速计,否则就老老实实开环。
解锁序列:enable 0 → 写占空比 → enable 1
这是和模拟版最大的区别,也是原生驱动的爽点。native 布局下所有通道都吃这套序列:
BASH
# 例:把 CPU 风扇(pwm2)设为 220/255 ≈ 86%
echo 0 > /sys/class/hwmon/hwmon9/pwm2_enable
echo 220 > /sys/class/hwmon/hwmon9/pwm2
echo 1 > /sys/class/hwmon/hwmon9/pwm2_enable
enable=1 写进去不会弹回,占空比也不会 EBUSY。芯片自动模式锁死的魔咒解除了。
模块重载(rmmod/modprobe)会把所有通道复位成出厂默认: pwm2/pwm3 → 255 && enable=0(全速)、pwm4/pwm5 → enable=2(自动)。 重载后必须重新跑一遍保持值,否则风扇全速咆哮。
顺手修好 CoolerControl:「无可控通道」之谜
驱动搞定后,需要重启守护进程:
BASH
sudo systemctl restart coolercontrold
journal 里立刻确认:
PLAINTEXT
CoolerControl: detected device "it8613" with channels [fan2, fan3, fan4, fan5]
UI 里终于可以绑定:
温度源绑
k10temp的 Tctl(这是回报给主板的真实 CPU 温度);fan5(机箱扇②)由于没有转速反馈,只能固定百分比或温度曲线开环,无法显示 RPM。
隐患:fnOS 升级内核后,驱动会消失吗?
飞牛升级后,这个驱动确实有风险。modules-load.d 和 dkms 注册是持久的,但 fnOS 的包管理器似乎在升级内核时并不会自动跑一次 dkms autoinstall。看构建记录:
老内核
6.1.0-42/48/49-amd64、6.12.18-trim、6.18.18-trim:有构建记录 ✅近期内核
c938 / c952 / c978 / c1032:没有自动触发的构建记录 ❌
所以升级后的剧本大概率是:新内核 + modules-load 尝试加载 it87 → 找不到 dkms 产物 → 主线 it87 不认识 0x8613 → 绑定失败 → 风扇回落到 BIOS 自动控制。
好消息是:这是安全的降级,不会炸机,顶多是噪音变大。手动恢复也就一条命令:
BASH
sudo dkms autoinstall -k $(uname -r)
兜底设计:让「恢复」这件事永不失败
光有手动命令不够——万一用户忘了呢?于是按「构建不成功也要让系统正常可用,且要给出明确提示」的原则,做了一套四级阶梯的自愈机制。
6.1 恢复脚本:/usr/local/sbin/it87-fan-recover.sh
思路是从上到下逐级尝试,任何一级成功就收工;全部失败也要优雅退出:
SH
#!/bin/sh
# IT8613E 风扇驱动开机自检兜底
# 逻辑:已绑定→静默退出;未绑定→dkms 模块直载→dkms autoinstall 重建→force_id=0x8620 主线兜底。
# 本脚本永远 exit 0,任何失败只记录与提示,绝不影响系统启动。
TAG="fan-recover"
K=$(uname -r)
log() { logger -t "$TAG" "$*"; }
bound() { grep -qs '^it8613$' /sys/class/hwmon/hwmon*/name; }
dkms_ko_present() { ls /lib/modules/$K/updates/dkms/it87.ko* >/dev/null 2>&1; }
# 1) 已绑定,无事可做
if bound; then
log "IT8613E 已绑定,无需处理"
exit 0
fi
log "IT8613E 未绑定(内核 $K),开始恢复"
# 2) dkms 模块已在但未加载(典型:新内核装好后 modules-load 先于 dkms 落地)
if dkms_ko_present; then
log "发现 dkms 模块,尝试直接加载"
depmod -a "$K" 2>/dev/null
modprobe it87 2>/dev/null
fi
# 3) 尝试 dkms 自动构建
if ! bound && ! dkms_ko_present; then
log "尝试 dkms autoinstall -k $K"
if dkms autoinstall -k "$K" >/dev/null 2>&1 && dkms_ko_present; then
depmod -a "$K" 2>/dev/null
modprobe it87 2>/dev/null
fi
fi
# 4) 成功路径
if bound; then
log "恢复成功:IT8613E 已绑定"
systemctl try-restart coolercontrold.service 2>/dev/null
rm -f /run/it87-fan-alert
exit 0
fi
# 5) 构建失败兜底:主线驱动 + force_id 模拟模式(基础控制可用)
log "WARN: dkms 构建失败/不可用,回退 force_id=0x8620 模拟模式"
modprobe -r it87 2>/dev/null
if modprobe it87 force_id=0x8620 2>/dev/null; then
MODE="force_id=0x8620 兜底模式(仅 pwm2/pwm3 可靠可控)"
else
MODE="完全失败(无风扇控制,BIOS 默认转速)"
fi
# 失败提示:wall 广播 + journal + 状态文件
MSG="[风扇驱动告警] IT8613E 原生驱动构建失败。当前: $MODE。请执行: sudo dkms autoinstall -k $(uname -r)"
echo "$MSG" > /run/it87-fan-alert
logger -t "$TAG" -p user.warning "$MSG"
wall "$MSG" 2>/dev/null
exit 0
设计要点:
阶梯 | 动作 | 结果 |
|---|---|---|
① | 已绑定( | 静默 exit 0 |
② | dkms 产物存在 → | 最常见,新内核装好后直载 |
③ | 产物不存在 → | 升级内核后的主战场 |
④ | 仍失败 → | 拿回 pwm2/pwm3 基础可控 |
⑤ | 提示: | 构建失败绝不沉默 |
核心原则:脚本永远 exit 0 ,也就是说无论哪一级失败,都只记录、提示,绝不让开机流程卡在它身上。成功路径还会顺手 systemctl try-restart coolercontrold.service,让 UI 立刻看到通道。
开机自愈:systemd oneshot
INI
[Unit]
Description=IT8613E fan driver auto-recovery (dkms fallback)
After=systemd-modules-load.service
After=local-fs.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/it87-fan-recover.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
依赖顺序是关键:挂在 systemd-modules-load.service 之后,让内核先按 modules-load.d 正常加载;如果那时没绑上(新内核+缺编译产物),我们的自愈脚本再补刀。RemainAfterExit=yes 让服务状态保持 active,一眼就能看出这步执行过。
实测验证
模拟最坏情况:把驱动卸载,让系统回到「裸奔」状态,再跑恢复链路:
BASH
sudo rmmod it87 # 模拟升级内核后的未绑定状态
sudo systemctl start it87-fan-recover.service
看 journal:
PLAINTEXT
fan-recover: IT8613E 未绑定(内核 6.18.18.c1032-trim),开始恢复
fan-recover: 发现 dkms 模块,尝试直接加载
fan-recover: 恢复成功:IT8613E 已绑定
几秒内 hwmon 里 it8613 回来了,coolercontrold 也重新 active。自愈链路成立。
最终结果
可以使用coolercontrold界面控制风扇转速:


