部件驱动自发现是南向硬件管理中,根据槽位在位状态自动识别部件、选择 CSR 并加载对应驱动 SO 的机制。本文面向驱动适配者和南向框架维护者,说明发现触发、SR 选择、热插拔卸载与排障方法。
认识部件驱动自发现
运行时入口在 Connector,驱动匹配与 dlopen 在 devmon。代码以 component_drivers 仓的 Connector、Scanner、EEPROM 以及 include/devmon/driver_abi.h 为准。运行时不按 PCIe VID 或 DID 搜索 DDS 文件来加载驱动。
适配步骤见《部件驱动适配指南》;总线与芯片接线见《总线芯片驱动开发指南》。
说明
本文只写发现触发、SR 选择与加载路径。DDS 代码生成与出包步骤见各品类适配指南。
目录
范围与术语
底板或上级板的 CSR 先实例化槽位 Connector。Connector 在部件在位后交出一份 SR JSON,devmon 再按 SR 里的 Unit.Compatible 加载对应 SO,并执行 ctor、init、start。
仓边界
| 层 | 组件 | 职责 |
|---|---|---|
| 发现入口 | component_drivers 的 libConnector.so | 监听在位、选择 SR、调用 Devmon。 |
| 在位采集 | Scanner、Accessor | 把硬件在位或 BoardId 同步到属性。 |
| 身份与 SR 原文 | EEPROM | 提供 GetSRData、头解析与验签。 |
| 解析与加载 | devmon | 选择 CSR、执行 dlopen、建对象树、卸载对象。 |
| 业务驱动 | 各品类 SO | 被加载后实现网卡、GPU、电源等接口。 |
术语
| 术语 | 含义 |
|---|---|
| CSR | 部件自描述的总称。运行时加载的是 SR JSON(来自文件或 EEPROM)。 |
| SR | Service Record。扩展名为 .sr,或位于 EEPROM 内的 JSON,含 Unit、Objects,常含 ManagementTopology。 |
| DDS | Device Description Schema(.dds)。定义对象路径与接口,用于代码生成,不作为运行时驱动匹配键。 |
| Connector | 槽位对象。Presence 为 1 时加载下级 SR,为 0 时卸载。 |
| Presence | Connector 在位。取值为常量 1,或 #/Scanner_xxx.Value 等引用。 |
| IdentifyMode | 下级身份识别方式。代码里只有 3 走 EEPROM;1 与 2 走本地 .sr。 |
| Compatible | SR 的 Unit.Compatible[]。按序尝试 lib${name}.so,逗号会先换成下划线。 |
| CSR1 与 CSR2 | SR 顶层 FormatVersion 小于 5.0 走 CSR1 的 device_loader;大于或等于 5.0 走 CSR2 的 device_manager。 |
| LoadStatus | Connector 加载结果。0 表示成功,255 表示默认或已卸载,其余为错误码。 |
DDS 文件名规则(如 14140130_[VID][DID]_[SVID][SSID].dds)见各品类适配指南与《部件驱动接口模型设计》,本文不把它当成发现算法。
端到端时序
启动到首次加载
discovery_service 在 hwproxy 就绪后调用 discovery::full_discovery:先加载 platform.sr(position 为 "00"),再加载 root.sr("01",带 SystemId=1)。CSR 文件在镜像、/data/opt/bmc/sr/gold、/data/opt/bmc/sr/temp 之间按 DataVersion 择优。
root SR 描述底板拓扑,包括 I2C、MUX、EEPROM、Scanner、Accessor 与槽位 Connector。每个对象同样经 Compatible 加载对应 SO。Connector 的 start() 只做两件事:预热 SR 缓存、检查初始 Presence。
热插拔
- 插入:
Presence.changed变为 1 后调用try_load_device()。同一GroupPosition已存在时,devmon 跳过重复创建。 - 拔出:变为 0 后调用
RemoveDevice,并置m_require_eeprom_refresh_on_next_load为 true。下次插入跳过 Connector 缓存,重新读 Chip。 - 拔出时不会
dlclose业务 SO。工厂缓存驱动句柄;卸载的是设备对象,不是.so文件。
下级 SR 里可以再定义 Connector,形成主板到背板、再到硬盘或电源的递归发现。
触发条件
实现位于 component_drivers 仓的 drivers/connector/connector/connector.cpp(生命周期)与 interface/i_connector.cpp(信号与加载)。
Connector 生命周期
| 阶段 | 做什么 | 不做什么 |
|---|---|---|
init | 用 from_variant 灌入 CSR,并订阅 Presence.property_changed()。 | 不读 EEPROM,不调用 AddDevice。 |
start | 调用 preload_sr_cache();若 Presence 为 1,则 mc::runtime::post(try_load_device)。 | preload 失败不改 LoadStatus。 |
stop | 清 SR 缓存;下次必须重读 EEPROM。 | 无。 |
对象表中必须有名为 Devmon 的对象,否则 try_load_device 与 RemoveDevice 会打错误日志后返回。调用的方法是 AddDeviceWithData(sr_data, connector_dict) 与 RemoveDevice(connector_dict)。
Connector 对象层还有未出现在 bmc.kepler.Connector 接口上的属性:Chip(EEPROM 引用)、Container、Position、IdChipAddr、CSRVersion。IdentifyMode 为 3 时靠 Chip 调用 GetSRData。
Presence 来源
CSR 示例:
"Presence": "#/Scanner_BoardPresence.Value"| 来源 | 行为 | 适用 |
|---|---|---|
| 常量 1 | start() 时就会异步加载。 | 永远在位的部件。 |
#/Scanner_xxx.Value | Scanner 周期读 Chip 的 BitIO 或 BlockIO,防抖后执行 Value.set_value。 | 热插拔(推荐)。 |
Accessor 的 Value | 读属性才访问硬件,无定时器。 | 更适合绑定 Id,不适合作为热插拔 Presence。 |
CSR2 Scanner(drivers/scanner/)按 Period 毫秒扫描。CSR1 Scanner(drivers/csr1/scanner/)额外有:
- 默认约 30s 启动延迟(环境变量
SCANNER_PERIOD_DELAY_TIME); SuccessDebounceCount与FailureDebounceCount(默认 10)满足后才改Value;- 同芯片偏移可合并,只让一个 master 读硬件。
说明
把 Presence 绑到 CSR1 Scanner 时,Connector 的 start() 那一刻 Presence 常常仍为 0。真正加载发生在 Scanner 第一次把 Value 写成 1 之后。
Reload 接口
接口为 bmc.kepler.Connector.Reload(Bom, Id, AuxId, IdentifyMode)。
- 若当前
LoadStatus为 SUCCESS,先RemoveDevice; - 写入非空的 Bom、Id、AuxId 和新的 IdentifyMode;
- 清 Connector 缓存与本地
csr_version.json缓存,并置 refresh; - 最多两次
try_load_device。
用于改识别方式或强制换一份 SR,而不依赖拔插。
SR 选择
实现为 IConnector::resolve_sr_data。单测见 tests/drivers/connector/test_connector.cpp。
本地文件名
目录默认 /opt/bmc/sr,可用环境变量 CONNECTOR_SR_DIR 覆盖。
{dir}/{Bom}_{Id}_{AuxId}.sr缺 Bom 或缺 AuxId 则少对应段。版本表 {dir}/csr_version.json 用去掉扩展名的文件名作 key,读取 DataVersion。
IdentifyMode
| 值 | 规格含义 | 代码行为 |
|---|---|---|
| 1 | BoardId 可读 | 不读 EEPROM。用 CSR 里的 Bom、Id、AuxId 拼本地 .sr。Id 常由 #/Accessor_xxx.Value 注入。 |
| 2 | BoardId 不可读(由配置上报) | 同上,走本地文件。 |
| 3 | EEPROM 自描述 | 读 Chip 的 BoardId 与 SR,再和本地比版本。 |
常量为 identify_mode::EEPROM = 3。其它值全部走本地文件。
IdentifyMode 为 3 时的决策
- 快路径:CSR2 的
eeprom_object::start()已解析 128 字节头并填好BoardId与DataVersion时,Connector 先用属性做身份和版本比较,需要 payload 时再GetSRData(通常打中芯片缓存)。 - 慢路径:CSR1 EEPROM 的
start()不读硬件,BoardId 为空,必然走到GetSRData。先走 CSR1 接口,失败再走 CSR2。 version_compare(a, b)表示 a 大于或等于 b(major.minor)。相等也选 EEPROM。本地严格更新则用本地内容,但对外DataVersion写成 EEPROM 的版本。- soft 合并仅针对 EEPROM 源,且
FormatVersion在 3.0(含)到 5.0(不含)之间。同名子字典以 soft 为底、EEPROM 覆盖。合并失败仍返回未合并的 EEPROM 数据。大于或等于 5.0 视为设备树 CSR,不再拆 soft。
注意
芯片返回硬件访问失败、验签失败或格式错误时,禁止降级到可能属于另一块板的本地文件。仅当完成码为成功但身份为空时,才使用 CSR 里配置的 Id。
三层缓存
| 层 | 位置 | 命中条件 | 失效 |
|---|---|---|---|
| 芯片内存 | chip_eeprom::m_sr_cache | 签名区一致,且 BoardId 与版本一致。 | 头解析失败,或调用 invalidate_sdr_cache。 |
| 芯片磁盘 | /data/opt/bmc/sr/backup/{uid}.bin | 文件尾部签名与当前签名区一致。 | 签名变化。 |
| Connector | IConnector::m_sr_cache | 来源、board_id、两个版本与文件路径一致。 | Presence 为 0、Reload,或 refresh 标志。 |
m_require_eeprom_refresh_on_next_load 初始为 true;preload 成功置 false;拔出或 Reload 再置 true。为 true 时跳过 Connector 缓存。缓存命中且 IdentifyMode 为 3 时,正式 load 可以零次访问 Chip。
EEPROM 与 LoadStatus
设备树入口:
- CSR2:
bmc.dev.Chip.SelfDescriptionRecord.GetSRData返回(uint8_t status, string json),同时刷新FormatVersion、DataVersion、BoardId。 - CSR1:
bmc.kepler.EepromData.GetSRData返回(status, json, uid)。
内部均落到 chip_eeprom::get_sdr_data(drivers/internal/chip/chip_eeprom/eeprom.cpp)。访问前锁 host_bus。
头与流水线
EEPROM 偏移以 8 字节步长计。开头 128 字节含 UID、sign_offset、csr_offset、CRC32。由此得到:
BoardId与 CSR1 的 uid 等于头内 24 字节 UID 字符串;FormatVersion等于{ver}.00;DataVersion等于{sr_version_high}.{sr_version_low:02}。
get_sdr_data 顺序:解析头,读签名区,查内存或磁盘缓存,读被签名区。签名区非全 0 则做 ECDSA-SHA256 验签(先内置 P-256 公钥,失败再读 /opt/bmc/trust/partner/device_desc_pubkey.bin),再从 csr_offset 解 gzip JSON。签名全 0 视为老硬件无签名。部分遗留 UID 在白名单内,签名参数非法或验签失败也可放行(不备份)。
解压失败返回完成码 4(获取 SR 失败),此时 Connector 才允许转本地 CSR。页读写与 CRC 实现细节见 EEPROM 源码,本文不展开。
LoadStatus
与 EEPROM 完成码对齐,由 Connector 在正式 load 时写入(preload 失败不改):
| 值 | 名称 | 典型原因 |
|---|---|---|
| 0 | SUCCESS | AddDeviceWithData 成功。 |
| 1 | HARDWARE_ACCESS_FAIL | I2C、总线或前级 MUX 失败。 |
| 2 | SIGNATURE_VERIFY_ERROR | 验签失败且不在白名单。 |
| 3 | DATA_FORMAT_ERROR | 头 CRC 失败、非 JSON 或 gzip,或本地 JSON 损坏。 |
| 4 | LOCAL_FILE_ERROR | .sr 不存在或读失败。 |
| 5 | OBJECT_PARSE_ERROR | 预留。 |
| 6 | INTEGRITY_VERIFY_ERROR | 预留。 |
| 7 | FORMAT_VERSION_ERROR | 预留。 |
| 255 | DEFAULT | 初始、已 Remove,或 Reload 清状态。 |
try_load_device 在拿到 SR 后调用 AddDevice;invoke 抛异常时打错误日志,不一定改写 LoadStatus(成功路径才置 0)。拿不到 SR 时把上述错误码写到 LoadStatus。
框架侧加载
Connector 交出 SR 之后的工作在 devmon。FormatVersion 小于 5.0 走 CSR1 的 device_loader;否则走 CSR2 的 device_manager::add_device。
从 Compatible 到 SO
Compatible: ["hisi_182x"]对应libhisi_182x.so。Compatible: ["Mellanox,CX5"]对应libMellanox_CX5.so。- CSR1 还会尝试
libCsr1${name}.so。 - 列表从前到后,第一个加载成功的即停;全部失败则解析失败,设备不会 start。
- SO 候选来自驱动安装目录与
/data/opt/bmc/drivers/gold、temp(flash_driver_store)。实现细节以 devmon 为准。
Unit.Type 必须等于该 SO 注册的某个 device_name(如 PCIeNicCard)。对象实例名来自 Unit.Name,并可按 Connector 的 position 重命名以保证全局唯一。
ManagementTopology
SR 的 ManagementTopology 把模板总线名映射到 Connector 字典里的 Buses(字符串数组,元素是底板已有总线对象名)。devmon 抽出可达的 Bus 与 Chip,在 device_manager(component_drivers 的 libInternal.so)里创建内部对象并接线 left_bus、host_bus、left_chip。设备树 SO 的 init() 不创建内部芯片,只按 object_name 执行 find_object。EEPROM 能 GetSRData 的前提是这条拓扑已经接好。详见《总线芯片驱动开发指南》。
ABI
以 component_drivers 仓的 include/devmon/driver_abi.h 为准。每个 SO 必须用 extern "C" 导出 register_device_driver。
typedef enum status {
STATUS_OK = 0,
STATUS_ERROR = 1,
STATUS_NOT_FOUND = 2,
STATUS_INVALID_ARG = 3,
STATUS_NOT_IMPLEMENTED = 4,
STATUS_TIMEOUT = 5,
STATUS_BUSY = 6,
STATUS_NO_MEMORY = 7,
} status_t;
struct device_driver {
const char* device_name;
const driver_ctor ctor; // (service, object_name) -> handle
const driver_init init; // (handle, csr_object, connector)
const driver_start start;
const driver_stop stop;
driver_dump dump; // 可为 nullptr
};一个 SO 可注册多种 device_name。以 drivers/pcie_nic_card/hisi/hi182x/hi182x_abi.cpp 为例,同一库导出 PCIeNicCard、NicPort、OpticalTransceiver。空指针参数返回 STATUS_ERROR,不要使用已不存在的 STATUS_INVALID_PARAM。
ctor 只创建对象并设置 service 与 object_name。init 灌入 CSR、建子对象关系,不开定时器和协议。start 才启动业务。stop 停业务并清理。Connector 自身就是这个模型:init 订阅信号,start 才检查 Presence。
热插拔 RemoveDevice 对主对象调 stop,并尝试 stop 同 position 下的子类型对象,不会因此卸载 .so。若调用 device_driver::unload(),必须先清空 m_devices 再 dlclose,因为函数指针在库内。
属性注入
CSR 的 Objects 按接口名组织,经 MC_REFLECT 与 from_variant 映射到对象属性。占位符(${Slot}、#/Scanner.Value 等)由 devmon 的 CSR 解析与引擎引用机制处理,不是 Connector 里的独立函数。Connector 传给框架的字典字段包括:ObjectName、Bom、Slot、GroupId、GroupPosition、Presence、Id、AuxId、Buses、SystemId、ManagerId、SilkText、Type、IdentifyMode、ChassisId、LoadStatus、Container、Chip。缺省时用默认值,避免类型转换失败。
适配检查清单
驱动写完却加载不上时按此核对,不必先怀疑 VID 或 DID 扫描。
底板或上级 CSR:
- [ ] 有对应槽位的 Connector 对象,且
Compatible能加载到libConnector.so。 - [ ]
Presence有来源:常量 1,或指向已创建的 Scanner 的Value。 - [ ]
IdentifyMode与板类型一致:EEPROM 自描述用 3;否则保证本地.sr的 Bom、Id、AuxId 能拼出文件。 - [ ] IdentifyMode 为 3 时,
Chip指向已接线的 EEPROM 对象(host_bus与left_bus非空)。 - [ ]
Buses列出下级 SR 拓扑要用到的底板总线名。 - [ ]
GroupPosition在同一 CSR 内不重复。
下级 SR 与驱动 SO:
- [ ] 文件在
/opt/bmc/sr/(或 gold、temp、EEPROM);IdentifyMode 不是 3 时,csr_version.json有对应条目。 - [ ]
Unit.Type等于 SO 里某个device_name。 - [ ]
Unit.Compatible能落到已安装的libxxx.so(注意逗号转下划线)。 - [ ]
Objects接口名与MC_REFLECT、DDS 生成的gen::接口一致。 - [ ] 需要热插拔时,不要把 Presence 只绑在无定时器的 Accessor 上。
品类目录、DDS 命名与出包步骤仍以《网卡驱动适配指南》至《风扇电源驱动适配指南》为准。
故障排查
按 LoadStatus
| LoadStatus | 先查 |
|---|---|
| 255 且部件已在位 | Presence 是否从未变成 1(Scanner 延迟或防抖);对象表有无 Devmon。 |
| 1 | I2C、MUX、EEPROM 总线;Chip 引用是否为空。 |
| 2 | 验签公钥、是否老 UID 白名单、签名区是否损坏。 |
| 3 | EEPROM 头 CRC、gzip 或 JSON;或本地 .sr 不是合法 JSON。 |
| 4 | /opt/bmc/sr/{Bom}_{Id}_{AuxId}.sr 是否存在;Id 是否已从 EEPROM 改写导致文件名变化。 |
| 0 但业务对象没有 | AddDevice 后 Compatible 与 Unit.Type 是否匹配;查看 devmon 侧加载日志。 |
日志关键字
[self-discovery]与[self-release]:Presence 边沿。SR source=EEPROM与SR source=LOCAL:最终选用哪份数据。merge soft data:soft 合并是否成功。Devmon.AddDeviceWithData failed:框架解析或建对象失败(此时 SR 已选出)。
Connector dump
LoadStatus 为 SUCCESS 时,Connector::dump() 在标准属性(. 前缀)之外输出 - 前缀字段,包括 SourcePath、SoftSourcePath、DataSource(EEPROM、LOCAL 或 NA),以及有效、EEPROM、本地的 FormatVersion 与 DataVersion。hwdiscovery 的 discovery dump 会汇总各 Connector,并补 flash 副本完整性字段。
注意
不要用主机上的 lspci 或 i2cdetect 作为 BMC 南向自发现的诊断手段。
附录(对照源码)
路径相对于对应 Git 仓根目录。component_drivers 见 GitCode 仓库;devmon 见 GitCode 仓库。
| 主题 | 路径 |
|---|---|
| Connector 状态机、SR 选择 | drivers/connector/connector/interface/i_connector.cpp |
| Connector ABI 与 dump | drivers/connector/connector/connector.cpp、connector_abi.cpp |
| CSR2 EEPROM 的 GetSRData | drivers/chip/interface/i_self_description_record.cpp |
| 头解析、验签、backup | drivers/internal/chip/chip_eeprom/eeprom.cpp |
| CSR1 GetSRData(start 不读硬件) | drivers/csr1/chip/common/interface/i_eeprom_data.cpp、drivers/csr1/chip/eeprom/eeprom.cpp |
| CSR2 与 CSR1 Scanner | drivers/scanner/scanner/、drivers/csr1/scanner/scanner/ |
| Accessor | drivers/accessor/accessor/ |
| ABI 头文件 | include/devmon/driver_abi.h |
| 启动发现 | libs/hwdiscovery/src/discovery/discovery.cpp(devmon) |
| Add 与 Remove | libs/hwdiscovery/src/discovery/hwdiscovery_root_interface.cpp(devmon) |
| Compatible 与拓扑 | libs/common/src/device/csr.cpp、manager.cpp(devmon) |
| SO 加载与 flash 择优 | libs/common/src/device_driver.cpp、flash_driver_store.cpp(devmon) |
| Connector 行为单测 | tests/drivers/connector/test_connector.cpp |