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 关键术语表

术语解释
SMBusSystem Management Bus,基于 I2C 的系统管理总线协议,本组件中通过 hwproxy 访问
MCTPManagement Component Transport Protocol,管理组件传输协议,本组件中通过 mctpd 访问
NCSINC-SI(Network Controller Sideband Interface),带外管理接口协议,本组件支持 over MCTP 方式
NVMe-MINVMe Management Interface,NVMe 设备管理接口协议
PLDMPlatform Level Data Model,平台级数据模型协议,用于平台管理消息交互
FRUField Replaceable Unit,现场可更换单元,FRU 数据为硬件部件的固件信息(含 elabel 电子标签)
SMLS.M.A.R.T. 相关日志解析库,用于硬盘健康状态与故障日志(SAS/SATA)解析
endpointMCTP 协议通信端点对象,由 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_dependenciesproperties 两个字段非空;propertiesprotocol_dependencies 均不可为空

chip_config 结构示例:

lua
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 资源树对象已就绪;
  • 配置解析失败(配置非法)会直接抛异常,需要在业务侧捕获处理;
  • 若配置中重复定义同一属性,后定义的不会覆盖先定义的,仅打印告警日志。

调试示例

lua
-- 在 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 日志,不会抛异常。

调试示例

lua
-- 对于单次访问返回值的属性,需要调用: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 对象)。

调试示例

lua
-- 对于轮询访问的属性,返回体为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 op

2.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)。

调试示例

lua
-- 解析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_ADDRESSIPV4 等格式
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名称介绍返回格式
1ChassisID解析ChassisID TLV报文{chassis_id:string, chassis_id_subtype:string}
2PortID解析PortID TLV报文{port_id:string, port_id_subtype:string}
3TTL解析TTL TLV报文{ttl:U16}
5SystemName解析SystemName TLV报文{system_name:string}
127OrgSpecific解析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 报文。

调试示例

lua
-- 使用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组件产物头文件安装路径功能简介
frudatalibmgmt_protocol::libfrudatalibfrudata.soinclude/frudataFRU 数据解析/生成,含 elabel 电子标签、IPMI FRU 信息
ncsi_protocollibmgmt_protocol::libncsi_protocollibncsi_protocol.soinclude/ncsi/ncsi_protocolNCSI 协议 C 库,含 LLDP over NCSI、NCSI socket 收发
sml_baselibmgmt_protocol::libsml_baselibsml_base.soinclude/sml/sml_baseSML 基础库(硬盘健康/诊断基础能力)
sml_custom_baselibmgmt_protocol::libsml_custom_baselibsml_custom_base.soinclude/sml/sml_baseSML 定制基础库
sml_historelibmgmt_protocol::libsml_historelibsml_histore.soinclude/sml/sml_historeSML 历史记录库
sml_lsi(可选)libmgmt_protocol::libsml_lsilibsml_lsi.soinclude/sml/sml_lsiSML LSI 厂商适配库(storelib_enable 使能)
sml_pmc(可选)libmgmt_protocol::libsml_pmclibsml_pmc.soinclude/sml/sml_pmcSML PMC RAID 卡适配库(storelib_enable 使能)
pd_log_parselibmgmt_protocol::libpd_log_parselibpd_log_parse.soinclude/sml/pd_log_parse硬盘日志解析(SAS/SATA/Seagate 等)
platformlibmgmt_protocol::libplatformlibplatform.soinclude/sml/platformSML 平台适配库
lsw_drvlibmgmt_protocol::liblsw_drvliblsw_drv.soinclude/lsw/lsw_drv交换芯片驱动库(lsw_8363 / lsw_sf2507,lsw_enable 使能)

CMake 使用示例:

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 扩展能力概述

组件支持代码级二次开发:新增协议类以支持新硬件协议。协议实现采用继承对象方法,相似协议中相同步骤的部分以继承的方式共享代码,差异部分通过重写子类函数实现。例如:

  • smbusstd_smbus 的传输方法相同,仅发送协议不同,因此 smbus 继承 std_smbus 的大部分函数,只重写协议拼装函数;
  • ncsi_huaweincsi_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。协议类需继承公共基类 protocolrequire 'libmgmt_protocol.protocol.protocol'),或继承相似协议类。

步骤二:实现必要函数

实现 ctorsend_requestvalidate_request_params 三个必要函数。相似协议场景下,仅重写差异函数即可。

示例代码

添加相似协议(NCSI 华为协议继承 NCSI 标准协议,重写拼装/解包函数):

protocol/ncsi_standard.lua(基类,拼装和解包拆成独立函数):

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)
end

protocol/ncsi_huawei.lua(子类,重写拼装函数,封装自己的部分后再调用基类):

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(基类,硬件访问拆成独立函数):

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)
  ...
end

protocol/smbus_5902.lua(子类,Write 和 Read 分开发送):

lua
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(基类):

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
end

protocol/smbus.lua(子类,ctor 中覆盖模板即可):

lua
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

验证方法

  1. 新增协议的单元测试:在 test/unit/protocol/ 下新建 test_<协议名>.lua,必须覆盖 protocol:ctor()protocol:validate_request_params()protocol:send_request() 三个基本函数;传输层由外部传入,打桩传输对象即可模拟硬件访问;
  2. 运行单元测试:./build.py -ut(或 make unit_test),确认新协议用例全部通过;
  3. 集成验证:在 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.loglibmgmt_protocol 运行日志(协议访问、配置解析、调度器运行等)

4.2 关键日志信息

日志片段日志级别含义解读建议处理动作
unable to create device spec parser with empty configERROR传入 device_spec_parser 的配置为空检查业务组件传入的配置文件是否为空
unable to create device spec parser with no properties or no protocol dependenciesERROR配置缺少 properties 或 protocol_dependencies检查配置文件结构是否完整
unable to validate request templateERROR属性 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 overrideWARN配置中重复定义同名属性,后者不生效检查配置文件,去除重复定义
unsupported action: <action>ERROR属性 action 不是 on_demand / on_schedule检查 action 字段拼写
protocol not support, <protocol>ERROR访问时协议未成功加载检查协议文件是否存在、依赖是否传入
period is not set, unable to start scheduler to scanERRORscheduler 周期未配置时调用 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 控制。调试时可将日志级别调低以输出协议访问细节:

lua
local log = require 'mc.logging'
log:setLevel(log.DEBUG)

开启后可在 /data/var/log/bmc/libmgmt_protocol.log 中看到协议创建、参数校验、调度器运行等 debug 信息。

复现问题方法

  1. 前置条件设置:确保 hwproxy / mctpd 传输组件已加载,目标硬件在线,endpoint / ref_chip 资源树对象可获取;
  2. 操作步骤:
    • 构造复现配置:在 test/test_data/test.lua 中配置目标属性(协议、action、request、response 解析函数);
    • 编写最小复现脚本,调用 device_spec_parser 后按问题场景执行单次访问或开启轮询;
    • 开启 debug 日志复现问题;
  3. 预期现象:单次访问返回 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 参考资料

附录B 修订记录

版本日期修订人修订内容
v1.02026-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_enableFalse使能存储相关依赖与 sml_lsi / sml_pmc 组件
lsw_enableFalse使能交换芯片依赖(lsw_8363 / lsw_sf2507)与 lsw_drv 组件
chipv2_enableFalse使能 chipv2 芯片特性(pfr spi_def 选择 chipv2 版本)

本地构建与测试

构建好的文件会存于 /opt/bmc/lualib/libmgmt_protocol

bash
./build.py -bt dt     # 本地构建
./build.py -ut        # 单元测试
./build.py -it        # 集成测试