Southbound Hardware Protocol Library
更新时间: 2025/10/17
在Gitcode上查看源码

During implementation of out-of-band management services, the BMC not only reads from and writes to CPLD registers but also communicates with the MCU and chips through complex protocols such as SMBus and MCTP. These complex protocols allow the BMC to transmit larger amounts of data and provide more advanced out-of-band management capabilities. However, they also increase protocol management costs. To simplify complex protocol management and allow developers to focus more on device management, openUBMC provides libmgmt_protocol, a hardware protocol management library, helping components to reduce development costs.

For details, see use cases of the network_adapter component.

NOTE

Applicable only to the Lua development framework.

Supported Protocols

NameDescriptionTransmission ChannelTransmission Component
std_smbusCommon SMBus protocolI2Chwproxy
smbusProprietary SMBus protocolI2Chwproxy
smbus_59025902-dedicated SMBus protocolI2Chwproxy
smbus_postboxNVIDIA SMBus SMBPBI protocolI2Chwproxy
ncsi_standardStandard protocol part of the NC-SI over MCTP protocolMCTP over PCIe VDMmctpd
ncsi_huaweiHuawei OEM extensions within the NC-SI over MCTP protocolMCTP over PCIe VDMmctpd
ncsi_mellanoxMellanox OEM extensions within the NC-SI over MCTP protocolMCTP over PCIe VDMmctpd
nvme_mi_standardStandard protocol part of the NVMe-MI over MCTP protocolMCTP over PCIe VDMmctpd

Directory Structure

bash
├─ libmgmt_protocol/           -- libmgmt_protocol library
 ├─ init.lua                 -- libmgmt_protocol entry file, which provides external functions to parse protocol configuration files
  ├─ protocol/ --Protocol folder
  ├─ protocol.lua          -- Protocol interface file, with no functional implementation
  ├─ ncsi_huawei.lua       -- Huawei OEM extensions within NS-CI Over MCTP
  ├─ ncsi_standard.lua     -- Standard NS-CI Over MCTP protocol
  ├─ ncsi_mellanox.lua     -- Mellanox OEM extensions within NS-CI Over MCTP
  ├─ nvme_mi_standard.lua  -- Standard NVMe-MI protocol
  ├─ smbus_5902.lua        -- 5902-dedicated SMBus protocol
  ├─ smbus_postbox.lua     -- NVIDIA SMBPBI protocol
  ├─ smbus.lua             -- Proprietary SMBus protocol
  ├─ std_smbus.lua         -- Standard SMBus protocol
  ├─ transport/               -- Protocol transport layer, which is used to encapsulate protocol transport functions
  ├─ hw_communicator.lua   -- Used to create a single access or polling accesses
  ├─ scheduler.lua         -- Plugin used to access hardware in polling mode
  ├─ common/
  ├─ init.lua              --  Used to share common protocol parsing tools
  ├─ bs_helper.lua         -- Common bitstring format
  ├─ lldp_tlv_parser.lua   -- LLDP TLV packet parsing function
  ├─ vdpci_lldp_parser.lua -- Dedicated parsing function of the vdpci LLDP packet

How to Use

This is a common Lua library, so every component can require it directly.

lua
-- require libmgmt_protocol
local libmgmt_protocol = require 'libmgmt_protocol'

-- Each component defines the access protocol configuration file for the hardware to which it belongs.
local config_obj = require 'path.to.config.obj'

-- Create an accessible object based on the file
-- An exception will be thrown if the configuration is incorrect.
local obj = libmgmt_protocol.device_spec_parser(config_obj)

-- Obtain the hardware property value based on the specific value in the configuration file.
-- For on_demand_property, call :value() to obtain the specific value.
-- If the property does not exist or the property value cannot be obtained, :value() returns nil.
-- The input parameter serves as a supplement to the configuration request data. If a conflict occurs, it will not override the request data in the configuration.
local on_demand_property = obj:OnDemandProperty({data = 'some data'}):value()

