给 Surface Pro 7 装飞牛:让内置电池变成 UPS
把 Surface Pro 7 当 NAS 用的时候,最别扭的一件事是:飞牛认不出它自带的电池。UPS 页面里空空如也,断电就直接硬关机。
现象
飞牛装好之后,系统里查不到任何电池设备:
ls /sys/class/power_supply
# 空目录
资源监控里的电池卡片、设置里的 UPS,自然都是空的。但硬件明明是好的——电池能充电、能续航。
原因:Surface 第 7 代把电池挪出了 ACPI
普通笔记本的电池走 ACPI 的 PNP0C0A 设备,内核的 acpi/battery 一加载,/sys/class/power_supply/BAT0 就出现了。发行版内核都会编这个,所以「笔记本当 NAS」一般是开箱即用的。
Surface Pro 7 属于微软第 7 代 Surface。从这一代开始,电池和供电信息不再通过标准 ACPI 暴露,改由 SSAM(Surface System Aggregator Module,板载嵌入式控制器) 直接提供,ACPI 命名空间里只留一个控制器节点:
MSHW0084 SSAM 控制器
MSHW0116 Surface Pro 7 的平台枢纽(7+ 是 MSHW0119)
硬件在、ACPI 也枚举出来了,但没有驱动去绑定它。而 NAS 内核里那些通用电池驱动——sbs-battery、max17042_battery、bq27xxx_battery、ds2760_battery——全都要 I2C 或 ACPI 电池接口,对 Surface 一个都用不上。
结果就是:系统认为这台机器没有电池。
思路:拿目标内核的头文件,把缺的驱动编出来
Linux 上游从 5.13 起就支持这套硬件了,代码一直都在,只是发行版没编。需要的模块有五个:
| 模块 | 作用 | 产出 |
|---|---|---|
surface_aggregator | SSAM 总线 / 控制器核心 | — |
surface_aggregator_registry | 设备注册表,按 ACPI ID 认机型 | 创建电池、供电等子设备 |
surface_battery | 电池驱动 | /sys/class/power_supply/BAT0 |
surface_charger | 供电驱动 | /sys/class/power_supply/ADP1 |
crc-itu-t | SSAM 用到的 CRC-ITU-T 校验 | — |
所以不需要换内核,只要用机器上现成的内核头文件做一次「外部模块编译」,把 .ko 塞进 /lib/modules/ 就行。
过程中踩到的三个坑
1. 缺 crc_itu_t
surface_aggregator 依赖内核的 crc_itu_t(),而 NAS 内核通常没开 CONFIG_CRC_ITU_T。加载时会报:
surface_aggregator: Unknown symbol crc_itu_t (err -2)
解决办法是从同一份内核源码里把 lib/crc/crc-itu-t.c 编成模块补上(6.9 之前这个文件在 lib/crc-itu-t.c)。
2. CONFIG_SURFACE_AGGREGATOR_BUS 没开,头文件退化成空实现
这个选项没启用时,include/linux/surface_aggregator/device.h 和 aggregator/bus.h 会把相关函数写成 static inline 空函数,和 bus.c 里的真实定义撞车:
bus.c:379:5: error: redefinition of 'ssam_bus_register'
不用改内核源码,编译时加一个宏就行:
make -C <headers> M=<src> KCFLAGS="-DCONFIG_SURFACE_AGGREGATOR_BUS" modules
3. 模块签名
内核 CONFIG_MODULE_SIG=y 但没有 CONFIG_MODULE_SIG_FORCE 时,未签名模块照样能加载(只会在 dmesg 里留一句 tainted)。所以编译时直接 CONFIG_MODULE_SIG=n,省掉配签名密钥的麻烦。如果目标内核开了 SIG_FORCE,这条路走不通。
安装
已经把这套东西打包开源了:FnOS-UPS-SurfacePro7
方式一:直接用预编译包(最快)
先去 Releases 下对应内核版本的压缩包,解压后一条命令:
uname -r # 先确认内核版本,例如 6.18.18.c1186-trim
tar -xzf FnOS-UPS-SurfacePro7-6.18.18.c1186-trim.tar.gz
cd FnOS-UPS-SurfacePro7-6.18.18.c1186-trim
sudo sh install.sh
脚本会校验 SHA256、复制模块到 /lib/modules/<内核版本>/extra/surface-battery/、执行 depmod、写 /etc/modules-load.d/surface-battery.conf 实现开机自启,最后尝试立即加载。
预编译包的 vermagic 必须和当前内核完全一致,版本对不上会报 invalid module format——那就用方式二。
方式二:源码编译(任意内核版本)
需要系统里有 Docker(飞牛自带),以及两个文件:
ls /usr/src/linux-headers-$(uname -r) # 要含 Module.symvers
ls /boot/config-$(uname -r)
然后:
git clone https://github.com/YunJian101/FnOS-UPS-SurfacePro7.git
cd FnOS-UPS-SurfacePro7
sudo sh install.sh
脚本会起一个临时的 Debian 容器装好 gcc/make,按 uname -r 对应的上游版本号下载内核源码,只解出需要的几个文件,编完直接装进系统。
验证
ls /sys/class/power_supply
# ADP1 BAT0
cat /sys/class/power_supply/BAT0/capacity # 电量百分比
cat /sys/class/power_supply/BAT0/status # Charging / Discharging / Full
cat /sys/class/power_supply/ADP1/online # 1 = 接着电
modinfo surface_battery
我这边实测的数据:
| 属性 | 值 |
|---|---|
| 电芯厂商 | SMP |
| 型号 | M1109598 |
| 满电电压 | 8.37 V |
| 循环次数 | 420 |
| SSAM 固件 | 14.800.139 |
重启后再 ls 一次,确认开机自动加载也正常。
已知问题
- 资源监控里的电池卡片仍可能是空的。 实测
appcgi.resmon.battery一直返回errno 100030002,即使内核已经正确暴露了BAT0;手动触发 uevent 重扫、重启resmon_service都无效。但 UPS 设置页面可以正常使用内置电池,不影响实际用途。 - 只验证了 x86_64。
- 模块未签名,内核开了
CONFIG_MODULE_SIG_FORCE则不可用。
内核升级之后
模块是按内核版本编的,vermagic 必须匹配。系统升级内核后重新跑一次 sudo sh install.sh 即可(方式二会自动按新内核现编)。
链接
- 仓库:https://github.com/YunJian101/FnOS-UPS-SurfacePro7
- 预编译包:https://github.com/YunJian101/FnOS-UPS-SurfacePro7/releases
- 原理与排障细节:仓库里的
docs/how-it-works.md
整套东西纯属折腾,但折腾完之后,那块 Surface 的电池终于能当 UPS 用了。
- 感谢你赐予我前进的力量

