ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

OpenBMC I2C传感器添加全流程:从设备树到Redfish打通链路

OpenBMC I2C传感器添加全流程:从设备树到Redfish打通链路 做OpenBMC硬件移植加传感器几乎是每个新板子bring-up都躲不过去的活。前面几篇讲了镜像构建、开机启动、ASPEED外设初始化这篇专门把I2C传感器这条线完整走一遍。先说结论OpenBMC里加一个I2C传感器不是改一个文件就完事你要让内核认识它再让entity-manager认识它再让dbus-sensors开始轮询它最后上层接口才能读到它。整条链路打通之后后面批量加传感器就是很机械的事。这篇适合正在做板卡移植、或者新板子传感器死活不出数的同学参考我会把从硬件的寄存器到Redfish接口的每一层都讲到。1. 先理清架构一条I2C传感器数据在OpenBMC里是怎么流动的1.1 从传感器寄存器到Redfish API的数据链路很多人第一次加传感器习惯性地搜OpenBMC sensor config找到一个JSON文件就往里写写完发现压根没生效。这是没有先搞清楚OpenBMC里传感器数据的流向。一条I2C温度传感器的数据从芯片到用户可见的接口要走完这么一长串传感器寄存器比如TMP75的温度寄存器 ↓ 通过 SDA/SCL 两根线 Aspeed I2C控制器AST2500/AST2600内部外设 ↓ I2C子系统 Linux内核中的hwmon驱动如tmp75、lm75驱动 ↓ 生成 /sys/class/hwmon/hwmonX/temp1_inputsysfs节点 ↓ dbus-sensors中的temperature-sensor进程轮询 xyz.openbmc_project.Sensor.Value D-Bus对象属性 ↓ Redfish/IPMI/WebUI 等上层应用 最终呈现给用户搞清楚这个链路排查问题的时候就有顺序了——从底层往上层一层层看卡在哪一层就修哪一层。千万别一上来就怀疑OpenBMC配置文件很多时候是内核驱动压根没加载。1.2 为什么OpenBMC要拆成这么多层直接让一个进程去读I2C寄存器然后暴露服务不是更简单吗OpenBMC没有这么干因为它的架构设计目标是多进程隔离BMC上跑了很多服务如果一个传感器进程崩溃了不能把整个带外管理系统带崩。所以在OpenBMC里entity-manager只负责发现硬件dbus-sensors只负责读数据更新D-Bus属性Redfish只负责把D-Bus属性翻译成HTTP请求。每一层都是独立的挂了可以单独重启。这个设计在单板调试时觉得繁琐但在大规模服务器管理场景就很重要了。而且D-Bus作为中间层让所有上层应用IPMI、Redfish、WebUI、命令行工具都能通过统一接口拿到传感器值不用各自直接操作硬件。1.3 添加传感器前必须搞清楚的三个硬件参数开始写配置之前先把这三个参数确认到位缺一个后面都白搭I2C总线号传感器挂在哪条I2C总线上。这个要看板卡原理图找到BMC的I2C controller编号。AST2600有16个I2C控制器I2C0~I2C15原理图上一般会标注I2C1、I2C5这样的编号。设备地址传感器的7位或8位I2C地址。一般芯片规格书会给出像TMP75系列常见的地址是0x48地址引脚A0/A1/A2的电平组合决定具体地址。芯片型号或者叫兼容字符串用来匹配内核驱动的compatible字段比如ti,tmp75、national,lm75。如果芯片没有现成内核驱动那就得自己写驱动或者换一种折中方案后面第2章会说。这三个参数不确定的情况下可以在已经能进shell的BMC上先手动探测确认无误再动配置。1.4 I2C基本功为什么这个总线特别容易出问题I2C传感器调试中的很多玄学问题根源都在I2C总线的电气特性上。I2C只有两根线SDA数据和SCL时钟所有设备都并联在总线上。为什么I2C要用开漏输出加外部上拉电阻因为只有开漏输出才支持线与——多个设备可以安全地共同驱动总线任何一个设备拉低总线整条总线就是低电平没有设备拉低时由外部上拉电阻把总线恢复到高电平。这个机制也决定了仲裁机制多主机同时发起通信时谁先拉低谁就获得总线使用权。实际调试中常见的问题上拉电阻过大比如10kΩ在长距离或高容性负载下会导致上升沿太缓、通信失败上拉电阻过小比如1kΩ但设备太多会导致低电平拉不下去或者功耗异常。所以板卡上I2C传感器读不到数据先量一下SCL和SDA的静态电平正常应该都是被上拉到高电平一般是3.3V或2.5V取决于BMC IO域和上拉电源如果有某根线是低电平那说明有设备把总线拉死了。还有一个容易混淆的点地址位数。I2C设备地址有7位和8位两种表示法。芯片手册上写0x90那是8位地址含读写位实际编程用的是0x487位地址。OpenBMC的配置里统一用7位地址。如果你用i2cdetect扫描它显示的是7位地址所以0x48就是0x48别看到一个0x90的手册值就写进去。2. 挂到内核I2C总线探测、设备树节点和hwmon节点的验证2.1 先在运行中的BMC上确认总线和地址我习惯的做法是板子能开机、BMC能进shell之后先用i2cdetect把所有I2C总线扫一遍确认要加的传感器在不在总线上。这能在动手改代码之前排除掉一大半硬件问题。# 查看BMC上有哪些I2C总线 i2cdetect -l # 扫描其中一条总线比如i2c-3 i2cdetect -y 3扫描结果里每一列代表一个地址地址位上的数字表示设备类型。比如0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- 48 -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --0x48位置显示48说明确实有一个设备在响应。如果显示UU说明这个地址已经被某个内核驱动占用了这也正常。如果全是--那就先别急着写配置检查上拉电阻、设备供电、地址引脚。用i2cget可以直接读芯片的寄存器。比如TMP75的温度寄存器是0x00读一下# 读i2c-3总线上0x48设备、寄存器0x00的值 i2cget -y 3 0x48 0x00 w读出来的值如果是0x19xx这种有效温度值说明硬件通路完全没问题。到这个阶段内核能不能识别这个设备其实还没关系——i2cdetect用的是最底层的I2C子系统不依赖具体的hwmon驱动。2.2 设备树里添加I2C传感器节点对于有标准hwmon驱动的I2C芯片设备树节点是关键。OpenBMC的内核是基于Linux内核定制的ASPEED设备的设备树文件在arch/arm/boot/dts/aspeed-bmc-厂商-板型.dts比如aspeed-bmc-ibm-rainier.dts、aspeed-bmc-facebook-yosemite4.dts这类文件。找到你板卡对应的dts在对应的I2C总线节点下添加传感器子节点i2c3 { status okay; tmp7548 { compatible ti,tmp75; reg 0x48; status okay; }; };这里i2c3对应I2C总线3tmp7548是设备节点名compatible必须和内核驱动匹配reg就是7位I2C地址status okay确保节点被启用。如果你不确定compatible字符串怎么写最稳妥去内核源码的drivers/hwmon/tmp75.c里看一眼of_match_table定义里面有内核实际支持的兼容字符串列表。别自己编一个匹配不上就是没设备节点。2.3 编译镜像后验证hwmon节点修改设备树后重新编译镜像烧录开机后检查sysfs# 查看所有hwmon设备 ls /sys/class/hwmon/ # 查看hwmon0的名称和温度值 cat /sys/class/hwmon/hwmon0/name cat /sys/class/hwmon/hwmon0/temp1_inputname文件会显示驱动名比如tmp75。temp1_input里的值一般是毫摄氏度所以47875表示47.875摄氏度。能读到这个值说明内核层已经通了。2.4 芯片没有内核驱动怎么办如果芯片很冷门内核里找不到对应的hwmon驱动有两条路写一个简单的内核驱动照着drivers/hwmon/tmp75.c改把寄存器和转换逻辑换成目标芯片的。BMC的kernel是独立的开源项目给OpenBMC加一个hwmon驱动走社区流程并不复杂但要做好review往返的准备。用户态直接通过/dev/i2c-N读写寄存器不建hwmon节点让dbus-sensors直接读I2C设备文件。这种方式不用改内核但要在OpenBMC的传感器daemon里增加对I2C设备的支持代码。坦率说不比写驱动省事而且把寄存器读写逻辑放在用户态鲁棒性更差。我的建议是优先走内核hwmon路线因为sysfs节点是OpenBMC整个传感器体系的标准接口后续的阈值、报警、Redfish映射全都能复用现成逻辑。3. entity-manager配置文件告诉BMC板上装了哪些传感器3.1 entity-manager是什么角色内核层通了只表示系统能读到这个传感器了。要让OpenBMC把它当成一个可管理的传感器来用还需要entity-manager这个硬件抽象层来发现和实体化它。entity-manager做的事情可以理解成BMC上电后它读取一组JSON配置文件根据里面的探测条件去匹配板上的实际硬件匹配成功就在D-Bus上创建对应的对象接口。这样dbus-sensors和上层应用就知道这块板子上有一个温度传感器叫CPU_TEMP挂在I2C3总线0x48地址上。配置文件在OpenBMC源码树里的位置一般是meta-厂商/recipes-phosphor/entity-manager/entity-manager/configurations/文件名.json编译后会被安装到BMC根文件系统的/usr/share/entity-manager/configurations/文件名.json3.2 配置文件结构拆解一个典型的I2C传感器entity-manager配置长这样{ Type: Board, Name: MyServerBoard, Probe: [ compatible \myvendor,myboard\ ], I2C: [ { Bus: 3, Address: 0x48, Name: CPU_TEMP, Type: TMP75 } ], Exposes: [ { Name: CPU_TEMP, Type: TMP75, ReadPath: /sys/class/hwmon/hwmon0/temp1_input } ] }关键字段的含义我整理成表格字段含义注意事项Type配置类型一般写Board表示这是板级配置Name配置名称会显示在日志里务必起得有意义Probe探测条件通过读取设备树或FRU数据匹配条件不匹配配置不生效I2C[].BusI2C总线号和内核i2c-N对应写错就找不到设备I2C[].Address7位I2C地址字符串形式注意是0x48不是0x90I2C[].Type设备类型用于entity-manager做匹配和资源分配Exposes[].ReadPathsysfs读取路径必须对应内核hwmon节点写错就是数据读不到Probe条件是这个文件里最容易卡住人的地方。它的工作机制是entity-manager在启动时遍历/sys/firmware/devicetree/base目录读取各节点的compatible属性然后和配置文件里的条件做对比。如果你板子的设备树中没有定义对应的compatible节点这个Probe永远不会匹配成功。所以一个实用的做法是设备树里除了传感器节点再定义一个板卡标识节点比如在根节点下加/ { compatible myvendor,myboard, aspeed,ast2600; };这样文件里写compatible \myvendor,myboard\就能匹配上。如果你不想依赖compatible也可以用frus方式通过读取板载EEPROM中的FRU信息来匹配但FRU数据可能没写入所以compatible方式最直接。3.3 在源码树中正确添加配置直接在运行中的BMC上改/usr/share/entity-manager/configurations/文件可以快速测试但要让配置随镜像固化必须在源码树里改。在meta层中添加或修改配置最规范的做法是创建bbappend文件meta-厂商/recipes-phosphor/entity-manager/entity-manager_%.bbappend内容FILESEXTRAPATHS:prepend : ${THISDIR}/${PN}: SRC_URI file://myserver-board.json然后把myserver-board.json放到同目录的files子目录或和bbappend相同路径下。重新编译entity-manager包bitbake entity-manager生成镜像时配置文件就会被打进根文件系统。3.4 加载并验证实体化结果更新配置后重启entity-manager或者直接重启BMC观察日志systemctl restart entity-manager journalctl -u entity-manager -f如果配置加载成功日志里会出现类似Creating sensor ...的记录。然后用busctl检查D-Bus对象是否生成busctl tree xyz.openbmc_project.EntityManager或者全局搜一下传感器路径busctl tree | grep -i temp能看到/xyz/openbmc_project/sensors/temperature/CPU_TEMP这样的对象说明entity-manager已经把这个传感器实体化出来了。如果没看到最可能的就是Probe条件没匹配成功或者是JSON语法有问题导致配置被忽略。4. dbus-sensors与阈值让读数变成可用的D-Bus属性4.1 dbus-sensors是怎么发现传感器的entity-manager创建了D-Bus对象但谁来真正定时去读sysfs节点、更新数值这就是dbus-sensors里各个daemon的活。OpenBMC的dbus-sensors包编译后会生成多个独立的daemon比如temperature-sensor、voltage-sensor、fan-sensor每个daemon只处理自己类型的传感器。这些daemon启动时会向entity-manager发一个D-Bus调用获取所有匹配该类型的传感器配置其中包括ReadPath、Name、Type、阈值等。然后每个daemon按照自己的轮询周期一般是1秒或几秒去读sysfs节点把读到的值通过xyz.openbmc_project.Sensor.Value接口更新到D-Bus属性上。在旧一些的OpenBMC版本里dbus-sensors还会读/etc/phosphor-sensors.d/sensors.json这种全局配置文件。现在新版本基本都走entity-manager的Exposes动态发现所以只需要关注entity-manager配置里的Exposes字段即可。4.2 阈值和告警配置传感器的管理价值很大程度体现在告警上。阈值可以直接在entity-manager配置的Exposes里定义{ Name: CPU_TEMP, Type: TMP75, ReadPath: /sys/class/hwmon/hwmon0/temp1_input, Thresholds: [ { Type: UpperCritical, Value: 95 }, { Type: UpperNonCritical, Value: 85 }, { Type: LowerNonCritical, Value: 0 } ] }常用阈值类型阈值类型含义典型应用UpperCritical上限紧急告警温度达到危险值触发关机保护UpperNonCritical上限非紧急告警温度偏高触发风扇提速LowerCritical下限紧急告警电压过低可能硬件故障LowerNonCritical下限非紧急告警电压偏低告警但不动作注意温度传感器阈值单位是度还是毫度取决于daemon的约定。OpenBMC内部传感器值的单位标准是xyz.openbmc_project.Sensor.Value的Unit属性可能是xyz.openbmc_project.Sensor.Value.Unit.DegreesC数值是整数一般直接用摄氏度。但sysfs里是毫摄氏度所以daemon在读的时候会做一次除法。如果你自己写配置务必确认单位换算不然阈值判断会差1000倍。4.3 阈值触发后会发生什么阈值触发不是只有D-Bus属性变化那么简单。OpenBMC里还有几个服务在监听这些阈值事件phosphor-led-manager根据告警状态点亮琥珀色指示灯phosphor-state-manager在UpperCritical触发时可能触发系统关机xyz.openbmc_project.Logging往系统日志写入SEL条目IPMI可以查看到Redfish/redfish/v1/Chassis/id/Sensors/name的Status会变成Critical所以一个传感器加好之后等于同时打通了告警日志、硬件指示灯和Redfish事件这三条下游链路。这也是为什么阈值配置值得认真写而不是随便填一个值。5. 排错实战I2C传感器最常见的四个坑5.1 i2cdetect扫不到设备的排查链路这是最基础但也是最常见的坑。现象很直接i2cdetect -y 3的0x48位置全是--或者显示XX表示总线异常。排查顺序我建议这样来确认总线编号对不上i2cdetect -l看到的bus编号和原理图上的I2C3不一定直接对应。ASPEED的I2C控制器在内核里注册顺序和硬件编号有关有的BSP里做了alias映射实际bus号可能偏移。所以要以运行时的i2cdetect -l输出为准。测静态电平i2cget -y 3 0x00 0x00随便读一个地址如果报Remote I/O error不一定是坏事但如果一直卡住没响应那可能是总线被拉死了。用万用表测SDA和SCL电平必然是接近电源电压如果接近0V说明有设备拉低了总线逐一带电断开I2C设备排查。确认地址引脚芯片的A0/A1/A2地址引脚的电平决定了实际地址。原理图上接法可能和你的预期不同经验是直接看数据手册对照地址表别靠猜。确认传感器供电很多I2C传感器是自己独立供电的比如3.3V standby电源如果供电没上设备不响应是必然的。我个人的建议在动配置文件之前所有硬件层级的问题都要用i2cdetect和i2cget确认不要带着硬件问题去改软件不然很容易浪费大量时间在配置明明没错但就是不通的循环里。5.2 设备树加了但/sys/class/hwmon下没有出现节点设备树里写了tmp7548compatible也对但系统起来后ls /sys/class/hwmon/就是看不到tmp75。排查链路检查dmesg日志dmesg | grep -i tmp75 dmesg | grep -i i2c如果有i2c i2c-3: new_device: Instantiated device tmp75 at 0x48这样的日志但随后没有hwmon注册信息多半是驱动probe失败了。如果连Instantiated device都没有说明设备树节点压根没被解析检查dts语法和status字段。检查内核配置OpenBMC内核裁剪比较激进某些hwmon驱动是模块化的CONFIG_SENSORS_TMP75可能没编进去。确认一下zcat /proc/config.gz | grep TMP75如果不是y或m需要在内核配置里加上这个驱动。检查compatible是否匹配用cat /proc/device-tree/i2caddr/tmp7548/compatible查看实际生效的compatible字符串再回去对比驱动源码里的of_match_table。有时候厂商的compatible写法不同比如ti,tmp75和tmp75不匹配。5.3 entity-manager不实体化传感器硬件节点有了sysfs也有了但busctl tree | grep sensor就是看不到传感器对象。最可能的原因是Probe条件没有匹配上。排查方法# 查看entity-manager日志 journalctl -u entity-manager -e日志中会打印加载了哪些配置文件、哪些匹配成功哪些匹配失败。如果看到你的配置文件被加载但没有匹配先检查Probe里的compatible字符串和板子设备树实际的值是否完全一致。字符串匹配是精确匹配大小写错一个字符都不行。还有一个不太容易注意的坑一个配置文件里如果同时有Probe和I2C两个字段entity-manager是先根据Probe判断这个配置是否适用于当前板子再实例化I2C里的设备。如果你只是想在现有板子上临时加个传感器最简单的办法是暂时去掉Probe字段或者改成空数组让配置无条件生效。但产品代码里不要这么干会导致配置在所有板型上都生效运气不好会发生设备地址冲突。5.4 D-Bus有传感器对象但值不更新busctl tree能看到/xyz/openbmc_project/sensors/temperature/CPU_TEMP但用busctl get-property读Value永远是0或者不变。这就要看ReadPath对不对了。实体化和读取是两码事entity-manager负责实体化dbus-sensors负责读取。如果Exposes里的ReadPath指向了一个不存在的文件daemon读不到数据D-Bus属性的值自然就是初始值。检查方法# 确认这个文件存在 cat /sys/class/hwmon/hwmon0/temp1_input # 直接看dbus-sensors的日志 journalctl -u xyz.openbmc_project.temperature-sensor -e另外有些板卡上sysfs节点的路径会因为hwmon加载顺序变化而变化比如hwmon0变成hwmon1。这种场景不要在ReadPath里写死hwmonX可以在entity-manager配置里用Sysfs相关的字段去做模糊匹配或者用下面的方式规避把传感器名字用i2c-3-0048这样的固定路径来确定。内核里/sys/bus/i2c/devices/3-0048/hwmon/hwmon0/temp1_input这种路径依赖于总线号和地址相对hwmonX更稳定但也要注意实际目录结构。还有一个底层原因I2C连续读取需要理解设备指针寄存器。有些传感器读取会遇到i2cget第一次成功、后续失败的情况。以TMP75为例读取温度的标准时序是先向设备写寄存器地址0x00设置指针然后重新发起读操作。如果你通过i2cget -y 3 0x48 0x00 w这种命令验证底层已经帮你处理了指针。但如果驱动实现有问题没有正确设置指针寄存器就读取那第二次读取就会拿到乱数据。排查时可以连续执行两次i2cget看是否都正常。5.5 多设备地址冲突怎么办如果一个I2C总线上出现了两个相同地址的设备比如两个0x48的温度传感器这会造成总线访问冲突可能两个都读不到也可能一个正常一个异常。解决思路通过A0/A1/A2引脚改成不同地址如果硬件已经固定了且板卡设计允许多路复用可以用I2C MUX如TCA9548把总线分成多路每个MUX通道接不同的设备。这时OpenBMC配置里要增加MUX的配置支持dbus-sensors对MUX的适配比较成熟但配置文件会复杂一些。我遇到这种情况时最优先建议还是改硬件地址引脚因为MUX方案增加了故障点调试成本高。6. 从busctl到Redfish API验证传感器正式上线6.1 D-Bus层面验证传感器对象出现、数值在更新之后做一次完整的验证# 查看所有温度传感器对象 busctl tree xyz.openbmc_project.Sensor.Value # 获取某个具体传感器的全部属性 busctl introspect xyz.openbmc_project.Sensor.Value \ /xyz/openbmc_project/sensors/temperature/CPU_TEMP # 直接读取Value属性 busctl get-property xyz.openbmc_project.Sensor.Value \ /xyz/openbmc_project/sensors/temperature/CPU_TEMP \ xyz.openbmc_project.Sensor.Value ValueValue属性应该是整数类型单位通过Unit属性体现。比如DegreesC、Volts、RPMS。6.2 命令行工具验证很多OpenBMC镜像集成了phosphor-sensor-util里面有sensor-util命令sensor-util all sensor-util temperature输出会是类似CPU_TEMP | 48.000 | degrees C这个命令本质也是通过D-Bus读的只是帮你格式化输出了。如果没有这个命令也不用急busctl一样能验证。6.3 Redfish API验证OpenBMC最常用的管理接口是Redfish。传感器加好之后Redfish服务bmcweb会自动把D-Bus传感器对象映射成Redfish的/redfish/v1/Chassis/ chassis-id/Sensors/资源。验证方法curl -k -u root:0penBmc https://bmc-ip/redfish/v1/Chassis/1/Sensors/在响应JSON里Name字段应该有CPU_TEMPReading字段应该有实际数值Status里有Health信息。如果Redfish里能看到读数说明整条链路已经通了。6.4 阈值告警实测最后一步是实测阈值。不用真实把硬件加热到95°C那么危险可以用busctl手动设置一个UpperCritical值使得当前温度已经超过阈值然后观察告警状态变化# 把UpperCritical阈值临时改成5此时温度肯定超过阈值 busctl call xyz.openbmc_project.Sensor.Value \ /xyz/openbmc_project/sensors/temperature/CPU_TEMP \ xyz.openbmc_project.Sensor.Value \ StartDelay 0 # 然后用set-property或者配置文件方式改阈值不过更规范的做法是直接修改entity-manager配置文件里的阈值重启entity-manager看效果。这样顺带也就验证了阈值的配置语法是否正确。测试完记得把阈值恢复成正常值。同时可以去Redfish查一下事件和日志curl -k -u root:0penBmc https://bmc-ip/redfish/v1/Systems/system/LogServices/EventLog/Entries/应该能看到一条类型为Critical的传感器告警记录。到这一步这个I2C传感器才算是真正完整上线了。结尾一点经验之谈多块板卡做I2C传感器bring-up做下来我的体会是先跑通一个传感器再批量复制。第一次加传感器不要急着在源码树里改一堆配置建议在已经能进shell的开发板上直接修改/usr/share/entity-manager/configurations/下的文件做验证确认实体化、读数、阈值、Redfish全链路都通了再同步回源码树重新编译镜像。复制配置的时候最容易翻车的点是总线号和地址写错尤其是一批传感器类型相同、只是挂在不同的总线上时看着像实际上光改个bus号也可能踩到运行时的bus编号和原理图编号不对应的问题。我的做法是写一个简单的shell脚本把所有配置里的Bus、Address、Name字段拉出来列个清单和原理图逐一比对比肉眼检视靠谱得多。另外如果传感器芯片在内核里没有现成驱动宁可在用户态先通过i2cget人工读寄存器验证芯片工作正常再决定是写驱动还是换方案。带着芯片能不能工作这个未知数去做上层配置出了问题会非常难定位。这篇把I2C传感器的完整添加链路讲完了下一篇我打算写写OpenBMC里风扇控制和热管理策略的配置到时候见。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进