-- For on_schedule_property, a scheduler object is returned.
-- No data needs to be input if the input parameter remains unchanged.
local on_schedule_property_scheduler = obj:OnScheduleProperty()
-- scheduler does not start automatically. You need to manually call :start() to start it, at which point it will return the data obtained for the first time.
local on_schedule_property = on_schedule_property_scheduler:start()
-- scheduler supports listening to data change signals. When the obtained data is different from the data cached by scheduler, scheduler sends
-- the on_data_change signal with the new data.
on_schedule_property_scheduler.on_data_change:on(function(data)
  print('new data received!')
end)
-- scheduler supports listening to error signals. When data fails to be obtained (nil) during each polling, the on_error signal is sent.
on_schedule_property_scheduler.on_error:on(function()
  error('unable to read data from hardware!')
end)
-- scheduler supports changing input parameters. After the corresponding function is called, the new parameters are used in the next polling.
on_schedule_property_scheduler:update_params({
  name = 'another_property_name', --: New property name
  protocol ='new protocol', --: New protocol name (Currently, protocols that have not been used cannot be loaded.)
  request = {...}, -- Input parameters required by the protocol
})
-- scheduler supports changing the polling period. After the corresponding function is called, the new parameter is used in the next polling.
on_schedule_property_scheduler:set_period(10) -- Poll data every 10 seconds.

-- scheduler keeps accessing data based on the configured polling period. To stop the polling, you need to call :deconstruct().
on_schedule_property_scheduler:deconstruct()

--For a non-existent property, calling any function of the scheduler takes no effect. This is used when you are unsure whether a property exists.
local not_exist_property = obj:NotExistProperty()
not_exist_property:start()                    -- no op
not_exist_property.on_data_change:on(function()
  assert ('never reach here')                  -- This line should never be reached.
end)
not_exist_property.on_error:on(function()
  assert ('never reach here')                  -- This line should never be reached
end)
not_exist_property:deconstruct()              -- no op

Property Access File Configuration

The property access file is used to configure fixed parameters required for hardware property access. It consists of protocol_dependencies and properties.

Currently, only Lua can be used for configuration.

lua
local a_network_adapter = {
  protocol_dependencies = {
    smbus = {           -- The ref_chip object must be input for all I2C protocols.
      ref_chip = nil,   -- The hardware is accessed through the hwproxy.
      buffer_len = 64,  -- Maximum number of bytes supported by the protocol
    },
    ncsi_huawei = {     -- The endpoint object needs to be input for all MCTP protocols.
      endpoint = nil    -- The hardware is accessed through the mctpd.
    }
  },
  properties = {
    ...
  }
}

return a_network_adapter

The configuration can be stored anywhere in a component. It is recommended that the configuration be stored separately to facilitate subsequent iteration.

Property Configuration

Each property configuration is keyed by the property name, with its value defined as a composite of the protocol name, access method, request body, and response function.

lua
properties = {
  ChipTemp = {        -- Property name, also the property access interface, e.g. obj:ChipTemp ()
    protocol = 'smbus',   -- Protocol name, which must be a supported protocol.
    action = 'on_schedule',   -- Single access or polling access
    period_in_sec = 2,      -- Period of polling access, in seconds.
    request = {             -- Request data required by the protocol. The specific format is checked by each protocol.
      opcode = 0x3,         -- opcode required by the SMBus protocol
      expect_data_len = 2   -- Return data length required by the SMBus protocol
    },
    response = function(data) -- Parsing function for the return data. It removes the protocol header, leaving only the actual business data.
      -- This function is called only when the hardware returns a response normally. It is not called when an error occurs.
      local r = bs.new([[<<temp:16>>]]):unpack(data, true) -- Support for unpacking binary data using bitstring
      return r.temp -- Return value of obj:ChipTemp ()
    end
  },
  MacAddressNCSI = {
    protocol = 'ncsi_huawei',
    action = 'on_demand',       -- Single access, which does not require period_in_sec.
    request = {
      huawei_cmd_id = 0x1,
      sub_cmd_id = 0x0
    },
    response = function(data)
      local r = bs.new([[<<
        _:8,
        mac_address_count:16/big,
        _:16,                     # libmgmt_protocol provides some common bitstring formats, for example, MAC_Address.
        mac_addrs:8/MAC_ADDRESS    
      >>]], libmgmt_protocol.common_bs_helper):unpack(data, true) -- Directly pass the corresponding input parameter when creating bs.
      return r.mac_addrs[1].mac_address
    end
  }
}

