本文档主要介绍如何在openUBMC上适配一张规范2.0的GPU卡。
南向部件驱动适配教程GPU篇
本教程面向从事GPU适配的南向部件驱动开发者,详细阐述了从接口设计到代码实现的完整流程。
目录
1. 概述
List of abbreviations 缩略语清单 :
| Abbreviations 缩略语 | Full spelling 英文全名 | Chinese explanation 中文解释 |
|---|---|---|
| BMC | Baseboard Management Controller | 基板管理控制器 |
| PCIe | Peripheral Component Interconnect Express | 高速外设组件互连 |
| GPU | Graphics Processing Unit | 图形处理器 |
2. 前置准备
2.1 准备编译环境
可参考社区的Docker开发环境搭建。在完成环境搭建后,推荐通过scripts目录下的smart_build.sh完成首次编译,如果希望通过手动构建的方式,可参考本项目的README.md文档
2.2 了解驱动规范
驱动规范定义了驱动需要实现的接口。GPU的驱动规范可参考本项目归档的GPU驱动规范
2.3 了解工程目录
Component Drivers项目采用分层架构设计,GPU驱动适配需要了解以下关键目录结构:
2.3.1 项目整体结构
component_drivers/
├── docs/ # 文档
├── drivers/ # 驱动实现目录
│ ├── pcie_gpu_card/ # GPU卡驱动
│ ├── bus/ # 总线驱动
│ └── chip/ # 芯片驱动
├── gen/ # 自动生成的接口定义
├── include/ # 公共头文件
├── libraries/ # 协议库
│ └── smbus/ # SMBus协议
└── tests/ # 测试代码2.3.2 GPU驱动目录结构
GPU驱动位于 drivers/pcie_gpu_card/ 目录下,按厂商和型号组织:
pcie_gpu_card/
├── innosilicon/ # 厂商目录(如:芯动科技)
│ ├── awm_m11p/ # 具体GPU型号目录
│ │ ├── awm_m11p_abi.cpp # ABI导出接口
│ │ ├── awm_m11p_card.cpp # GPU卡主对象实现
│ │ ├── awm_m11p_card.h # GPU卡主对象头文件
│ │ ├── awm_m11p_gpu.cpp # GPU处理器对象实现
│ │ ├── awm_m11p_gpu.h # GPU处理器对象头文件
│ │ ├── awm_m11p_memory.cpp # GPU内存对象实现
│ │ ├── awm_m11p_memory.h # GPU内存对象头文件
│ │ └── meson.build # 构建配置
│ ├── csr/ # 设备配置目录
│ │ ├── [VID_DID_SVID_SSID].dds # 设备描述文件
│ │ └── [VID_DID_SVID_SSID].sr # 部件自描述记录
│ ├── interface/ # 接口实现目录
│ │ ├── gpu/
│ │ | ├── power.h/.cpp # GPU功耗接口实现
│ │ | └── status.h/.cpp # GPU状态接口实现
│ │ ├── gpu.h/.cpp # GPU接口实现
│ │ ├── memory.h/.cpp # 内存接口实现
│ │ ├── pcie_card.h/.cpp # PCIe卡接口实现
│ │ ├── pcie_device.h/.cpp # PCIe设备接口实现
│ │ └── processor.h/.cpp # 处理器接口实现
│ └── meson.build # 构建配置
└── meson.build # 构建配置2.3.3 关键目录说明
| 目录类型 | 路径 | 说明 | 作用 |
|---|---|---|---|
| 接口定义 | gen/include/device_tree/interface/ | 自动生成的接口基类定义 | 定义GPU、Memory、PCIeDevice等接口的属性和方法签名 |
| 厂商实现 | drivers/pcie_gpu_card/[厂商]/[型号]/ | 厂商具体驱动实现 | 继承gen接口,实现GPU管理的实际功能逻辑 |
| 协议库 | libraries/ | 通信协议实现 | SMBus等协议封装,用于与GPU通信 |
| 配置文件 | drivers/pcie_gpu_card/[厂商]/csr/ | 设备配置文件 | DDS文件定义设备接口,SR文件定义硬件拓扑 |
目录关系如下:
2.3.4 GPU设备对象层次
GPU驱动中涉及的主要设备对象:
| 对象类型 | 说明 | 示例类 |
|---|---|---|
| GPU卡主对象 | 代表整个GPU卡 | awm_m11p_card |
| GPU处理器对象 | 代表GPU芯片本身 | awm_m11p_gpu |
| GPU内存对象 | 代表GPU显存 | awm_m11p_memory |
这些对象通过继承不同的gen接口来实现相应的功能,如PCIeDevice、Gpu、Memory等接口
2.4 了解GPU支持的协议类型
不同厂商的GPU支持的带外管理协议不同,这需要开发者去向厂家了解对应的协议。一般来讲,GPU的带外管理协议是在SMBus协议基础之上实现的,例如NVIDIA GPU的SMBPBI协议。通过了解GPU的带外管理协议,就可以清楚接下来可以实现哪些接口并且如何实现它们
3. 适配流程
以适配芯动科技的风华1号为例
3.1 创建目录
在 drivers/pcie_gpu_card/ 下创建如下目录与文件:
pcie_gpu_card/
├── innosilicon/ # 厂商目录(如:芯动科技)
│ ├── awm_m11p/ # 具体GPU型号目录
│ │ ├── awm_m11p_abi.cpp # ABI导出接口
│ │ ├── awm_m11p_card.cpp # GPU卡主对象实现
│ │ ├── awm_m11p_card.h # GPU卡主对象头文件
│ │ ├── awm_m11p_gpu.cpp # GPU处理器对象实现
│ │ ├── awm_m11p_gpu.h # GPU处理器对象头文件
│ │ ├── awm_m11p_memory.cpp # GPU内存对象实现
│ │ ├── awm_m11p_memory.h # GPU内存对象头文件
│ │ └── meson.build # 构建配置
│ ├── csr/ # 设备配置目录
│ │ ├── [VID_DID_SVID_SSID].dds # 设备描述文件
│ │ └── [VID_DID_SVID_SSID].sr # 部件自描述记录
│ ├── interface/ # 接口实现目录
│ │ ├── gpu/
│ │ | ├── power.h/.cpp # GPU功耗接口实现
│ │ | └── status.h/.cpp # GPU状态接口实现
│ │ ├── gpu.h/.cpp # GPU接口实现
│ │ ├── memory.h/.cpp # 内存接口实现
│ │ ├── pcie_card.h/.cpp # PCIe卡接口实现
│ │ ├── pcie_device.h/.cpp # PCIe设备接口实现
│ │ └── processor.h/.cpp # 处理器接口实现
│ └── meson.build # 构建配置
└── meson.build # 构建配置说明:
awm_m11p_card为GPU卡主对象,组合并管理GPU处理器与内存等子对象;awm_m11p_gpu为GPU处理器对象,承载Gpu、Gpu.Status、Gpu.Power等接口实现;awm_m11p_memory为显存对象,实现Memory相关接口;interface/下的文件继承gen/include/device_tree/interface/中对应基类,负责协议交互与属性更新;csr/目录中的.dds与.sr文件分别定义对象/接口与硬件管理拓扑;meson.build用于声明源文件、依赖与安装目标(最终生成lib<model>.so)。
3.2 配置设备描述文件
文件命名规则
格式:14140130_[VID][DID]_[SVID][SSID].dds
示例:14140130_19e50222_19e50052.dds
说明:VID=0x19e5, DID=0x0222, SVID=0x19e5, SSID=0x0052DDS文件内容
{
"Schema": "dds-v1",
"Type": "Component",
"DeviceCategory": "PCIeGpuCard",
"ID": "1fe01010_1fe01010",
"Objects": {
"PCIeGpuCard": {
"Path": "/bmc/dev/Systems/:SystemId/PCIeGpuCard/:Id",
"Interfaces": [
"bmc.dev.PCIeDevice",
"bmc.dev.PCIeCard"
]
},
"GraphicsController": {
"Path": ":Parent/GraphicsController/:Id",
"Interfaces": [
"bmc.dev.Processor",
"bmc.dev.Gpu",
"bmc.dev.Gpu.Power",
"bmc.dev.Gpu.Status"
]
},
"Memory": {
"Path": ":Parent/Memory/:Id",
"Interfaces": [
"bmc.dev.Memory"
]
}
}
}关键字段说明
| 字段 | 说明 | 注意事项 |
|---|---|---|
DeviceCategory | 设备类别 | 必须是"PCIeGpuCard" |
Objects | 对象定义 | 键名必须与ABI中的device_name一致 |
Path | 对象路径 | :SystemId、:Id是动态参数,:Parent表示父对象 |
Interfaces | 接口列表 | 必须与自动生成接口头文件中MC_OBJECT宏中声明的接口一致 |
配置要点
- 接口列表要完整:
Interfaces必须包含代码中MC_OBJECT声明的所有接口 - 对象名称要匹配:
Objects的键名(如PCIeGpuCard)必须与ABI注册时的device_name一致 - 路径格式固定:主对象路径和子对象路径格式不要修改
3.3 配置部件自描述记录
SR文件定义硬件拓扑、芯片配置和对象初始值,用于复杂的硬件管理场景。
文件命名规则
格式:14140130_[VID][DID]_[SVID][SSID].sr
示例:14140130_19e50222_19e50052.sr
说明:文件名必须与DDS文件一致SR文件结构
{
"FormatVersion": "5.00",
"DataVersion": "5.00",
"Unit": {
"Type": "PCIeGpuCard",
"Name": "PCIeGpuCard_1",
"Compatible": ["awm_m11p"]
},
"ManagementTopology": {
// 硬件拓扑配置(I2C总线、芯片连接关系)
},
"Objects": {
// 对象初始配置和属性值
}
}Unit配置(部件单元)
| 字段 | 说明 | 注意事项 |
|---|---|---|
Type | 部件类别标识 | 必须是"PCIeGpuCard" |
Name | 部件主管理对象名称 | 一般是"PCIeGpuCard_1",必须与"Objects"下的一致,在同一文件中必须唯一 |
Compatible | 兼容性标识数组 | 必须与pcie_gpu_card/innosilicon/awm_m11p/meson.build中指定的驱动名称一致,一般使用型号名称,支持多个兼容的设备型号 |
ManagementTopology配置(I2C拓扑)
一般GPU卡通过SMBus提供带外能力
"ManagementTopology": {
"Anchor": {
"Buses": [
"I2cMux_Chan"
]
},
"I2cMux_Chan": {
"Chips": [
"Chip_M11P" // GPU提供带外管理能力的芯片
]
}
},
"Objects": {
"Chip_M11P": {
"Address": 158, // I2C地址:0x9E
"AddrWidth": 1,
"OffsetWidth": 1,
"WriteTmout": 100,
"ReadTmout": 100,
"HealthStatus": 0
}
}GPU卡对象配置(PCIeGpuCard_1)
定义GPU卡的初始属性值:
bmc.dev.PCIeDevice
| 属性 | 说明 | 建议实现方式 |
|---|---|---|
| DeviceName | PCIe设备名称 | 通常会包含槽位号和型号 |
| FunctionClass | 功能分类 | GPU卡固定为3 |
| VendorId | 制造商Id | 按需配置 |
| DeviceId | 设备Id | 按需配置 |
| SubSystemVendorId | 子厂商Id | 按需配置 |
| SubSystemDeviceId | 子设备Id | 按需配置 |
| Slot | 槽位号 | 固定配"${Slot}" |
| DeviceType | 设备类型 | 取值参考Redfish标准规范中PCIeDevice资源的同名属性 |
| SlotType | PCIe设备所在槽位的类型 | 取值参考Redfish标准规范中PCIeDevice资源的同名属性 |
| FunctionProtocol | PCIe功能协议 | 取值参考Redfish标准规范中PCIeDevice资源的同名属性,对于GPU卡,固定配"PCIe" |
| FunctionType | PCIe功能类型 | 取值参考Redfish标准规范中PCIeDevice资源的同名属性,对于GPU卡,固定配"Physical" |
| ComponentType | PCIe设备部件类型 | PCIe卡固定为8 |
| Container | 容纳此PCIe设备的Component对象名称,由上级连接器传入 | 固定配"${Container}" |
| GroupPosition | 资源协作路径信息,由上级连接器传入、拼接而成,可作为对象唯一标识 | 固定配"PCIeDevice_1_${GroupPosition}" |
bmc.dev.PCIeCard
| 属性 | 说明 | 建议实现方式 |
|---|---|---|
| Name | 产品名称 | 按需配置 |
| Model | 产品型号 | 按需配置 |
| Manufacturer | 厂商 | 按需配置 |
| PartNumber | 部件编码 | 按需配置 |
| Description | 描述 | 按需配置 |
| BoardName | 单板名称 | 按需配置 |
GPU对象配置(GraphicsController_1)
bmc.dev.Processor
| 属性 | 说明 | 建议实现方式 |
|---|---|---|
| SystemId | 系统ID | 一般配置1 |
| Id | 处理器ID | 一般配置1 |
| Presence | 在位状态 | 一般配置1 |
| Manufacturer | 厂商 | 按需配置 |
| Model | 型号 | 按需配置 |
| Location | 位置信息 | 引用父对象PCIeGpuCard_1.Location |
bmc.dev.Gpu
| 属性 | 说明 | 建议实现方式 |
|---|---|---|
| TemperatureCelsius | GPU温度 | 一般需要通过带外管理协议获取,本例通过配置Scanner实现 |
bmc.dev.Gpu.Power
| 属性 | 说明 | 建议实现方式 |
|---|---|---|
| PowerWatts | 功耗 | 配置默认值0 |
| PowerBrakeState | 功耗限制状态 | 配置默认值255 |
| ExternalPowerSufficient | 外部供电是否充足 | 配置默认值255 |
bmc.dev.Gpu.Status
| 属性 | 说明 | 建议实现方式 |
|---|---|---|
| ECCModeEnabled | ECC模式当时使能状态 | 配置默认值255 |
| ECCModePendingEnabled | ECC模式重启后使能状态 | 配置默认值255 |
| ResetRequired | 是否需要重置 | 配置默认值255 |
| DoubleBitErrorPageCount | 双bit失效故障页计数 | 配置默认值0 |
| SingleBitErrorPageCount | 单bit失效故障页计数 | 配置默认值0 |
内存对象配置(Memory_1)
bmc.dev.Memory
| 属性 | 说明 | 建议实现方式 |
|---|---|---|
| DimmNum | 内存Id | 一般配置1 |
| Manufacturer | 厂商 | 按需配置 |
SR文件配置要点
- 父子关系:使用
@Parent字段指定对象的层次关系 - 动态参数:
${Slot}等参数会在运行时替换 - 初始值:255通常表示"无效值"或"未初始化"
- 接口完整性:SR中定义的接口必须与DDS文件中声明的接口一致
3.4 创建GPU卡主对象
参考 awm_m11p_card.h:
#ifndef __AWM_M11P_CARD_H__
#define __AWM_M11P_CARD_H__
#include "interface/pcie_card.h"
#include "interface/pcie_device.h"
#include <mc/engine.h>
namespace dev {
class awm_m11p_card : public mc::engine::object<awm_m11p_card> {
public:
// MC_OBJECT宏定义:类名、对象类型、路径模式、接口列表
MC_OBJECT(awm_m11p_card, "PCIeGpuCard",
"/bmc/dev/Systems/1/PCIeGpuCard/${object_name}",
(PCIeDevice)(PCIeCard))
awm_m11p_card() = default; // 构造函数
~awm_m11p_card() = default; // 析构函数
// 生命周期方法
bool start();
bool stop();
bool init(mc::mutable_dict& csr_object, const mc::dict& connector);
bool replace_dynamic_property(mc::mutable_dict& object, const mc::dict& connector);
// 接口成员变量
PCIeDevice m_pcie_device;
PCIeCard m_pcie_card;
uint8_t SystemId;
};
} // namespace dev
#endif // __AWM_M11P_CARD_H__关键说明
1. 头文件包含:
"interface/pcie_card.h"包含<device_tree/interface/PCIeCard.h>,该路径实际指向gen/include/device_tree/interface/下的同名文件。这是因为在根目录下的meson.build有一句include_directories('gen/include'),,gen/include目录被添加到了编译系统的include路径中。其他的头文件包含同理
2. MC_OBJECT宏的参数:
- 第1个:类名
- 第2个:设备类型(固定为
"PCIeGpuCard") - 第3个:对象路径模板(固定格式)
- 第4个:实现的接口列表(按需选择)
3. 接口选择原则:
| 接口 | 必需性 | 说明 |
|---|---|---|
PCIeDevice、PCIeCard | 必需 | 基础接口 |
3.5 实现GPU卡主对象
#include "awm_m11p_card.h"
#include <mc/core/timer.h>
#include <mc/log.h>
#include <thread>
#include <vector>
namespace dev {
// 启动方法:启动GPU卡任务,本示例不涉及
bool awm_m11p_card::start() {
return true;
}
// 停止方法:停止GPU卡任务并清理资源,本示例不涉及
bool awm_m11p_card::stop() {
return true;
}
// 在运行时替换SR配置文件中的动态占位符为实际值,bmc.dev.PCIeDevice接口下存在需要替换的动态占位符
bool awm_m11p_card::replace_dynamic_property(mc::mutable_dict& object, const mc::dict& connector) {
m_pcie_device.replace_dynamic_property(object, connector);
return true;
}
// 初始化方法,调用replace_dynamic_property,确保对象初始化时使用的是替换后的实际值
bool awm_m11p_card::init(mc::mutable_dict& csr_object, const mc::dict& connector) {
try {
replace_dynamic_property(csr_object, connector);
from_variant(csr_object, *this);
} catch (const std::exception& e) {
elog("awm_m11p_card init failed, exception: ${exception}", ("exception", e.what()));
return false;
}
return true;
}
} // namespace dev
MC_REFLECT(dev::awm_m11p_card,
((SystemId, "SystemId"))((m_pcie_device, "bmc.dev.PCIeDevice"))((m_pcie_card,
"bmc.dev.PCIeCard")))关键说明
| 方法 | 职责 |
|---|---|
init() | 加载配置文件中的属性值,使用from_variant自动映射 |
start() | 启动方法,如果有轮询任务,应当在本函数启动 |
stop() | 停止方法,如果有轮询任务,应当在本函数停止 |
replace_dynamic_property() | 在运行时替换SR配置文件中的动态占位符为实际值 |
3.5 实现GPU对象
参考 awm_m11p_gpu.cpp:
#include "awm_m11p_gpu.h"
#include "mc/log.h"
namespace dev {
// 启动方法:启动GPU任务,本示例不涉及
bool awm_m11p_gpu::start() {
return true;
}
// 停止方法:停止GPU任务并清理资源,本示例不涉及
bool awm_m11p_gpu::stop() {
return true;
}
// 初始化方法
bool awm_m11p_gpu::init(mc::mutable_dict& config, const mc::dict& connector) {
try {
from_variant(config, *this);
} catch (const std::exception& e) {
elog("awm_m11p_gpu init failed, exception: ${exception}", ("exception", e.what()));
return false;
}
return true;
}
// 恢复默认属性
bool awm_m11p_gpu::reset_default_properties() {
m_processor.Presence = false;
m_gpu.InfoRomVersion = "";
m_gpu.BuildDate = "";
m_gpu.UUID = "";
m_gpu.Utilization = 0;
m_gpu.TemperatureCelsius = 0;
m_gpu_power.PowerWatts = 0;
m_gpu_power.PowerBrakeState = 255;
m_gpu_power.ExternalPowerSufficient = 255;
m_gpu_status.ECCModeEnabled = 255;
m_gpu_status.ECCModePendingEnabled = 255;
m_gpu_status.ResetRequired = 255;
return true;
}
} // namespace dev
MC_REFLECT(dev::awm_m11p_gpu,
((m_processor, "bmc.dev.Processor"))((m_gpu, "bmc.dev.Gpu"))(
(m_gpu_power, "bmc.dev.Gpu.Power"))((m_gpu_status, "bmc.dev.Gpu.Status")))关键说明
| 方法 | 职责 |
|---|---|
reset_default_properties() | 恢复默认属性 |
3.6 实现接口类
- interface目录按接口定义层级分布
- 接口类继承自
gen命名空间的基类,负责具体的协议实现和数据更新 - 由于本示例中的GPU并未涉及复杂协议,因此接口类并不需要做什么修改
- 通用的接口,如bmc.dev.PCIeDevice,对于所有PCIe设备都会涉及,因此需要考虑放到公共目录,避免重复代码
- 轮询任务通常在接口类实现,以下是网卡的一个案例
void NetworkAdapter::start_ncsi_update_task_huawei(ncsi_over_mctp_hw_ptr ncsi_over_mctp,
mc::milliseconds interval) {
// 创建定时器实例
m_ncsi_timer = new mc::core::timer(this);
// 记录协议实体地址,当驱动对象实现层在调用接口类时完成赋值
m_ncsi_over_mctp_huawei = ncsi_over_mctp;
// 具体的轮询动作
m_ncsi_timer->timeout.connect([this]() {
update_firmware_version_by_ncsi();
});
m_ncsi_timer->set_single_shot(false);
// 启动轮询
m_ncsi_timer->start(interval);
}
void NetworkAdapter::stop_ncsi_update_task() {
if (m_ncsi_timer) {
// 停止轮询
m_ncsi_timer->stop();
// 删除定时器实例
delete m_ncsi_timer;
m_ncsi_timer = nullptr;
}
if (m_ncsi_over_mctp_huawei) {
m_ncsi_over_mctp_huawei = nullptr;
}
}3.7 实现ABI导出
ABI(Application Binary Interface)是驱动程序与框架交互的标准接口
参考 awm_m11p_abi.cpp:
#include "awm_m11p_card.h"
#include "awm_m11p_gpu.h"
#include "awm_m11p_memory.h"
#include "devmon/driver_abi.h"
#include "mc/log.h"
#include <iostream>
#include <memory>
#ifndef DEVICE_DRIVER_NAME
#define DEVICE_DRIVER_NAME "awm_m11p"
#endif
using namespace dev;
extern "C" {
// ============================================
// GPU卡对象ABI函数
// ============================================
// 创建GPU卡对象:分配内存并设置基本属性
driver_handle_t create_awm_m11p_card(void* service, const char* name) {
// 参数检查
// 创建awm_m11p_card对象
// 设置service和对象名称
// 返回对象指针
}
// 初始化GPU卡对象:加载配置
status_t init_awm_m11p_card(driver_handle_t device, void* csr_object, void* connector) {
// 参数检查
// 类型转换
// 调用对象的init方法
// 返回状态码
}
// 启动GPU卡对象:启动设备和协议
status_t start_awm_m11p_card(driver_handle_t device) {
// 参数检查
// 调用对象的start方法
// 返回状态码
}
// 停止GPU卡对象:停止设备和清理资源
status_t stop_awm_m11p_card(driver_handle_t device) {
// 参数检查
// 调用对象的stop方法
// 返回状态码
}
// ============================================
// GPU对象ABI函数
// ============================================
// 创建GPU对象:分配内存并设置基本属性
driver_handle_t create_awm_m11p_gpu(void* service, const char* name) {
// 参数检查
// 创建awm_m11p_card对象
// 设置service和对象名称
// 返回对象指针
}
// 初始化GPU对象:加载配置
status_t init_awm_m11p_gpu(driver_handle_t device, void* csr_object, void* connector) {
// 参数检查
// 类型转换
// 调用对象的init方法
// 返回状态码
}
// 启动GPU对象:启动设备和协议
status_t start_awm_m11p_gpu(driver_handle_t device) {
// 参数检查
// 调用对象的start方法
// 返回状态码
}
// 停止GPU对象:停止设备和清理资源
status_t stop_awm_m11p_gpu(driver_handle_t device) {
// 参数检查
// 调用对象的stop方法
// 返回状态码
}
// ============================================
// 内存对象ABI函数
// ============================================
// 创建内存对象:分配内存并设置基本属性
driver_handle_t create_awm_m11p_memory(void* service, const char* name) {
// 参数检查
// 创建awm_m11p_card对象
// 设置service和对象名称
// 返回对象指针
}
// 初始化内存对象:加载配置
status_t init_awm_m11p_memory(driver_handle_t device, void* csr_object, void* connector) {
// 参数检查
// 类型转换
// 调用对象的init方法
// 返回状态码
}
// 启动内存对象:启动设备和协议
status_t start_awm_m11p_memory(driver_handle_t device) {
// 参数检查
// 调用对象的start方法
// 返回状态码
}
// 停止内存对象:停止设备和清理资源
status_t stop_awm_m11p_memory(driver_handle_t device) {
// 参数检查
// 调用对象的stop方法
// 返回状态码
}
// ============================================
// 驱动注册
// ============================================
device_driver_t awm_m11p_card_device_driver = {.device_name = "PCIeGpuCard", // 设备类型名(必须与DDS文件一致)
.ctor = create_awm_m11p_card,
.init = init_awm_m11p_card,
.start = start_awm_m11p_card,
.stop = stop_awm_m11p_card};
device_driver_t awm_m11p_gpu_device_driver = {.device_name = "GraphicsController", // 设备类型名(必须与DDS文件一致)
.ctor = create_awm_m11p_gpu,
.init = init_awm_m11p_gpu,
.start = start_awm_m11p_gpu,
.stop = stop_awm_m11p_gpu};
device_driver_t awm_m11p_memory_device_driver = {.device_name = "Memory", // 设备类型名(必须与DDS文件一致)
.ctor = create_awm_m11p_memory,
.init = init_awm_m11p_memory,
.start = start_awm_m11p_memory,
.stop = stop_awm_m11p_memory};
// 驱动数组:包含所有设备类型的驱动
device_driver_t awm_m11p_driver[] = {awm_m11p_card_device_driver, awm_m11p_gpu_device_driver,
awm_m11p_memory_device_driver};
// 驱动注册函数:框架调用此函数获取驱动列表
status_t register_device_driver(device_driver_t** device_driver, uint8_t* count) {
*device_driver = awm_m11p_driver;
*count = sizeof(awm_m11p_driver) / sizeof(awm_m11p_driver[0]);
return STATUS_OK;
}
}- 所有ABI函数必须用
extern "C"包装,确保C语言兼容 - 每个设备类型需要4个函数:
create、init、start、stop device_name必须与DDS文件中的对象类型匹配register_device_driver是框架调用的入口函数
3.8 配置构建系统
参考 meson.build:
# 源文件列表
awm_m11p_sources = files(
'awm_m11p_abi.cpp',
'awm_m11p_card.cpp',
'awm_m11p_gpu.cpp',
'awm_m11p_memory.cpp',
) + interface_sources
# 动态库(用于部署)
libawm_m11p = shared_library(
'awm_m11p',
awm_m11p_sources,
include_directories: include_dirs + innosilicon_interface_inc,
dependencies: [dev_deps],
name_prefix: 'lib',
name_suffix: 'so',
install: true,
install_dir: drivers_install_dir,
)
# 指定部署路径
if build_tests and meson_build
custom_target(
'copy_libawm_m11p',
output: 'copy_libawm_m11p',
command: ['cp', libawm_m11p.full_path(), drivers_install_dir],
depends: libawm_m11p,
build_by_default: true,
)
endif关键说明
- 库名会成为驱动SO文件名:
libawm_m11p.so,注意和CSR配置一致 - 依赖关系要明确列出,避免链接错误
3.9 编写开发者测试
单元测试
- 在
tests/drivers路径下新增pcie_gpu_card/innosilicon/awm_m11p目录 - 在此目录下新增
test_awm_m11p_abi.cpp - 代码框架可以参考test_hi182x_abi.cpp
class hi182x_test_service : public mc::engine::service {
public:
hi182x_test_service(const std::string& name) : mc::engine::service(name) {
}
};
// 该类继承自TestWithEngine,用于测试Hi182x网卡驱动的ABI接口
class Hi182xAbiTest : public mc::test::TestWithEngine {
public:
// 测试套件初始化(所有测试用例执行前调用一次)
static void SetUpTestSuite() {
// 调用基类的SetUpTestSuite初始化测试引擎
TestWithEngine::SetUpTestSuite();
// 初始化并启动测试服务
m_test_service.init();
m_test_service.start();
// 动态加载Hi182x驱动库
load_driver();
}
// 测试套件清理(所有测试用例执行后调用一次)
static void TearDownTestSuite() {
// 停止测试服务
m_test_service.stop();
// 关闭动态库句柄,释放资源
if (m_driver_handle != nullptr) {
dlclose(m_driver_handle);
m_driver_handle = nullptr;
}
// 清理测试引擎资源
TestWithEngine::TearDownTestSuite();
}
// 单个测试用例初始化(每个测试用例执行前调用)
void SetUp() override {
}
// 单个测试用例清理(每个测试用例执行后调用)
void TearDown() override {
}
// 动态加载驱动库并注册设备驱动
static void load_driver() {
// 使用dlopen动态加载驱动库,RTLD_LAZY表示延迟绑定符号
m_driver_handle = dlopen("./opt/bmc/drivers/libhisi_182x.so", RTLD_LAZY);
if (m_driver_handle == nullptr) {
MC_THROW(mc::system_exception, "Failed to load driver: ${error}", ("error", dlerror()));
}
// 从动态库中查找并获取register_device_driver函数
register_device_driver_func func = reinterpret_cast<register_device_driver_func>(
dlsym(m_driver_handle, "register_device_driver"));
MC_ASSERT_THROW(func, mc::bad_function_call_exception,
"Failed to get export_device_driver function: ${error}",
("error", dlerror()));
// 调用驱动注册函数,获取设备驱动列表
device_driver_t* devices = nullptr;
uint8_t devices_count = 0;
status_t ret = func(&devices, &devices_count);
MC_ASSERT_THROW(ret == STATUS_OK, mc::bad_function_call_exception,
"Failed to get device driver manager: ${error}", ("error", ret));
// 清空并重新填充设备驱动映射表
m_devices.clear();
// 预分配空间,提高性能
m_devices.reserve(devices_count);
// 遍历所有设备驱动类型,存储到映射表中(key为device_name,value为device_driver_t)
for (uint8_t i = 0; i < devices_count; i++) {
auto device_driver = std::make_shared<device_driver_t>(devices[i]);
m_devices.emplace(device_driver->device_name, device_driver);
}
}
// 连接器(connector)用于传递上级设备的信息给子设备
mc::dict create_test_connector(uint8_t system_id, uint8_t position, uint8_t slot) {
return mc::dict{{"SystemId", system_id}, {"Position", position}, {"Slot", slot}};
}
// 测试服务实例,提供引擎服务环境
static hi182x_test_service m_test_service;
// 设备驱动映射表,key为设备类型名(如"PCIeNicCard"),value为设备驱动信息
static std::unordered_map<std::string, std::shared_ptr<device_driver_t>> m_devices;
// 动态库句柄,用于dlopen/dlclose操作
static void* m_driver_handle;
};
// ========== 静态成员变量定义 ==========
// 初始化测试服务实例,服务名称为"bmc.kepler.test_hi182x"
// 该服务为测试提供引擎服务环境,支持设备对象的创建和管理
hi182x_test_service Hi182xAbiTest::m_test_service{"bmc.kepler.test_hi182x"};
// 初始化设备驱动映射表(默认为空)
// 在SetUpTestSuite中通过load_driver()填充,存储所有注册的设备驱动类型
// key: 设备类型名称(如"PCIeNicCard"、"NicPort"、"OpticalTransceiver")
// value: 设备驱动信息(包含ctor、init、start、stop等函数指针)
std::unordered_map<std::string, std::shared_ptr<device_driver_t>> Hi182xAbiTest::m_devices;
// 初始化动态库句柄为nullptr
// 在SetUpTestSuite中通过dlopen加载驱动库后设置
// 在TearDownTestSuite中通过dlclose释放
void* Hi182xAbiTest::m_driver_handle = nullptr;- 代码框架编写完后,接下来按需编写测试用例,建议覆盖init/create/start/stop各阶段,并覆盖相应的异常场景
结语
GPU驱动适配当前并未涉及到复杂协议,未来在适配各家厂商GPU的时候可能仍会遇到各种各样的问题,欢迎驱动开发者们积极在社区沟通交流,共同建设南向设备驱动规范。