南向驱动是基于硬件组件驱动程序框架,采用分层架构设计,支持多种设备类型和通信协议。本文档面向硬盘适配的设备驱动开发者,提供从设计到实现的完整开发流程,并基于现有代码实例分析,提供一篇快速适配的开发指南。
前置阅读:
相关文档:
- 驱动需实现的接口契约见 3.3 硬盘驱动规范,机器可读定义在
dmc/(总览:Device Management Contract) - 硬盘背板(CSR 方式)适配见官网《硬盘背板开发指南》
- SSD 存储基础见官网《SSD 存储介绍》
- MCTP 协议背景见官网《MCTP 介绍》
1. 概述
1.1 什么是南向硬盘驱动适配?
南向硬盘驱动适配是指在BMC系统中,为不同厂商的PCIe NVMe硬盘提供统一的驱动程序接入方案,使BMC能够管理和监控硬盘设备。
1.2 适配后可以实现什么功能?
- 获取硬盘基本信息(厂商、型号、序列号、固件版本等)
- 监控硬盘状态(温度、健康状态、寿命等)
- 读取SMART信息(读写次数、错误率等)
- 获取硬盘容量和使用情况
- 故障诊断和日志收集
1.3 需要了解的关键概念
设备对象
| 对象类型 | 说明 | 示例 |
|---|---|---|
| NVMe硬盘主对象 | 代表整个PCIe NVMe硬盘 | hw_nvme_pcie4 |
接口
- gen命名空间:自动生成的接口基类,定义属性(位于
dev::gen命名空间) - dev命名空间:厂商具体实现类,实现功能
- 接口组合:一个设备可以实现多个接口,如
PCIeDevice、NVMe、NVMe.Smart等
协议库
- NVMe-MI over MCTP:NVMe管理接口协议,通过PCIe或SMBus与硬盘通信
- NVMe-MI over VPD:通过VPD(Vital Product Data)访问硬盘信息
- 不同厂商可能使用不同的协议扩展
1.4 驱动分层架构
- 分层架构:构建了一个从接口定义到硬件交互的完整体系,接口定义层(DMC)→设备接口层→设备对象层→设备驱动层,每层职责明确,协同工作;
- 设计理念:接口与实现分离,先是
dmc/(Device Management Contract)建立接口契约,再在"设备接口层"生成/落地 C++ 接口,由各厂商提供"设备驱动层"的具体部件驱动实现;
2. 前置准备
2.1 准备编译环境
可参考社区的Docker开发环境搭建。在完成环境搭建后,推荐通过scripts目录下的smart_build.sh完成首次编译,如果希望通过手动构建的方式,可参考本项目的README.md文档
2.2 明确硬盘信息
必需信息
| 信息项 | 说明 | 示例 |
|---|---|---|
| VID | Vendor ID(厂商ID) | 0x19e5 |
| DID | Device ID(设备ID) | 0x0222 |
| SVID | Subsystem Vendor ID | 0x19e5 |
| SSID | Subsystem Device ID | 0x0052 |
协议信息
- 支持的通信协议(NVMe-MI标准协议、厂商OEM扩展等)
- 协议命令集(获取温度、SMART信息等命令的定义)
- 数据格式(响应数据的字节序、字段定义等)
硬件信息
- 硬盘容量
- 是否支持NVMe-MI over PCIe
- 是否支持NVMe-MI over SMBus
- 特殊功能(如厂商特定SMART信息等)
2.3 了解驱动规范
驱动规范定义了驱动需要实现的接口。硬盘的驱动规范可参考本项目归档的硬盘驱动规范,机器可读定义在dmc/(总览:Device Management Contract)
2.4 了解工程目录
Component Drivers项目采用分层架构设计,硬盘驱动适配需要了解以下关键目录结构:
2.4.1 项目整体结构
component_drivers/
├── drivers/ # 设备驱动实现目录(各类硬件设备驱动)
├── gen/ # 自动生成的代码目录(接口基类)
├── libraries/ # 协议库目录(通信协议封装)
├── tests/ # 测试代码目录
├── docs/ # 文档目录
├── include/ # 公共头文件目录
└── meson.build # 构建配置文件2.4.2 硬盘驱动目录结构(drivers目录)
drivers/目录包含所有硬件设备的具体驱动实现,按设备类型组织。以下为PCIe NVMe硬盘驱动的核心目录结构(节选,完整内容见代码仓):
drivers/
├── pcie_nvme/ # PCIe NVMe硬盘驱动(本指南重点)
│ └── huawei/ # 华为硬盘驱动
│ ├── csr/ # 设备配置文件
│ │ ├── 14140224_PROTOCOL_19e5.dds # 设备描述(DDS)
│ │ └── 14140224_PROTOCOL_19e5.sr # 资源描述(SR)
│ ├── hw_nvme_pcie4/ # 华为NVMe PCIe4硬盘实现
│ │ ├── hw_nvme_pcie4.h # 硬盘主对象头文件
│ │ ├── hw_nvme_pcie4.cpp # 硬盘主对象实现
│ │ ├── hw_nvme_pcie4_abi.cpp # ABI导出接口
│ │ └── meson.build # 构建配置
│ ├── interface/ # 接口实现目录(继承gen基类)
│ │ ├── pcie_device/ # PCIe设备接口子目录
│ │ │ ├── oem/ # OEM扩展
│ │ │ ├── pcie_function/ # PCIe功能接口
│ │ │ └── status/ # 状态接口
│ │ ├── pcie_device.h/cpp # PCIe设备接口实现
│ │ ├── nvme/ # NVMe子接口
│ │ │ ├── management.h/cpp # 管理接口
│ │ │ ├── product_info.h/cpp # 产品信息接口
│ │ │ ├── status.h/cpp # 状态接口
│ │ │ ├── smart.h/cpp # SMART接口
│ │ │ ├── basic_management.h/cpp # 基础管理接口
│ │ │ ├── nvme_mi.h/cpp # NVMe-MI接口
│ │ │ ├── log_collection.h/cpp # 日志收集接口
│ │ │ ├── vendor_specific_smart.h/cpp # 厂商特定SMART接口
│ │ │ ├── multi_record.h/cpp # 多记录接口
│ │ │ └── oem.h/cpp # OEM扩展接口
│ │ ├── upgrade.h/cpp # 升级接口
│ │ ├── cooling.h/cpp # 温度监控接口
│ │ └── meson.build
│ └── meson.build
│
├── bus/ # 总线驱动
├── chip/ # 芯片驱动
├── internal/ # 内部实现(驱动框架基础类)
└── meson.build关键目录说明
| 目录 | 说明 | 用途 |
|---|---|---|
| pcie_nvme/ | PCIe NVMe硬盘驱动根目录 | 按厂商组织硬盘驱动 |
| huawei/csr/ | 设备配置文件目录 | 存放DDS/SR配置文件,定义设备接口 |
| huawei/hw_nvme_pcie4/ | 华为NVMe硬盘实现 | 包含硬盘对象的实现代码 |
| huawei/interface/ | 接口实现目录 | 继承gen基类,实现具体协议逻辑和数据更新 |
| internal/ | 内部基础框架 | 提供驱动框架的基础类 |
2.4.3 硬盘设备对象层次
硬盘驱动中涉及的主要设备对象:
| 对象类型 | 说明 | 示例类 |
|---|---|---|
| NVMe硬盘主对象 | 代表整个PCIe NVMe硬盘,组合各接口实现 | hw_nvme_pcie4 |
| 接口实现对象 | 硬盘主对象的成员,每个对应一个管理接口 | NVMe_Smart、Cooling |
与GPU驱动不同,硬盘驱动只有一个主对象,各功能(SMART、温度、状态等)以接口成员的方式组合在主对象中,通过继承不同的gen接口实现相应功能,如bmc.dev.NVMe.Smart、bmc.dev.Cooling等接口
2.4.4 gen目录 - 自动生成的接口基类
gen/目录包含自动生成的接口基类定义,驱动代码继承这些基类实现功能。注意:此目录的代码由工具自动生成,不要手动修改。
目录结构
gen/
├── include/ # 头文件目录
│ └── device_tree/
│ ├── base.h # 基础类定义
│ ├── interface/ # 接口基类定义
│ │ ├── NVMe.h # NVMe接口基类
│ │ ├── NVMe/ # NVMe子接口
│ │ │ ├── Management.h # 管理接口基类
│ │ │ ├── ProductInfo.h # 产品信息接口基类
│ │ │ ├── Status.h # 状态接口基类
│ │ │ ├── Smart.h # SMART接口基类
│ │ │ ├── BasicManagement.h # 基础管理接口基类
│ │ │ ├── NVMeMI.h # NVMe-MI接口基类
│ │ │ ├── LogCollection.h # 日志收集接口基类
│ │ │ ├── MultiRecord.h # 多记录接口基类
│ │ │ ├── VendorSpecificSmart.h # 厂商特定SMART接口基类
│ │ │ └── Oem.h # OEM扩展接口基类
│ │ ├── PCIeDevice.h # PCIe设备接口基类
│ │ └── Cooling.h # 温度监控接口基类
│ │
│ └── object/ # 对象组合定义
│ └── NVMe/
│ └── nvme.h # NVMe对象组合基类
│
├── src/ # 源文件目录(与include结构对应)
└── meson.build关键概念
| 概念 | 说明 | 示例 |
|---|---|---|
| 接口基类 | 定义接口的属性和方法签名 | gen::NVMe定义了MediaType、Protocol等属性 |
| 对象组合 | 将多个接口组合成完整对象 | NVMe对象组合了PCIeDevice、NVMe、Smart等接口 |
| 命名空间 | 生成的代码位于dev::gen命名空间 | 驱动代码继承dev::gen::NVMe实现dev::NVMe |
| 自动生成 | 基于模型文件自动生成,不手动修改 | 通过修改模型文件(JSON)重新生成 |
使用方式
驱动开发时,需要继承对应的gen基类并实现功能:
// 继承gen命名空间的基类(双参模板:自身类型 + gen基类)
class NVMe_Smart : public mc::engine::interface<NVMe_Smart, gen::NVMe_Smart> {
public:
// 实现具体功能
void update_smart_info();
void start_mctp_update_task(...);
};
// 注册(在实现文件末尾)
MC_REFLECT(dev::NVMe_Smart, (gen::NVMe_Smart), ())2.4.5 libraries目录 - 通信协议库
libraries/目录包含各种通信协议的封装实现,为驱动提供标准化的协议接口。
目录结构
libraries/
├── mctp/ # MCTP协议库(管理组件传输协议)
│ ├── mctp.h # MCTP协议接口定义
│ ├── mctp.cpp # MCTP协议实现
│ ├── pcie_transport.h # PCIe传输层实现
│ ├── pcie_transport.cpp
│ └── meson.build
│
├── nvme/ # NVMe协议库
│ ├── nvme_mi_over_mctp.h/cpp # NVMe-MI标准协议
│ ├── nvme_mi_over_mctp_huawei.h/cpp # 华为NVMe-MI协议扩展
│ ├── nvme_mi_vpd.h/cpp # NVMe-MI VPD接口
│ ├── sff_vpd.h/cpp # SFF VPD协议
│ ├── vpd_protocol.h # VPD协议公共定义
│ ├── vpd_str_utils.h # VPD字符串工具
│ └── meson.build
│
└── meson.build协议库说明
| 协议库 | 说明 | 传输方式 | 应用场景 |
|---|---|---|---|
| mctp | 管理组件传输协议(DMTF标准) | PCIe、SMBus | 作为底层传输协议,封装上层协议消息 |
| nvme | NVMe管理接口协议 | MCTP over PCIe/SMBus | 硬盘管理的主要协议,获取硬盘状态、SMART信息 |
| nvme_mi_vpd | NVMe-MI VPD协议 | VPD | 获取硬盘VPD信息、温度等数据 |
协议分层关系
应用层: 硬盘驱动代码
↓
协议层: NVMe-MI over MCTP (nvme_mi_over_mctp_huawei)
↓
传输层: MCTP (mctp + pcie_transport)
↓
物理层: PCIe硬件 / SMBus硬件使用示例
// 1. 创建MCTP对象
mctp* mctp_object = new mctp(this, phy_addr,
MCTP_MESSAGE_TYPE::MCTP_MESSAGE_TYPE_NVME_MI, "");
// 2. 创建NVMe-MI协议对象
auto nvme_mi_huawei = std::make_shared<nvme_mi_over_mctp_huawei>(*mctp_object);
// 3. 发送NVMe-MI请求
auto response = nvme_mi_huawei->get_smart_info();
// 4. 创建VPD对象
auto nvme_mi_vpd_obj = std::make_shared<nvme_mi_vpd>(this);
nvme_mi_vpd_obj->init();
// 5. 读取VPD数据
double temperature = nvme_mi_vpd_obj->get_temperature();2.4.6 目录之间的关系
工作流程
- 代码生成:根据模型文件生成
gen/目录下的接口基类 - 接口实现:在
drivers/*/interface/下继承gen基类,实现具体功能 - 驱动组装:在
drivers/*/具体型号/下组合接口,创建完整驱动对象 - 协议调用:驱动通过
libraries/中的协议库与硬件通信 - 框架支持:
drivers/internal/提供基础设施支持
2.5 驱动协议架构
2.6 底层协议确定(了解硬盘支持的协议类型)
在适配硬盘驱动前,首先要确定硬盘支持的底层通信协议。不同厂商的硬盘可能支持不同的协议组合。华为实现会在启动时读取VPD Common Header自动探测协议类型(NVMe-MI或SFF),并支持MCTP over PCIe失败后回退SMBus。
协议类型
| 协议 | 说明 | 适用场景 |
|---|---|---|
| NVMe-MI over MCTP over PCIe | 通过PCIe总线进行MCTP通信 | 硬盘直接连接到PCIe总线 |
| NVMe-MI over MCTP over SMBus | 通过SMBus/I2C进行MCTP通信 | 硬盘通过SMBus桥接芯片管理(华为实现回退时使用地址29) |
| NVMe-MI VPD | 通过VPD访问硬盘信息 | 获取温度、基本状态等信息 |
厂商协议支持对比
| 厂商 | NVMe-MI over MCTP over PCIe | NVMe-MI over MCTP over SMBus | NVMe-MI VPD | 说明 |
|---|---|---|---|---|
| 华为 | ✓ | ✓ | ✓ | 主要通过PCIe,VPD用于带外信息获取 |
2.7 NVMe硬盘支持的能力
本节以华为NVMe PCIe4硬盘为例,详细列举硬盘驱动可以支持的各种能力,帮助开发者了解可以实现哪些功能接口。
硬盘级能力
华为NVMe硬盘主对象实现了以下接口:
| 能力类别 | 接口名称 | 说明 | 可获取的信息 |
|---|---|---|---|
| PCIe设备信息 | PCIeDevice | PCIe设备基础信息 | VID/DID、BDF地址、设备名称、设备类型 |
| NVMe基本信息 | NVMe | NVMe设备功能信息 | 槽位、媒体类型、协议、速率、寿命百分比 |
| 管理信息 | NVMe.Management | NVMe管理信息 | 协议类型、是否支持MCTP over PCIe、VPD芯片、SSD芯片 |
| 产品信息 | NVMe.ProductInfo | 产品制造信息 | 型号、序列号、部件号、厂商、固件版本 |
| 多记录信息 | NVMe.MultiRecord | 扩展信息记录 | 容量、接口类型等 |
| 状态信息 | NVMe.Status | 设备状态信息 | 健康状态、功能状态、链路状态、就绪状态 |
| 温度监控 | Cooling | 温度信息 | 当前温度、温度状态、目标温度、最高温度 |
| 基础管理 | NVMe.BasicManagement | 基础管理信息 | PCIe链路状态、重置需求、功能状态、SMART警告 |
| NVMe-MI接口 | NVMe.NVMeMI | NVMe-MI命令接口 | 发送NVMe-MI命令、获取SMART信息、获取识别数据 |
| 日志收集 | NVMe.LogCollection | 日志导出功能 | 硬盘日志文件、故障诊断信息、事件记录 |
| SMART信息 | NVMe.Smart | SMART健康信息 | 温度、可用容量、介质错误、错误日志条数 |
| 厂商特定SMART | NVMe.VendorSpecificSmart | 厂商特定SMART | 厂商自定义的SMART属性 |
| OEM扩展 | NVMe.Oem | 厂商扩展功能 | 厂商自定义扩展属性 |
能力获取方式
华为NVMe硬盘通过两种协议获取不同的信息:
| 协议类型 | 获取的信息 | 更新频率 | 数据来源 |
|---|---|---|---|
| NVMe-MI over MCTP over PCIe | 产品信息、SMART信息、日志、识别数据 | 120秒 | 硬盘固件 |
| NVMe-MI VPD | 温度、基本状态、管理信息 | 3秒~10秒 | 硬盘VPD |
更新频率策略:
- 快速变化数据(3秒):温度
- 中速变化数据(10秒):状态、基础管理信息
- 慢速变化数据(120秒):产品信息、SMART信息、厂商特定SMART
3. 适配流程详解
假设我们要适配一个新厂商"XYZ"的NVMe硬盘"xyz_nvme"
3.1 创建目录结构
在 drivers/pcie_nvme/ 下创建:
pcie_nvme/
└── xyz/ # 厂商目录
├── xyz_nvme/ # 型号目录
│ ├── xyz_nvme.h # 主对象头文件
│ ├── xyz_nvme.cpp # 主对象实现
│ ├── xyz_nvme_abi.cpp # ABI导出
│ └── meson.build # 构建配置
├── interface/ # 接口实现目录
│ ├── pcie_device.h
│ ├── pcie_device.cpp
│ ├── nvme.h
│ ├── nvme.cpp
│ ├── nvme/
│ │ ├── management.h
│ │ ├── management.cpp
│ │ ├── smart.h
│ │ ├── smart.cpp
│ │ └── ...
│ ├── cooling.h
│ ├── cooling.cpp
│ └── meson.build
├── csr/ # 设备配置目录
│ └── [VID_DID_SVID_SSID].dds # DDS文件(设备接口定义)
└── meson.build3.2 配置设备描述文件(DDS)
DDS文件定义设备的接口和路径,是框架识别和加载驱动的关键配置。
文件命名规则
格式:14140224_PROTOCOL_[VID].dds
示例:14140224_PROTOCOL_19e5.dds
说明:VID=0x19e5,14140224是PCIe NVMe的分类代码DDS文件内容
以下为华为实现的完整DDS示例(共17个接口,新适配厂商可按实际支持能力裁剪):
{
"Schema": "dds-v1",
"Type": "Component",
"DeviceCategory": "PCIeNVMe",
"ID": "N/A",
"Objects": {
"PCIeNVMe": {
"Path": "/bmc/dev/Systems/:SystemId/PCIeNVMe/:Id",
"Interfaces": [
"bmc.dev.PCIeDevice",
"bmc.dev.PCIeDevice.PCIeFunction",
"bmc.dev.PCIeDevice.Oem",
"bmc.dev.PCIeDevice.Status",
"bmc.dev.NVMe",
"bmc.dev.NVMe.Management",
"bmc.dev.NVMe.ProductInfo",
"bmc.dev.NVMe.MultiRecord",
"bmc.dev.NVMe.Status",
"bmc.dev.Cooling",
"bmc.dev.NVMe.BasicManagement",
"bmc.dev.NVMe.NVMeMI",
"bmc.dev.NVMe.LogCollection",
"bmc.dev.NVMe.Smart",
"bmc.dev.NVMe.VendorSpecificSmart",
"bmc.dev.Upgrade",
"bmc.dev.NVMe.Oem"
]
}
}
}关键字段说明
| 字段 | 说明 | 注意事项 |
|---|---|---|
DeviceCategory | 设备类别 | 必须是"PCIeNVMe" |
ID | 设备标识 | DDS模板中固定为"N/A",由框架按实际设备填充 |
Objects | 对象定义 | 键名必须与ABI中的device_name一致 |
Path | 对象路径模板 | :SystemId、:Id是动态参数占位符 |
Interfaces | 接口列表 | 必须与MC_OBJECT宏中声明的接口一致 |
配置要点
- 接口列表要完整:DDS中的
Interfaces必须包含代码中MC_OBJECT声明的所有接口 - 对象名称要匹配:
Objects的键名(如PCIeNVMe)必须与ABI注册时的device_name一致 - 路径格式固定:路径格式不要修改。注意DDS中
Path使用:SystemId/:Id占位符,与代码中MC_OBJECT的路径模板"/bmc/dev/Systems/1/PCIeNVMe/${object_name}"(单系统场景SystemId为字面量1,${object_name}对应:Id)语义对应 - 可选接口:如果不支持某些接口,可以删除对应的接口定义
3.3 创建硬盘主对象
代码示例:xyz_nvme.h
参考 hw_nvme_pcie4.h:
#ifndef XYZ_NVME_H
#define XYZ_NVME_H
#include <mc/engine.h>
#include <mc/timer.h>
#include <nvme/nvme_mi_vpd.h>
#include <nvme/nvme_mi_over_mctp_xyz.h>
#include <mctp/mctp.h>
#include "interface/pcie_device.h"
#include "interface/nvme.h"
#include "interface/nvme/management.h"
#include "interface/nvme/product_info.h"
#include "interface/nvme/smart.h"
#include "interface/cooling.h"
namespace dev {
using nvme_mi_vpd_ptr = std::shared_ptr<nvme_mi_vpd>;
using nvme_mi_over_mctp_ptr = std::shared_ptr<nvme_mi_over_mctp_xyz>;
using mctp_ptr = std::shared_ptr<mctp>;
class xyz_nvme : public mc::engine::object<xyz_nvme> {
public:
// MC_OBJECT宏定义:类名、对象类型、路径模式、接口列表
MC_OBJECT(
xyz_nvme, "PCIeNVMe",
"/bmc/dev/Systems/1/PCIeNVMe/${object_name}",
(PCIeDevice)(NVMe)(NVMe_Management)(NVMe_ProductInfo)(NVMe_Status)
(Cooling)(NVMe_BasicManagement)(NVMe_NVMeMI)(NVMe_Smart)
)
xyz_nvme(); // 构造函数
~xyz_nvme(); // 析构函数
// 生命周期方法
bool init(mc::mutable_dict& csr_object, const mc::dict& connector);
bool start();
bool stop();
// 接口成员变量
uint8_t m_system_id;
PCIeDevice m_pcie_device;
NVMe m_nvme;
NVMe_Management m_nvme_management;
NVMe_ProductInfo m_nvme_product_info;
NVMe_Status m_nvme_status;
Cooling m_cooling;
NVMe_BasicManagement m_nvme_basic_management;
NVMe_NVMeMI m_nvme_nvme_mi;
NVMe_Smart m_nvme_smart;
private:
bool init_nvme_mi_vpd_protocol();
bool start_mctp_protocol();
void start_vpd_update_tasks();
void start_mctp_update_tasks();
nvme_mi_vpd_ptr m_nvme_mi_vpd_obj;
nvme_mi_over_mctp_ptr m_nvme_mi_over_mctp_obj;
mctp* m_mctp_object; // 管理MCTP对象生命周期
// 更新间隔
mc::milliseconds m_vpd_cooling_interval = mc::milliseconds(3000);
mc::milliseconds m_vpd_status_interval = mc::milliseconds(10000);
mc::milliseconds m_mctp_smart_interval = mc::milliseconds(120000);
};
} // namespace dev
#endif // XYZ_NVME_H注意:MC_REFLECT反射注册不放在头文件中,统一放在实现文件(xyz_nvme.cpp)末尾,见3.4节。
关键说明
1. MC_OBJECT宏的参数:
- 第1个:类名
- 第2个:设备类型(固定为
"PCIeNVMe") - 第3个:对象路径模板(固定格式)
- 第4个:实现的接口列表(按需选择)
2. 接口选择原则:
| 接口 | 必需性 | 说明 |
|---|---|---|
PCIeDevice、NVMe | 必需 | 基础接口 |
NVMe_Management | 必需 | 管理接口 |
NVMe_ProductInfo | 可选 | 支持产品信息时添加 |
NVMe_Status | 可选 | 支持状态监控时添加 |
Cooling | 可选 | 支持温度监控时添加 |
NVMe_Smart | 可选 | 支持SMART信息时添加 |
3.4 实现硬盘主对象
代码示例:xyz_nvme.cpp
#include "xyz_nvme.h"
#include <mc/log.h>
namespace dev {
// 构造函数:初始化MCTP对象指针为nullptr
xyz_nvme::xyz_nvme() : m_mctp_object(nullptr) {
}
// 析构函数:释放MCTP对象,防止内存泄漏
xyz_nvme::~xyz_nvme() {
if (m_mctp_object) {
delete m_mctp_object;
m_mctp_object = nullptr;
}
}
// 初始化方法:从配置文件加载硬盘属性
bool xyz_nvme::init(mc::mutable_dict& csr_object, const mc::dict& connector) {
try {
// 使用from_variant自动加载配置到成员变量
from_variant(csr_object, *this);
ilog("xyz_nvme initialized successfully");
} catch (const std::exception& e) {
elog("xyz_nvme init failed, exception: ${exception}", ("exception", e.what()));
return false;
}
return true;
}
// 启动方法:启动硬盘和所有协议
bool xyz_nvme::start() {
// 初始化VPD协议
if (!init_nvme_mi_vpd_protocol()) {
elog("Failed to initialize VPD protocol");
return false;
}
// 启动MCTP协议
if (!start_mctp_protocol()) {
elog("Failed to start MCTP protocol");
return false;
}
// 启动VPD更新任务
start_vpd_update_tasks();
// 启动MCTP更新任务
start_mctp_update_tasks();
ilog("xyz_nvme started successfully");
return true;
}
// 停止方法:停止硬盘并清理资源
bool xyz_nvme::stop() {
// 停止所有接口的更新任务
m_cooling.stop_vpd_update_task();
m_nvme_status.stop_vpd_update_task();
m_nvme_smart.stop_mctp_update_task();
ilog("xyz_nvme stopped");
return true;
}
// 初始化NVMe-MI VPD协议
bool xyz_nvme::init_nvme_mi_vpd_protocol() {
m_nvme_mi_vpd_obj = std::make_shared<nvme_mi_vpd>(this);
// 初始化VPD对象
// ...
return true;
}
// 启动MCTP协议:创建MCTP通信和NVMe-MI协议实例
bool xyz_nvme::start_mctp_protocol() {
// 获取BDF地址
// 创建MCTP对象(赋值给成员变量)
m_mctp_object = new mctp(this, phy_addr,
MCTP_MESSAGE_TYPE::MCTP_MESSAGE_TYPE_NVME_MI, "");
// 创建NVMe-MI协议实例
m_nvme_mi_over_mctp_obj = std::make_shared<nvme_mi_over_mctp_xyz>(*m_mctp_object);
// 创建传输层并启动
// ...
return true;
}
// 启动VPD更新任务
void xyz_nvme::start_vpd_update_tasks() {
m_cooling.start_vpd_update_task(m_nvme_mi_vpd_obj, m_vpd_cooling_interval);
m_nvme_status.start_vpd_update_task(m_nvme_mi_vpd_obj, m_vpd_status_interval);
}
// 启动MCTP更新任务
void xyz_nvme::start_mctp_update_tasks() {
m_nvme_smart.start_mctp_update_task(m_nvme_mi_over_mctp_obj, m_mctp_smart_interval);
}
// MC_REFLECT宏:反射信息注册(放在实现文件末尾)
MC_REFLECT(dev::xyz_nvme,
((m_system_id, "SystemId"))
((m_pcie_device, "bmc.dev.PCIeDevice"))
((m_nvme, "bmc.dev.NVMe"))
((m_nvme_management, "bmc.dev.NVMe.Management"))
((m_nvme_product_info, "bmc.dev.NVMe.ProductInfo"))
((m_nvme_status, "bmc.dev.NVMe.Status"))
((m_cooling, "bmc.dev.Cooling"))
((m_nvme_basic_management, "bmc.dev.NVMe.BasicManagement"))
((m_nvme_nvme_mi, "bmc.dev.NVMe.NVMeMI"))
((m_nvme_smart, "bmc.dev.NVMe.Smart")))
} // namespace dev关键说明
| 方法 | 职责 |
|---|---|
init() | 加载配置文件中的属性值,使用from_variant自动映射 |
start() | 初始化协议、启动协议通信、注册回调和定时器 |
stop() | 清理资源 |
协议启动流程:
- 初始化VPD协议
- 获取BDF(Bus/Device/Function)
- 创建MCTP对象
- 创建NVMe-MI协议实例
- 为接口绑定更新任务
华为实现的增强逻辑(可参考):
- 协议自动探测:
start()时先读取VPD Common Header(校验和+ClassCode),自动判别硬盘走NVMe-MI还是SFF协议,SFF场景跳过MCTP - 传输回退:MCTP over PCIe启动失败时,回退到MCTP over SMBus(PHY地址29)
- BDF变化感知:监听
pcie_device_bdf_changed信号,BDF变化时自动重启MCTP协议
3.5 实现接口类
核心要点
接口类继承自gen命名空间的基类,负责具体的协议实现和数据更新。
1. 接口类定义
// 继承自生成的基类(双参模板)
class NVMe_Smart : public mc::engine::interface<NVMe_Smart, gen::NVMe_Smart> {
public:
// 启动MCTP更新任务:创建定时器周期性更新属性
void start_mctp_update_task(nvme_mi_over_mctp_ptr nvme_mi,
mc::milliseconds interval,
std::function<void()> on_update_callback = nullptr);
// 停止更新任务:停止并释放定时器
void stop_mctp_update_task();
// 更新方法:通过NVMe-MI协议获取数据并更新属性
void update_smart_info();
private:
mc::timer_ptr m_mctp_timer;
nvme_mi_over_mctp_ptr m_nvme_mi;
};
// MC_REFLECT注册:指定基类(放在实现文件末尾)
MC_REFLECT(dev::NVMe_Smart, (gen::NVMe_Smart), ())2. 关键实现方法
// 启动更新任务:创建定时器周期性更新属性
void NVMe_Smart::start_mctp_update_task(nvme_mi_over_mctp_ptr nvme_mi,
mc::milliseconds interval) {
m_nvme_mi = nvme_mi;
m_mctp_timer = mc::make_shared<mc::timer>(this);
m_mctp_timer->timeout.connect([this]() {
update_smart_info();
});
m_mctp_timer->set_single_shot(false);
m_mctp_timer->start(interval);
// 立即执行一次更新
update_smart_info();
}
// 停止更新任务:停止并释放定时器
void NVMe_Smart::stop_mctp_update_task() {
if (m_mctp_timer) {
m_mctp_timer->stop();
m_mctp_timer.reset();
}
}
// 更新SMART信息:通过NVMe-MI协议获取并更新SMART属性
void NVMe_Smart::update_smart_info() {
try {
// 调用协议获取SMART信息
auto smart_data = m_nvme_mi->get_smart_info();
// 解析响应数据
// 更新属性:TemperatureCelsius = parsed_temperature
TemperatureCelsius = smart_data.temperature;
AvailableSparePercent = smart_data.available_spare;
MediaErrors = smart_data.media_errors;
} catch (const std::exception& e) {
elog("Failed to update SMART info: ${error}", ("error", e.what()));
}
}关键要点:
- 接口类必须继承对应的
gen基类(双参模板mc::engine::interface<自身, gen基类>) - 使用定时器周期性调用update方法
- update方法负责:获取数据 → 解析 → 更新属性
- 属性更新后会自动触发信号通知上层
3.6 实现ABI导出
ABI(Application Binary Interface)是驱动程序与框架交互的标准接口。
代码示例:xyz_nvme_abi.cpp
#include <devmon/driver_abi.h>
#include <mc/log.h>
#include "xyz_nvme.h"
using namespace dev;
extern "C" {
// ============================================
// 硬盘主对象ABI函数
// ============================================
// 创建硬盘对象:分配内存并设置基本属性
driver_handle_t create_xyz_nvme(void* service, const char* name) {
if (!service || !name) {
elog("create_xyz_nvme: invalid parameters");
return nullptr;
}
auto* nvme = new xyz_nvme();
nvme->set_service(static_cast<mc::engine::service*>(service));
nvme->set_object_name(name);
ilog("Created xyz_nvme object: ${name}", ("name", name));
return nvme;
}
// 初始化硬盘对象:加载配置
status_t init_xyz_nvme(driver_handle_t device, void* csr_object, void* connector) {
if (!device || !csr_object || !connector) {
elog("init_xyz_nvme: invalid parameters");
return STATUS_INVALID_PARAM;
}
auto* nvme = static_cast<xyz_nvme*>(device);
auto& csr = *static_cast<mc::mutable_dict*>(csr_object);
auto& conn = *static_cast<mc::dict*>(connector);
if (!nvme->init(csr, conn)) {
elog("init_xyz_nvme failed");
return STATUS_ERROR;
}
return STATUS_OK;
}
// 启动硬盘对象:启动设备和协议
status_t start_xyz_nvme(driver_handle_t device) {
if (!device) {
elog("start_xyz_nvme: invalid parameters");
return STATUS_INVALID_PARAM;
}
auto* nvme = static_cast<xyz_nvme*>(device);
if (!nvme->start()) {
elog("start_xyz_nvme failed");
return STATUS_ERROR;
}
return STATUS_OK;
}
// 停止硬盘对象:停止设备和清理资源
status_t stop_xyz_nvme(driver_handle_t device) {
if (!device) {
elog("stop_xyz_nvme: invalid parameters");
return STATUS_INVALID_PARAM;
}
auto* nvme = static_cast<xyz_nvme*>(device);
nvme->stop();
return STATUS_OK;
}
// ============================================
// 驱动注册
// ============================================
// 硬盘主对象驱动结构体:定义设备名和生命周期函数
device_driver_t xyz_nvme_device_driver = {
.device_name = "PCIeNVMe", // 设备类型名(必须与DDS文件一致)
.ctor = create_xyz_nvme, // 创建函数
.init = init_xyz_nvme, // 初始化函数
.start = start_xyz_nvme, // 启动函数
.stop = stop_xyz_nvme // 停止函数
};
// 驱动数组:包含所有设备类型的驱动
device_driver_t xyz_nvme_driver_array[] = {
xyz_nvme_device_driver
};
// 驱动注册函数:框架调用此函数获取驱动列表
status_t register_device_driver(device_driver_t** device_driver, uint8_t* count) {
*device_driver = xyz_nvme_driver_array;
*count = sizeof(xyz_nvme_driver_array) / sizeof(xyz_nvme_driver_array[0]);
ilog("Registered xyz_nvme device driver: ${count} devices", ("count", *count));
return STATUS_OK;
}
} // extern "C"关键说明
- 所有ABI函数必须用
extern "C"包装,确保C语言兼容 - 每个设备类型需要4个函数:
create、init、start、stop device_name必须与DDS文件中的对象类型匹配register_device_driver是框架调用的入口函数,通过驱动数组返回
3.7 配置构建编译
代码示例:meson.build
# 源文件列表
xyz_nvme_sources = files(
'xyz_nvme.cpp',
'xyz_nvme_abi.cpp',
)
# 接口实现源文件
interface_sources = files(
'interface/pcie_device.cpp',
'interface/nvme.cpp',
'interface/nvme/management.cpp',
'interface/nvme/smart.cpp',
'interface/cooling.cpp',
)
# 合并源文件
all_sources = xyz_nvme_sources + interface_sources
# 包含目录
include_dirs = [
include_directories('.'),
include_directories('interface'),
gen_inc,
internal_inc
]
# 静态库(用于测试与复用)
libxyz_nvme_static = static_library(
'xyz_nvme',
all_sources,
include_directories: include_dirs,
dependencies: [
dev_deps,
libnvme_dep,
libmctp_dep
],
install: false
)
# 动态库(用于部署)
libxyz_nvme = shared_library(
'xyz_nvme',
include_directories: include_dirs,
name_prefix: 'lib',
name_suffix: 'so',
install: true,
install_dir: drivers_install_dir,
link_whole: [libxyz_nvme_static]
)关键说明
- 库名会成为驱动SO文件名:
libxyz_nvme.so install_dir使用drivers_install_dir变量(框架定义,实际安装到构建根目录的opt/bmc/drivers)- 依赖关系要明确列出,避免链接错误
4. 最佳实践建议
4.1 代码规范
命名规范
| 类型 | 规范 | 示例 |
|---|---|---|
| 类名 | 厂商_型号 | xyz_nvme |
| 文件名 | 与类名一致 | xyz_nvme.h |
| 成员变量 | m_前缀 | m_nvme_mi_vpd_obj |
| 常量 | 大写+下划线 | DEFAULT_TIMEOUT |
日志规范
- 启动/停止:使用
ilog - 错误:使用
elog - 调试信息:使用
dlog - 日志要包含上下文信息
ilog("Starting xyz_nvme: ${name} at slot ${slot}",
("name", disk_name)("slot", slot_id));错误处理
- 所有可能失败的操作都要检查返回值
- 使用
try-catch捕获异常 - 记录详细的错误信息
try {
bool ret = operation();
if (!ret) {
elog("Operation failed: ${reason}", ("reason", error_msg));
return false;
}
} catch (const std::exception& e) {
elog("Exception caught: ${error}", ("error", e.what()));
return false;
}4.2 性能优化
定时任务间隔
- 根据数据变化频率设置不同的更新间隔,避免不必要的通信开销
- 快速变化数据:3秒(3000ms) - 温度
- 中速变化数据:10秒(10000ms) - 状态、基础管理信息
- 慢速变化数据:120秒(120000ms) - SMART信息、产品信息
实际应用示例:
// 不同接口使用不同的更新间隔
m_cooling.start_vpd_update_task(vpd_obj, mc::milliseconds(3000)); // 温度:3秒
m_nvme_status.start_vpd_update_task(vpd_obj, mc::milliseconds(10000)); // 状态:10秒
m_nvme_smart.start_mctp_update_task(mctp_obj, mc::milliseconds(120000)); // SMART:120秒内存管理
- 优先使用智能指针(
shared_ptr) - 及时清理定时器等资源
- 重要:MCTP对象必须使用成员变量管理,防止严重内存泄漏
MCTP对象管理:
// 头文件声明
class xyz_nvme : public mc::engine::object<xyz_nvme> {
private:
mctp* m_mctp_object; // 使用成员变量管理
};
// 实现文件
xyz_nvme::xyz_nvme() : m_mctp_object(nullptr) {}
xyz_nvme::~xyz_nvme() {
// 在析构函数中释放,防止内存泄漏
if (m_mctp_object) {
delete m_mctp_object;
m_mctp_object = nullptr;
}
}
bool xyz_nvme::start_mctp_protocol() {
// 赋值给成员变量,而不是局部变量
m_mctp_object = new mctp(this, phy_addr, ...);
}定时器资源管理:
// 清理定时器(mc::timer_ptr 为共享指针)
if (m_timer) {
m_timer->stop();
m_timer.reset();
}协议优化
- 批量请求减少通信次数
- 缓存不变的数据
- 失败时使用指数退避
4.3 测试建议
单元测试
- 测试每个接口的更新方法
- 测试异常情况处理
- 使用mock对象模拟协议
TEST(xyz_nvme, test_init) {
xyz_nvme nvme;
mc::mutable_dict csr_object = {...};
mc::dict connector = {...};
EXPECT_TRUE(nvme.init(csr_object, connector));
}集成测试
- 测试完整的生命周期:
create→init→start→stop - 测试与实际硬件的通信
- 测试长时间运行的稳定性
回归测试
- 每次修改后重新测试基本功能
- 检查是否影响其他硬盘驱动
- 验证Redfish接口数据正确
结语
南向硬盘驱动适配是一项系统性工作,需要对硬件特性、通信协议、软件架构都有深入理解。本文档提供了一套完整的适配流程和代码示例,但实际适配中还会遇到各种具体问题,欢迎大家积极在社区沟通交流和贡献。