libmgmt_protocol
版本信息
| 项目 | 内容 |
|---|---|
| 组件版本 | 1.100.14 |
| 首发版本 | 0.0.1 |
| 文档作者 | [请填写文档编写者社区账号] |
| 最后更新 | 2026-09-04 |
1. 组件概述
1.1 组件简介
libmgmt_protocol 是针对南向多样化硬件传输协议创建的 Lua 协议库,屏蔽底层硬件访问差异,为上层的 network_adapter、storage、nsm 等业务组件提供统一的硬件属性访问框架。
组件仅对协议数据进行操作(协议报文拼装、响应数据解析),不负责具体的传输。传输依赖底层传输组件(hwproxy / mctpd)保证正确性,组件本身不感知底层传输介质。
组件同时承载了南向硬件访问所需的多个基础 C 库(frudata、ncsi_protocol、sml 系列、lsw),以 Conan 组件形式对外提供编译接口。
1.2 解决什么问题
BMC 业务组件访问各类硬件(网卡、硬盘、RAID 卡、交换芯片等)时,需要面对以下问题:
- 协议种类繁多:SMBus、NCSI、NVMe-MI、PLDM 等协议报文格式各异,且同一协议族下还有厂商私有扩展(如华为、Mellanox、Emulex、QLogic OEM 协议);
- 访问模式多样:既有单次读取(如读 MAC 地址),也有周期性轮询(如实时温度监控),还需要在数据变化/出错时得到通知;
- 配置与代码耦合:若将协议拼装逻辑散落在各业务组件中,硬件变更时修改成本高。
本组件通过"协议库 + 配置驱动"的方式解决上述问题:业务组件只需编写一份属性访问配置文件(声明协议、访问方式、请求参数、响应解析函数),即可获得统一的硬件访问对象,无需关心协议细节。协议实现集中在组件内,新硬件/新协议只需新增配置文件或新增协议类,无需改动业务代码。
1.3 核心功能
- 核心功能一:多协议支持。支持 SMBus 系(std_smbus / smbus / smbus_5902 / smbus_postbox / smbus_riser)、NCSI 系(ncsi_standard / ncsi_huawei / ncsi_mellanox)、NVMe-MI(nvme_mi_standard)、PLDM 系(pldm_standard / pldm_huawei / pldm_sensor / pldm_emulex_fru / pldm_qlogic_fru)共 14 种协议,覆盖 i2c 与 MCTP 两大类传输通道。
- 核心功能二:统一硬件属性访问框架。通过
device_spec_parser解析配置生成访问对象,支持单次访问(OnDemandProperty)和轮询访问(OnScheduleProperty)两种模式;轮询调度器支持周期调整、参数更新、数据变更/错误信号订阅。 - 核心功能三:通用报文解析工具。提供基于 bitstring 的通用解包(
common_bs_helper)、通用响应数组解析(create_array_parser)、LLDP TLV 报文解析、vdpci LLDP 专属报文解析。 - 核心功能四:南向基础 C 库。随仓提供 frudata(FRU 数据解析)、ncsi_protocol(NCSI 协议 C 库)、sml 系列(硬盘 SMART/日志解析,含 platform、sml_base、sml_histore、sml_lsi、sml_pmc、pd_log_parse)、lsw(交换芯片驱动)等 C 库,供各业务组件编译链接。
1.4 关键术语表
| 术语 | 解释 |
|---|---|
| SMBus | System Management Bus,基于 I2C 的系统管理总线协议,本组件中通过 hwproxy 访问 |
| MCTP | Management Component Transport Protocol,管理组件传输协议,本组件中通过 mctpd 访问 |
| NCSI | NC-SI(Network Controller Sideband Interface),带外管理接口协议,本组件支持 over MCTP 方式 |
| NVMe-MI | NVMe Management Interface,NVMe 设备管理接口协议 |
| PLDM | Platform Level Data Model,平台级数据模型协议,用于平台管理消息交互 |
| FRU | Field Replaceable Unit,现场可更换单元,FRU 数据为硬件部件的固件信息(含 elabel 电子标签) |
| SML | S.M.A.R.T. 相关日志解析库,用于硬盘健康状态与故障日志(SAS/SATA)解析 |
| endpoint | MCTP 协议通信端点对象,由 mctpd 提供,所有 MCTP 类协议(NCSI/NVMe-MI/PLDM)依赖它收发报文 |
| ref_chip | 硬件访问芯片引用对象,由 hwproxy 提供,所有 i2c 类协议(SMBus 系)依赖它读写总线 |
| on_demand | 单次访问模式,访问一次硬件属性并返回结果 |
| on_schedule | 轮询访问模式,按固定周期持续访问硬件属性,支持信号订阅 |
| scheduler | 轮询调度器对象,由 on_schedule 模式返回,负责周期轮询、数据缓存与信号触发 |
| protocol_dependencies | 属性访问配置文件中的协议依赖段,声明各协议运行所需的传输对象(ref_chip / endpoint) |
1.5 外部交互边界图
┌────────────────────────────────────────────────────────────────┐
│ 业务组件层 │
│ network_adapter / storage / nsm / ...(Lua 业务) │
└───────────────┬────────────────────────────────────────────────┘
│ require 'libmgmt_protocol' / device_spec_parser
┌───────────────▼────────────────────────────────────────────────┐
│ libmgmt_protocol(本组件) │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Lua 协议层(协议报文拼装 / 响应解析,不负责传输) │ │
│ │ protocol/: smbus系 · ncsi系 · nvme_mi · pldm系 │ │
│ │ transport/: hw_communicator(单次/轮询)· scheduler │ │
│ │ common/: bs_helper · lldp_tlv_parser · ... │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ C 库(编译链接接口):frudata · ncsi_protocol · │ │
│ │ sml(platform/sml_base/sml_histore/sml_lsi/ │ │
│ │ sml_pmc/pd_log_parse)· lsw │ │
│ └───────────────────────────────────────────────────────────┘ │
└───────┬──────────────────────────────┬──────────────────────────┘
│ ref_chip(i2c/SMBus) │ endpoint(MCTP)
┌───────▼───────────────┐ ┌──────────▼──────────────────────────┐
│ hwproxy(传输组件) │ │ mctpd(传输组件) │
│ i2c / SMBus 总线 │ │ MCTP over PCIe VDM / SMBus │
└───────┬───────────────┘ └──────────┬──────────────────────────┘
│ │
┌───────▼──────────────────────────────▼──────────────────────────┐
│ 南向硬件(网卡/硬盘/RAID卡/交换芯片等) │
└─────────────────────────────────────────────────────────────────┘说明:
- 组件自身不持有任何硬件访问通道,
ref_chip/endpoint对象由使用方传入; - 使用者需要自行加载对应的传输组件(hwproxy / mctpd)和资源树 interface,否则传输会失败;
- 组件可被多个组件同时加载。
2. API 使用说明与示例
libmgmt_protocol 对外接口为 Lua 接口(资源协作接口为 N/A,本组件不对外提供 D-Bus/busctl 接口),主入口为 require 'libmgmt_protocol' 后调用 device_spec_parser 生成访问对象。
2.1 device_spec_parser(chip_config)
功能说明
解析硬件属性访问配置文件,生成硬件访问对象。配置文件声明了协议依赖(protocol_dependencies)和属性集合(properties),解析成功后对象按属性名暴露访问接口,业务组件通过 obj:<属性名>() 即可访问硬件属性。
| 属性 | 内容 |
|---|---|
| 接口名 | N/A(Lua 接口) |
| 首发版本 | 0.0.1 |
| 废弃状态 | 正常可用 |
参数说明
| 参数名 | 方向 | 类型 | 描述 | 取值范围 |
|---|---|---|---|---|
| chip_config | 输入 | table | 硬件属性访问配置文件,必须包含 protocol_dependencies 与 properties 两个字段 | 非空;properties 与 protocol_dependencies 均不可为空 |
chip_config 结构示例:
local a_network_adapter = {
protocol_dependencies = {
smbus = { -- 所用通过I2C的协议都需要传入ref_chip对象,
ref_chip = nil, -- 通过hwproxy对硬件进行访问
buffer_len = 64, -- 协议支持的最大字节数
},
ncsi_huawei = { -- 所有通过mctp的协议都需要传入endpoint对象,
endpoint = nil -- 通过mctpd对硬件进行访问
}
},
properties = {
ChipTemp = { -- 属性名,也是属性访问接口, obj:ChipTemp()
protocol = 'smbus', -- 协议名,必须是已支持的协议
action = 'on_schedule', -- 单次访问还是轮询访问
period_in_sec = 2, -- 轮询访问的周期,单位为秒
request = { -- 协议所需的请求数据,具体格式由各协议检查
opcode = 0x3, -- smbus协议需要的opcode
expect_data_len = 2 -- smbus需要的返回数据长度
},
response = function(data) -- 返回数据解析函数,会去掉协议中协议的部分,data仅包括具体数据
-- 仅在硬件正常返回响应时才会调用此函数,有错误时不会调用此函数
local r = bs.new([[<<temp:16>>]]):unpack(data, true) -- 支持使用bitstring进行二进制解包
return r.temp -- 返回值为obj:ChipTemp()的返回值
end
}
}
}
return a_network_adapter更复杂配置可参考 network_adapter 的配置。
返回值与异常
| 返回值 | 含义 | 触发条件 | 处理建议 |
|---|---|---|---|
| obj | 硬件访问对象 | 配置合法,解析成功 | 通过 obj:<属性名>() 访问硬件属性 |
| 异常(log:raise) | 配置非法 | 配置为空 / 缺少 properties 或 protocol_dependencies / 属性引用了未在 protocol_dependencies 中声明的协议 | 检查配置文件是否完整、协议名是否拼写正确 |
应用场景
所有业务组件访问南向硬件属性的入口。典型流程:业务组件加载自身配置文件 → 调用 device_spec_parser 生成对象 → 按属性名调用单次或轮询接口。
限制条件
- 调用前必须确保底层传输组件(hwproxy / mctpd)已加载、
ref_chip/endpoint资源树对象已就绪; - 配置解析失败(配置非法)会直接抛异常,需要在业务侧捕获处理;
- 若配置中重复定义同一属性,后定义的不会覆盖先定义的,仅打印告警日志。
调试示例
-- 在 skynet 服务中调试
local libmgmt_protocol = require 'libmgmt_protocol'
local config = require 'path.to.config.obj'
-- 若配置有问题这里会抛异常
local obj = libmgmt_protocol.device_spec_parser(config)
print(obj:OnDemandProperty({data = 'some data'}):value())2.2 OnDemandProperty 单次访问接口
功能说明
单次访问硬件属性。obj:<属性名>(request_data) 内部按 action = 'on_demand' 分发到单次访问通道,返回值为可调用的访问对象,调用 :value() 获得具体结果。
| 属性 | 内容 |
|---|---|
| 接口名 | N/A(Lua 接口) |
| 首发版本 | 0.0.1 |
| 废弃状态 | 正常可用 |
参数说明
| 参数名 | 方向 | 类型 | 描述 | 取值范围 |
|---|---|---|---|---|
| request_data | 输入 | table/nil | 运行时传入的请求数据补充(如 port_id、data),与配置中的 request 合并,若冲突以配置值为准 | 可为 nil,表示完全使用配置中的请求数据 |
返回值与异常
| 返回值 | 含义 | 触发条件 | 处理建议 |
|---|---|---|---|
| value() 返回值 | 属性值 | 硬件正常返回且响应解析成功 | 直接使用返回值 |
| nil | 访问失败或属性不存在 | 传输失败 / 响应长度不足 / 属性不存在 | 检查传输组件与请求参数;nil 不抛异常,需业务侧判空 |
| true | 发送成功无解析 | 未配置 response 解析函数且发送成功 | 无需处理返回值 |
应用场景
低频、非实时性要求的属性读取,如读取 MAC 地址、版本信息等。
限制条件
- 单次访问接口不缓存数据,每次调用都会发起一次硬件访问;
- 若
request_data校验失败(如传入配置中未允许的参数),接口返回 nil 并打印 error 日志,不会抛异常。
调试示例
-- 对于单次访问返回值的属性,需要调用:value()获取具体的值
-- 若属性不存在或者无法获取属性值:value()会返回nil
-- 入参为配置请求数据的补充,若冲突则不会覆盖配置中的请求数据
local on_demand_property = obj:OnDemandProperty({data = 'some data'}):value()2.3 OnScheduleProperty 轮询访问接口
功能说明
按配置周期持续轮询硬件属性。obj:<属性名>() 按 action = 'on_schedule' 分发到轮询通道,返回 scheduler 调度器对象。scheduler 不会自动开始轮询,需手动调用 :start() 开启;支持数据变更信号(on_data_change)、错误信号(on_error)订阅,以及运行期更换参数和周期。
| 属性 | 内容 |
|---|---|
| 接口名 | N/A(Lua 接口) |
| 首发版本 | 0.0.6(信号机制引入) |
| 废弃状态 | 正常可用 |
参数说明
| 参数名 | 方向 | 类型 | 描述 | 取值范围 |
|---|---|---|---|---|
| period_in_sec | 配置 | number | 轮询周期,配置于属性 period_in_sec 字段 | 必须 ≥ 0;= 0 时 scheduler 无法 start |
| request_data | 输入 | table/nil | 同单次访问,配置 request 与运行时 request 合并 | 可为 nil |
scheduler 对象方法:
| 方法 | 说明 |
|---|---|
| :start() | 开启轮询,返回第一次获取的数据;period_in_sec ≤ 0 时返回 nil |
| :update_params(params) | 更换入参,下一次轮询时生效 |
| :set_period(sec) | 更换轮询周期,秒为单位;≤ 0 的入参被忽略 |
| :pause() / :resume() | 暂停 / 恢复轮询 |
| :deconstruct() | 停止轮询,释放调度器 |
| on_data_change 信号 | 当获取数据不同于 scheduler 缓存数据时触发,携带新数据 |
| on_error 信号 | 每次轮询获取数据失败(nil)时触发 |
返回值与异常
| 返回值 | 含义 | 触发条件 | 处理建议 |
|---|---|---|---|
| scheduler 对象 | 轮询调度器 | 属性存在 | 调用 :start() 开启轮询 |
| :start() 返回首次数据 | 第一次轮询结果 | 轮询已开启 | 使用或忽略 |
| nil(start) | 无法启动 | period_in_sec ≤ 0 | 检查配置中是否配置周期 |
| on_error 信号 | 轮询失败通知 | 每次轮询获取数据为 nil | 检查传输组件与硬件状态 |
应用场景
实时性要求高的监控类属性,如芯片温度、电源状态等需要持续关注的属性;也适用于需要感知数据变化(如状态翻转)的场景。
限制条件
- scheduler 依赖 skynet 框架(fork/sleep),不能在非 skynet 环境下运行;
- scheduler 不会自动开启,必须手动调用
:start(); - 不使用时必须调用
:deconstruct()停止轮询,否则会持续占用协程; - 对不存在的属性,scheduler 的所有函数均为 no op,可用于不确定属性是否存在时的安全调用;
- 同一属性的相同参数调度器会被复用(返回同一个 scheduler 对象)。
调试示例
-- 对于轮询访问的属性,返回体为scheduler对象
-- 若请求数据不需要变化,则不用传入任何数据
local on_schedule_property_scheduler = obj:OnScheduleProperty()
-- scheduler不会自动开启,需要手动调用:start()开启,同时会返回第一次获取的数据
local on_schedule_property = on_schedule_property_scheduler:start()
-- scheduler支持订阅数据变更信号,当获取数据不同于scheduler缓存数据时,会发送
-- on_data_change信号和新的数据
on_schedule_property_scheduler.on_data_change:on(function(data)
print('new data received!')
end)
-- scheduler支持订阅错误信号,当每次轮询获取数据失败(nil)时,会发送on_error信号
on_schedule_property_scheduler.on_error:on(function()
error('unable to read data from hardware!')
end)
-- scheduler支持更换入参,调用后下一次轮询时便会使用新的参数
on_schedule_property_scheduler:update_params({
name = 'another_property_name', -- 新属性名
protocol = 'new protocol', -- 新协议名称(暂不支持加载未使用过的协议)
request = {...}, -- 协议所需的入参
})
-- scheduler支持更换轮询周期,调用后下一次轮询时便会使用新的参数
on_schedule_property_scheduler:set_period(10) -- 10s轮询一次
-- scheduler会根据配置的轮询周期一直访问数据,在不使用时需要调用:deconstruct()停止轮询访问
on_schedule_property_scheduler:deconstruct()
-- 对于不存在的属性,调用scheduler的任意函数都不会起效,用于在不确定属性是否存在时
local not_exist_property = obj:NotExistProperty()
not_exist_property:start() -- no op
not_exist_property.on_data_change:on(function()
assert('never reach here') -- 此行永远无法到达
end)
not_exist_property.on_error:on(function()
assert('never reach here') -- 此行永远无法到达
end)
not_exist_property:deconstruct() -- no op2.4 create_array_parser(obj_meta, obj_length)
功能说明
创建 bitstring 数组响应解析函数,用于解析由多个相同结构体组成的响应数据(如 MAC 地址列表)。创建时传入单个结构体的 bitstring 定义与单个结构体长度,返回解析函数。
| 属性 | 内容 |
|---|---|
| 接口名 | N/A(Lua 接口) |
| 首发版本 | 0.0.1 |
| 废弃状态 | 正常可用 |
参数说明
| 参数名 | 方向 | 类型 | 描述 | 取值范围 |
|---|---|---|---|---|
| obj_meta | 输入 | string | 单个结构体的 bitstring 定义字符串 | 非空 |
| obj_length | 输入 | number | 单个结构体数据长度(字节) | > 0 |
返回值与异常
| 返回值 | 含义 | 触发条件 | 处理建议 |
|---|---|---|---|
| 解析函数 | 数组解析器 | 参数合法 | 作为属性 response 解析函数使用 |
| 异常(log:raise) | 参数非法 | obj_meta 为空或 obj_length ≤ 0 | 检查入参 |
| nil(解析时) | 数据长度不足 | 响应长度 < obj_length | 检查硬件返回是否完整 |
应用场景
解析固定长度的结构体数组响应,例如网卡返回的多个 MAC 地址、多个传感器数据。
限制条件
- 响应数据长度必须是 obj_length 的整数倍,否则多余部分不参与解析;
- 单个结构体定义建议复用
libmgmt_protocol.common_bs_helper提供的通用格式(如MAC_ADDRESS)。
调试示例
-- 解析MAC地址数组,单个MAC为6字节
local parse_mac_list = libmgmt_protocol.create_array_parser(
[[
_:8,
mac_address_count:16/big,
_:16,
mac_addrs:8/MAC_ADDRESS
]], 8)2.5 通用解析工具(common_bs_helper / lldp_tlv_parser / vdpci_lldp_parser)
功能说明
libmgmt_protocol 将 common 模块中的解析工具统一暴露到入口对象,便于业务组件复用。
| 属性 | 内容 |
|---|---|
| 接口名 | N/A(Lua 接口) |
| 首发版本 | 0.0.1 |
| 废弃状态 | 正常可用 |
| 工具 | 说明 |
|---|---|
common_bs_helper | 通用 bitstring 格式定义,创建 bs 时作为第二个参数传入,提供 MAC_ADDRESS、IPV4 等格式 |
lldp_tlv_parser | 基于 IEEE 802.1ab 标准解析 LLDP TLV 报文,无法解析的报文会抛异常,需用 pcall 包裹 |
vdpci_lldp_parser | 华为网卡经 MCTP 自定义通道获取的 LLDP 报文专属解析函数,无法解析时抛异常,需用 pcall 包裹 |
支持的通用 bitstring 格式:
| 名称 | bitstring名称 | 返回格式 |
|---|---|---|
| Mac地址 | MAC_ADDRESS | {mac_address = '00:11:22:33:44:55'} |
| Ipv4地址 | IPV4 | {ipv4 = '192.168.1.255'} |
支持的 LLDP TLV 报文:
| TLV ID | 名称 | 介绍 | 返回格式 |
|---|---|---|---|
| 1 | ChassisID | 解析ChassisID TLV报文 | {chassis_id:string, chassis_id_subtype:string} |
| 2 | PortID | 解析PortID TLV报文 | {port_id:string, port_id_subtype:string} |
| 3 | TTL | 解析TTL TLV报文 | {ttl:U16} |
| 5 | SystemName | 解析SystemName TLV报文 | {system_name:string} |
| 127 | OrgSpecific | 解析OrgSpecific TLV报文,目前用于获取vlanid | {vlan_id:U16} |
参数说明
| 参数名 | 方向 | 类型 | 描述 | 取值范围 |
|---|---|---|---|---|
| data | 输入 | string | 待解析的二进制报文数据 | 非空 |
返回值与异常
| 返回值 | 含义 | 触发条件 | 处理建议 |
|---|---|---|---|
| 解析结果 table | 解析成功 | 报文符合对应 TLV 结构 | 直接使用 |
| 异常 | 解析失败 | 报文无法按标准解析 | 使用 pcall 捕获 |
应用场景
common_bs_helper:配合bs.new在属性 response 函数中做二进制解包;lldp_tlv_parser/vdpci_lldp_parser:解析网卡 LLDP 报文,获取交换机/端口/VLAN 信息。
限制条件
- 所有解析失败均以异常形式抛出,业务侧必须用
pcall保护; vdpci_lldp_parser仅适用于华为网卡 MCTP 自定义通道返回的 LLDP 报文。
调试示例
-- 使用common_bs_helper解包
local r = bs.new([[
_:8,
mac_address_count:16/big,
_:16,
mac_addrs:8/MAC_ADDRESS
]], libmgmt_protocol.common_bs_helper):unpack(data, true)
-- 解析LLDP报文
local ok, result = pcall(libmgmt_protocol.lldp_tlv_parser.parse, data)2.6 C 库接口总览
随仓发布的 C 库通过 Conan 组件对外提供编译接口(组件名格式 libmgmt_protocol::<组件名>),头文件随包安装。接口定义详见各库头文件。
| C库 | Conan组件 | 产物 | 头文件安装路径 | 功能简介 |
|---|---|---|---|---|
| frudata | libmgmt_protocol::libfrudata | libfrudata.so | include/frudata | FRU 数据解析/生成,含 elabel 电子标签、IPMI FRU 信息 |
| ncsi_protocol | libmgmt_protocol::libncsi_protocol | libncsi_protocol.so | include/ncsi/ncsi_protocol | NCSI 协议 C 库,含 LLDP over NCSI、NCSI socket 收发 |
| sml_base | libmgmt_protocol::libsml_base | libsml_base.so | include/sml/sml_base | SML 基础库(硬盘健康/诊断基础能力) |
| sml_custom_base | libmgmt_protocol::libsml_custom_base | libsml_custom_base.so | include/sml/sml_base | SML 定制基础库 |
| sml_histore | libmgmt_protocol::libsml_histore | libsml_histore.so | include/sml/sml_histore | SML 历史记录库 |
| sml_lsi(可选) | libmgmt_protocol::libsml_lsi | libsml_lsi.so | include/sml/sml_lsi | SML LSI 厂商适配库(storelib_enable 使能) |
| sml_pmc(可选) | libmgmt_protocol::libsml_pmc | libsml_pmc.so | include/sml/sml_pmc | SML PMC RAID 卡适配库(storelib_enable 使能) |
| pd_log_parse | libmgmt_protocol::libpd_log_parse | libpd_log_parse.so | include/sml/pd_log_parse | 硬盘日志解析(SAS/SATA/Seagate 等) |
| platform | libmgmt_protocol::libplatform | libplatform.so | include/sml/platform | SML 平台适配库 |
| lsw_drv | libmgmt_protocol::liblsw_drv | liblsw_drv.so | include/lsw/lsw_drv | 交换芯片驱动库(lsw_8363 / lsw_sf2507,lsw_enable 使能) |
CMake 使用示例:
find_package(libmgmt_protocol REQUIRED)
target_link_libraries(your_target PRIVATE libmgmt_protocol::libncsi_protocol libmgmt_protocol::libfrudata)注:sml_lsi / sml_pmc 组件需构建选项
storelib_enable使能后可用,lsw_drv 需lsw_enable使能,详见附录C。
3. 组件扩展案例
3.1 扩展能力概述
组件支持代码级二次开发:新增协议类以支持新硬件协议。协议实现采用继承对象方法,相似协议中相同步骤的部分以继承的方式共享代码,差异部分通过重写子类函数实现。例如:
smbus和std_smbus的传输方法相同,仅发送协议不同,因此 smbus 继承 std_smbus 的大部分函数,只重写协议拼装函数;ncsi_huawei和ncsi_mellanox都是ncsi_standard的 oem 命令部分,因此继承ncsi_standard的所有函数,仅添加额外拼装和解包步骤。
3.2 扩展点说明
扩展点为 include/libmgmt_protocol/protocol/ 目录下的协议类文件。新增协议需实现以下必要函数:
| 函数 | 说明 |
|---|---|
protocol:ctor(params) | 构建函数。params 为协议配置文件中的 protocol_dependencies,由使用组件定义(如 mctp 中传入 endpoint,smbus 传入 ref_chip 和 buffer_len),需与组件配置文件协同 |
protocol:send_request(request) | 传输请求函数。request 为各属性配置的 request 请求结构体(运行态与配置静态数据的结合体);返回响应体具体数据(若配置了响应解析函数,该数据为解析函数入参);请求失败返回 nil;无需解析函数时返回 true 代表发送成功 |
protocol:validate_request_params(request) | 参数检查函数。解析配置文件时调用一次(入参为静态数据),运行时再调用一次(入参为动态数据,为 nil 则跳过);返回 true 才可继续传输,false 则不会传输 |
可重写点(相似协议共享代码时):
| 重写点 | 说明 | 示例 |
|---|---|---|
construct_request_data(ctx, request) | 请求报文拼装 | ncsi_huawei 重写基类拼装函数,先封装自己的部分再调用基类函数 |
unpack_response_data(ctx, rsp_data_bin) | 响应报文解析 | ncsi_huawei 直接返回自己的解析数据 |
send_and_receive(data, len) | 硬件访问 | smbus_5902 在发送前检查硬件可访问性,Write/Read 分开发送 |
request_params_template | 参数白名单 | smbus 在 ctor 中覆盖基类模板,缩小允许的参数集合 |
3.3 二次开发指导
步骤一:新增协议文件
在 include/libmgmt_protocol/protocol/ 下新建 <协议名>.lua。协议类需继承公共基类 protocol(require 'libmgmt_protocol.protocol.protocol'),或继承相似协议类。
步骤二:实现必要函数
实现 ctor、send_request、validate_request_params 三个必要函数。相似协议场景下,仅重写差异函数即可。
示例代码
添加相似协议(NCSI 华为协议继承 NCSI 标准协议,重写拼装/解包函数):
protocol/ncsi_standard.lua(基类,拼装和解包拆成独立函数):
-- ncsi_standard拼装函数只需要拼装ncsi标准协议的部分
function ncsi_standard:construct_request_data(ctx, request)
return req_ctx, req_bin
end
-- ncsi_standard解析函数只需要解析ncsi标准协议的部分
function ncsi_standard:unpack_response_data(ctx, rsp_data_bin)
return ...
end
-- 将协议拼装和解包拆成独立的函数
function ncsi_standard:send_request(request)
local req_ctx, req_bin = self:construct_request_data(ctx, request)
local ok, rsp_data_bin = pcall(self.endpoint.Request, self.endpoint, req_ctx, req_bin, 0)
return self:unpack_response_data(ctx, rsp_data_bin)
endprotocol/ncsi_huawei.lua(子类,重写拼装函数,封装自己的部分后再调用基类):
local ncsi_huawei = class(ncsi_standard)
-- ncsi_huawei重写基类封装函数,封装自己的部分,再调用基类封装函数
function ncsi_huawei:construct_request_data(ctx, request)
local request_with_ncsi_huawei_info = ...
return ncsi_huawei.super.construct_request_data(ctx, request_with_ncsi_huawei_info)
end
-- ncsi_huawei重写基类解析函数,直接返回自己的解析数据
function ncsi_huawei:unpack_response_data(ctx, rsp_data_bin)
return data
end重写硬件访问函数(smbus_5902 继承 std_smbus,发送前检查硬件可访问性):
protocol/std_smbus.lua(基类,硬件访问拆成独立函数):
-- std_smbus硬件访问使用hwproxy的WriteRead函数
function std_smbus:send_and_receive(data, len)
return pcall(function()
return self.ref_chip:WriteRead(ctx:new(), data, len)
end)
end
-- 将硬件访问拆成独立的函数
function std_smbus:send_request(request)
...
local ok, rsp_bin = self:send_and_receive(data, len)
...
endprotocol/smbus_5902.lua(子类,Write 和 Read 分开发送):
local smbus_5902 = class(std_smbus)
-- smbus_5902要求在发送之前检查硬件是否可以访问,所以必须将Write和Read单独发送
-- 可以通过重写基类硬件访问函数,加入自己所需的步骤
function smbus_5902:send_and_receive(data, len)
if self:check_idle() then
self:send(data)
if self:check_idle() then
return self:receive(len)
end
end
end重写参数检查(smbus 私有协议继承 std_smbus,缩小允许的参数集合):
protocol/std_smbus.lua(基类):
-- std_smbus允许arg和data参数
local request_params_template<const> = {
opcode = true,
expect_data_len = true,
arg = true,
data = true
}
-- 在构建函数时存储request_params_template
function std_smbus:ctor()
self.request_params_template = request_params_template
end
function std_smbus:validate_request_params(request)
for key in pairs(req) do
if not self.request_params_template[key] then
return false
end
end
return true
endprotocol/smbus.lua(子类,ctor 中覆盖模板即可):
local smbus = class(std_smbus)
local request_params_template<const> = {
opcode = true,
expect_data_len = true
}
-- 在构建时会先调用基类构建函数,再调用子类构建函数
-- 在这里存储request_params_template即可覆盖
function smbus:ctor()
self.request_params_template = request_params_template
end验证方法
- 新增协议的单元测试:在
test/unit/protocol/下新建test_<协议名>.lua,必须覆盖protocol:ctor()、protocol:validate_request_params()、protocol:send_request()三个基本函数;传输层由外部传入,打桩传输对象即可模拟硬件访问; - 运行单元测试:
./build.py -ut(或make unit_test),确认新协议用例全部通过; - 集成验证:在
test/test_data/test.lua中配置新协议属性,运行./build.py -it(或make joint_test)验证配置解析与单次/轮询访问。
注意事项
- 协议名必须与配置文件
protocol_dependencies中的键保持一致,且与protocol/下的文件名一致(require 'libmgmt_protocol.protocol.<协议名>'); - 传输对象(endpoint / ref_chip)由使用方在配置中传入,协议内必须判空,避免空指针异常;
- 请求失败时
send_request应返回 nil 而非抛异常,失败信号由 scheduler 的 on_error 统一处理; - 新增协议文件后需同步补充单元测试,保持协议必要函数覆盖。
4. 日志说明
4.1 一键日志收集
执行系统一键日志收集功能时,自动收集本组件的以下日志文件(日志路径由 config.cfg 中的 logger / logpath 配置决定):
| 文件路径 | 内容说明 |
|---|---|
| /data/var/log/bmc/libmgmt_protocol.log | libmgmt_protocol 运行日志(协议访问、配置解析、调度器运行等) |
4.2 关键日志信息
| 日志片段 | 日志级别 | 含义解读 | 建议处理动作 |
|---|---|---|---|
unable to create device spec parser with empty config | ERROR | 传入 device_spec_parser 的配置为空 | 检查业务组件传入的配置文件是否为空 |
unable to create device spec parser with no properties or no protocol dependencies | ERROR | 配置缺少 properties 或 protocol_dependencies | 检查配置文件结构是否完整 |
unable to validate request template | ERROR | 属性 request 参数不在协议允许的参数模板内 | 检查属性配置的 request 参数是否合法 |
no protocol info in the config file. protocol: <name> | ERROR | 属性引用了 protocol_dependencies 中未声明的协议 | 在 protocol_dependencies 中补充该协议的依赖(endpoint / ref_chip) |
duplicated config for property <name>. will not override | WARN | 配置中重复定义同名属性,后者不生效 | 检查配置文件,去除重复定义 |
unsupported action: <action> | ERROR | 属性 action 不是 on_demand / on_schedule | 检查 action 字段拼写 |
protocol not support, <protocol> | ERROR | 访问时协议未成功加载 | 检查协议文件是否存在、依赖是否传入 |
period is not set, unable to start scheduler to scan | ERROR | scheduler 周期未配置时调用 start | 检查属性配置的 period_in_sec |
5. 问题定界指南
5.1 典型问题定界
| 现象描述 | 是否为本组件问题 | 判断依据 | 关键证据收集方法 |
|---|---|---|---|
| 单次访问返回 nil / 轮询持续触发 on_error | 否(大概率) | libmgmt_protocol 只负责协议报文拼装与解析,传输由 hwproxy / mctpd 完成;若底层传输组件未加载、endpoint / ref_chip 未就绪,本组件必然访问失败 | 抓取 /data/var/log/bmc/libmgmt_protocol.log 与 hwproxy / mctpd 日志,确认传输侧是否有错误 |
| device_spec_parser 抛异常 | 是 | 异常信息明确指向配置非法(配置为空 / 缺字段 / 协议未声明) | 收集抛异常时的配置文件与日志,核对协议名、action、request 参数 |
| 属性读取到错误值 | 是(可能) | 响应解析函数或协议解包逻辑与硬件实际返回格式不符 | 抓取原始响应报文(协议层 debug 日志),比对 bitstring 模板与实际数据结构 |
| scheduler 不轮询 / 无数据 | 否(可能) | scheduler 依赖 skynet 框架,非 skynet 环境或未调用 :start() 都会导致不轮询 | 确认运行环境与是否手动调用 start;检查 period_in_sec 配置 |
5.2 错误码速查表
本组件为 Lua 协议库,不定义数值错误码,错误以异常信息(log:raise)或日志片段形式暴露,速查如下:
| 错误码/异常 | 含义 | 可能原因 | 排查建议 |
|---|---|---|---|
unsupported action: <action> | 不支持的访问模式 | 属性 action 字段拼写错误 | 检查 action 是否为 on_demand / on_schedule |
cannot have on_schedule task with period_in_sec < 0 | 轮询周期为负 | period_in_sec 配置非法 | 检查轮询属性周期配置 |
unable to validate request template | 请求参数不合法 | request 含协议不允许的参数 | 对照各协议 request_params_template 检查参数 |
cannot create parser without obj_meta | 解析器模板为空 | create_array_parser 传入空模板 | 检查 obj_meta 参数 |
cannot create parser with obj_length <= 0 | 结构体长度非法 | create_array_parser 传入长度 ≤ 0 | 检查 obj_length 参数 |
response length(%s) is less than template length(%s) | 响应数据过短 | 硬件返回不完整或模板长度配置错误 | 抓取原始报文核对长度 |
unable to validate request_data when accessing property | 运行时参数校验失败 | request_data 传入协议不允许的参数 | 检查运行时入参 |
5.3 调试方法
开启调试日志
组件日志级别由 mc.logging 控制。调试时可将日志级别调低以输出协议访问细节:
local log = require 'mc.logging'
log:setLevel(log.DEBUG)开启后可在 /data/var/log/bmc/libmgmt_protocol.log 中看到协议创建、参数校验、调度器运行等 debug 信息。
复现问题方法
- 前置条件设置:确保 hwproxy / mctpd 传输组件已加载,目标硬件在线,endpoint / ref_chip 资源树对象可获取;
- 操作步骤:
- 构造复现配置:在
test/test_data/test.lua中配置目标属性(协议、action、request、response 解析函数); - 编写最小复现脚本,调用
device_spec_parser后按问题场景执行单次访问或开启轮询; - 开启 debug 日志复现问题;
- 构造复现配置:在
- 预期现象:单次访问返回 nil、scheduler 触发 on_error、或抛异常;收集该过程中的组件日志与传输组件日志用于定界。
6. 常见问题解答
Q1:为什么单次访问 :value() 返回 nil?
- 问题描述:调用
obj:OnDemandProperty():value()返回 nil,但硬件看起来正常。 - 一句话答案:传输失败、响应解析失败或属性不存在都会返回 nil,且不抛异常。
- 根因说明:
:value()在传输失败(send_request返回 nil)或响应长度不足时返回 nil;属性不存在时返回空对象(所有方法为 no op);运行时入参校验失败也会返回 nil。 - 解决方案:先确认属性名存在且拼写正确;再检查传输组件(hwproxy/mctpd)与 endpoint/ref_chip 是否就绪;开启 debug 日志确认请求是否真实发出。
- 规避方案(如适用):在业务侧对 :value() 返回值判空,并订阅 scheduler 的 on_error 信号感知传输异常。
- 适用版本:全部版本。
- 相关文档链接(可选):无。
Q2:scheduler 调用了 :start() 但没有数据输出?
- 问题描述:轮询属性调用 :start() 后没有返回数据,也没有报错。
- 一句话答案:period_in_sec 未配置(为 0 或 nil)时 scheduler 无法启动,start 返回 nil。
- 根因说明:scheduler:start() 在
period_in_sec <= 0时打印 error 日志并返回 nil,不会开启轮询。 - 解决方案:检查属性配置中是否配置了大于 0 的
period_in_sec。 - 规避方案(如适用):可通过
:set_period(sec)在运行时设置周期后再 start。 - 适用版本:全部版本。
- 相关文档链接(可选):无。
Q3:对不存在的属性调用接口会不会报错?
- 问题描述:业务代码中不确定某属性是否存在,直接调用后担心崩溃。
- 一句话答案:不会报错,不存在的属性返回空对象,所有调用均为 no op。
- 根因说明:
device_spec_parser生成的访问对象对未知属性返回empty_retrieve_obj,其 value/start/update_params/set_period/deconstruct 及信号订阅均为空函数。 - 解决方案:无需处理,可安全调用;若需区分属性是否存在,可在配置解析后检查对象字段。
- 规避方案(如适用):无。
- 适用版本:全部版本。
- 相关文档链接(可选):无。
Q4:访问 MCTP 类协议(NCSI/PLDM)属性失败?
- 问题描述:配置了 ncsi_huawei 或 pldm_standard 属性,访问始终失败。
- 一句话答案:先确认 protocol_dependencies 中是否传入了有效的 endpoint 对象,且 mctpd 服务正常运行。
- 根因说明:所有 MCTP 类协议依赖 endpoint 对象收发报文,endpoint 为空、未判空(旧版本)或 mctpd 未加载都会导致发送失败。
- 解决方案:在配置文件的 protocol_dependencies 中传入 endpoint;确认 mctpd 已加载且目标 endpoint 已发现(busctl 查询资源树)。
- 规避方案(如适用):访问前先确认 endpoint 资源存在。
- 适用版本:全部版本;1.100.7 起协议层已增加 endpoint 判空保护。
- 相关文档链接(可选):无。
Q5:如何更换轮询属性的请求参数或轮询周期?
- 问题描述:scheduler 启动后,需要动态修改请求数据或访问周期。
- 一句话答案:调用 scheduler 的
:update_params(params)和:set_period(sec),下一次轮询时生效。 - 根因说明:scheduler 每次轮询使用缓存的 params 与 period,运行期可通过上述方法更新缓存。
- 解决方案:
update_params传入新的 request(新协议名暂不支持加载未使用过的协议);set_period传入大于 0 的秒数。 - 规避方案(如适用):无。
- 适用版本:全部版本。
- 相关文档链接(可选):无。
附录
附录A 参考资料
- 目录结构、测试方法等详见仓库 doc 目录
- PCIE设备带外管理接口规范(smbus, std_smbus,含部分标准smbus协议规范)
- CloudServer C 5.0.0 MCU IIC_SMBUS软件接口文档(std_smbus)
- NCSI标准协议 DSP0222(ncsi_standard,包括sideband/over mctp)
- MCTP标准协议 DSP0236(mctp)
- Hi182X华为MCTP命令定义(ncsi_huawei,包含ncsi/pldm等协议)
- LLDP官方网站(LLDP)
- NVIDIA SMBus Post-Box Interface(SMBPBI)
- Nvme-MI Specification(NVME-MI)
附录B 修订记录
| 版本 | 日期 | 修订人 | 修订内容 |
|---|---|---|---|
| v1.0 | 2026-09-04 | [请填写] | 初始版本创建 |
附录C 构建与测试
目录结构
libmgmt_protocol/
├─ include/ -- include文件夹会在打包时移至/opt/bmc/lualib中,无需额外配置lua加载路径
│ └─ libmgmt_protocol/ -- libmgmt_protocol库
│ ├─ init.lua -- libmgmt_protocol入口文件,提供device_spec_parser等对外函数
│ ├─ protocol/ -- 协议文件夹
│ │ ├─ protocol.lua -- 协议interface文件,协议基类
│ │ ├─ std_smbus.lua / smbus.lua / smbus_5902.lua / smbus_postbox.lua / smbus_riser.lua -- SMBus系协议
│ │ ├─ ncsi_standard.lua / ncsi_huawei.lua / ncsi_mellanox.lua -- NCSI系协议
│ │ ├─ nvme_mi_standard.lua -- NVMe-MI标准协议
│ │ └─ pldm_standard.lua / pldm_huawei.lua / pldm_sensor.lua / pldm_emulex_fru.lua / pldm_qlogic_fru.lua -- PLDM系协议
│ ├─ transport/ -- 协议传输层,用于封装协议传输函数
│ │ ├─ hw_communicator.lua -- 用于创建单次访问/轮询
│ │ └─ scheduler.lua -- 用于轮询访问硬件的插件
│ ├─ common/ -- 共享常用协议解析工具
│ │ ├─ init.lua -- 对外暴露common_bs_helper / lldp_tlv_parser / vdpci_lldp_parser
│ │ ├─ bs_helper.lua -- 通用bitstring格式
│ │ ├─ lldp_tlv_parser.lua -- lldp tlv报文解析函数
│ │ └─ vdpci_lldp_parser.lua -- vdpci lldp专属报文解析函数
│ └─ bios/ -- bios组件仓闭源代码(pfr / bios_firmware / bin_parser / cms等)
├─ src/
│ └─ lualib-src/ -- 闭源C库代码
│ ├─ frudata/ -- FRU数据C库(libfrudata)
│ ├─ ncsi_protocol/ -- NCSI协议C库(libncsi_protocol)
│ ├─ sml/ -- SML日志解析库(platform / sml_base / sml_histore / sml_lsi / sml_pmc / pd_log_parse / smlib / sysc)
│ └─ lsw/ -- 交换芯片驱动库(liblsw_drv)
├─ test/
│ ├─ test_data/ -- 测试协议配置文件目录
│ ├─ unit/ -- 单元测试(协议/传输/common/bios)
│ └─ integration/ -- 集成测试(skynet框架)
├─ mds/service.json -- 组件版本信息与依赖描述
├─ conanfile.py -- Conan 构建配置(组件、头文件打包、依赖)
├─ CMakeLists.txt -- CMake 构建入口
├─ Makefile -- 单元/集成测试入口
└─ build.py -- 构建脚本构建选项
| 选项 | 默认值 | 说明 |
|---|---|---|
| storelib_enable | False | 使能存储相关依赖与 sml_lsi / sml_pmc 组件 |
| lsw_enable | False | 使能交换芯片依赖(lsw_8363 / lsw_sf2507)与 lsw_drv 组件 |
| chipv2_enable | False | 使能 chipv2 芯片特性(pfr spi_def 选择 chipv2 版本) |
本地构建与测试
构建好的文件会存于 /opt/bmc/lualib/libmgmt_protocol:
./build.py -bt dt # 本地构建
./build.py -ut # 单元测试
./build.py -it # 集成测试