Gitii/ms-a1-fan-controller

GitHub: Gitii/ms-a1-fan-controller

为 Minisforum MS-A1 提供的 Linux hwmon 内核驱动,通过逆向厂商专有 EC 协议在 Secure Boot 下实现只读的风扇转速与温度传感器数据访问。

Stars: 1 | Forks: 0

# Minisforum MS-A1 EC 风扇 / 温度驱动 已签名的 Linux 内核模块 (`msa1_ec`),通过标准的 `hwmon` sysfs ABI 暴露 Minisforum MS-A1 专有的嵌入式控制器。在 UEFI Secure Boot 和内核 lockdown 下运行,无需 `iopl()`,无需 `/dev/port`,无需用户态 I/O 技巧。 ## 为什么需要这个驱动 MS-A1 的 BIOS **并未**通过 ACPI 暴露其风扇/温度 EC —— 没有 `ECDT` 表,没有 `PNP0C09` 设备,且 `/sys/kernel/debug/ec/ec0/io` 永远不会出现。只能通过在非标准端口 `0x6C / 0x68` 上直接进行 I/O,并使用厂商命令 `0xDD`(温度)和 `0xD5`(风扇)来访问该 EC。在 Secure Boot 下,内核运行在 `integrity` lockdown 模式中,这会阻断所有访问这些端口的用户态路径(`iopl`、`/dev/port`、`/dev/mem`)。与 EC 通信的唯一方法是从内核内部进行——因此有了本驱动。在 Secure Boot 下加载它需要使用 MOK (Machine Owner Key) 签名,并一次性注册该密钥。 ## 您将获得 ``` /sys/class/hwmon/hwmonN/ (where the msa1_ec device lands) name = msa1_ec temp1_input CPU DTS temperature (millidegrees C) temp1_label "CPU" temp2_input Motherboard temperature temp2_label "Board" fan1_input Fan 1 RPM fan1_label "Fan 1" fan2_input Fan 2 RPM fan2_label "Fan 2" fan3_input Fan 3 RPM fan3_label "Fan 3" /sys/kernel/debug/msa1_ec/ dump_d5 Full 256-byte EC dump via cmd 0xD5 (discovery aid) dump_dd Full 256-byte EC dump via cmd 0xDD ``` 所有内容均为只读。在此硬件上**无法**进行风扇/PWM 写入 —— EC 被固件锁定,不向任何 OS 暴露写入路径。有关完整的逆向工程分析,请参阅 [docs/FINDINGS.md](docs/FINDINGS.md)。 ## 快速开始 ``` # 0. 一次性先决条件 sudo apt install -y dkms build-essential mokutil openssl \ linux-headers-$(uname -r) lm-sensors # 1. 一次性:生成 + 注册 MOK 签名密钥 sudo make setup-mok sudo reboot # complete enrollment in the blue MOK Manager screen # 2. 重启后,验证注册情况 mokutil --list-enrolled | grep -A1 'msa1-ec' # 3. 通过 DKMS 构建、签名、安装并加载 sudo make dkms-install # 4. 确认 sensors | sed -n '/msa1_ec/,/^$/p' make status ``` 卸载: ``` sudo modprobe -r msa1_ec sudo make dkms-uninstall sudo mokutil --delete /var/lib/shim-signed/mok/msa1.der # optional ``` ## 构建与签名对照表 | 命令 | 作用 | |----------------------|-------------------------------------------------------------------| | `make` | 针对当前运行的内核进行 kbuild(不安装、不签名) | | `sudo make sign` | 使用 `/var/lib/shim-signed/mok/msa1.{priv,der}` 对构建的 `.ko` 进行签名 | | `sudo make install` | 将签名的 `.ko` 放入 `/lib/modules/$(uname -r)/extra/` + depmod | | `sudo make load` | 重新加载模块,跟踪 dmesg | | `sudo make reload` | install + load | | `sudo make setup-mok`| 生成 MOK 密钥对 + 请求注册 | | `sudo make dkms-install` | 完整的 DKMS 工作流(抵御内核更新) | | `sudo make dkms-uninstall` | 移除 DKMS 包 + 框架配置 | | `make status` | 单页健康状态摘要 | | `make user` | 构建用户态 CLI 读取器 (`ms_a1_ec_read`) | | `make dsdt` | 提取 + 反编译 DSDT/SSDTs 并 grep 搜索 fan/WMI/SMI 符号 | | `make clean` | kbuild 清理 | ## 布局 ``` . ├── src/ │ ├── msa1_ec.c kernel module (~470 LoC, single file) │ ├── Kbuild obj-m := msa1_ec.o │ └── Makefile kbuild wrapper for out-of-tree builds ├── userspace/ │ └── ms_a1_ec_read.c dependency-free CLI reader (reads /sys/class/hwmon) ├── packaging/ │ ├── setup-mok.sh one-time MOK keypair generation + enrollment │ └── install.sh one-shot DKMS install with sign config ├── docs/ │ └── FINDINGS.md full reverse-engineering writeup (why writes are impossible) ├── dkms.conf DKMS module manifest ├── Makefile top-level convenience targets ├── devbox.json devbox environment (gcc, make, openssl, lm_sensors) └── LICENSE GPL-2.0 ``` ## 模块参数 ``` sudo modprobe msa1_ec big_endian=1 # try if fan RPMs look wrong (~16k bogus) sudo modprobe msa1_ec debug=1 # log every EC transaction (very noisy) sudo modprobe msa1_ec force=1 # skip DMI check - DANGEROUS, see below sudo modprobe msa1_ec allow_scan=1 # expose the scan_cmds discovery debugfs file ``` | 参数 | 默认值 | 效果 | |---------------|---------|----------------------------------------------------------------------------------------| | `big_endian` | 0 (LE) | RPM 字节序(低位偏移存 LSB 与 低位偏移存 MSB)。如果 RPM 看起来不对,请切换此项。 | | `debug` | 0 | 详细的每笔事务 `dev_info` 日志记录。 | | `force` | 0 | 绕过 DMI 限制。**会在任何系统上写入 0x6C/0x68** - 在非 MS-A1 上请保持关闭。 | | `allow_scan` | 0 | 暴露 `/sys/kernel/debug/msa1_ec/scan_cmds`。请参阅下文的 *发现辅助工具*。 | ## 发现辅助工具 该模块提供了三个 debugfs 文件,用于逆向工程 EC 的命令集(主要用于构建风扇控制 / Phase 6): | 文件 | 总是存在? | 作用 | |--------------|-----------------|------------------------------------------------------------------------------| | `dump_d5` | 是 | 使用命令 `0xD5`(已知读取风扇)进行 256 字节偏移扫描。显示风扇命令会对哪些偏移量做出响应。 | | `dump_dd` | 是 | 使用命令 `0xDD`(已知读取温度)进行 256 字节偏移扫描。 | | `scan_cmds` | 仅当 `allow_scan=1` 时 | 在少数已知及探索性偏移量上扫描约 45 个候选命令字节。识别 EC 对哪些命令字节做出 ACK。 | ``` # 持续转储: sudo cat /sys/kernel/debug/msa1_ec/dump_d5 sudo cat /sys/kernel/debug/msa1_ec/dump_dd # Command-byte 扫描(需要主动选择启用,请参阅下方的安全说明): sudo modprobe -r msa1_ec sudo modprobe msa1_ec allow_scan=1 sudo cat /sys/kernel/debug/msa1_ec/scan_cmds ``` ### `scan_cmds` 安全性 扫描器仅执行**读取**操作(cmd, offset, 等待 OBF, 读取数据)—— 它永远不会 发出语义未知的写入操作。它还会**强制跳过**这些 已知的会执行写入/状态更改的 ACPI EC 操作码: | 跳过项 | 原因 | |---------|-----| | `0x81` | ACPI WR_EC — 将值字节写入 EC RAM | | `0x82` | ACPI Burst Enable — 更改 EC 模式 | | `0x83` | ACPI Burst Disable — 更改 EC 模式 | 即便如此,如果未知的固件以不同方式解释我们的 `offset` 字节,对其进行“读取”探测也可能产生副作用。因此,扫描器 **默认关闭**。将其输出视为一次性诊断结果,然后卸载模块,并在不带 `allow_scan=1` 的情况下重新加载以进行正常操作。 ## 验证 执行 `modprobe msa1_ec` 后: ``` # 1. 模块已加载,签名已被接受(内核必须未被 taint) cat /proc/sys/kernel/tainted # -> 0 lsmod | grep msa1_ec dmesg | grep msa1_ec # -> "EC probe OK, CPU = X.YYY °C", "loaded" # 2. hwmon 设备存在 ls /sys/class/hwmon/ | while read h; do [ "$(cat /sys/class/hwmon/$h/name 2>/dev/null)" = "msa1_ec" ] && echo $h done # 3. 实时读数 sensors | sed -n '/msa1_ec/,/^$/p' # 4. RPM 跟踪负载(在第二个终端中运行): sudo apt install -y stress stress --cpu 8 --timeout 60 & watch -n1 'sensors | grep -A6 msa1_ec' # 5. 原始 EC 转储 - 对于发现写入寄存器非常有用 sudo cat /sys/kernel/debug/msa1_ec/dump_d5 sudo cat /sys/kernel/debug/msa1_ec/dump_dd ``` 如果 `tainted` 不为 0,或者您在 dmesg 中看到 `Loading of unsigned module is rejected`,说明签名链已断开 —— 请参阅 [故障排除](#troubleshooting)。 ## EC 协议参考 从 [AIDA64 论坛帖子][aida-thread] 逆向工程而来,由 JaxJiang 于 2025-10-15 发布。 ``` Transport (x86 port I/O) ──────────────────────── CMD port = 0x6C (write command byte) DATA port = 0x68 (write offset / read result) status @ 0x6C, bit 0 OBF: data available status @ 0x6C, bit 1 IBF: EC busy Sequence (1-byte read) ────────────────────── wait IBF=0 outb(cmd, 0x6C) cmd = 0xDD or 0xD5 wait IBF=0 outb(offset, 0x68) wait OBF=1 val = inb(0x68) Temperatures via cmd 0xDD (signed °C) ───────────────────────────────────── 0x20 -> CPU DTS temperature 0x21 -> motherboard temperature Fan RPM via cmd 0xD5 (2 bytes per fan) ────────────────────────────────────── 0x14 / 0x15 -> fan3 (low / high byte) 0x16 / 0x17 -> fan2 0x18 / 0x19 -> fan1 ``` ## 为什么用户态 I/O 不起作用 那些显而易见的方法 —— `iopl(3)` + `inb`/`outb`,或者 `/dev/port` —— 都被 内核随 Secure Boot 提供的 **integrity lockdown** 模式阻止了: - `iopl(3)` / `ioperm()` -> 被阻止 - `/dev/port` 读写 -> 被阻止 - `/dev/mem`, `/dev/kmem` -> 被阻止 - `ec_sys` debugfs 后备方案本应该有效,但 MS-A1 根本没有暴露 ACPI EC,因此永远不会创建 `/sys/kernel/debug/ec/ec0/io` 注册 MOK 密钥**不会**解除 lockdown —— 这是一个常见的 误解。MOK 只是让内核信任您签名的模块。 唯一可行的路径是从内核空间执行端口 I/O,这 正是 `msa1_ec` 所做的。 ## 故障排除 ### `modprobe: ERROR: could not insert 'msa1_ec': Required key not available` 内核拒绝了模块签名。 - 检查 MOK 是否已注册:`mokutil --list-enrolled | grep -A1 msa1-ec` - 重新签名:`sudo make sign && sudo make install && sudo make load` ### `not running on a Minisforum MS-A1; not loading` DMI 不匹配。请使用以下命令验证: ``` cat /sys/class/dmi/id/sys_vendor # expect: Micro Computer (HK) Tech Limited cat /sys/class/dmi/id/product_name # expect: MS-A1 ``` 如果您确定这是一台具有不同 DMI 字符串的 MS-A1,请在 `src/msa1_ec.c` -> `msa1_dmi_table` 中添加匹配项并重新编译。 ### 风扇报告 ~16000 RPM 或无意义的值 字节序可能错误。请切换字节顺序: ``` sudo modprobe -r msa1_ec sudo modprobe msa1_ec big_endian=1 ``` ### `implausible CPU temp -128 m°C — refusing to register` EC 实际上并没有响应。可能的原因: - 在非 MS-A1 硬件上加载了 `force=1` - BIOS 更改了 EC 协议(检查 BIOS 版本) - EC 处于异常状态 - 尝试 `sudo make unload && sudo make load` ### 内核更新后模块消失 如果没有 DKMS,`.ko` 将绑定到某一个内核版本。 请使用 DKMS 安装路径(`sudo make dkms-install`),这样每次内核升级时 DKMS 都会重新构建 + 重新签名模块。 ## 为什么没有风扇控制 我们尝试过 —— 进行了穷尽测试。包含 308 次探测的命令字节扫描、完整的 DSDT/SSDT 反编译、WMI 命名空间检查以及 SMI/SMM 分析都证实 MS-A1 的风扇控制器已被**固件锁定**:EC 仅暴露了两个读取 命令,固件没有 ACPI 风扇方法,AMD OverDrive WMI 命名空间 是个占位符,唯一的写入路径是通过专有且未公开文档的 SMI 处理程序。没有任何 OS —— 无论是 Linux 还是 Windows —— 能够控制这些风扇。 完整的调查记录在 **[docs/FINDINGS.md](docs/FINDINGS.md)** 中。 如需间接控制(通过 `amd-pstate` 调整 CPU 功率)或硬件级 控制(外部微控制器),请参阅 [替代方案部分](docs/FINDINGS.md#5-what-is-possible)。 ## 安全性 - DMI 限制可防止在其他系统上意外加载。除非您准备好 对那些系统的键盘控制器造成干扰(EC 端口与 i8042 窗口重叠),否则不要 禁用它(`force=1`)。 - `scan_cmds`(通过 `allow_scan=1` 启用)会探测未知的 EC 命令字节。 它仅执行读取并强制跳过已知的写入类 ACPI 命令,但请将其视为一种 诊断工具,并在正常使用时重新加载且不带 `allow_scan`。 ## 许可证 GPL-2.0。请参阅每个源文件中的 SPDX 头文件。
标签:Linux内核驱动, Secure Boot, 云资产清单, 传感器, 客户端加密, 嵌入式控制器, 硬件监控, 逆向工程