NOTE

For more complex configurations, see the configurations in src/lualib/hardware_config of the network_adapter repository.


Parameters in the SMBus Request Body

The request parameters of I2C-related protocols, such as the general SMBus, private SMBus, and 5902-dedicated SMBus, can be flexibly implemented. The details are as follows:

expect_data_len

  • -1: Read request with a variable response length. Data is read based on the maximum single-frame length.
  • \> 0: Read request, or a write request that relies on the write response. Data is read based on the smaller value between expect_data_len and the maximum single-frame length.
  • = 0 or nil: If the data field exists in the request, the request is a write request that does not rely on the write response. Data is written based on the smaller value between the data length and the maximum single-frame length. If there is no the data field (theoretically impossible), data is read or written based on the maximum single-frame length.

batch_write

If this parameter is set to true, the data batch write function of the framework can be called. This is usually used in firmware upgrade scenarios. In normal cases, data must be contained.


write_without_read

The value true indicates a write request that does not rely on write response. In normal cases, data must be contained and expect_data_len is not contained.


offset

If the value is -1, the offset field of the request packet is fixed to 0 in the multi-frame write request scenario.


align_len

After the parameter is set, the last frame of a multi-frame write request will be padded with '\x00' to the length set by the parameter if its length is insufficient.


max_frame_len

The maximum length of a single frame of a command can be specified.


data_object_index

This parameter specifies the index of a managed chip, which is used when a single MCU manages multiple chips.

For details about the supported parameters, see the request_params_template field of each protocol.


Common Protocol Parser Library

The framework provides common bitstring formats and protocol response data parsing functions to parse the response data returned by the protocol.

Common bitstring Formats

The common bitstring formats are stored in include/libmgmt_protocol/common/init.lua. Call libmgmt_protocol.common_bs_helper when needed.

The table below lists supported formats.

Namebitstring NameResponse Format
MAC addressMAC_ADDRESS{mac_address = '00:11:22:33:44:55'}
IPv4 addressIPV4{ipv4 = '192.168.1.255'}

Common Response Data Parsing Function

The table below lists the supported function.

NameDescriptionInput ParameterResponse Format
create_array_parserParses the bitstring array.(A single bitstring definition string, the length of a single data record)Table array

LLDP TLV Packet Parsing Function

LLDP TLV packets are parsed based on the IEEE 802.1ab standard protocol.

If a packet cannot be parsed, an exception will be thrown. In this case, use pcall.

The table below lists supported LLDP TLV parsing functions.

TLV IDNameDescriptionResponse Format
1ChassisIDParses ChassisID TLV packets{chassis_id:string, chassis_id_subtype:string}
2PortIDParses PortID TLV packets{port_id:string, port_id_subtype:string}
3TTLParses TTL TLV packets{ttl:U16}
5SystemNameParses SystemName TLV packets{system_name:string}
127OrgSpecificParses OrgSpecific TLV packets to obtain vlanid.{vlan_id:U16}

vdpci LLDP Packet Parsing Function

This is the dedicated parsing function for LLDP packets obtained from Huawei NICs through the customized MCTP channel.

If a packet cannot be parsed, an exception will be thrown. In this case, use pcall.


Adding a Protocol

Protocol addition uses the OOB inheritance method. The same steps in similar protocols will be inherited, while the different part needs to be modified. For example:

  • The transmission methods of smbus and std_smbus are the same, but their sending protocols are different. Therefore, smbus inherits most functions of std_smbus, except the protocol assembling function.
  • Both ncsi_huawei and ncsi_mellanox are OEM extensions of ncsi_standard. Therefore, they inherit all functions of ncsi_standard and only add extra assembling and unpacking steps.

Necessary functions for adding a protocol:

lua
-- @function Constructor
-- @param    request:table
function protocol:ctor(params)
  -- params: protocol_dependencies in the protocol configuration file, which is defined by the component using the protocol.
  -- For example, endpoint needs to be input for the MCTP protocol, and ref_chip and buffer_len need to be input for the SMBus protocol.
  --   
