MCTP protocol driver for openUBMC
Access rate limit exceeded. Please log in to continue.
mctpd
1. 什么是mctpd?
mctpd 是实现 MCTP(Management Component Transport Protocol,管理组件传输协议)服务的独立组件,对内提供资源树功能接口,支持 BMC 与平台管理子系统各部件之间的信息交互。
MCTP 是一种独立于物理介质的协议,用于计算机系统中各部件之间的信息交互。此协议独立于底层物理总线,是一种独立于总线的"数据链路层"协议。MCTP 协议由 DMTF PMCI(Distributed Management Task Force Platform Management Component Intercommunications,分布式管理任务和平台管理组件互联组)工作组定义,该协议为平台管理子系统的监控&管理功能提供访问接口,进而可通过 DMTF CIM(Common Information Model,数据模型)实现对平台可管理功能的访问、传输和配置。
本库提供以下功能:
- MCTP 协议通信:支持 Request/Send 两种消息模式,实现 BMC 与管理组件之间的数据收发
- 多物理层支持:支持 PCIe VDM、SMBus、SMBus_OEM 三种物理传输介质
- 多上层协议支持:支持 MCTP Control、PLDM、NCSI、NVMe、SPDM、Vendor Defined-PCI 等消息类型
- Endpoint 管理:自动发现并管理 MCTP Endpoint 对象,提供统一的通信接口
- 路由管理:自动获取和维护 MCTP 路由表,支持动态路由更新
mctpd 可被多个组件加载使用,network_adapter、storage 等业务组件通过 mctp_lib 提供的接口创建Transport 资源树对象并尝试获取 Endpoint 资源树对象,实现 MCTP 通讯。
2. 架构设计
2.1 整体架构
mctpd 采用分层架构设计:
┌──────────────────────────────────────────────────────────┐
│ 业务组件 │
│ (network_adapter, storage, ...) │
├──────────────────────────────────────────────────────────┤
│ mctp_lib API │
│ (Lua 公开接口层) │
├──────────────────────────────────────────────────────────┤
│ mctpd 服务层 │
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ mctp_engine │ │ mctp_endpoint│ │ mctp_discovery │ │
│ └──────────────┘ └──────────────┘ └─────────────────┘ │
├──────────────────────────────────────────────────────────┤
│ 上层协议层 (upper) │
│ ┌──────┐ ┌─────┐ ┌──────┐ ┌──────┐ ┌─────┐ ┌─────────┐│
│ │ PLDM │ │NCSI │ │ NVMe │ │ VDPCI│ │SPDM │ │ General ││
│ └──────┘ └─────┘ └──────┘ └──────┘ └─────┘ └─────────┘│
├──────────────────────────────────────────────────────────┤
│ 传输层 (transport) │
│ ┌──────────────┐ │
│ │ mctp_protocol │ │
│ └──────────────┘ │
├──────────────────────────────────────────────────────────┤
│ 物理层 (physical) │
│ ┌────────────┐ ┌──────────────┐ ┌─────────┐ │
│ │ PCIe VDM │ │ SMBus │ │ Serial │ │
│ └────────────┘ └──────────────┘ └─────────┘ │
├──────────────────────────────────────────────────────────┤
│ 硬件/驱动 │
│ mctp 驱动 (PCIe) │ hwproxy + 用户态驱动 (SMBus) │
└──────────────────────────────────────────────────────────┘
2.2 核心组件
| 组件 | 文件 | 职责 |
|---|---|---|
| mctp_engine | src/lualib/mctp_engine.lua | MCTP 通信常驻协程,管理 Endpoint 和协议栈 |
| mctp_endpoint | src/lualib/mctp_endpoints/ | Endpoint 对象定义,提供 Request/Send 方法 |
| mctp_discovery | src/lualib/mctp_discovery.lua | MCTP 自发现流程处理 |
| mctp_routing_table | src/lualib/mctp_routing_table.lua | 路由表分析与处理 |
| mctp_eid_table | src/lualib/mctp_eid_table.lua | 维护 Endpoint 对象地址表 |
| mctp_mdb_mgmt | src/lualib/mctp_mdb_mgmt.lua | mctpd 资源树对象管理 |
| mctp_worker | src/lualib/mctp_worker.lua | 从mctp驱动读取数据的 worker 定义 |
| libmctp | src/lualib-src/libmctp/ | C 层 MCTP 协议实现(传输层、物理层、上层协议) |
| l_mctp | src/lualib-src/l_mctp/ | C 层函数的 Lua 绑定 |
| mctp_lib | include/mctp_lib/ | 公开 Lua API,资源树对象创建与获取 |
| hwproxy smbus 插件 | include/hwproxy/plugins/smbus/ | SMBus 通信插件 |
2.3 目录结构
├── build.py -- 构建脚本
├── gen -- 自动生成代码
├── include -- include 文件夹会在打包时移至 /opt/bmc/lualib 中,无需额外配置 lua 加载路径
│ ├── hwproxy
│ │ └── plugins
│ │ └── smbus -- smbus 插件
│ │ ├── cmds.lua -- 插件方法实现
│ │ └── init.lua -- 插件初始化文件
│ └── mctp_lib -- mctp_lib 库
│ └── init.lua -- mctp_lib 入口文件,提供对外函数,创建和获取资源树对象接口
├── mds -- 模组定义文件
│ ├── errors.json -- 错误码定义
│ ├── model.json -- 对象和接口定义
│ ├── schema.json -- 自动生成文件
│ └── service.json -- 组件版本信息描述和组件、接口依赖描述
├── src -- 源码
│ ├── config -- 驱动配置文件
│ │ ├── chipv1 -- 芯片版本1
│ │ │ └── mctp_drv_config.lua -- 芯片版本1 mctp 消息配置文件
│ │ └── chipv2 -- 芯片版本2
│ │ └── mctp_drv_config.lua -- 芯片版本2 mctp 消息配置文件
│ ├── lualib -- lua 层代码
│ │ ├── mctp.lua -- 引入 C 层封装好的函数
│ │ ├── mctp_app.lua -- mctpd 组件初始化
│ │ ├── mctp_cmds.lua -- MCTP Control 类型信息的二进制结构封装
│ │ ├── mctp_config.lua -- mctpd 的默认配置(重试次数、超时时间、重试间隔)
│ │ ├── mctp_def.lua -- mctp 协议关键字宏定义
│ │ ├── mctp_discovery.lua -- Partial Endpoint Discovery 阶段的处理
│ │ ├── mctp_dump.lua -- mctp 相关信息导出
│ │ ├── mctp_eid_table.lua -- 维护 endpoint 对象地址表
│ │ ├── mctp_endpoints -- Endpoint 对象的定义
│ │ │ ├── ep_general.lua -- 通用类型的 Endpoint 对象的定义
│ │ │ ├── ep_mctp.lua -- Endpoint 对象的公共类
│ │ │ ├── ep_mctp_ctrl.lua -- MCTP Control 类型的 Endpoint 对象的定义
│ │ │ ├── ep_ncsi.lua -- NC-SI 类型的 Endpoint 对象的定义
│ │ │ ├── ep_nvme.lua -- NVMe 类型的 Endpoint 对象的定义
│ │ │ ├── ep_pldm.lua -- PLDM 类型的 Endpoint 对象的定义
│ │ │ ├── ep_spdm.lua -- SPDM 类型的 Endpoint 对象的定义
│ │ │ ├── ep_vdpci_endpoint.lua -- Vendor Defined-PCI 类型的 Endpoint 对象的定义
│ │ │ └── init.lua -- mctp_endpoint 入口文件,提供对外函数创建和获取 EndPoint 对象接口
│ │ ├── mctp_engine.lua -- 用于 mctp 通讯的常驻协程
│ │ ├── mctp_mdb_mgmt.lua -- mctpd 的资源树对象的管理
│ │ ├── mctp_routing_table.lua -- 对收到的 Routing Table 进行分析和处理
│ │ ├── mctp_utils.lua -- mctp 通用工具文件
│ │ └── mctp_worker.lua -- 与 imu 进行交互的 worker 定义
│ ├── lualib-src -- C 层代码
│ │ ├── CMakeLists.txt -- 编译文件
│ │ ├── l_mctp -- opt/bmc/luaclib/l_mctp 提供lua调用C库的接口实现
│ │ │ ├── CMakeLists.txt
│ │ │ ├── l_base.h -- 模板类头文件
│ │ │ ├── l_mctp.cpp -- 定义给 lua 层调用的函数
│ │ │ ├── l_mctp_ctrl_msg.cpp -- 定义 MCTP Control 信息的数据包格式并向 lua 层提供对应的接口
│ │ │ └── l_mctp_ctrl_msg.h -- 定义 MCTP Control 信息的数据包格式并向 lua 层提供对应的接口
│ │ └── libmctp -- usr/lib64/mctp mctp协议库的实现
│ │ ├── CMakeLists.txt
│ │ ├── csc8.cpp -- CRC8 校验方法
│ │ ├── csc8.h -- CRC8 校验方法
│ │ ├── csc32.cpp -- CRC32 校验方法
│ │ ├── csc32.h -- CRC32 校验方法
│ │ ├── mctp_base.cpp -- 公共方法
│ │ ├── mctp_base.h -- 公共方法
│ │ ├── mctp_cmds.h -- mctp 信息的 hdr 和 msg 的结构定义
│ │ ├── physical -- 物理层协议类定义
│ │ │ ├── channel_protocol.cpp -- 与 imu 进行交互的方法
│ │ │ ├── channel_protocol.h -- 与 imu 进行交互的方法
│ │ │ ├── hw_pcie_vdm_channel.cpp -- 与 mctp 驱动进行交互的方法
│ │ │ ├── hw_pcie_vdm_channel.h -- 与 mctp 驱动进行交互的方法
│ │ │ ├── pcie_vdm_protocol.cpp -- PCIeVdm 的协议类
│ │ │ ├── pcie_vdm_protocol.h -- PCIeVdm 的协议类
│ │ │ ├── serial_protocol.cpp -- 串口协议的协议类
│ │ │ ├── serial_protocol.h -- 串口协议的协议类
│ │ │ ├── smbus_protocol.cpp -- SMBus 的协议类
│ │ │ └── smbus_protocol.h -- SMBus 的协议类
│ │ ├── protocol.h -- 协议类的基类
│ │ ├── transport -- 传输层协议类定义
│ │ │ ├── mctp_protocol.cpp -- mctp 信息的数据包填充
│ │ │ └── mctp_protocol.h -- mctp 信息的数据包填充
│ │ └── upper -- 上层协议类定义
│ │ ├── general_protocol.cpp -- 通用 Endpoint 的协议类
│ │ ├── general_protocol.h -- 通用 Endpoint 的协议类
│ │ ├── ncsi_protocol.cpp -- NC-SI 的协议类
│ │ ├── ncsi_protocol.h -- NC-SI 的协议类
│ │ ├── nvme_protocol.cpp -- NVMe 的协议类
│ │ ├── nvme_protocol.h -- NVMe 的协议类
│ │ ├── pldm_protocol.cpp -- PLDM 的协议类
│ │ ├── pldm_protocol.h -- PLDM 的协议类
│ │ ├── spdm_protocol.cpp -- SPDM 的协议类
│ │ ├── spdm_protocol.h -- SPDM 的协议类
│ │ ├── vd_protocol.cpp -- Vendor Defined-PCI 的协议类
│ │ └── vd_protocol.h -- Vendor Defined-PCI 的协议类
│ └── service
│ ├── main.lua -- mctpd 服务入口函数
│ ├── smbus_write_read_service.lua -- smbus 读写服务入口函数
│ └── write_service.lua -- pcie 写服务入口函数
├── test
│ ├── integration
│ ├── test_utils
│ └── unit
3. 核心概念
3.1 Endpoint 对象
mctpd 可以被多个组件加载,PCIe 物理层协议类型的 Endpoint 依赖 IMU 和 mctpd 驱动保证传输的实现,使用者需要自行创建所用 MessageType 的 Endpoint,并使用 Endpoint 封装的方法进行 MCTP 通讯;SMBus 物理层协议类型的 Endpoint 依赖 CSR 配置 MctpBinding 对象、Endpoint 对象以及关联的 Chip 对象,若网卡/硬盘插在硬盘背板上,则需要 CPLD 支持 SMBus 通信协议;SMBus_OEM 物理层协议类型的 Endpoint 依赖 CSR 配置 MctpBinding 对象、Endpoint 对象以及关联的 Chip 对象。
Endpoint 对象提供了 Request 和 Send 方法供组件进行 MCTP 通讯,其中 Request 方法会有返回值,Send 方法不会有返回值。当前 NCSI over MCTP、NVMe over MCTP 以及 PLDM over MCTP、VDPCI、spdm over mctp、通用协议都在 libmgmt_protocol 组件中完成了封装,建议使用封装好的方法进行通讯。
- SMBus 协议类型 Endpoint CSR 配置样例
"MctpBinding_1": { // MctpBinding 对象配置
"BmcSMBusEid": 8, // SMBus 协议使用的 BMC 的 Eid
"BmcSMBusPhyAddr": 16 // SMBus 协议使用的 BMC 的物理地址
}
"Endpoint_1": { // Endpoint 对象配置
"TargetEid": "${Slot} |> expr(8 + $1)", // Endpoint 的 EID,通常为 MctpBinding 的 eid 加上 slot
"TargetPhyAddr": 73, // Endpoint 的物理地址(7bit)
"MessageType": 2, // Endpoint 的消息协议类型,具体支持的类型详见表"目前可创建的 Endpoint"
"MediumType": 128, // Endpoint 的物理层协议类型,具体支持的类型详见表"目前可创建的 Endpoint"
"RefChip": "#/Chip_SmbusChip" // Endpoint 关联的 Chip 对象
}
"Chip_SmbusChip": { // Chip 对象配置
"Address": 146, // Endpoint 对象对应的物理硬件的地址(8bit)
"AddrWidth": 1, // 硬件的地址位宽
"OffsetWidth": 1, // 硬件的偏移位宽
"WriteTmout": 100, // 写超时
"ReadTmout": 100, // 读超时
"HealthStatus": 0 // 健康状态
}
// 记得在 ManagementTopology 对应总线下配置 Chip,声明 Chip 位于哪路总线下
"I2cMux_9545Chan": {
"Chips": [
"Chip_SmbusChip"
]
}
- 目前可创建的 Endpoint
| 名称 | MessageTypeCode | 支持的物理层类型以及 MediumTypeCode | 代码中描述 |
|---|---|---|---|
| MCTP Control | 0x00 | PCIe(0x0F)、SMBus(0x02)、SMBus_OEM(0x80) | MCTP_MESSAGE_TYPE_MCTP_CTRL |
| Platform Level Data Model | 0x01 | PCIe(0x0F)、SMBus(0x02)、SMBus_OEM(0x80) | MCTP_MESSAGE_TYPE_PLDM |
| NC-SI over MCTP | 0x02 | PCIe(0x0F)、SMBus(0x02)、SMBus_OEM(0x80) | MCTP_MESSAGE_TYPE_NCSI |
| NVMe over MCTP | 0x04 | PCIe(0x0F)、SMBus(0x02)、SMBus_OEM(0x80) | MCTP_MESSAGE_TYPE_NVME |
| SPDM over MCTP | 0x05 | PCIe(0x0F)、SMBus(0x02)、SMBus_OEM(0x80) | MCTP_MESSAGE_TYPE_SPDM |
| Vendor Defined-PCI | 0x7E | PCIe(0x0F)、SMBus(0x02)、SMBus_OEM(0x80) | MCTP_MESSAGE_TYPE_VDPCI |
3.2 协议层
mctpd 的协议栈采用分层设计,从上到下依次为上层协议层、传输层和物理层。每层协议都继承自基类 protocol,通过 protocol_ctx 上下文对象在层间传递协议头信息。上层协议(如 PLDM、NCSI、NVMe)负责消息内容的封装与解析,传输层(mctp_protocol)负责 MCTP 协议头的填充与分帧,物理层(PCIe VDM、SMBus)负责封装物理层协议封装与解析。
4. 关键设计
4.1 mctpd 业务流程
4.1.1 mctpd 启动流程
sequenceDiagram
actor 用户
participant hwdiscovery
participant 业务组件
participant sd_bus资源树
participant mctpd主服务
participant mctpd子服务
participant IMU
hwdiscovery->>+sd_bus资源树:硬件自发现对象数据
mctpd主服务->>+sd_bus资源树:AddObject<br>获取对象数据
sd_bus资源树-->>-mctpd主服务:对象数据
activate mctpd主服务
mctpd主服务->>mctpd主服务:创建SMBus协议Endpoint对象
deactivate mctpd主服务
mctpd主服务->>mctpd主服务:监听pmu和smbios上电信号,<br>两个有其中一个变为1则表示上电
mctpd主服务->>mctpd子服务: 打开mctp驱动,创建worker线程,<br>开始循环从mctp驱动读取数据;<br>启动mctp自发现流程,<br>获取Owner的eid和物理地址、<br>BMC的eid和物理地址
loop 获取路由信息
mctpd主服务->>+mctpd子服务: 发送路由获取请求
mctpd子服务->>+IMU: 发送请求
IMU-->>-mctpd子服务: 响应路由获取请求
mctpd子服务-->>-mctpd主服务: 处理响应请求,创建路由对象并上树
end
loop 创建PCIe协议Endpoint
mctpd主服务->>mctpd主服务: 拉起transport接口创建信号监听,<br>每次收到接口创建信号(路由存在),<br>创建对应地址的PCIe协议Endpoint
end
业务组件->>+mctpd主服务:业务检测到mctpd上电,调用接口创建transport并获取Endpoint
mctpd主服务-->>-业务组件:返回transport和Endpoint对象
- mctpd 启动后会监听 compute 组件 Pmu 对象的 Status 和 bios 组件 SmBios 对象的 SmBiosStatus,二者有一个变为上电且 mctpd 处于下电状态,将 mctpd 改为上电状态,重置 mctp 驱动,启动 mctp 自发现流程。
- mctp 自发现流程: 1、IMU 发送 Prepare for Endpoint Discovery Request 到 BMC 2、BMC 处理 Prepare for Endpoint Discovery Request,设置 Owner eid 和物理地址并返回响应 3、IMU 发送 Endpoint Discovery Request 到 BMC 4、BMC 处理 Endpoint Discovery Request 并返回响应 5、IMU 发送 Set Endpoint ID Request 到 BMC 6、BMC 处理 Set Endpoint ID Request,设置 BMC eid 和物理地址并返回响应 7、mctpd 拉起任务每隔 5s 从 IMU 获取一次路由信息并更新路由表
- 业务组件调用 mctp_lib.get_endpoint_and_transport 方法创建 transport 并获取 Endpoint 对象(如果路由存在),通过 Endpoint 对象的 Send 和 Request 方法发送请求。
4.1.2 mctp over pcie 请求消息处理流程
sequenceDiagram
actor 用户
participant 业务组件
participant mctpd主服务
participant mctpd PCIe写服务
participant mctpd PCIe读worker线程
participant mctp驱动
participant IMU
loop mctp over pcie请求
用户->>+业务组件:输入命令
业务组件->>+mctpd主服务:根据命令确定对应的Endpoint,<br>封装报文数据,发送mctp请求
mctpd主服务->>+mctpd PCIe写服务:拆分数据,使每帧数据<br>不超过传输单元上限,<br>封装报文协议头,逐帧<br>发送数据到mctp写服务
mctpd PCIe写服务->>+mctp驱动:发送数据到驱动
mctp驱动->>+IMU:发送数据,IMU转发到对端硬件
IMU-->>-mctp驱动:响应数据
loop mctp读数据
mctpd PCIe读worker线程->>+mctp驱动:尝试从驱动读数据
mctp驱动-->>-mctpd PCIe读worker线程:返回数据
end
mctpd PCIe读worker线程-->>mctpd主服务:返回响应数据到mctpd主服务
mctpd主服务-->>-业务组件:解封响应数据,<br>并将多帧数据整合返回
业务组件-->>-用户:处理数据,返回结果
end
4.1.3 mctp over smbus/smbus_oem 请求消息处理流程
sequenceDiagram
actor 用户
participant 业务组件
participant mctpd主服务
participant mctpd SMBus读写服务
participant hwproxy
participant 用户态驱动
loop mctp over smbus/smbus_oem请求
用户->>+业务组件:输入命令
业务组件->>+mctpd主服务:根据命令确定对应的Endpoint,<br>封装报文数据,发送mctp请求
mctpd主服务->>+mctpd SMBus读写服务:拆分数据使每帧数据不超过<br>传输单元上限,封装报文协议头,<br>逐帧发送数据到mctpd SMBus读写服务
mctpd SMBus读写服务->>+hwproxy:多帧消息整合为一次请求数据,<br>调用插件请求,发送数据到hwproxy
hwproxy->>+用户态驱动:发送请求数据到驱动
用户态驱动-->>-hwproxy:返回响应数据到hwproxy
hwproxy-->>-mctpd SMBus读写服务:返回响应数据到mctpd SMBus读写服务
mctpd SMBus读写服务-->>-mctpd主服务:返回响应数据到mctpd主服务
mctpd主服务-->>-业务组件:解封响应数据,<br>并将多帧数据整合返回
业务组件-->>-用户:处理数据,返回结果
end
- 以上均为 Request 方法的流程,如果调用的是 Endpoint 的 Send 方法,则仅会将报文发送到对端,不会去尝试获取响应。
4.2 mctpd 对象类图
classDiagram
direction LR
class protocol {
#m_rx_handler: std::optional~handler_t~
#m_tx_handler: std::optional~handler_t~
#pop(protocol_ctx &,slice_t &, int) bool
#push(protocol_ctx &,slice_t &, int) bool
#rx_process(protocol_ctx &,slice_t &, int) bool
#tx_process(protocol_ctx &,slice_t &, int) bool
}
class pldm_protocol {
#m_tx_buf: buffer_t
#pop(protocol_ctx &,slice_t &, int) bool
#push(protocol_ctx &,slice_t &, int) bool
}
class mctp_protocol {
-m_eid: uint8_t
-m_max_size: size_t
-m_route_policy: mctp_route_policy
-m_tx_buf: buffer_t
-m_rx_ctx: std::map~uint32_t, package_ctx~
-m_control_rx: std::optional~control_rx_handler_t~
#pop(protocol_ctx &,slice_t &, int) bool
#push(protocol_ctx &,slice_t &, int) bool
#process(protocol_ctx &, mctp_hdr&,slice_t &, int) bool
#process_ctrl_msg(protocol_ctx &,slice_t &, int) bool
}
class xxx_protocol {
#m_tx_buf: buffer_t
#pop(protocol_ctx &,slice_t &, int) bool
#push(protocol_ctx &,slice_t &, int) bool
}
protocol <|.. pldm_protocol
protocol <|.. mctp_protocol
protocol <|.. xxx_protocol
class protocol_ctx {
#m_prev: protocol_ctx
+visit(ctx_vistor_t &) bool
+cast()
+set_prev(protocol_ctx *)
+get_prev() protocol_ctx *
+name(): char *
}
class pldm_ctx {
+completion_code: uint8_t
+command_code: uint8_t
+rq: uint8_t
+iid: uint8_t
+pldm_type: uint8_t
+visit(ctx_vistor_t &) bool
+name(): char *
}
class mctp_ctx {
+src: mctp_eid_t
+dest: mctp_eid_t
+bool: tag_owner
+tag: uint8_t
+visit(ctx_vistor_t &) bool
+name(): char *
}
class xxx_ctx {
+xxx
+visit(ctx_vistor_t &) bool
+name(): char *
}
protocol_ctx <|.. pldm_ctx
protocol_ctx <|.. mctp_ctx
protocol_ctx <|.. xxx_ctx
class mctp_engine {
+owner_eid
+owner_phy_addr
+bmc_eid
+bmc_phy_addr
+smbus_bmc_eid
+smbus_bmc_phy_addr
+wakeup_request()
+request()
+mctp_control_rx()
+rx()
}
mctp_engine "1" o-- "0..n" protocol
class ep_mctp {
+eid
+phy_addr
+medium_type
+ep_status
+ref_chip
+mctp_ctx
+request()
+send()
+prepare_mctp_request()
+rx()
+process()
}
ep_mctp "0..n" o-- "1" mctp_engine
class ep_pldm {
+pldm_ctx
+request()
+send()
+prepare_pldm_request()
+process()
+check_ctx_value()
}
class ep_xxx {
+xxx_ctx
+request()
+send()
+prepare_xxx_request()
+process()
+check_ctx_value()
}
ep_pldm ..|> ep_mctp
ep_xxx ..|> ep_mctp
ep_mctp "0..n" o-- "1" protocol_ctx
5. 最佳实践
-
使用封装好的通讯方法:使用Endpoint资源树对象提供的Request/Send方法进行mctp通讯。
-
引用必要的资源树接口:使用 mctp_lib 的 API 前,需在 service.json 中引用对应的接口依赖,例如使用
get_endpoint_and_transport需引用bmc.kepler.Systems.Mctp.PCIeTransport和bmc.kepler.Systems.Mctp.PCIeEndpoint。 -
区分 PCIe 与 SMBus Endpoint:PCIe Endpoint 通过物理地址唯一标识,SMBus Endpoint 需额外提供 Position 参数(GroupPosition)以区分同一总线上相同物理地址的 Endpoint。
-
超时设置:调用 get_endpoint_and_transport_with_timeout 获取Endpoint资源树对象时,可根据业务场景设置合理的超时时间,默认超时为 300000ms(5 分钟)。
6. API 参考
6.1 Lua API(include/mctp_lib/init.lua)
| 函数 | 描述 |
|---|---|
mctp_lib.get_mctp_path(system_id) |
返回 MCTP 资源树基础路径 |
mctp_lib.get_mctp_routing(system_id) |
返回 Routing 子路径 |
mctp_lib.get_mctp_transport(system_id) |
返回 Transport 子路径 |
mctp_lib.get_mctp_endpoint(system_id) |
返回 Endpoint 子路径 |
mctp_lib.create_mctp_endpoint_path(phy_addr, msg_type, position, system_id) |
构造完整 Endpoint 对象路径字符串 |
mctp_lib.get_endpoint_and_transport(bus, module_name, phy_addr, msg_type, position, system_id) |
创建 Transport 对象并等待对应 Endpoint 创建,返回 (endpoint, transport) |
mctp_lib.get_endpoint_and_transport_with_timeout(bus, module_name, ep_info, timeout) |
同上,支持自定义超时时间(毫秒),默认 300000ms |
mctp_lib.get_mctp_pcie_binding(bus, system_id) |
获取 BMC MCTP Binding 对象(PCIe),包含 OwnerEid、BmcEid 等信息 |
mctp_lib.get_pcie_endpoint(bus, phy_addr, msg_type, system_id) |
直接获取资源树上已存在的 PCIe Endpoint 对象 |
mctp_lib.get_smbus_endpoint(bus, position, phy_addr, msg_type, system_id) |
直接获取资源树上已存在的 SMBus Endpoint 对象 |
mctp_lib.bdf_to_phy_addr(bus_num, dev_num, func_num) |
将 PCI BDF 地址转换为 MCTP 物理地址 |
6.2 使用示例
- NCSI 类型的 Endpoint
-- context 是用户上下文
-- ncsi_req_ctx 是协议头信息
-- ncsi_rsp_ctx 是协议头的校验项目,目前只校验 PacketType
-- request_data 是具体的命令参数(二进制字符串)
-- timeout 是等待返回的超时时间,单位是 ms
-- 需要返回值:
local ok, rsp_data_bin = pcall(endpoint.Request, endpoint, context, request_data_bin, timeout, ncsi_req_ctx, ncsi_rsp_ctx)
-- 不需要返回值:
local ok = pcall(endpoint.Send, endpoint, context, request_data_bin, timeout, ncsi_req_ctx)
- NVMe 类型的 Endpoint
-- context 是用户上下文
-- nvme_req_ctx 是协议头信息
-- nvme_rsp_ctx 是协议头的校验项目,目前只校验 MsgType
-- request_data 是具体的命令参数(二进制字符串)
-- timeout 是等待返回的超时时间,单位是 ms
-- 需要返回值:
local ok, rsp_data_bin = pcall(endpoint.Request, endpoint, context, request_data_bin, timeout, nvme_req_ctx, nvme_rsp_ctx)
-- 不需要返回值:
local ok = pcall(endpoint.Send, endpoint, context, request_data_bin, timeout, nvme_req_ctx)
- PLDM 类型的 Endpoint
-- context 是用户上下文
-- pldm_req_ctx 是协议头信息
-- pldm_rsp_ctx 是协议头的校验项目,目前只校验 CommandCode
-- request_data 是具体的命令参数(二进制字符串)
-- timeout 是等待返回的超时时间,单位是 ms
-- 需要返回值:
local ok, rsp_data_bin = pcall(endpoint.Request, endpoint, context, request_data_bin, timeout, pldm_req_ctx, pldm_rsp_ctx)
-- 不需要返回值:
local ok = pcall(endpoint.Send, endpoint, context, request_data_bin, timeout, pldm_req_ctx)
- SPDM over MCTP 类型的 Endpoint
-- context 是用户上下文
-- request_data 是具体的命令参数(二进制字符串)
-- timeout 是等待返回的超时时间,单位是 ms
-- spdm_req_ctx 是 SPDM 请求上下文,包含协议头校验项目
-- spdm_rsp_ctx 是 SPDM 响应上下文,包含协议头校验项目
-- 传递上下文:由 mctp 负责封装报文
-- 需要返回值:
local ok, rsp_data_bin = pcall(endpoint.Request, endpoint, context, request_data_bin, timeout, spdm_req_ctx, spdm_rsp_ctx)
-- 不需要返回值:
local ok = pcall(endpoint.Send, endpoint, context, request_data_bin, timeout, spdm_req_ctx)
-- 不传递上下文:说明组件已自行封装报文
-- 需要返回值:
local ok, rsp_data_bin = pcall(endpoint.Request, endpoint, context, request_data_bin, timeout)
-- 不需要返回值:
local ok = pcall(endpoint.Send, endpoint, context, request_data_bin, timeout)
- Vendor Defined-PCI 类型的 Endpoint
-- context 是用户上下文
-- request_data 包含了厂商自定义的协议头信息和具体的命令参数(二进制字符串)
-- timeout 是等待返回的超时时间,单位是 ms
-- 需要返回值:
local ok, rsp_data_bin = pcall(endpoint.Request, endpoint, context, request_data_bin, timeout, {}, {})
-- 不需要返回值:
local ok = pcall(endpoint.Send, endpoint, context, request_data_bin, timeout, {})
更多 API 详情请参阅 include/mctp_lib/ 目录下的源文件。
7. 常见问题
7.1 如何区分 SMBus 的 Endpoint 和 PCIe 的 Endpoint
可以从资源树路径判断。PCIe 的 Endpoint 路径为 /bmc/kepler/Systems/:SystemId/Mctp/Endpoint/phyaddr/msg_type,物理地址唯一;而因为 SMBus 来源为 CSR 配置,且因为 9545 分路可以在同一总线下配置同样物理地址的 Endpoint,因此需要添加 Position 作为唯一标识区分不同的 Endpoint,即 SMBus 的 Endpoint 路径为 /bmc/kepler/Systems/:SystemId/Mctp/Endpoint/position_phyaddr/msg_type。
├── /bmc/kepler/Systems
│ └── /bmc/kepler/Systems/1
│ └── /bmc/kepler/Systems/1/Mctp
│ └── /bmc/kepler/Systems/1/Mctp/Endpoint
│ ├── /bmc/kepler/Systems/1/Mctp/Endpoint/0101020E_29
│ │ └── /bmc/kepler/Systems/1/Mctp/Endpoint/0101020E_29/4
│ ├── /bmc/kepler/Systems/1/Mctp/Endpoint/0101020F_29
│ │ └── /bmc/kepler/Systems/1/Mctp/Endpoint/0101020F_29/4
│ └── /bmc/kepler/Systems/1/Mctp/Endpoint/171
│ └── /bmc/kepler/Systems/1/Mctp/Endpoint/171/4
7.2 为什么 MCTP 报文发不通(PCIe 协议)
这个问题涉及到许多方面,建议通过以下流程排查:
1、确认 IMU 处于上电状态:
这个可以在 web 端查看电源是否上电,或者看 mctpd 日志,关键字 Power ON
2025-04-26 18:07:16.430174 mctpd NOTICE: mctp_mdb_mgmt.lua(246): mctp_mdb_mgmt: pmu status change to 1, OS Power ON
2、上电后,确认 IMU 是否发送 discovery 报文到 BMC:
mctpd 需要和 IMU 建立连接才能收发报文。IMU 上电后会广播 discovery 报文,mctpd 收到后会回复消息,IMU 就会和 BMC 建立连接。可以通过日志确认建立到哪一步。关键字 set owner_eid(收到 discovery 报文,设置 IMU 的 eid 和物理地址)、set bmc_eid(收到 set_ep_id 报文,设置 BMC 的 eid 和物理地址)、start update route table(开始从 IMU 获取路由信息)、add routing entry(收到 IMU 返回的路由信息报文,设置路由信息)
2025-04-26 18:07:17.038622 mctpd NOTICE: mctp_engine.lua(272): mctp_engine: set owner_eid=8, owner_phy_addr=8
2025-04-26 18:07:17.109490 mctpd NOTICE: mctp_engine.lua(281): mctp_engine: set bmc_eid=11, bmc_phy_addr=768
2025-04-26 18:07:17.109862 mctpd NOTICE: mctp_discovery.lua(234): mctp_discovery: start update route table
2025-04-26 18:07:17.335796 mctpd NOTICE: mctp_routing_table.lua(52): mctp_routing_table: add routing entry, phys_address=150, starting_eid=9, entry_type=0
收到路由信息后,mctpd 会注册路由对象,资源树路径为 /bmc/kepler/Systems/1/Mctp/Routing/phyaddr,如果路由里没有预期的网卡、盘的地址(phyaddr),需要排查对应的硬件是否有回应 IMU 的 discovery 报文。
└─/bmc/kepler/Systems/1/Mctp/Routing
├─/bmc/kepler/Systems/1/Mctp/Routing/150
├─/bmc/kepler/Systems/1/Mctp/Routing/173
└─/bmc/kepler/Systems/1/Mctp/Routing/3
3、上电后,确认调用方组件有注册 Transport,且 Transport 的地址在路由信息内:
network_adapter、storage 等组件会通过 mctpd 提供的接口注册 Transport 对象,mctpd 会根据 Transport 的物理地址是否在路由内决定是否创建 Endpoint 对象,没有对应地址的路由信息,mctpd 就不会根据 Transport 对象创建 Endpoint 对象。Transport 的路径为 /bmc/kepler/Systems/1/Mctp/Transport/phyaddr/msg_type,这个路径是注册在调用方组件的资源树上(如 network_adapter、storage)。
├─/bmc/kepler/Systems
│ └─/bmc/kepler/Systems/1
│ ├─/bmc/kepler/Systems/1/Mctp
│ │ └─/bmc/kepler/Systems/1/Mctp/Transport
│ │ └─/bmc/kepler/Systems/1/Mctp/Transport/150
│ │ ├─/bmc/kepler/Systems/1/Mctp/Transport/150/1
│ │ └─/bmc/kepler/Systems/1/Mctp/Transport/150/2
Endpoint 创建的日志关键字:add eid info、create endpoint,可以通过 module_name 确定是哪个组件要注册 Endpoint,对应的物理地址等信息也会记录在日志里。
2025-04-26 18:08:01.820337 mctpd NOTICE: mctp_eid_table.lua(81): mctp_eid_table: add eid info, module_name=network_adapter,msg_src_eid=9,phy_addr=150,msg_type=1, medium_type=15, ep_status=1
2025-04-26 18:08:01.822045 mctpd NOTICE: init.lua(86): mctp_endpoints: create endpoint, phy_addr=150, eid=9, msg_type=1, medium_type=15
2025-04-26 18:08:01.980190 mctpd NOTICE: mctp_eid_table.lua(81): mctp_eid_table: add eid info, module_name=network_adapter,msg_src_eid=9,phy_addr=150,msg_type=2, medium_type=15, ep_status=1
2025-04-26 18:08:01.986384 mctpd NOTICE: init.lua(86): mctp_endpoints: create endpoint, phy_addr=150, eid=9, msg_type=2, medium_type=15
4、如果上面的关键点都排查了(确定上电,有路由,有 Endpoint),但还是发不通,排查消息在链路的哪一层断掉: PCIe 协议的 Endpoint 消息链路为 BMC -> mctp 驱动 -> IMU -> Endpoint,若需要澄清非 BMC 问题,需要在 BMC 发往驱动前加下日志打印,确定 BMC 将报文给了驱动。
5、如果加上打印后发现报文收发都有打印,但是 BMC 还是报请求超时,则需要结合日志具体分析响应报文是否存在结构上的问题导致校验不通过,被 mctpd 静默丢弃。
日志关键字:nvme_protocol: unmatch crc、ncsi_protocol: check sum、ncsi_protocol: incorrect payload size、mctp_protocol: unexpected fragment size、mctp_protocol: sequence number、smbus_protocol: incorrect packet size、smbus_protocol: header error, wr、smbus_protocol: incorrect payload size, data.size、pcie_vdm_protocol: vdm header error, vdm_code、pcie_vdm_protocol: incorrect vdm header, payload_len、pcie_vdm_protocol: incorrect payload size、pcie_vdm_protocol: incorrect packet size 等
8. 相关文档
| 文档 | 说明 |
|---|---|
| CHANGELOG.md | 版本变更历史 |
9. 贡献
欢迎提交 Issue 和 Pull Request。
详细的贡献规范请参阅 openUBMC 社区贡献指南。
10. 许可协议
mctpd 采用 Mulan PSL v2 开源协议。
详情请参阅:http://license.coscl.org.cn/MulanPSL2
11. 支持
此开源项目非华为产品,仅提供有限支持。
如果有任何问题、建议、或者发现任何 bug,请在 Issues 页面提交一个新的 Issue。详细的描述可以帮助我们快速理解并解决问题。
也欢迎通过邮件方式与我们进行交流。community@public.openubmc.cn
12. 变更日志
详细的版本历史请参阅 CHANGELOG.md。