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
| Name | Description | Transmission Channel | Transmission Component |
|---|---|---|---|
| std_smbus | Common SMBus protocol | I2C | hwproxy |
| smbus | Proprietary SMBus protocol | I2C | hwproxy |
| smbus_5902 | 5902-dedicated SMBus protocol | I2C | hwproxy |
| smbus_postbox | NVIDIA SMBus SMBPBI protocol | I2C | hwproxy |
| ncsi_standard | Standard protocol part of the NC-SI over MCTP protocol | MCTP over PCIe VDM | mctpd |
| ncsi_huawei | Huawei OEM extensions within the NC-SI over MCTP protocol | MCTP over PCIe VDM | mctpd |
| ncsi_mellanox | Mellanox OEM extensions within the NC-SI over MCTP protocol | MCTP over PCIe VDM | mctpd |
| nvme_mi_standard | Standard protocol part of the NVMe-MI over MCTP protocol | MCTP over PCIe VDM | mctpd |
Directory Structure
├─ 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 packetHow to Use
This is a common Lua library, so every component can require it directly.
-- 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 opProperty 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.
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_adapterThe 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.
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 betweenexpect_data_lenand the maximum single-frame length.= 0 or nil: If thedatafield 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 thedatalength and the maximum single-frame length. If there is no thedatafield (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.
| Name | bitstring Name | Response Format |
|---|---|---|
| MAC address | MAC_ADDRESS | {mac_address = '00:11:22:33:44:55'} |
| IPv4 address | IPV4 | {ipv4 = '192.168.1.255'} |
Common Response Data Parsing Function
The table below lists the supported function.
| Name | Description | Input Parameter | Response Format |
|---|---|---|---|
create_array_parser | Parses 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 ID | Name | Description | Response Format |
|---|---|---|---|
| 1 | ChassisID | Parses ChassisID TLV packets | {chassis_id:string, chassis_id_subtype:string} |
| 2 | PortID | Parses PortID TLV packets | {port_id:string, port_id_subtype:string} |
| 3 | TTL | Parses TTL TLV packets | {ttl:U16} |
| 5 | SystemName | Parses SystemName TLV packets | {system_name:string} |
| 127 | OrgSpecific | Parses 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
smbusandstd_smbusare the same, but their sending protocols are different. Therefore,smbusinherits most functions ofstd_smbus, except the protocol assembling function. - Both
ncsi_huaweiandncsi_mellanoxare OEM extensions ofncsi_standard. Therefore, they inherit all functions ofncsi_standardand only add extra assembling and unpacking steps.
Necessary functions for adding a protocol:
-- @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.
endAdding 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.
-- 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)
endProtocol encapsulation is required for Huawei OEM extensions: protocol/ncsi_huawei.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
endOverriding 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
-- 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)
...
endprotocol/smbus_5902.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
endOverriding 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
-- 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
endReference:
protocol/smbus.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
endTesting
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.