OpenBMC中ASPEED HWMON驱动集成实战指南:从设备树到sysfs的全链路解析
你有没有遇到过这样的场景?BMC系统已经跑起来了,IPMI也能连上,但风扇转速读出来一直是0 RPM——明明硬件接好了,信号也测过是正常的。或者更糟,PWM调速完全没反应,风扇要么狂转,要么干脆不转。
别急,这大概率不是你的代码写错了,而是ASPEED HWMON驱动和设备树之间的“最后一公里”没打通。
在OpenBMC开发中,尤其是基于AST2500/AST2600这类主流BMC SoC时,aspeed-pwm-tach驱动看似简单,实则暗藏玄机。它不像普通GPIO那样“写个1就亮”,它的稳定运行依赖于设备树、时钟、引脚复用、内核HWMON框架等多个环节的精密配合。
本文不讲空泛理论,我们直接下探到寄存器级,手把手拆解如何让ASPEED的硬件监控模块真正“活”起来,并为上层OpenBMC服务提供可靠数据支撑。
为什么非要用ASPEED原生HWMON?软件轮询不行吗?
先说结论:能用硬件就别靠软件。
很多开发者初期会尝试用GPIO + 定时器去读风扇Tach信号——比如每100ms读一次电平变化,算出RPM。听起来可行,但在生产级BMC系统中,这种做法有三大硬伤:
- 精度差:软件定时不准,尤其在系统负载高时,采样周期抖动大;
- CPU占用高:多个风扇+温度轮询会让kworker线程忙个不停;
- 无法闭环控制:做不到PWM输出与Tach输入的硬件联动。
而ASPEED的ast-hwmon模块恰恰解决了这些问题。它内置了:
- 独立的PWM发生器(支持25kHz高频输出);
- Tach计数器(可自动捕获脉冲周期);
- 可配置的采样定时器(无需CPU干预);
- 中断机制(风扇堵转告警);
换句话说,一旦配置正确,这个模块就能在后台默默工作,几乎不消耗CPU资源。
设备树:驱动能否加载的“生死状”
我们先来看一个真实案例:某客户反馈aspeed-pwm-tach驱动始终加载失败,dmesg里只有一行冰冷的日志:
aspeed-pwm-tach: probe of pwm_tach failed with error -2错误码-2是ENOENT—— 资源不存在。问题出在哪?设备树配置漏了关键字段。
下面是修复前后的对比:
❌ 错误配置(缺少clocks)
&pwm_tach { compatible = "aspeed,ast2600-pwm-tach"; reg = <0x1e785000 0x500>; interrupts = <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>; fan@0 { reg = <0>; aspeed,fan-tach-src = <0>; aspeed,pwm-output = <0>; }; };看起来结构完整,但缺了最关键的一环:时钟使能。
ASPEED的外设模块大多默认是断电状态,必须通过clocks属性通知内核打开门控时钟,否则寄存器访问全部无效——相当于你试图操作一个没通电的芯片。
✅ 正确配置(补全clocks与pinctrl)
&pwm_tach { compatible = "aspeed,ast2600-pwm-tach"; reg = <0x1e785000 0x500>; interrupts = <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>; clocks = <&syscon ASPEED_CLK_GATE_PWMCLK>; clock-names = "clk"; resets = <&syscon ASPEED_RESET_PWM>; pinctrl-names = "default"; pinctrl-0 = <&pwm0 &tach0>; fan@0 { reg = <0>; aspeed,fan-tach-src = <0>; aspeed,pwm-output = <0>; }; };关键点说明:
| 字段 | 作用 | 常见坑点 |
|---|---|---|
compatible | 匹配驱动,必须严格对应 | 写成aspeed,pwm-tach会匹配失败 |
reg | 寄存器基址 | AST2600是0x1e785000,AST2400不同 |
clocks | 使能模块时钟 | 忘记添加会导致probe失败或kernel hang |
pinctrl-0 | 设置引脚复用 | 若未配置,PWM信号可能被当作GPIO |
💡 提示:可以用
cat /sys/kernel/debug/pinctrl/*/pingroups查看当前pinmux状态,验证是否生效。
驱动内部发生了什么?一步步看probe流程
当设备树正确后,Linux内核就会调用aspeed_pwm_tach_probe()函数。我们来拆解它的核心逻辑:
第一步:获取资源并映射寄存器
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); pt->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(pt->base)) return PTR_ERR(pt->base);这里将物理地址0x1e785000映射为虚拟地址,后续所有寄存器读写都通过pt->base进行。
第二步:开启时钟
pt->clk = devm_clk_get(&pdev->dev, "clk"); if (IS_ERR(pt->clk)) return PTR_ERR(pt->clk); ret = clk_prepare_enable(pt->clk); if (ret) { dev_err(&pdev->dev, "failed to enable clk\n"); return ret; }如果这一步失败(比如设备树没写clocks),clk_get返回错误,probe直接退出。
第三步:复位模块
reset_control_assert(rst); udelay(10); reset_control_deassert(rst);确保模块处于干净初始状态。
第四步:初始化PWM与Tach通道
/* 设置PWM频率为25kHz */ aspeed_pwm_set_freq(pt, 0, 25000); /* 启用TACH0采样 */ aspeed_tach_enable(pt, 0);这些操作会写入对应的控制寄存器,例如:
- PWM频率由PT0CR寄存器设置;
- Tach使能位在TACH_CTRL中;
你可以用devmem 0x1e785000直接查看这些寄存器值,确认配置是否生效。
第五步:注册到HWMON子系统
pt->hwmon_dev = devm_hwmon_device_register_with_info( &pdev->dev, "aspeed", pt, &aspeed_hwmon_ops, NULL);这是最关键的一步。成功后,会在/sys/class/hwmon/下生成目录,比如hwmon0。
HWMON接口暴露:用户空间怎么读数据?
注册成功后,系统会自动生成标准sysfs节点:
/sys/class/hwmon/hwmon0/ ├── name → aspeed ├── fan1_input → 17850 (RPM) ├── pwm1 → 128 (占空比,0~255) └── subsystem -> ../../../../class/hwmon这些文件的背后,是由你在驱动中定义的hwmon_ops回调函数支撑的。
如何实现read回调?
static int aspeed_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { struct aspeed_pwm_tach *pt = dev_get_drvdata(dev); switch (type) { case hwmon_fan: if (attr == hwmon_fan_input) { *val = read_tach_count(pt, channel); // 从寄存器读脉冲 return 0; } break; case hwmon_pwm: if (attr == hwmon_pwm_output) { *val = pt->pwm_duty[channel]; // 返回当前设定值 return 0; } break; } return -EOPNOTSUPP; }当你执行cat /sys/class/hwmon/hwmon0/fan1_input时,内核就会调用这个函数,返回实际测量值。
⚠️ 注意:
read_tach_count()并不是每次都读硬件——有些版本的驱动使用定时器定期刷新缓存值,避免频繁访问寄存器。
常见问题排查手册:五个高频“踩坑”场景
🛑 场景一:fanX_input 永远是0
可能原因:
- Tach通道未使能(检查设备树aspeed,fan-tach-src);
- 引脚复用错误(PWM/Tach引脚被当成GPIO用了);
- 风扇信号未接入或损坏;
- 寄存器超时(tach_timeout设置太短);
调试命令:
# 查看是否有有效计数 devmem 0x1e785100 # TACH_COUNT_REG0 dmesg | grep tach # 是否有timeout日志🛑 场景二:pwmX 写入无反应
典型表现:
echo 255 > /sys/class/hwmon/hwmon0/pwm1 # 但风扇速度不变排查步骤:
1. 检查pinctrl是否启用PWM模式;
2. 使用示波器测量对应引脚是否有PWM波形;
3. 确认风扇是否支持PWM调速(有些是电压调速);
4. 检查clk是否enable:bash cat /sys/kernel/debug/clk/clk_summary | grep pwm
🛑 场景三:驱动加载时报 -EBUSY
错误日志:
aspeed-pwm-tach: resource busy原因:寄存器地址范围已被其他驱动占用。
解决方法:
- 检查是否有重复定义的node;
- 确保没有其他模块(如custom-gpio)占用了PWM资源;
上层应用如何消费这些数据?phosphor-hwmon 的角色
在OpenBMC中,光有sysfs还不够。我们需要把这些原始数据变成可管理的传感器实体。
这就是phosphor-hwmon的作用。它是一个守护进程,会定期扫描所有hwmon节点,并做以下事情:
- 解析
name、label等属性; - 将
fan1_input映射为 D-Bus 接口xyz.openbmc_project.Sensor.Fan; - 生成 IPMI SDR 记录,供远端KVM/IPMI客户端查询;
- 支持阈值监控与告警上报;
举个例子:
{ "Name": "fan1", "Unit": "rpm", "Value": 17850, "Scale": 0, "Thresholds": { "LowerCritical": 500 } }这样,WebUI、REST API、SNMP都能统一看到风扇状态。
高阶技巧:动态更新与调试建议
动态修改PWM占空比(测试用)
echo 1 > /sys/class/hwmon/hwmon0/pwm1_enable # 0=manual, 1=auto echo 180 > /sys/class/hwmon/hwmon0/pwm1 # 设置占空比注意:只有当pwm1_enable=0时才能手动写入。
开启HWMON调试日志
在内核启动参数加:
module.aspeed_pwm_tach.debug=1然后通过dmesg查看详细操作流程。
使用device tree overlay热插拔传感器(实验性)
虽然生产环境不推荐,但在开发阶段可以利用overlay动态添加新风扇定义,实现“热插拔”式调试。
写在最后:稳定性比功能更重要
在BMC系统中,风扇控制是安全相关的功能。即使软件崩溃,也不能让服务器因散热失效而烧毁。
因此,在设计时务必考虑:
- 硬件默认PWM输出是否设为“全速”?
- 是否设置了最小安全转速?
- 断网或BMC宕机时,风扇能否保持运转?
ASPEED芯片本身支持一些安全机制,比如:
- 独立的硬件看门狗控制PWM;
- 备用固件模式下仍可维持基本散热;
合理利用这些特性,才能构建真正可靠的BMC系统。
如果你正在做OpenBMC移植或定制开发,不妨现在就去检查一下你的设备树和dmesg日志——也许那个一直读不到的风扇,只差一行clocks配置。
欢迎在评论区分享你遇到过的HWMON“诡异bug”,我们一起排雷。