1. 添加I2C传感器前先把这三件事想清楚做OpenBMC开发尤其是做到第11篇这种阶段基本已经过了“怎么编译”“怎么烧录”的新手期真正开始接触具体硬件外设了。添加I2C传感器看着简单无非就是温度、电压、风扇转速这一类但真做起来牵扯到的东西一点都不少硬件上要确认总线号和设备地址内核侧要改设备树、确认驱动加载用户态还要让dbus-sensor和entity-manager认账。任何一个环节漏了传感器就是“识别不到”或“读数乱跳”的结果。先说清楚这篇要解决什么问题在OpenBMC平台上通过I2C总线新增一个传感器比如温度传感器TMP75、电压传感器INA219这类常见器件从硬件确认、设备树配置、内核验证一直到用户态识别和上层dbus接口暴露完整走通一遍流程。适合谁看正在做OpenBMC硬件移植的工程师或者准备在自己的板子上加监控传感器、但还没把整条链路理清楚的朋友。我建议动手之前先把下面三件事想明白。这不是废话我在实际开发中因为跳过这些步骤返工过好几次。1.1 硬件侧总线、地址、上拉一个都不能少I2C是OpenBMC设备监控的主力通信方式几乎所有的温度传感器、电压监测芯片、EEPROM、时钟芯片都挂在I2C总线上。但I2C这个协议本身很依赖硬件设计的正确性软件配置再对硬件有问题照样跑不起来。先说总线选择。OpenBMC通常管理着一组I2C bus每条bus上挂着若干设备。你需要确认你的传感器挂在哪条总线上Bus号是多少。这个信息一般来原理图比如“TMP75 on I2C Bus 3地址0x48”。拿到原理图后顺着传感器的SCL和SDA走线看连到了CPU或BMC的哪一组I2C控制器再对应dts里的bus编号。然后是设备地址。I2C的7位地址由器件引脚决定。拿TMP75来说A0、A1、A2三个引脚接高或接低决定地址的高3位基地址是0x48所以可选地址范围是0x48到0x4F。千万别以为所有传感器都是固定地址很多器件都支持通过引脚配置多个不同地址这在同一条总线上挂多个相同器件时特别重要。你还得注意7位地址和8位地址的区别Linux内核和i2c-tools里用的是7位地址数据手册里有的会写成8位左移了一位对不上就会出现找不到设备的问题。最后是上拉电阻。I2C是开漏结构SCL和SDA必须有外部上拉电阻才能工作。常见的上拉阻值在2.2kΩ到10kΩ之间总线速度越高、挂的设备越多需要越小阻值。阻值太大信号上升沿太慢阻值太小又可能拉不动或功耗过大。如果板子已经正常跑起来这个一般不用操心但如果是你自己在设计底板扩展传感器一定要把上拉电阻加上而且只加一组不要每个传感器都加一排并联后的等效阻值会低到你怀疑人生。1.2 软件侧OpenBMC的两层架构到底怎么协作OpenBMC的软件栈说简单也简单说复杂也复杂。从传感器这条链路来看它分成明显的两层第一层是内核空间。内核负责I2C控制器的驱动、I2C总线抽象、具体传感器的驱动比如lm75、ina2xx这些以及通过sysfs导出传感器数据。这个层面的核心就是设备树你得让内核知道这条总线上有一个传感器它是什么型号地址是多少有没有别名。设备树搞定了内核起来之后对应驱动就会probe然后出现类似/sys/class/hwmon/hwmon0/temp1_input这样的文件。第二层是用户空间。OpenBMC的监控体系在用户态跑了两个关键服务entity-manager和dbus-sensor。entity-manager负责扫描硬件拓扑读取配置文件JSON格式识别出板子上有哪些传感器、它们被称为什么名字dbus-sensor则根据entity-manager给出的信息去读取对应的sysfs节点定时采样然后通过D-Bus接口把数据发布出去。上层应用比如Web界面、Redfish、IPMI去查传感器读数查的都是dbus-sensor发布出来的数据。这两层的关系可以这样理解设备树告诉内核“这里有什么”entity-manager告诉用户态“这里有什么东西可以读”dbus-sensor负责“把这个东西读出来并发布”。你添加一个传感器如果只是想让内核知道改设备树就够了但如果想让OpenBMC的监控体系真正“看到”这个传感器三层都得打通。注意OpenBMC里不同厂商的发行版对传感器配置的细节有差异但核心都是“设备树 entity-manager配置 dbus-sensor”这条链路。我下面写的流程在新版本的OpenBMC上基本通用。2. 设备树配置告诉内核传感器长在哪设备树是OpenBMC添加传感器绕不过去的一步。你要做的事情简单说就是在对应I2C控制器的节点下面增加一个子节点描述这个传感器。听起来容易但里面有几个细节不处理好的话后面折腾半天都找不出原因。2.1 找到正确的I2C总线节点OpenBMC的kernel设备树文件通常放在arch/arm/boot/dts/aspeed-ast2500.dtsi、aspeed-g4.dtsi或aspeed-g6.dtsi这类公共文件里而具体板子的dts文件则对应你使用的机型比如meta-evb/meta-evb-aspeed/meta-evb-ast2500/recipes-kernel/linux/linux-aspeed/devictrees/aspeed-bmc-evb.dts。打开dts文件后先搜i2c关键字你会看到类似这样的结构i2c3 { status okay; /* 传感器子节点将来加在这里 */ };这里有几个关键检查点这个i2c3对应的就是I2C Bus 3控制器状态status okay表示启用。如果你要用的那条总线还没有定义你需要先根据原理图确认它连到了哪个控制器然后在dts里补充对应的节点并把status改成“okay”。如果status是“disabled”内核不会初始化这条总线后面什么都是白搭。我遇到过一种情况板子的某个I2C控制器本身没有独立引脚而是通过复用引脚出来的这种除了把status设为okay还要配置pinctrl确认引脚复用正确。不然总线虽然注册了但物理上没有连通仍然扫描不到设备。2.2 传感器子节点的关键属性在总线节点下添加传感器子节点时至少有这几个属性必须写对compatible这个属性告诉内核该用哪个驱动。TMP75、LM75这类传感器用的是ti,tmp75或lm75INA219用的是ti,ina219。具体选哪个值最好查一下内核文档或驱动源码里的of_match_table不要凭感觉。regI2C设备地址7位地址用十六进制表示。这个是硬件决定的必须跟原理图严格一致。label非必须但强烈建议加。给传感器起个容易识别的名字比如CPU_Temp、PSU_Voltage后面在sysfs和entity-manager配置时能省不少事。除此之外不同传感器可能还有自己的可选属性。比如INA219可以配置shunt-resistor分流电阻阻值如果驱动支持你可以在设备树里直接指定避免每次都在用户态换算。注意reg属性最容易踩坑。我曾经把一个8位设备地址0x90写进设备树结果设备一直无法识别后来才发现I2C协议里7位地址是0x488位地址是0x90两者是同一个设备但驱动和设备树都要求7位形式。类似问题的排查准则是一切以i2cdetect扫描结果为准。2.3 一个完整示例以TMP75温度传感器为例假设你有一块AST2500的BMC板子TMP75挂在I2C Bus 7上地址0x4C想用来监测CPU附近温度。那么你需要在dts里这样写i2c7 { status okay; tmp754c { compatible ti,tmp75; reg 0x4C; label CPU_Temp; }; };注意节点名里的4c只是标识作用真正决定地址的是reg属性。写完这个后续的操作顺序是重新编译内核的dtb - 集成进BMC镜像 - 烧录到板子 - 重启后用i2cdetect和dmesg验证。我见过很多人在这里容易犯一个低级错误——修改了dts之后只重新编译了kernel但没有把新的dtb打包进最终的BMC镜像里导致怎么改都不生效。后面专门用一节说编译和验证流程这里先记住你改的是哪个仓库的哪个文件就要确保那个文件所在的层被bitbake包含。3. 编译、烧录与内核侧验证设备树改完之后看起来只是一段简单的文本但从这段文本到系统真正识别出传感器中间隔着一整个编译部署流程。这个流程对老手来说轻车熟路但对刚开始接触OpenBMC的朋友来说可能绕了不少弯路。3.1 哪些层会被修改编译时被触发OpenBMC的构建系统基于Yocto/bitbake整个镜像由多个meta层叠加而成。你修改的dts文件通常在某个板级layer里比如meta-vendor/recipes-kernel/linux/linux-obmc/*.bbappend或linux-aspeed相关的layerbbappend文件里会用SRC_URI把你自定义的dts文件加进来覆盖或追加到内核源码树里。编译时你通常执行的是类似这样的命令bitbake core-image-minimal-meta或者直接编译整个BMC镜像bitbake openbmc-dev编译过程中kernel相关任务会自动重新提取、打补丁、配置、编译并生成新的dtb文件。如果你不清楚你的修改有没有被包含可以再加一句bitbake -c cleanall linux-aspeed bitbake -c build linux-aspeed注意cleanall会把内核源码目录和所有编译产物都清掉会有一次几乎从零开始的编译耗时很长但对保证修改被重新加载很有效。如果你只改了设备树不要每次都cleanall直接用bitbake linux-aspeed重新编译验证dts是否被纳入了编译范围就行。3.2 烧录后第一件事确认设备树生效烧录完成后重启BMC第一件事不是急着看传感器数据而是先确认设备树内容真的变了。用tftp更新镜像后登录到BMC的Linux shell执行cat /proc/device-tree/i2c7/.../name或者更直接的方式看dmesgdmesg | grep -i i2c正常情况你会看到类似下面的输出i2c i2c-7: new_device: Instantiated device tmp75 at 0x4c lm75 i2c-7:0070: hwmon0: (null)/0出现“new_device”和“lm75”相关的日志说明内核已经成功识别到了设备。如果dmesg里完全没有输出先别怀疑设备树没生效用i2cdetect手动扫描一下总线看看硬件层面到底能不能看到设备。3.3 用i2cdetect和i2cget手动读取原始数据i2cdetect是调试I2C设备的必备工具在你确认设备树配置之前应该先用它确认硬件连接i2cdetect -y -r 7这个命令会在Bus 7上扫描所有地址如果设备地址是0x4C你会看到类似下面这样的输出0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- 4c -- -- ...看到0x4C有响应说明硬件链路是通的设备地址无误。这时候再用i2cget直接读取原始寄存器验证传感器是否能返回有效数据。TMP75的11位温度数据存在寄存器0x00里读取方式i2cget -y 7 0x4c 0x00 w输出类似0x1b50这样的原始值换算一下TMP75的温度值高字节是整数部分低字节高3位是小数部分每个bit代表0.125°C。0x1b50左移3位校正后大概是27°C这个值只要在合理范围就说明传感器本身工作正常。到了这个阶段内核侧已经能看到数据了。前面提到的/sys/class/hwmon/hwmon0/temp1_input此时应该也能读到对应的数值。但这距离OpenBMC上层识别还很远。内核识别只是第一步接下来才是真正的OpenBMC特色让传感器出现在D-Bus上。经验分享我在调试时喜欢先手动验证寄存器和sysfs再配置用户态。因为这一步能帮你区分问题出在底层还是上层。如果sysfs里已经能读到正确的温度值后面entity-manager或dbus-sensor配置有问题那就是纯软件问题如果连sysfs都没有那就回到设备树和内核驱动去排查少走很多弯路。4. 用户态识别让OpenBMC把传感器“收编”如果你只需要在BMC的Linux shell里手动看传感器数据到上面一步就结束了。但OpenBMC的意义在于它拥有一套完整的监控和管理框架传感器读数应该通过D-Bus暴露出来供Redfish、IPMI、Web UI等上层服务调用。这就要说到entity-manager和dbus-sensor了。4.1 dbus-sensor与entity-manager是怎么配合的在OpenBMC里entity-manager是一个“硬件数据库”。它扫描系统里的I2C总线、GPIO、EEPROM这些基础设备然后根据预置的配置模板JSON文件识别出当前板子上有哪些“entity”实体。这里的entity可以是传感器、风扇、电源、主板、CPU等。识别完成后entity-manager会把信息整理成D-Bus对象发布为xyz.openbmc_project.EntityManager服务并把传感器配置信息保存在/var/configuration/目录下。dbus-sensor是一个“读数代理”。它从entity-manager那里了解应该读取哪些传感器的sysfs节点然后周期性地读取把数据标准化之后以xyz.openbmc_project.Sensor.Value接口发布到D-Bus上。上层服务读取传感器数据时只需要访问这个接口就行不需要关心底层是I2C还是GPIO也不关心是怎么换算的。所以你添加传感器的用户态部分本质上就是两件事给entity-manager提供一份正确的配置文件确保它能识别出板上有这个传感器再确认dbus-sensor能把sysfs的数据读出来并通过D-Bus发出去。4.2 编写传感器的JSON配置文件entity-manager的配置通常由板级layer提供一般放在meta-vendor/recipes-phosphor/entity-manager/configurations/目录下文件名类似board.json。如果你不用自己的layer也可以直接修改entity-manager源码仓库里的配置文件。新建一个JSON文件内容大致如下{ Exposes: [ { Name: CPU_Temp, Type: Temperature, ReadPath: /sys/class/hwmon/hwmon0/temp1_input, Scale: 1000, Unit: xyz.openbmc_project.Sensor.Value.Unit.DegreesC } ], Probe: [ compatible \ti,tmp75\ ] }这个JSON里重点字段解释一下Name传感器的逻辑名称后面会在D-Bus路径里体现比如/xyz/openbmc_project/sensors/temperature/CPU_Temp。Type传感器类型Temperature、Voltage、Current、Fan等。类型选择直接影响dbus-sensor把它发布到哪个路径下比如temperature类型的会发布到sensors/temperature/下。ReadPath读取路径就是前面确认过的sysfs文件路径。这个必须是实际存在的路径否则dbus-sensor会一直报错。Scale缩放因子。sysfs里的原始值通常是毫单位比如毫摄氏度除以这个值就得到实际值。对TMP75来说temp1_input直接返回毫摄氏度所以Scale设1000表示读数除以1000得到摄氏度。Probe探测条件是配置跟硬件的匹配规则。entity-manager会根据这里的条件判断这个配置是否适用于当前板子。如果你不想用自动探测也可以直接用固定的配置但那样的话换板子时可能会匹配不上或者匹配上不该匹配的设备。模块放好后需要重新编译并部署entity-manager。命令大致如下bitbake phosphor-entity-manager bitbake phosphor-dbus-sensor然后生成新的BMC镜像并烧录。重启后用busctl或dbus-sensor自带的工具检查一下传感器是否真的注册上了。命令行方式busctl tree xyz.openbmc_project.EntityManager busctl tree xyz.openbmc_project.Sensor.Value如果一切正常你会看到类似/xyz/openbmc_project/sensors/temperature/CPU_Temp这样的路径读取这个路径的Value属性就能拿到实时温度值。5. 常见问题与排查技巧实录最后这部分我把自己在实际添加I2C传感器过程中踩过的坑、见过的错误做法、排查方法整理一下。有些问题看起来一个比一个蠢但真的遇到了卡你个半天很正常。5.1 设备树改了但dmesg里没有任何反应这是最高频的问题。排查顺序如下确认设备树文件有没有真正编译进dtb。简单做法是在BMC上执行cat /proc/device-tree/查看当前生效的设备树内容搜索你的传感器节点在不在。不在的话回到bitbake流程检查你的bbappend或者dts文件路径对不对。确认I2C总线状态是不是okay。很多dts里保留了未启用节点你加了子节点但总线整体还是disabled自然不会有任何反应。确认bus编号对不对。设备树里的i2c7对应系统里的/dev/i2c-7。可以用ls /dev/i2c-*确认。老内核或者总线复用的情况下编号可能跟你在dts里看到的不一样。确认地址有没有写错。7位地址和8位地址的坑前面说过了。还有一个坑是reg写成了小数或者十进制设备树解析会失败报错信息一般在dmesg里能看到。5.2 地址冲突与总线复用同一条I2C总线上不同设备的地repeat绝不能一样。如果两个设备都是TMP75地址都是0x4C那么扫描时只能看到一个响应另一个永远识别不到。解决办法是通过硬件引脚配置不同地址或者在PCB设计时就规划好地址分配。还有一个容易被忽略的问题I2C总线复用。BMC的I2C总线有时会接PGA比如PCA9546来扩展出多路总线。物理上你可能只有一个I2C控制器但通过mux可以分出4路或8路独立总线。这种情况下你在dts里看到的i2c7可能实际上是一个mux下的子总线设备树的写法、sysfs路径、i2cdetect的总线编号都会跟普通的I2C有所区别。如果传感器在mux后面还需要配置mux相关的设备树节点否则内核根本不知道如何切换总线通道去访问那个传感器。5.3 entity-manager识别不到新传感器如果你确认sysfs里已经有传感器节点了但D-Bus上没有新传感器问题基本出在JSON配置的Probe条件或者ReadPath上。检查Probe条件是否匹配。比如你配置了compatible ti,tmp75但实际设备树里写的是national,lm75systemd的probe就匹配不上。检查ReadPath路径是否准确。用ls /sys/class/hwmon/看看到底生成了哪些hwmon目录目录编号可能会因为内核启动顺序变化而改变比如这次是hwmon0下次变成hwmon1。如果你的配置里写死了hwmon0那就会出现“有时候能读到有时候不能读”的诡异现象。更稳妥的做法是如果驱动和内核支持的话用设备树里的label或者固定sysfs路径有的驱动可以通过device-tree里的label属性让sysfs路径更稳定。5.4 读数异常0x7FFF、负数、跳变传感器读数如果是0x7FFF这是典型的“无数据”标志说明驱动已经probe成功但读到的数据无效。可能原因有芯片功能本来就不正常比如供电不稳、接线不良。用i2cget手动读寄存器试试看返回什么。传感器寄存器地址配置不对。有的器件支持寄存器分页比如16位ADC需要先写控制寄存器再读转换结果如果驱动不支持或初始化不正确就会一直拿到无效数据。误用到其他传感器的驱动。内核驱动是根据compatible匹配的如果compatible写错比如把INA219写成TMP75驱动会尝试以错误的寄存器布局去解释数据读出来的数值自然毫无意义。温度跳变、电压突变也很常见。排查时先排除是不是传感器本身的问题手动连续读取十几次看数据是否在合理范围内小幅度波动。如果手动读值稳定但dbus-sensor报出来的值不稳定检查dbus-sensor采样时间和滤波设置。某些传感器类型在OpenBMC里有自己的平滑或滤波算法配置不当会导致读数波动被放大。5.5 一张表总结问题现象 vs 排查重点现象排查重点常见根因i2cdetect扫不到设备硬件连接、地址、总线编号上拉电阻缺失、地址写错、总线复用未配置i2cdetect能扫到但sysfs无节点设备树compatible/reg/总线status驱动不匹配、status为disabled、节点没编入dtbsysfs有节点但报0错误内核驱动与芯片通信芯片供电或接线问题、驱动初始化不完整entity-manager不识别JSON配置Probe/ReadPathcompatible条件不匹配、路径写死hwmon编号dbus-sensor不发布数据dbus-sensor日志、Scale配置Scale过大/过小、ReadPath不存在读数跳变、负值原始寄存器、滤波配置、驱动类型驱动类型错误、滤波参数不合适、硬件干扰这张表并不是标准答案但排查方向和这基本八九不离十。实际开发中遇到的每一个“灵异现象”最后都能落回到上面某一行里。最后再说一个我个人的小习惯每次在OpenBMC里新增I2C传感器我会先在板子上单独用i2cdetect确认硬件通路再手动读一次寄存器确认数值符合预期然后才去改设备树。设备树生效后再去看sysfssysfs确认无误后才配置entity-manager。每一步都验证完再往下走。虽然看起来麻烦但排查问题的时间会少一半以上。添加I2C传感器这件事说到底是“硬件、内核、用户态”三段式的工程每段都有各自的验证方法按顺序走一次性打通的成功率能高很多。