部件适配-部件驱动 2.0-南向硬盘驱动适配指南
更新时间: 2026/08/15
在Gitcode上查看源码

南向驱动是基于硬件组件驱动程序框架,采用分层架构设计,支持多种设备类型和通信协议。本文档面向硬盘适配的设备驱动开发者,提供从设计到实现的完整开发流程,并基于现有代码实例分析,提供一篇快速适配的开发指南。

前置阅读

相关文档

1. 概述

1.1 什么是南向硬盘驱动适配?

南向硬盘驱动适配是指在BMC系统中,为不同厂商的PCIe NVMe硬盘提供统一的驱动程序接入方案,使BMC能够管理和监控硬盘设备。

1.2 适配后可以实现什么功能?

  • 获取硬盘基本信息(厂商、型号、序列号、固件版本等)
  • 监控硬盘状态(温度、健康状态、寿命等)
  • 读取SMART信息(读写次数、错误率等)
  • 获取硬盘容量和使用情况
  • 故障诊断和日志收集

1.3 需要了解的关键概念

设备对象

对象类型说明示例
NVMe硬盘主对象代表整个PCIe NVMe硬盘hw_nvme_pcie4

接口

  • gen命名空间:自动生成的接口基类,定义属性(位于 dev::gen 命名空间)
  • dev命名空间:厂商具体实现类,实现功能
  • 接口组合:一个设备可以实现多个接口,如PCIeDeviceNVMeNVMe.Smart

协议库

  • NVMe-MI over MCTP:NVMe管理接口协议,通过PCIe或SMBus与硬盘通信
  • NVMe-MI over VPD:通过VPD(Vital Product Data)访问硬盘信息
  • 不同厂商可能使用不同的协议扩展

1.4 驱动分层架构

  1. 分层架构:构建了一个从接口定义到硬件交互的完整体系,接口定义层(DMC)→设备接口层→设备对象层→设备驱动层,每层职责明确,协同工作;
  2. 设计理念:接口与实现分离,先是 dmc/(Device Management Contract)建立接口契约,再在"设备接口层"生成/落地 C++ 接口,由各厂商提供"设备驱动层"的具体部件驱动实现;

2. 前置准备

2.1 准备编译环境

可参考社区的Docker开发环境搭建。在完成环境搭建后,推荐通过scripts目录下的smart_build.sh完成首次编译,如果希望通过手动构建的方式,可参考本项目的README.md文档

2.2 明确硬盘信息

必需信息

信息项说明示例
VIDVendor ID(厂商ID)0x19e5
DIDDevice ID(设备ID)0x0222
SVIDSubsystem Vendor ID0x19e5
SSIDSubsystem Device ID0x0052

