ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenBMC I2C传感器接入:从设备树到D-Bus的完整流程

OpenBMC I2C传感器接入:从设备树到D-Bus的完整流程 搞过 OpenBMC 移植的朋友应该都有同感给板子加一颗 I2C 传感器听起来就是“焊个芯片、写个配置”的活儿实际一上手才发现从硬件引脚的电气属性、内核设备树到 entity-manager 的 JSON、D-Bus 顶层的数据对象一整条链路全都得打通。这篇开发笔记我以“在 OpenBMC 上通过 I2C 添加传感器”为主线把整个流程和踩过的坑一起记录下来。内容适合刚接触 OpenBMC 的嵌入式工程师也适合正在做硬件移植、BSP 适配的同行希望能帮你们少走几步弯路。1. 先搞清楚要往哪儿“塞”传感器整体设计链路1.1 OpenBMC 里传感器数据从哪里来到哪里去OpenBMC 的传感器数据流我习惯把它分成四层物理层、内核层、服务层、应用层。物理层就是板上那颗实际的传感芯片通常挂在 I2C、SMBus 或者 ADC 接口上内核层负责把芯片的寄存器读写抽象成 hwmon 或 iio 子系统服务层里最常见的是 phosphor-hwmon 和 entity-manager前者把内核 sysfs 的数据搬到 D-Bus 上后者负责“告诉系统我有哪些硬件”应用层就是 IPMI、Redfish 或者 WebUI 从 D-Bus 读取并对外呈现。很多第一次做 OpenBMC 移植的人容易犯一个毛病只改了设备树然后发现 xyz.openbmc_project.Sensor.Value 上根本没有新设备。原因就是没有理解这条链路是“接力跑”内核识别到芯片只是第一步后面得有服务去监听 sysfs 的变化、再注册成 D-Bus 对象。这块我会在第 3 节详细拆。1.2 I2C 传感器的电气基础为什么非得上拉电阻在开始加设备之前我觉得有必要把 I2C 的原理快速复习一遍因为后面排查问题全靠这点底子。I2C 是个两线制总线SCL 和 SDA 都是开漏输出线上必须接上拉电阻才能产生高电平。开漏输出的好处是支持“线与”和总线仲裁多个设备可以同时往 SDA 上拉低谁先拉低谁就赢得仲裁避免多主机冲突。上拉电阻的取值很讲究太小会导致灌电流过大甚至让通信波形变形太大会使得上升沿过慢总线频率上不去。常见的 100kbps 模式用 4.7k 到 10k 都行400kbps 快速模式一般建议 2.2k 到 4.7k。我之前在一块板子上换了上拉电阻后 I2C 波形直接从“锯齿”变成“方波”所以如果你发现传感器时好时坏第一个要看的就是示波器抓 SCL/SDA 波形别急着怀疑固件。I2C 时序本身不复杂主机发 START接着发 7 位地址加读写位从机回 ACK然后根据寄存器地址和数据长度收发音节最后 STOP。很多驱动问题最后都卡在“地址对不对”“读命令是否支持”这些细节上所以我建议在调试阶段先用 i2c-tools 手动操作一遍确认能够读到寄存器值再继续往下走。1.3 传感器类型确认是 I2C、SMBus 还是 PMBusOpenBMC 上常见的传感器有几类温度传感器比如 TMP75、LM75电压电流传感器比如 INA219、INA260、INA230风扇控制芯片比如 MAX31785EEPROM 也算广义的 I2C 设备。这些东西和 kernel 的 i2c 驱动以及 hwmon 子系统配合都很好但要注意 SMBus 和 I2C 并不完全等价。SMBus 可看作 I2C 的一个子集但包结构、超时机制、SMBALERT 中断等有差异。PMBus 则是在 SMBus 基础上定义电源管理命令的协议典型代表是各种 VR 控制器、电源管理芯片。内核里 i2c-core 对三种协议都有支持但在设备树里 compatible 要选对不然驱动匹配不上。我建议你在确认芯片型号后先去 kernel 源码的 Documentation/hwmon 目录看有没有对应文档确认驱动名和 sysfs 属性再决定设备树怎么写。2. 环境准备从硬件到内核的打通2.1 确认 I2C 总线编号和引脚复用这一步看似基础实际很容易栽跟头。华硕、技嘉这类 X86 服务器主板上的 BMC 通常是 Aspeed AST2500/AST2600I2C 控制器有很多组每组对应不同的引脚。硬件设计工程师可能把传感器挂在了第 8 组 I2C 上但是设备树默认只开启了其中几路或者引脚被复用成了 GPIO这样就导致 i2cget 的时候出现“Device not found”。所以我拿到一张新板子第一件事就是把原理图“翻译”成一张表格哪个 I2C 控制器、哪一组引脚、挂载哪些器件、器件地址多少。然后在内核起来之后先去看 /sys/bus/i2c/devices/ 下列出了哪些 bus再用 i2cdetect -l 确认编号。这里特别提醒i2c 总线编号可能跟 dts 里的 alias 有关不一定连续别想当然认为 i2c0 就是 i2c controller0。2.2 在设备树中添加 I2C 传感器节点确认硬件和总线没问题之后就可以改设备树了。OpenBMC 的内核设备树在 Aspeed 平台通常存放在 arch/arm/boot/dts/aspeed-bmc-xxx.dts 或对应的 dtsi 里。添加一个 I2C 温度传感器典型写法是这样i2c4 { status okay; tmp7548 { compatible ti,tmp75; reg 0x48; }; };这里两个关键点compatible 必须和内核驱动匹配上建议直接抄内核文档里的示例而不是凭记忆写reg 是芯片的 7 位 I2C 地址左移一位后的值比如芯片地址 0x487 位dts 里写 0x48 就行但如果你是看 datasheet 里的地址字节那可能还得确认最低位是 R/W 位地址文本上要注意区分。我当时第一次调 INA260 的时候把 reg 写成了 0x80结果怎么都枚举不到。后来仔细看了 TI 的 datasheet发现 A0、A1 引脚决定的是 7 位地址地址范围是 0x40 到 0x4F我那个 0x80 完全是画蛇添足。这个问题在论坛上遇到也很频繁所以建议所有添加 I2C 设备的工程师都在提交记录里写上“地址来源datasheet page X”方便日后 review。2.3 内核配置与驱动确认设备树里面添加节点之后还不够。内核需要开启对应的驱动比如 TMP75 需要 CONFIG_SENSORS_TMP75INA260 需要 CONFIG_SENSORS_INA260如果你用的芯片是自定义逻辑可能需要启用 CONFIG_SENSORS_XXX。OpenBMC 通常用 menuconfig 或者直接改 defconfig按你的平台源码树方式操作即可。检查驱动是否加载可以用 dmesg 看有没有 “i2c 4-0048: probed”之类的日志也可以 ls /sys/bus/i2c/devices/4-0048/hwmon/hwmon*/ 看 sysfs 下有没有 temp1_input、curr1_input 这类节点。通常在移植阶段我会先用一个临时 rootfs 直接手动 modprobe 驱动确认 sysfs 属性正常再回 OpenBMC 的集成环境里做配置。2.4 没有 hwmon 驱动怎么办使用 i2c-dev 直接读写也有不少情况是传感器太新、内核没有现成驱动或者它就是个简单的 IO 扩展器、E-ink 显示屏之类不属于 hwmon 体系。此时可以启用 CONFIG_I2C_CHARDEV通过 /dev/i2c-N 接口在用户态用 ioctl 读写。这也是 OpenBMC 里调试传感器的万能办法i2cget、i2cset、i2cdetect 都依赖这个节点。当然真正产品化的话我不建议长期用用户态轮询 I2C资源占用和稳定性都不如内核驱动。但作为调试手段特别是确认芯片地址、寄存器映射是否正确的阶段i2c-tools 几乎是必备工具。OpenBMC 的 image 里通常有 i2c-tools如果精简版本里没有可以在 image recipe 里加上。3. entity-managerOpenBMC 配置传感器的“总开关”3.1 为什么必须用 entity-manager很多从嵌入式 Linux 转过来的人会觉得 OpenBMC 这套东西过于绕明明 hwmon 节点都有了直接读文件不就行了确实单机调试完全可以这样做但是 OpenBMC 面向的是数据中心场景上层 Redfish、IPMI、WebUI 都要通过 D-Bus 统一访问传感器而且不同机型硬件配置差异很大不可能为每块板子写死一套代码。entity-manager 的作用就是通过 JSON 配置描述硬件拓扑让通用服务自动生成 D-Bus 传感器对象。你这个需求如果只是“添加一颗 I2C 传感器”那流程其实是确认内核驱动能产生 hwmon 节点接着让 entity-manager 找到这颗 sensor 并关联到正确的 D-Bus 路径最后让 phosphor-hwmon 去读取数据并且周期刷新。没有 entity-manager后面这些服务都不知道该监听哪些 sysfs 路径。3.2 JSON 配置文件写法以 INA260 为例在 OpenBMC 中entity-manager 的配置一般放在源码仓库的 configuration 目录。举个例子我要添加一个挂在 i2c4 上的 INA260 电流传感器配置大概长这样{ Type: Board, Name: MyBoard_INA260, Probe: [ xyz.openbmc_project.EntityManager.FindDevice({ \Type\: \IN260\, \Address\: \0x40\ }) ], Elements: [ { Type: IN260, Name: INA260, Address: 0x40, Bus: 4, Thresholds: [ { Type: Critical, Direction: GreaterThan, Value: 10.0 } ] } ] }这个 JSON 的 Type 是用来给 FindDevice 探针匹配的“装备类型”不是芯片型号写错不会报错只是永远匹配不上。要注意 Bus 这个字段必须和设备树里实际的 i2c 总线编号一致如果你有 PCA9546 这类 I2C 多路复用器Bus 字段写法还会更复杂比如 “4-0070” 这种表示 mux 后的总线。这一点我在 5.4 节会展开。3.3 探针规则 Probe 怎么写Probe 是 entity-manager 的灵魂。它是一段可以返回 true/false 的表达式系统启动时会扫描所有 I2C 设备、GPIO、EEPROM 等通过 Probe 来决定是否启用对应的配置。最常见的两个探针是 FindDevice 和 FindAllDevices。FindDevice 要求精确匹配单个设备FindAllDevices 则会匹配总线上所有同样类型和地址的芯片适用于多个同型号传感器的情况。这里有个容易踩的坑如果探针条件写的太宽泛比如只匹配 Type 不匹配地址那么同一总线上其他型号的芯片也可能被错误关联导致 hwmon 读到错乱数据。我见过一个案例I2C 上有 24C02 EEPROM 和 LM75 温度传感器探针写成 FindDevice({ Type: EEPROM })结果 LM75 也被识别成 EEPROM系统一直报错。严谨的做法是把 Address 和 Bus 都写上最好再配合 Compatible 属性一起匹配。3.4 phosphor-hwmon 的数据刷新机制当 entity-manager 认定某个设备存在后它会通过 D-Bus 暴露一个 inventory 对象phosphor-hwmon 会监听这个对象的事件然后对匹配的 hwmon sysfs 路径进行周期轮询并更新到 xyz.openbmc_project.Sensor.Value 接口上。注意phosphor-hwmon 默认读取频率不高通常在几百毫秒到几秒级别。如果上层业务对实时性要求高比如需要 50ms 以内报警那就不能只靠 phosphor-hwmon 的轮询得考虑改写驱动或者在服务层用中断触发。大多数服务器 BMC 场景下几百毫秒的刷新足够覆盖电源、温度、风扇的控制需求所以默认配置就够用。4. 构建、烧录与验证全流程4.1 把配置应用到你的 OpenBMC 镜像里添加了 entity-manager JSON 和设备树之后需要在对应的 layer 里把配置打进最终镜像。OpenBMC 源码里通常每个机型有独立的 meta- 层以 entity-manager 的配置为例你可以放在meta-vendor/recipes-phosphor/configuration/entity-manager/entity-manager_%.bbappendbbappend 里把 JSON 文件复制到 /usr/share/entity-manager/configurations/ 目录。如果配置文件是自研的还需要在 recipe 中加上 FILESEXTRAPATHS_prepend 和 SRC_URI 的路径声明。设备树文件则在 kernel 的 bbappend 或者单独的内核源码里修改后重新编译。编译的时候我习惯只编自己改的包先编 entity-manager 试试配置格式有没有问题bitbake entity-manager等到确认配置没问题再整机编bitbake machine-image4.2 烧录与首次启动验证烧录进板子之后启动过程中就要紧盯串口日志。更高效的做法是启动完成后直接几条命令验证i2cdetect -y 4 i2cget -y 4 0x40 0x00如果回显正常说明硬件链路 OK。接着看 D-Bus 上有没有传感器对象busctl tree xyz.openbmc_project.Sensor.Value再直接读取某个传感器的数值busctl get-property xyz.openbmc_project.Sensor.Value \ /xyz/openbmc_project/sensors/current/MyBoard_INA260 \ xyz.openbmc_project.Sensor.Value Value这一连串操作里我比较建议把 busctl 的输出和 sysfs 里的原始值做对比。比如 INA260 的 curr1_input 是 520 mAD-Bus 上应该也是 0.52 或者 52.0取决于小数位 scaling。如果量纲不对多半是 entity-manager 配置里没有写对 scale 或者 sensor type 映射。4.3 数据处理与阈值设置联动传感器数据裸读出来之后OpenBMC 里还有一层“阈值”逻辑。IPMI、Redfish 能够上报传感器状态比如 upper critical、lower critical靠的就是 entity-manager 里配置的 Thresholds以及 D-Bus 上的 CriticalAlarm 等属性。我习惯在配置 Thresholds 时把单位按实际业务需求换算清楚。INA260 默认电流寄存器 1 LSB 代表 1.25 mA直接读到的 raw 值乘以 1.25 得到毫安但 OpenBMC 的 sensor value 接口通常用 double 表示单位约定随 sensor type 而定。电流传感器这条路径上单位经常是“安培”如果把毫安当安培写阈值报警就全乱套了。建议在配置里明确 Unit 字段比如Unit: xyz.openbmc_project.Sensor.Value.Unit.Amperes这样上层展示可以自动带上 A 的单位。4.4 给风扇/电源管理接口做联动有些传感器不只是为了“看一眼温度”而是要参与风扇调速或者电源控制。OpenBMC 里通常会有 phosphor-cooling-type 或者 xyz.openbmc_project.Control.ThermalMode 这样的上层服务去消费传感器数据。假如你添加的是进气温度传感器想让它参与风扇 PID 控制那除了 entity-manager 配置外可能还需要在 cooling 的配置里把 sensor 路径关联上。这块不同版本的 OpenBMC 差异比较大我建议先确认你使用的版本里风扇控制是基于“D-Bus sensor 路径”还是直接的 hwmon 路径。早期版本经常直接读 hwmon后期基本统一到 D-Bus 了。如果版本比较老可能还需要额外配置一个 service 来桥接。5. 常见故障与排查技巧实录5.1 I2C 总线上找不到设备这类问题占了排查量的一半。先用 i2cdetect -y 看有没有设备地址出现。如果扫描没有任何 ACK说明物理链路可能有问题。我的排查顺序是先量 SCL/SDA 对地电压是不是接近 VDD然后用示波器看有没有正常的时钟沿再确认地址引脚有没有接错导致设备地址变化最后查设备树里 reg 偏移是否填写正确。如果你发现探测到的地址和 datasheet 对不上比如 TMP75 明明应该是 0x48 却扫出 0x49大概率是 A0/A1 引脚电平不对或者原理图设计时把地址引脚接到了错误电平。极少数情况是芯片进入了低功耗模式或者 shutdown 状态需要先给它一个特定寄存器写入才能唤醒。5.2 设备存在但是读数恒为 0 或明显离谱设备能被枚举说明地址对了内核驱动也 probe 成功了。但读数不对通常是寄存器解析问题或者配置问题。比如一开始就提到 INA260 的电流寄存器 LSB 是 1.25 mA如果你用普通 16 位整数直接右移得到安培那肯定不对。需要根据 datasheet 的精度做换算。还有一种情况是传感器电源没给够或者芯片处于 reset 状态。有些芯片需要初始化序列比如开启 ADC、设置平均模式、写入配置寄存器。这部分在设备树层面是无法完成的需要驱动或用户态初始化。如果你用的是内核 hwmon 驱动而驱动本身不带初始化那就得自行在 entity-manager 服务启动前用 i2cset 做初始化。这是很多“读数为 0”案例的隐藏原因。5.3 hwmon 节点有但 D-Bus 对象没出现这个问题的根源基本在 entity-manager 的 Probe 或配置路径上。可以先看 entity-manager 日志确认有没有报错再检查 JSON 里 Bus 编号和 sysfs 里实际 bus 是否匹配如果配置完全正确可能还需要重启 entity-manager 服务让配置重新加载。碰到这种情况我喜欢用以下方式快速验证把 JSON 里的 Probe 先简化成 always true比如Probe: [ xyz.openbmc_project.EntityManager.FindDevice({ \Type\: \IN260\ }) ]如果这样 D-Bus 对象立刻出现说明确实是探针匹配条件的问题再逐步收紧条件。这种“排除法”帮我快速定位了非常多配置文件问题。5.4 存在 I2C MUX 时的地址与总线陷阱最后提一下 I2C MUX也就是多路复用器在 BMC 主板上实在太常见了。比如用 PCA9546 把一条物理 I2C 扩展出 4 路那 sensor 节点的 Bus 就不是简单的 I2C 控制器编号而是 mux 逻辑后面的虚拟总线。实体 Bus 可能是 4但在 sysfs 里设备显示为 4-0070 之类表示挂在地址 0x70 的 mux 后面。在内核设备树里这种结构要写成多级子节点i2c4 { status okay; pca954670 { compatible nxp,pca9546; reg 0x70; #address-cells 1; #size-cells 0; i2c-mux-idle-disconnect; i2c0 { reg 0; temp48 { compatible ti,tmp75; reg 0x48; }; }; }; };注意 i2c-mux-idle-disconnect 这个属性加上之后 mux 在空闲时会断开后续通道能够避免多路传感器之间的总线干扰。但如果你的上层服务在切换通道时读写太快可能也会引入时序问题所以要么加这个属性要么在服务逻辑里保证切换后等待足够时间。这块坑特别深我在 bringup 阶段经常花上半天调这种“时好时坏”的问题。5.5 常见问题速查表现象优先排查点可能原因i2cdetect 扫描无设备电压、上拉、地址引脚芯片没供电、SDA/SCL 接反、地址错误设备能扫到但 dmesg 无 probe 日志设备树 compatiblecompatible 与驱动不匹配driver 加载但读数异常寄存器换算、电源换算因子错误、芯片未初始化D-Bus 无传感器对象entity-manager JSONProbe 匹配不上、Bus 编号错误只有部分传感器出现在 D-BusMUX 配置I2C MUX 通道未切换、子节点地址写错读数偶尔跳变波形、上拉电阻总线电容过大、上拉电阻偏大导致边沿过缓OpenBMC 传感器接入这块我个人的建议是不要一上来就急着改代码先把硬件拓扑、设备树、entity-manager 配置、上层 D-Bus 对象这四层的关系图画出来每层验证一遍再整体联调。实际做下来绝大部分问题都集中在地址写错、Probe 匹配过宽、以及 I2C MUX 的通道处理上。先把这几处盯住了一个新传感器的 bringup 往往半天就能搞定。
返回列表