end

-- @function Transmission request function
-- @param    request:table
-- @return   binary|boolean|nil
function protocol:send_request(request)
  -- request: request struct of each property in the protocol configuration file, containing the variables at runtime and the static configurations.
  -- The specific data of the response body is returned. If the response parsing function is configured in the configuration file, the input parameter of the response parsing function is returned.
  -- If the request fails, nil is returned.
  -- If the response parsing function does not need to be called, true is returned, indicating that the request is sent successfully.
end

-- @function Transmission request function
-- @param    request:table
-- @return   binary
function protocol:validate_request_params(request)
  -- request: request struct of each property in the protocol configuration file.
  -- It will be called once for parsing the configuration file. The input parameter is the static data in the configuration file.
  -- It will be called once again at runtime. The input parameter is the dynamic data passed in at runtime. If the value is nil, the function will not be called.
  -- The transmission can proceed only when true is returned. If false is returned, the transmission will not be performed.
end

Adding a New Protocol

The above functions are required.

Adding a Similar Protocol

Similar protocol code can be shared by overriding base functions.


Overriding Assembling and Unpacking Functions

Huawei's OEM-extended NC-SI adds additional assembling and unpacking compared to the standard protocol.

Reference: protocol/ncsi_standard.lua.

lua
-- The ncsi_standard assembling function  assembles only the part of the standard NC-SI protocol.
function ncsi_standard:construct_request_data(ctx, request)
  return req_ctx, req_bin
end

-- The ncsi_standard parsing function parses only the part of the standard NC-SI protocol.
function ncsi_standard:unpack_response_data(ctx, rsp_data_bin)
  return ...  
end

-- Split protocol assembling and unpacking into independent functions.
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 encapsulation is required for Huawei OEM extensions: protocol/ncsi_huawei.lua

lua
local ncsi_huawei = class(ncsi_standard)

-- ncsi_huawei overrides the base encapsulation function to add its own implementation, and then calls the base encapsulation function.
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 overrides the base parsing function to return its own parsed data instead of using the base implementation.
function ncsi_standard:unpack_response_data(ctx, rsp_data_bin)
  return data
end

Overriding the Hardware Access Function

The encapsulation method of smbus_5902 is the same as that of std_smbus. However, more conditional checks are required during hardware access.

protocol/std_smbus.lua

lua
-- std_smbus uses the WriteRead function of hwproxy for hardware access.
function std_smbus:send_and_receive(data, len)
  return pcall(function()
    return self.ref_chip:WriteRead(ctx:new(), data, len)
  end)
end

-- Split hardware access into an independent function.
function std_smbus:send_request(request)
  ...
  local ok, rsp_bin = self:send_and_receive(data, len)
  ...
end

protocol/smbus_5902.lua

lua
local smbus_5902 = class(std_smbus)

-- smbus_5902 requires checking whether the hardware can be accessed before transmission. Therefore, Write and Read must be sent separately.
-- You can add the required steps by overriding the base hardware access function.
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

Overriding Parameter Check

The parameter check logic of the proprietary SMBus protocol is the same as that of std_smbus, but the acceptable parameters are different.

Reference: protocol/std_smbus.lua

lua
-- std_smbus can accept the arg and data parameters.
local request_params_template<const> = {
  opcode = true,
  expect_data_len = true,
  arg = true,
  data = true
}

-- Store request_params_template when constructing the function.
function std_smbus:ctor()
  self.request_params_template = request_params_template
end

-- Separate out request_params_template.
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

Reference: protocol/smbus.lua

lua
local smbus = class(std_smbus)

local request_params_template<const> = {
  opcode = true,
  expect_data_len = true
}

-- Because of class inheritance, request_params_template will be replaced.
-- The local request_params_template will be used in validate_request_params.
function smbus:ctor()
  self.request_params_template = request_params_template
end

Testing

Unit tests focus on independent protocols. Each protocol unit test must cover all protocol classes, including:

  • protocol:ctor()
  • protocol:validate_request_params()
  • protocol:send_request()

Since no actual hardware drivers are called, the stub data can be passed into each function to test the functionality.