协议信息

  • 支持的通信协议(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_SmartCooling

与GPU驱动不同,硬盘驱动只有一个主对象,各功能(SMART、温度、状态等)以接口成员的方式组合在主对象中,通过继承不同的gen接口实现相应功能,如bmc.dev.NVMe.Smartbmc.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定义了MediaTypeProtocol等属性
对象组合将多个接口组合成完整对象NVMe对象组合了PCIeDeviceNVMeSmart等接口
命名空间生成的代码位于dev::gen命名空间驱动代码继承dev::gen::NVMe实现dev::NVMe
自动生成基于模型文件自动生成,不手动修改通过修改模型文件(JSON)重新生成
使用方式

驱动开发时,需要继承对应的gen基类并实现功能:

cpp
// 继承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作为底层传输协议,封装上层协议消息
nvmeNVMe管理接口协议MCTP over PCIe/SMBus硬盘管理的主要协议,获取硬盘状态、SMART信息
nvme_mi_vpdNVMe-MI VPD协议VPD获取硬盘VPD信息、温度等数据
协议分层关系
应用层:  硬盘驱动代码

协议层:  NVMe-MI over MCTP (nvme_mi_over_mctp_huawei)

传输层:  MCTP (mctp + pcie_transport)

物理层:  PCIe硬件 / SMBus硬件
使用示例
cpp
// 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 目录之间的关系

工作流程
  1. 代码生成:根据模型文件生成gen/目录下的接口基类
  2. 接口实现:在drivers/*/interface/下继承gen基类,实现具体功能
  3. 驱动组装:在drivers/*/具体型号/下组合接口,创建完整驱动对象
  4. 协议调用:驱动通过libraries/中的协议库与硬件通信
  5. 框架支持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 PCIeNVMe-MI over MCTP over SMBusNVMe-MI VPD说明
华为主要通过PCIe,VPD用于带外信息获取

2.7 NVMe硬盘支持的能力

本节以华为NVMe PCIe4硬盘为例,详细列举硬盘驱动可以支持的各种能力,帮助开发者了解可以实现哪些功能接口。

硬盘级能力

华为NVMe硬盘主对象实现了以下接口:

能力类别接口名称说明可获取的信息
PCIe设备信息PCIeDevicePCIe设备基础信息VID/DID、BDF地址、设备名称、设备类型
NVMe基本信息NVMeNVMe设备功能信息槽位、媒体类型、协议、速率、寿命百分比
管理信息NVMe.ManagementNVMe管理信息协议类型、是否支持MCTP over PCIe、VPD芯片、SSD芯片
产品信息NVMe.ProductInfo产品制造信息型号、序列号、部件号、厂商、固件版本
多记录信息NVMe.MultiRecord扩展信息记录容量、接口类型等
状态信息NVMe.Status设备状态信息健康状态、功能状态、链路状态、就绪状态
温度监控Cooling温度信息当前温度、温度状态、目标温度、最高温度
基础管理NVMe.BasicManagement基础管理信息PCIe链路状态、重置需求、功能状态、SMART警告
NVMe-MI接口NVMe.NVMeMINVMe-MI命令接口发送NVMe-MI命令、获取SMART信息、获取识别数据
日志收集NVMe.LogCollection日志导出功能硬盘日志文件、故障诊断信息、事件记录
SMART信息NVMe.SmartSMART健康信息温度、可用容量、介质错误、错误日志条数
厂商特定SMARTNVMe.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.build

3.2 配置设备描述文件(DDS)

DDS文件定义设备的接口和路径,是框架识别和加载驱动的关键配置。

文件命名规则

格式:14140224_PROTOCOL_[VID].dds
示例:14140224_PROTOCOL_19e5.dds
说明:VID=0x19e5,14140224是PCIe NVMe的分类代码

DDS文件内容

以下为华为实现的完整DDS示例(共17个接口,新适配厂商可按实际支持能力裁剪):

json
{
    "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宏中声明的接口一致

配置要点

  1. 接口列表要完整:DDS中的Interfaces必须包含代码中MC_OBJECT声明的所有接口
  2. 对象名称要匹配Objects的键名(如PCIeNVMe)必须与ABI注册时的device_name一致
  3. 路径格式固定:路径格式不要修改。注意DDS中Path使用:SystemId/:Id占位符,与代码中MC_OBJECT的路径模板"/bmc/dev/Systems/1/PCIeNVMe/${object_name}"(单系统场景SystemId为字面量1,${object_name}对应:Id)语义对应
  4. 可选接口:如果不支持某些接口,可以删除对应的接口定义

3.3 创建硬盘主对象

代码示例:xyz_nvme.h

参考 hw_nvme_pcie4.h

cpp
#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. 接口选择原则:

接口必需性说明
PCIeDeviceNVMe必需基础接口
NVMe_Management必需管理接口
NVMe_ProductInfo可选支持产品信息时添加
NVMe_Status可选支持状态监控时添加
Cooling可选支持温度监控时添加
NVMe_Smart可选支持SMART信息时添加

3.4 实现硬盘主对象

代码示例:xyz_nvme.cpp

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()清理资源

协议启动流程:

  1. 初始化VPD协议
  2. 获取BDF(Bus/Device/Function)
  3. 创建MCTP对象
  4. 创建NVMe-MI协议实例
  5. 为接口绑定更新任务

华为实现的增强逻辑(可参考):

  • 协议自动探测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. 接口类定义

cpp
// 继承自生成的基类(双参模板)
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. 关键实现方法

cpp
// 启动更新任务:创建定时器周期性更新属性
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

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个函数:createinitstartstop
  • device_name必须与DDS文件中的对象类型匹配
  • register_device_driver是框架调用的入口函数,通过驱动数组返回

3.7 配置构建编译

代码示例:meson.build

meson
# 源文件列表
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
  • 日志要包含上下文信息
cpp
ilog("Starting xyz_nvme: ${name} at slot ${slot}",
     ("name", disk_name)("slot", slot_id));

错误处理

  • 所有可能失败的操作都要检查返回值
  • 使用try-catch捕获异常
  • 记录详细的错误信息
cpp
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信息、产品信息

实际应用示例:

cpp
// 不同接口使用不同的更新间隔
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对象管理:

cpp
// 头文件声明
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, ...);
}

定时器资源管理:

cpp
// 清理定时器(mc::timer_ptr 为共享指针)
if (m_timer) {
    m_timer->stop();
    m_timer.reset();
}

协议优化

  • 批量请求减少通信次数
  • 缓存不变的数据
  • 失败时使用指数退避

4.3 测试建议

单元测试

  • 测试每个接口的更新方法
  • 测试异常情况处理
  • 使用mock对象模拟协议
cpp
TEST(xyz_nvme, test_init) {
    xyz_nvme nvme;
    mc::mutable_dict csr_object = {...};
    mc::dict connector = {...};

    EXPECT_TRUE(nvme.init(csr_object, connector));
}

集成测试

  • 测试完整的生命周期:createinitstartstop
  • 测试与实际硬件的通信
  • 测试长时间运行的稳定性

回归测试

  • 每次修改后重新测试基本功能
  • 检查是否影响其他硬盘驱动
  • 验证Redfish接口数据正确

结语

南向硬盘驱动适配是一项系统性工作,需要对硬件特性、通信协议、软件架构都有深入理解。本文档提供了一套完整的适配流程和代码示例,但实际适配中还会遇到各种具体问题,欢迎大家积极在社区沟通交流和贡献。