南向风扇电源驱动适配指南
南向驱动是基于硬件组件驱动程序框架,采用分层架构设计,支持多种设备类型和通信协议。本文档面向风扇和电源适配的设备驱动开发者,提供从设计到实现的完整开发流程,并基于现有代码实例分析,提供一篇快速适配的开发指南。
章节总览与分册选型见《部件驱动适配指南》。电源接口契约见
dmc/intf/bmc/dev/PowerSupply*(含Control/Metrics/Status/Oem/LogCollection/...子接口);电源对象路径与接口组合见dmc/path/bmc/dev/PowerSupply/PowerSupply.json;风扇部件对象见dmc/path/bmc/dev/Fan/{Fan,Fans,FanType}.json(Fan 对象可选bmc.dev.Fru/bmc.dev.PCB/bmc.dev.Temperature,CESA 表 58);总览:《Device Management Contract》。
1. 概述
1.1 什么是南向风扇电源驱动适配
南向风扇电源驱动适配是指在 BMC 系统中,为不同厂商的电源(PSU)设备提供统一的驱动程序接入方案,使 BMC 能够管理和监控电源设备。电源设备通常包含内置风扇,因此本文档同时涵盖电源和风扇的管理。
风扇与电源的关系:
- 内置风扇:电源设备通常内置 1-4个风扇,用于散热
- 统一管理:风扇作为电源的一部分,通过电源驱动统一管理
- PMBus 协议:通过 PMBus 协议的风扇相关命令监控和控制风扇
1.2 适配后可以实现什么功能
电源功能:
- 获取电源基本信息(厂商、型号、序列号、固件版本等)
- 监控电源状态(输入/输出电压、电流、功率、温度等)
- 管理电源模式(主用/备用、正常/休眠模式)
- 故障诊断和健康状态监控
- 电源复位操作
风扇功能:
- 监控风扇转速(RPM - 每分钟转数)
- 监控风扇状态(运行状态、故障状态)
- 控制风扇转速(设置目标转速或 PWM 占空比)
- 风扇告警和故障诊断
- 支持多风扇管理(1-4个风扇)
1.3 需要了解的关键概念
契约与代码分层
在开始之前先厘清四个易混淆的概念:dmc/、gen/、dev 命名空间、以及厂商驱动目录。它们之间的关系见 1.4。
| 概念 | 位置 | 说明 |
|---|---|---|
| 接口契约(dmc) | dmc/intf/bmc/dev/*.json | 定义 bmc.dev.* 接口的属性、方法、类型与描述(如 PowerSupply.json、PowerSupply/Metrics.json、Fan/Control.json),是南向接口契约的单一事实源 |
| 对象路径(dmc) | dmc/path/bmc/dev/** | 定义设备树对象挂载路径与接口组合,如 PowerSupply/PowerSupply.json、Fan/{Fan,Fans,FanType}.json |
| 接口基类(gen) | gen/include/device_tree/interface/ | 与契约对应的 C++ 接口基类,位于 dev::gen 命名空间,如 gen::PowerSupply、gen::PowerSupply_Control、gen::Fan;当前仓库没有自动重新生成的工具链,改动接口时需要人工同步维护 |
| 驱动实现(dev) | drivers/ 下各驱动目录 | 厂商/协议实现,位于 dev 命名空间,如 dev::OnePowerPmbus、dev::IPowerSupply |
| 设备描述(CSR) | 驱动目录内 csr/*.dds / *.sr | .dds 声明设备对象到 D-Bus 路径的映射与接口列表,.sr 是型号级实例配置(总线拓扑、静态属性、Compatible 等) |
设备对象
| 对象类型 | 说明 | 代码示例 |
|---|---|---|
| 电源主对象 | 代表整个电源设备,承载 OnePowerBase 公共逻辑并组合各接口 | dev::OnePowerPmbus(drivers/psu/pmbus/) |
| 电源公共基类 | 协议无关的电源对象基类:协议管理、属性采集、更新回调、升级、BMC 主从带外管理等 | dev::OnePowerBase(drivers/psu/common/) |
| 风扇框/风扇对象 | 风扇部件对象(非 PSU 内置风扇) | dev::Fans / dev::Fan / dev::FanType(drivers/fan/huawei/) |
接口
- gen 命名空间(
dev::gen):与 dmc 契约对应的接口基类,只声明属性与虚方法,用MC_INTERFACE("bmc.dev.Xxx")声明接口名;改动契约时需要人工同步修改 - dev 命名空间:驱动实现类。接口实现类通过
mc::engine::interface<Impl, gen::Base>双模板参数继承对应 gen 基类 - 接口组合:一个设备可实现多个接口(
PowerSupply、PowerSupply.Control、PowerSupply.Metrics、PowerSupply.Status、PowerSupply.Oem等),组合关系在.dds/.sr与MC_OBJECT中声明
协议库
libraries/protocol:power_supply_protocol_base电源协议抽象基类(ps_protocol_t)libraries/pmbus:PMBus 电源管理总线协议(业界标准),含厂商变种实现(如pmbus_FP1420、pmbus_qb900等)libraries/smbus、libraries/canbus等:底层通信协议封装;不同协议族(PMBus/CANbus/SMC)使用不同的驱动目录- 电源驱动按“通信协议”组织而非按厂商,公共部分下沉到
drivers/psu/common
1.4 驱动分层架构
- 分层架构:从契约定义到硬件交互的完整体系:契约层(
dmc)→ 基类层(gen,dev::gen接口基类,需随 dmc 人工同步)→ 实现层(dev,厂商/协议实现)→ 部署层(.so+csr配置由 devmon 加载并实例化到 D-Bus 设备树); - 设计理念:接口与实现分离。“改契约先改
dmc”(dmc/intf定义接口、dmc/path定义对象路径与接口组合),与契约对应的 C++ 接口基类维护在gen/(当前仓库无自动生成工具,需人工同步),具体部件驱动在drivers/下实现;驱动只对devmon导出统一的 ABI(register_device_driver),设备实例由型号级 CSR(.dds/.sr)描述并按需加载。
2. 前置准备
2.1 明确电源信息
必需信息
| 信息项 | 说明 | 示例 |
|---|---|---|
| I2C 地址 | 电源的 I2C 从设备地址 | 0x58 |
| 协议类型 | 支持的协议 | pmbus |
| 槽位信息 | 电源槽位编号 | 1, 2 |
协议信息
- 支持的 PMBus 命令集(标准命令、厂商扩展命令)
- 数据格式(LINEAR11、LINEAR16 等格式)
- 特殊功能(休眠模式、N+R 支持等)
硬件信息
- 额定功率
- 输入电压范围(AC/DC)
- 输出电压/电流规格
- 风扇数量和控制方式
2.2 驱动代码结构熟悉
Component Driver 采用分层架构设计,代码组织清晰,各个目录职责明确。理解这些目录的结构和作用是快速适配驱动的基础。
2.2.1 整体目录结构
component_drivers/
├── dmc/ # Device Management Contract(接口契约与对象路径,单一事实源)
│ ├── intf/bmc/dev/ # 接口契约 JSON(bmc.dev.* 的属性/方法)
│ │ ├── PowerSupply.json + PowerSupply/ # 电源接口契约及子接口
│ │ ├── Fan.json + Fan/、Fans.json、FanType.json # 风扇接口契约
│ │ ├── Cooling.json / Upgrade.json / SharedManagement.json / Fru.json ...
│ │ └── ...
│ └── path/bmc/dev/ # 对象路径与接口组合 JSON
│ ├── PowerSupply/PowerSupply.json
│ ├── Fan/{Fan,Fans,FanType}.json
│ └── ...
├── drivers/ # 设备驱动实现目录(按部件类型组织)
├── gen/ # 与 dmc 契约对应的接口/对象 C++ 基类(dev::gen 命名空间,需随 dmc 人工同步)
├── libraries/ # 协议库目录(通信协议封装)
├── include/devmon/ # 公共头文件(含 driver_abi.h,定义统一驱动 ABI)
├── tests/ # 测试代码目录
├── docs/ # 文档目录(分册规范与适配指南)
├── mds/ # 模型/服务描述文件
└── meson.build # 构建配置文件说明
dmc/ 是接口契约与对象路径的单一事实源:dmc/intf 定义“能提供什么属性/方法”,dmc/path 定义“对象挂在设备树哪个路径、组合哪些接口”。电源/风扇驱动需要实现的接口都以这两个目录中的 JSON 为准,细节见《Device Management Contract》。
2.2.2 drivers 目录 - 设备驱动实现
drivers/ 目录包含所有硬件设备的具体驱动实现,第一层按部件类型组织(如 psu、fan、bus、chip、pcie_nic_card 等),第二层电源按“通信协议”组织(公共实现统一放在 common/),风扇部件按厂商组织。
目录结构
drivers/
├── psu/ # 电源驱动(本指南重点)
│ ├── common/ # 电源公共实现(协议无关,多协议复用)
│ │ ├── one_power_base.h/.cpp # OnePowerBase 电源公共基类
│ │ ├── interface/ # 电源接口实现(继承 dev::gen 基类)
│ │ │ ├── i_power_supply.h/cpp # PowerSupply 接口实现
│ │ │ ├── power_supply/ # 电源子接口实现
│ │ │ │ ├── i_control.h/cpp # Control
│ │ │ │ ├── i_metrics.h/cpp # Metrics
│ │ │ │ ├── i_status.h/cpp # Status
│ │ │ │ ├── i_log_collection.h/cpp / i_oem.h/cpp / i_upgrade.h/cpp
│ │ │ │ ├── i_capacitor.h/cpp (+ Capacitor/i_capacitor_metric.h/cpp)
│ │ │ │ ├── i_dual_inputs.h/cpp / i_dual_channel.h/cpp
│ │ │ ├── shared_management.h/cpp # SharedManagement 接口实现
│ │ │ └── meson.build
│ │ └── meson.build
│ ├── csr/ # 电源设备描述配置(各协议型号的 .dds/.sr)
│ │ ├── 14191046_PSU_pmbus.dds/.sr
│ │ ├── 14191046_PSU_pmbus_qb900.dds/.sr
│ │ ├── 14191046_PSU_canbus.dds/.sr
│ │ ├── 14191046_PSU_canbus_tpsu.dds/.sr
│ │ └── 14191046_PSU_smc.dds/.sr
│ ├── pmbus/ # PMBus 协议电源驱动
│ │ ├── one_power.h/.cpp # OnePowerPmbus 电源主对象
│ │ ├── psu_abi.cpp # ABI 导出接口(register_device_driver)
│ │ └── meson.build
│ ├── pmbus_qb900/ # PMBus QB900 变种(OnePowerPmbusQb900)
│ ├── canbus/ # CANbus 协议电源(OnePowerCanbus)
│ ├── canbus_tpsu/ # CANbus-TPSU 协议电源
│ ├── smc/ # SMC 协议电源
│ └── meson.build
│
├── fan/ # 风扇部件驱动
│ └── huawei/ # 华为风扇框驱动(Fans/Fan/FanType 对象)
│ ├── smc/ # fan_object / fans_object / fantype_object + fan_abi.cpp
│ ├── interface/ # i_fan / i_fans / i_fantype / i_shared_management 及子接口
│ └── csr/ # 14100363_*.dds/.sr
│
├── bus/ # 总线驱动(i2c、i2c_mux、i2c_over_hisport、localbus ...)
├── chip/ # 芯片驱动(eeprom、lm75、pca9555、cpld_register ...)
├── pcie_nic_card/ # 网卡驱动(hisi、wangxun、mellanox ...)
├── pcie_gpu_card/ # GPU 卡驱动(nvidia、innosilicon、moorethreads ...)
├── internal/ # 内部实现(驱动框架基础类)
└── meson.build关键目录说明
| 目录 | 说明 | 用途 |
|---|---|---|
| psu/common/ | 电源公共实现 | OnePowerBase 提供协议无关的公共能力;interface/ 下是所有协议电源共享的接口实现(IPowerSupply_*) |
| psu/csr/ | 电源设备描述配置 | 集中存放各协议的 .dds/.sr 型号配置,定义设备接口、路径与实例化参数 |
| psu/pmbus/ 等协议目录 | 各协议电源驱动 | 每个协议目录一个电源主对象(如 OnePowerPmbus)+ psu_abi.cpp,通过协议工厂表把 协议名 映射到 libraries/pmbus 中的具体实现类 |
| fan/huawei/ | 风扇部件驱动 | 提供 Fans/Fan/FanType 三类对象(风扇框/风扇/风扇类型)及其接口实现、CSR 配置 |
| internal/ | 内部基础框架 | 提供驱动框架的基础类 |
2.2.3 gen 目录 - 接口/对象 C++ 基类
gen/ 目录存放与 dmc/ 契约对应的接口/对象 C++ 基类(dev::gen 命名空间),驱动代码继承这些基类实现功能。代码随仓库提交,meson 只负责编译。
注意
当前仓库内没有随构建执行的代码生成/重新生成工具链,gen/ 下的代码需要人工维护:新增/修改接口属性时,除同步修改 dmc/ 契约 JSON 外,还需人工同步修改 gen/include/device_tree/interface/ 下的头文件(及其 gen/src/ 实现),否则接口实现类将无法通过编译或属性缺失。仓库中 dmc/CSR 与 gen/驱动实现存在的不一致(如 .dds/.sr 仍引用 bmc.dev.Cooling、接口实现类与 MC_OBJECT 声明不齐等)多数与未做到同步有关。
目录结构
gen/
├── include/ # 头文件目录
│ ├── device_tree/
│ │ ├── base.h # 基础类定义
│ │ ├── runtime_registrar.h # 运行时注册
│ │ ├── interface/ # 接口基类定义(namespace dev::gen)
│ │ │ ├── PowerSupply.h # 电源接口基类 gen::PowerSupply
│ │ │ ├── PowerSupply/ # 电源子接口基类
│ │ │ │ ├── Control.h # gen::PowerSupply_Control
│ │ │ │ ├── Metrics.h # gen::PowerSupply_Metrics
│ │ │ │ ├── Status.h # gen::PowerSupply_Status
│ │ │ │ ├── Oem.h / LogCollection.h / Capacitor.h / DualInputs.h / DualChannel.h ...
│ │ │ ├── Cooling.h / Upgrade.h / SharedManagement.h
│ │ │ ├── Fan.h + Fan/ # gen::Fan 及子接口(Control/Status/Metrics/PWMChannel/Oem/LogCollection)
│ │ │ ├── Fans.h + Fans/、FanType.h
│ │ │ └── ...
│ │ └── object/ # 对象组合定义(部分部件类型)
│ └── resource_tree/ # 资源树(总线/芯片/扫描器等)接口基类
├── src/ # 源文件目录(与 include 结构对应)
└── meson.build # 编成 libdevice_tree.so / libresource_tree.so关键概念
| 概念 | 说明 | 示例 |
|---|---|---|
| 接口基类 | 定义接口的属性与虚方法,用 MC_INTERFACE("bmc.dev.Xxx") 声明接口名 | gen::PowerSupply(即 dev::gen::PowerSupply)定义了 Model、Manufacturer、SerialNumber 等属性 |
| 接口实现类 | 用双模板参数继承接口基类,实现具体逻辑 | class IPowerSupply : public mc::engine::interface<IPowerSupply, gen::PowerSupply> |
| 命名空间 | 接口基类位于 dev::gen,驱动实现位于 dev | 驱动代码中写 gen::PowerSupply 即 dev::gen::PowerSupply |
| 同步维护 | 以 dmc/ 契约为准,gen/ 基类需人工同步 | 在 dmc/intf/bmc/dev/PowerSupply.json 新增属性后,同步在 gen/include/device_tree/interface/PowerSupply.h 增加对应 property<>(当前仓库无自动生成工具) |
| 对象基类 | 部分部件类型提供对象基类(mc::app::object 派生) | gen/include/device_tree/object/... |
使用方式
接口基类的风格如下(以电源接口为例,摘自仓库实际代码):
// gen/include/device_tree/interface/PowerSupply.h(namespace dev::gen)
class MC_API PowerSupply : public mc::engine::interface<PowerSupply> {
public:
MC_INTERFACE("bmc.dev.PowerSupply")
property<std::string> FirmwareVersion; // 电源固件版本
property<std::string> Manufacturer; // 厂商
property<std::string> Model; // 电源型号
property<std::string> SerialNumber; // 序列号
property<uint32_t> SlotNumber; // 槽位号
// ...
};驱动中实现接口时,不要直接继承 gen 基类,而是用双模板参数形式,并在 .cpp 中通过单参数 MC_REFLECT 完成反射注册:
// 厂商接口实现类(dev 命名空间)
class IPowerSupply : public mc::engine::interface<IPowerSupply, gen::PowerSupply> {
public:
void init(OnePowerBase* base_ptr);
void start(); // 内部注册更新回调
void stop();
bool update_power_supply(std::string_view property, const mc::variant& info);
private:
OnePowerBase* m_base;
};
// .cpp 文件末尾:反射注册(单参数,无需列出 gen 基类)
MC_REFLECT(dev::IPowerSupply)2.2.4 libraries 目录 - 通信协议库
libraries/ 目录包含各种通信协议的封装实现,为驱动提供标准化的协议接口。除电源/风扇相关的 protocol/pmbus/smbus 外,还包含网卡/硬盘等部件使用的 mctp、pldm、ncsi、nvme 等协议库。
目录结构
libraries/
├── protocol/ # 电源协议抽象基类
│ ├── power_supply_protocol.h/.cpp # power_supply_protocol_base(ps_protocol_t)
│ ├── protocol.h/.cpp # 协议公共定义
│ └── meson.build
│
├── pmbus/ # PMBus 协议库(含厂商/型号变种)
│ ├── pmbus.h/.cpp # PMBus 协议实现(继承 power_supply_protocol_base)
│ ├── pmbus_FP1420.h/.cpp / pmbus_qb900.h/.cpp / pmbus_eb1000_1.h/.cpp ...
│ ├── powerconverter_pmbus.h/.cpp
│ └── meson.build
│
├── smbus/ # SMBus/I2C 协议库
│ ├── smbus.h/.cpp、std_smbus.h/.cpp、smbus_postbox.h/.cpp
│ └── meson.build
│
├── canbus/ # CANbus 协议(电源/机框等使用)
├── mctp/ ncsi/ ncsi_over_mctp/ pldm/ pldm_over_mctp/ nvme/ # 带外管理/板卡协议
├── ipmb/ lldp/ imu/ ssu_oob/ # 其他协议
└── meson.build协议库说明
| 协议库 | 说明 | 传输方式 | 应用场景 |
|---|---|---|---|
| protocol | 电源协议抽象基类 power_supply_protocol_base | - | 定义电源协议的通用接口(读取/控制/状态/升级),所有电源协议类的基类 |
| pmbus | PMBus 电源管理总线协议 | I2C/SMBus | 电源管理的主要协议;厂商变种(pmbus_FP1420、pmbus_qb900、pmbus_eb1000_1 等)以子类形式扩展 |
| smbus | 系统管理总线协议 | I2C | 底层总线通信,PMBus 的物理承载 |
| canbus | CANbus 协议(含 TPSU 变种) | CAN | 采用 CANbus 的电源/机框场景 |
协议分层关系(以 PMBus 电源为例)
应用层: 电源驱动代码(OnePowerBase → OnePowerPmbus)
↓
协议层: libraries/protocol(power_supply_protocol_base)
↓
libraries/pmbus(pmbus 及厂商变种)
↓
传输层: libraries/smbus(smbus)
↓
物理层: I2C 硬件使用示例
协议库与驱动之间通过“协议工厂表”解耦:drivers/psu/pmbus/one_power.cpp 中把协议名字符串映射到具体的协议实现类,运行时由 OnePowerBase::init_protocol() 按名创建:
// drivers/psu/pmbus/one_power.cpp:PMBus 协议工厂表(节选)
#define DEFINE_PS_PROTOCOL(name, ClassType) \
{name, [](mc::dict& data) -> std::unique_ptr<ps_protocol_t> { \
return std::make_unique<ClassType>(data); \
}}
static const ps_protocol_map_t pmbus_protocol_map = {
DEFINE_PS_PROTOCOL("pmbus", pmbus),
DEFINE_PS_PROTOCOL("pmbus_qb900", pmbus_qb900),
DEFINE_PS_PROTOCOL("pmbus_FP1420", pmbus_FP1420),
DEFINE_PS_PROTOCOL("psu_pmbus", pmbus_FP1420),
// ...
};
// 读取属性(接口方法大多返回 std::optional,便于判空/重试)
auto voltage = m_ps_protocol->get_input_voltage(); // 输入电压(V)
auto current = m_ps_protocol->get_output_current_amps(); // 输出电流(A)
auto power = m_ps_protocol->get_input_power_watts(); // 输入功率(W)
auto temp = m_ps_protocol->get_env_temperature_celsius(); // 环境温度(℃)
auto rpm = m_ps_protocol->get_psu_fan_speed_rpm(); // 风扇转速(RPM)
// 控制(如设置风扇 PWM 占空比,0-100)
m_ps_protocol->set_fan_speed_percent(30);2.2.5 目录之间的关系
工作流程
- 契约定义:在
dmc/中维护接口(intf)与对象路径/接口组合(path) - 基类维护:
gen/下与契约对应的 C++ 接口/对象基类(dev::gen)需人工同步维护(当前仓库无自动生成工具链) - 接口实现:在
drivers/*/common/interface等位置用mc::engine::interface<Impl, gen::Base>实现接口 - 驱动组装:在协议驱动目录(如
drivers/psu/pmbus)组合接口、继承公共基类(OnePowerBase),创建完整驱动对象并导出 ABI - 协议调用:驱动通过
libraries/中的协议库与硬件通信 - 部署加载:配套
csr下的.dds/.sr,由 devmon 在设备树中实例化设备对象
2.3 驱动协议架构
以 PMBus 电源为例,驱动、协议库与物理总线的分层关系如下(其他协议族如 CANbus、SMC 结构相同,只是中间协议库不同):
2.4 底层协议确定
仓库中电源驱动按协议族组织:drivers/psu/ 下有 pmbus、pmbus_qb900、canbus、canbus_tpsu、smc 五个协议目录,公共能力(OnePowerBase 与 IPowerSupply_* 接口实现)统一在 common/。
协议类型
| 协议 | 驱动目录 | 说明 | 适用场景 |
|---|---|---|---|
| PMBus | drivers/psu/pmbus | 业界标准电源管理协议(SMBus/I2C),同一驱动内通过协议工厂表支持 pmbus、pmbus_qb900、pmbus_FP1420、pmbus_eb1000_1 等具体实现 | 标准 PMBus 电源 |
| CANbus / CANbus-TPSU | drivers/psu/canbus、drivers/psu/canbus_tpsu | CANbus 通信的电源 | 使用 CANbus 的电源 |
| SMC | drivers/psu/smc | 通过 SMC 通道访问电源 | 带 SMC 通道的整机电源 |
协议实现关系
| 协议库(libraries) | 关系 | 说明 |
|---|---|---|
protocol/power_supply_protocol.h | 抽象基类 | 定义 power_supply_protocol_base,统一读取/控制/状态/升级等虚方法 |
pmbus/pmbus.h | 具体实现 | pmbus 实现标准 PMBus 命令 |
pmbus/pmbus_*.h | 厂商/型号变种 | 在标准 pmbus 之上扩展厂商私有命令(如 pmbus_FP1420、pmbus_qb900、pmbus_PDC3KD5412_LC 等),由 drivers/psu/pmbus/one_power.cpp 的协议工厂表注册 |
2.5 电源与风扇支持的能力
本节以 drivers/psu/pmbus 的 OnePowerPmbus(PMBus 电源主对象)为例,说明当前驱动已具备的能力,帮助开发者明确适配时需要实现/复用哪些接口。
说明
电源主对象的能力由两部分决定:接口实现类(drivers/psu/common/interface,各协议共享)和对象上实际组合的接口列表(MC_OBJECT 宏 + 型号 .sr/.dds 声明)。可按型号裁剪,也可随 dmc 契约演进补充新接口。
电源级能力
OnePowerPmbus 通过 MC_OBJECT 组合了以下 8 个接口(接口实现均在 drivers/psu/common/interface,各协议电源复用):
| 能力类别 | 接口名称 | 说明 | 可获取/可设置的信息 |
|---|---|---|---|
| 电源基本信息 | PowerSupply | 电源设备基础信息 | 厂商、型号、序列号、部件号、固件版本、硬件版本、生产日期、槽位、协议类型、供电类型、是否参与整机功率计算等 |
| 指标接口 | PowerSupply.Metrics | 电源电气与温度指标 | 输入/输出电压、电流、功率、输入频率、额定功率/额定电流、环境/入口/内部/原边芯片/副边芯片温度、风扇转速 FanSpeedRPM、累计运行时长等 |
| 控制接口 | PowerSupply.Control | 电源控制功能 | SetFanMinPWM(风扇最小 PWM)、SetWorkMode(工作模式)、SetSleepMode(休眠)、Reset(复位)、SetAlarmLatch/SetPowerLatch(锁存)、SetOutputPowerLimitWatts(输出功率限值) |
| 状态接口 | PowerSupply.Status | 电源状态信息 | 在位 Presence、Vout/Iout/Temperature/Input/CML/Mfr_Specific/Other 告警状态、风扇状态 Fans_1_2/Fans_3_4、通信状态 CommunicationStatus、设备模式、输入/输出状态 |
| 日志接口 | PowerSupply.LogCollection | 电源故障日志 | 黑匣子信息导出 |
| OEM 接口 | PowerSupply.Oem | 厂商私有能力 | 私有寄存器读写(get_register_value)等 |
| 升级接口 | Upgrade(bmc.dev.Upgrade) | 固件升级/激活 | 升级触发、进度/状态上报、激活(PMBus 升级接口 pmbus_upgrade) |
| 共享管理 | SharedManagement(bmc.dev.SharedManagement) | 多 BMC/共享电源场景 | 电源控制权(ControlOwner)、BMC 主从(Active/Standby)下带外管理启停 |
可选增强接口(按电源型号能力在 .sr/.dds 与对象上按需启用):PowerSupply.Capacitor/PowerSupply.Capacitor.Metrics(超级电容)、PowerSupply.DualInputs(双路输入)、PowerSupply.DualChannel(双通道/轮询)。对应实现同样位于 drivers/psu/common/interface/power_supply/。
风扇管理能力
电源内置的风扇通过以下方式进行管理:
风扇状态监控
通过PowerSupply.Status接口获取风扇状态:
| 状态属性 | 说明 | 可能的值 |
|---|---|---|
| Fans_1_2 | 风扇 1 和风扇 2 的状态字节 | PMBus STATUS_FANS_1_2(0x81)寄存器值 |
| Fans_3_4 | 风扇 3 和风扇 4 的状态字节 | PMBus STATUS_FANS_3_4(0x82)寄存器值 |
注意
状态字节为寄存器原始值。驱动解析 health_event 时主要关注高位置位(如 bit7 风扇故障 fan_fault、bit6 风扇转速超控等),其余位的具体含义需以电源厂商的 PMBus 规格书为准,不同型号存在差异。
风扇转速监控
电源内置风扇转速通过协议库读取(对应 Metrics 接口的 FanSpeedRPM 属性):
| 监控项 | 协议接口(libraries/pmbus) | 说明 | 单位 |
|---|---|---|---|
| 风扇转速 | get_psu_fan_speed_rpm() | 读取电源当前风扇转速(返回 std::optional) | RPM |
| 风扇占空比 | get_fan_speed_percent() | 读取风扇 PWM 占空比(0-100) | % |
风扇转速控制
通过 PowerSupply.Control 接口及协议库控制风扇:
| 控制项 | 接口/方法 | 说明 |
|---|---|---|
| 风扇最小 PWM | IPowerSupply_Control::SetFanMinPWM()(属性 Control.FanMinPWM) | 设置风扇最小 PWM 占空比;取值范围/含义以 dmc 契约与厂商规格书为准 |
| 直接调速 | 协议库 set_fan_speed_percent() | 直接按占空比控制风扇 |
| 自动调速 | 电源固件自动控制 | 固件根据温度自动调节风扇转速 |
说明
- 电源内置风扇通常由电源固件按温度自动调速,BMC 侧一般只设置下限(最小 PWM);
- 风扇部件(风扇框/风扇模块)的调速不在电源驱动内,而是由
drivers/fan/huawei的Fans/Fan对象提供(PWM 通道、Fan.Control等接口),见 2.2.2。
控制示例:
// 通过 Control 接口设置风扇最小 PWM(示例:40%)
m_power_supply_control.SetFanMinPWM(40);
// 通过协议直接控制风扇占空比
m_ps_protocol->set_fan_speed_percent(40);风扇管理流程
能力获取方式
以 OnePowerPmbus(PMBus)为例,数据获取分为“上电读取”与“周期采集”两个阶段:
| 阶段 | 获取方式 | 内容 | 说明 |
|---|---|---|---|
| 上电/带外启动 | 启动时带重试读取 | 厂商、型号、序列号、部件号、生产日期、固件版本、电源类型、输入电压类型、硬件版本、额定功率,以及输入/输出电压初始值 | 保证设备树尽早出现非零的关键值 |
| 周期采集(快) | 500ms 定时器 | 输入功率、健康事件(health_event:vout/iout/temperature/input/fans_1_2/cml/mfr_specific/other 故障位)、通信状态 | read_frequency_property() |
| 周期采集(慢) | 每约 3 秒 | 输出/输入电流、电压、功率、环境/原边芯片/副边芯片温度、风扇转速 | 在快采中分帧读取 |
| 周期采集(慢) | 每约 5 秒 | 控制数据(休眠/工作模式、N+R 支持)、设备模式、额定功率、电源类型 | read_slow_property() |
说明
- 实际周期与分帧策略见
OnePowerPmbus::read_frequency_property()/read_slow_property()(drivers/psu/pmbus/one_power.cpp);CANbus/SMC 协议驱动的采集策略可能不同; - 产品信息(型号、序列号等)来自电源 FRU/寄存器数据,通常只在启动与低速周期读取,避免占用总线;
- 监测任务统一由
OnePowerBase的start_monitor()/restart_monitor()管理,500ms 是其基类默认值,子类可覆写。
3. 适配流程详解
场景假设:仓库需要支持一款新电源“XYZ”(PMBus 协议族),或使用全新通信协议的新电源。下面先判断适配形态,再按步骤落地。文中示例以
drivers/psu/pmbus的OnePowerPmbus为蓝本。
3.1 确定适配形态与目录结构
与早期“按厂商在 drivers/psu/huawei 下新建驱动”不同,当前电源驱动已按协议组织,且公共能力全部抽到 drivers/psu/common。因此多数适配不需要新建驱动目录:
| 适配形态 | 场景 | 需要做的事 |
|---|---|---|
| 新增电源型号(已有协议) | 新电源使用 PMBus/CANbus/SMC 等仓库已覆盖的协议 | 在 drivers/psu/csr/ 增加该型号的 .dds/.sr;若命令集差异较大,在 libraries/pmbus 增加厂商变种协议子类并注册到 drivers/psu/pmbus/one_power.cpp 的协议工厂表 |
| 新增协议/全新通道 | 电源使用仓库未覆盖的通信协议 | 参考 drivers/psu/pmbus 新建协议驱动目录 + 配套协议库与 CSR |
仅在“新增协议驱动”时才需要创建如下目录(目录名/类名与 pmbus 对齐,以 xyz 为例):
psu/
├── common/ # 公共实现:直接复用,无需复制修改
│ ├── one_power_base.h/.cpp # OnePowerBase
│ └── interface/ # IPowerSupply_* 接口实现(所有协议电源共享)
├── csr/ # 型号配置:增加 14191046_PSU_xyz.dds / .sr
├── xyz/ # 新协议驱动目录(对齐 pmbus 结构)
│ ├── one_power.h # OnePowerXyz 电源主对象头文件
│ ├── one_power.cpp # 主对象实现(协议工厂表、start/stop、数据采集)
│ ├── psu_abi.cpp # ABI 导出(register_device_driver)
│ └── meson.build
└── meson.build # 增加 subdir('xyz')3.2 配置型号描述(CSR:.dds + .sr)
型号级 CSR 成对存放在 drivers/psu/csr/:
.dds(Device Description Standard,Schema: "dds-v1"):声明设备对象到 D-Bus 路径的映射、以及该对象挂载的接口列表;.sr(Self-Description Record):型号级实例配置,描述总线/芯片拓扑(ManagementTopology)、Unit(Type/Compatible)与各接口的静态属性,供 devmon 建树和驱动加载时使用。
文件命名规则
格式:[分类代码]_PSU_<协议>.dds 与 [分类代码]_PSU_<协议>.sr
示例:14191046_PSU_pmbus.dds / 14191046_PSU_pmbus.sr
说明:14191046 是电源的分类代码;<协议> 与驱动/协议库对应(pmbus、pmbus_qb900、canbus、canbus_tpsu、smc)DDS 文件内容(摘自 drivers/psu/csr/14191046_PSU_pmbus.dds)
{
"Schema": "dds-v1",
"Type": "Component",
"DeviceCategory": "OnePower",
"ID": "N/A",
"Objects": {
"OnePower": {
"Path": "/bmc/dev/Chassis/:SystemId/PowerSubsystem/PowerSupplies/:Id",
"Interfaces": [
"bmc.dev.PowerSupply",
"bmc.dev.Cooling",
"bmc.dev.PowerSupply.Control",
"bmc.dev.PowerSupply.Metrics",
"bmc.dev.PowerSupply.Status",
"bmc.dev.PowerSupply.LogCollection",
"bmc.dev.PowerSupply.Oem"
]
}
}
}关键字段说明
| 字段 | 说明 | 注意事项 |
|---|---|---|
DeviceCategory | 设备类别 | 电源为 "OnePower"(风扇部件为 "Fan" 等,见 2.2.2) |
Objects | 对象定义 | 键名(如 OnePower)与主对象 MC_OBJECT 的第 2 个参数(对象类型名)对应 |
Path | 对象 D-Bus 路径模板 | :SystemId、:Id 等为动态参数;对象路径契约以 dmc/path/bmc/dev/PowerSupply/PowerSupply.json 为准 |
Interfaces | 接口列表 | 应与代码 MC_OBJECT 声明、型号 .sr 中实际配置的接口保持一致 |
.sr 文件要点(摘自 14191046_PSU_pmbus.sr)
{
"FormatVersion": "5.00",
"DataVersion": "5.00",
"Unit": { "Type": "PSU", "Name": "OnePower_0", "Compatible": ["psu_pmbus"] },
"ManagementTopology": {
"Anchor": { "Buses": ["I2c_2", "I2c_3"] },
"I2c_2": { "Chips": ["Smc_ExpBoardSMC"] },
"I2c_3": { "Chips": ["Eeprom_PsuChip"] }
},
"Objects": {
"OnePower_0": {
"PhysicalInterface": "pmbus",
"RefChip": "#/Eeprom_PsuChip",
"bmc.dev.PowerSupply": {
"SlotNumber": "${Slot}",
"Protocol": "pmbus",
"...": "..."
},
"bmc.dev.PowerSupply.Metrics": { "...": "..." },
"bmc.dev.PowerSupply.Status": { "...": "..." }
}
}
}配置要点:
Unit匹配关系要对齐:.sr的Unit.Type(电源为PSU)与驱动 ABI 的device_name对应;Compatible与驱动加载/协议工厂表使用的名称(如psu_pmbus)对应,保证 devmon 能匹配到正确的.so;RefChip与拓扑要完整:ManagementTopology声明电源所在总线与关联芯片(如 EEPROM),Objects.<对象>.RefChip引用该芯片,主对象init()据此解析出硬件访问通道;- 接口静态属性写在对应接口段:
SlotNumber、Protocol等由.sr静态下发(经from_variant加载),运行期通过接口属性读取; - 风扇调速需求段(可选):需要“风扇随温度调速”的场景可在
.sr中追加冷却/风扇需求(如CoolingRequirement_*),由上层协同使用。
3.3 创建电源主对象(协议驱动)
电源主对象是每个协议驱动的核心:它组合各接口实现类,并承载协议相关的初始化和数据采集逻辑。公共逻辑(协议创建、属性读取分发、升级、BMC 主从带外管理、监控定时器管理等)已在
OnePowerBase中实现,协议驱动只需完成少量协议相关的工作,参考对象:drivers/psu/pmbus/one_power.h(OnePowerPmbus)。
代码示例:one_power.h(XYZ 协议电源主对象)
#ifndef ONE_POWER_XYZ_H
#define ONE_POWER_XYZ_H
#include <mc/app/object.h>
#include <mc/engine.h>
#include <mc/timer.h>
#include <memory>
#include "../common/interface/i_power_supply.h"
#include "../common/interface/power_supply/i_control.h"
#include "../common/interface/power_supply/i_log_collection.h"
#include "../common/interface/power_supply/i_metrics.h"
#include "../common/interface/power_supply/i_oem.h"
#include "../common/interface/power_supply/i_status.h"
#include "../common/interface/power_supply/i_upgrade.h"
#include "../common/interface/shared_management.h"
#include "../common/one_power_base.h"
namespace dev {
class OnePowerXyz : public mc::app::object<OnePowerXyz>, public OnePowerBase {
public:
// MC_OBJECT 参数:类名 / 对象类型名 / 路径模板 / 接口列表
MC_OBJECT(
OnePowerXyz, "OnePower", "/bmc/dev/Chassis/1/PowerSubsystem/PowerSupplies/${SlotNumber}",
(IPowerSupply)(IPowerSupply_Control)(IPowerSupply_Metrics)(IPowerSupply_Status)
(IPowerSupply_LogCollection)(IPowerSupply_Oem)(IPowerSupply_Upgrade)(IPowerSupply_SharedManagement))
OnePowerXyz() = default;
~OnePowerXyz() = default;
bool init(mc::mutable_dict& csr_object, const mc::dict& connector);
bool start();
bool stop();
void from_variant(const mc::dict& d, dev::OnePowerXyz& obj);
// 实现 OnePowerBase 纯虚接口
const ps_protocol_map_t& get_protocol_map() const override; // 协议工厂表(协议名 -> 协议对象)
IPowerSupply& get_power_supply() override; // 返回 m_power_supply
const IPowerSupply& get_power_supply() const override;
IPowerSupply_SharedManagement& get_shared_mgmt() override;
const IPowerSupply_SharedManagement& get_shared_mgmt() const override;
// 接口成员变量(与 MC_OBJECT / MC_REFLECT 一一对应)
IPowerSupply m_power_supply;
IPowerSupply_Control m_power_supply_control;
IPowerSupply_Metrics m_power_supply_metrics;
IPowerSupply_Status m_power_supply_status;
IPowerSupply_LogCollection m_power_supply_log_collection;
IPowerSupply_Oem m_power_supply_oem;
IPowerSupply_Upgrade m_power_supply_upgrade;
IPowerSupply_SharedManagement m_shared_mgmt;
private:
// 协议名/物理通道(来自 .sr 静态属性)
mc::engine::property<std::string> RefChip{"RefChip"};
mc::engine::property<std::string> PhysicalInterface{"PhysicalInterface"};
std::string get_active_interface() const; // 返回 PhysicalInterface
// 协议相关的数据采集(按需覆写 start_monitor 等)
void read_frequency_property();
void read_slow_property();
bool start_oob_management() override; // 启动带外数据采集
};
} // namespace dev
#endif // ONE_POWER_XYZ_H对应的 MC_REFLECT 注册(one_power.cpp 末尾),把接口成员映射到 bmc.dev.* 接口名:
MC_REFLECT(dev::OnePowerXyz,
((RefChip, "RefChip"))((PhysicalInterface, "PhysicalInterface"))
((m_power_supply, "bmc.dev.PowerSupply"))
((m_power_supply_control, "bmc.dev.PowerSupply.Control"))
((m_power_supply_metrics, "bmc.dev.PowerSupply.Metrics"))
((m_power_supply_status, "bmc.dev.PowerSupply.Status"))
((m_power_supply_log_collection, "bmc.dev.PowerSupply.LogCollection"))
((m_power_supply_oem, "bmc.dev.PowerSupply.Oem"))
((m_power_supply_upgrade, "bmc.dev.Upgrade"))
((m_shared_mgmt, "bmc.dev.SharedManagement")))说明
- 若协议型号只支持部分接口,可在
MC_OBJECT/MC_REFLECT/.dds/.sr中同步裁剪; - 如需温度数值、告警状态等属性,无需新增
Cooling等“额外接口”:温度数值在PowerSupply.Metrics(EnvTemperatureCelsius等),告警/风扇状态位在PowerSupply.Status,见 2.5。
关键说明
1. MC_OBJECT 宏的参数:
- 第 1 个:类名(如
OnePowerXyz) - 第 2 个:对象类型名(电源为
"OnePower",与.dds的Objects键名/DeviceCategory对应) - 第 3 个:对象路径模板(
${SlotNumber}为属性占位,实际槽位号由.sr下发) - 第 4 个:对象组合的接口列表(按型号能力增删)
2. 接口选择原则:
| 接口 | 必需性 | 说明 |
|---|---|---|
PowerSupply | 必需 | 电源基础信息 |
PowerSupply.Control | 常见 | 工作/休眠模式、复位、风扇最小 PWM 等控制 |
PowerSupply.Metrics | 常见 | 电压/电流/功率/温度/风扇转速等指标 |
PowerSupply.Status | 常见 | 在位与各类告警/风扇状态 |
PowerSupply.LogCollection/Oem/Upgrade/SharedManagement | 可选 | 日志、私有能力、升级、共享管理 |
PowerSupply.Capacitor/DualInputs/DualChannel | 按型号 | 超级电容、双路输入、双通道电源 |
3.4 实现电源主对象(one_power.cpp)
电源主对象的实现重点有三处:协议工厂表(get_protocol_map)、生命周期(init/start/stop)、数据采集(start_oob_management 与周期读取)。其余公共逻辑(属性更新分发、监控定时器管理、升级、BMC 主从等)复用 OnePowerBase,参考实现:drivers/psu/pmbus/one_power.cpp。
协议工厂表与生命周期
// one_power.cpp(节选,示意)
#include "one_power.h"
#include <mc/engine/property/ref_object.h>
#include <mc/engine/service.h>
#include <mc/log.h>
#include <mc/timer.h>
#include <xyz/pmbus_xyz.h> // 新协议实现(libraries/xyz,若有)
namespace dev {
// 协议工厂表:协议名字符串 -> 具体协议对象(构造参数为 mc::dict&,包含芯片/总线信息)
#define DEFINE_PS_PROTOCOL(name, ClassType) \
{name, [](mc::dict& data) -> std::unique_ptr<ps_protocol_t> { \
return std::make_unique<ClassType>(data); \
}}
static const ps_protocol_map_t xyz_protocol_map = {
DEFINE_PS_PROTOCOL("pmbus_xyz", pmbus_xyz),
// ...支持多个协议/厂商变种时在此追加
};
const ps_protocol_map_t& OnePowerXyz::get_protocol_map() const
{
return xyz_protocol_map;
}
// init:from_variant 装载 .sr 静态属性,并解析 RefChip 引用(协议访问硬件的通道)
bool OnePowerXyz::init(mc::mutable_dict& csr_object, const mc::dict& connector)
{
m_self = this;
try {
OnePowerXyz::from_variant(csr_object, *this); // 装载 SlotNumber/Protocol/静态属性
m_ref_obj = RefChip.get_value().as<mc::engine::ref_object*>();
if (m_ref_obj == nullptr) {
elog("OnePowerXyz init FAILED: RefChip ref_object is NULL");
return false;
}
} catch (const std::exception& e) {
elog("OnePowerXyz init FAILED: ${exception}", ("exception", e.what()));
return false;
}
return true;
}
void OnePowerXyz::from_variant(const mc::dict& d, dev::OnePowerXyz& obj)
{
mc::from_variant(d, obj);
}
// start:创建协议 -> 初始化/启动接口 -> 评估并启动带外采集
bool OnePowerXyz::start()
{
// 1. 按 .sr 的 PhysicalInterface(协议名)创建协议对象,失败不阻断上线
init_protocol(get_active_interface(), m_power_supply.SlotNumber.get_value().as<uint8_t>());
// 2. 接口对象 init(base)/start():start 时向 base 注册“属性名->属性值”更新回调
m_power_supply.init(this);
m_power_supply_control.init(this);
m_power_supply_metrics.init(this);
m_power_supply_status.init(this);
m_power_supply_log_collection.init(this);
m_power_supply_oem.init(this);
m_power_supply_upgrade.init(this);
// ...各接口 start() 统一在协议就绪后调用(参考 pmbus 实现)
// 3. 主从/共享电源场景:按 ControlOwner 建立 BMC 主从监听,再评估是否启动采集
setup_oob_management();
evaluate_oob_management();
return true;
}
bool OnePowerXyz::stop()
{
teardown_oob_management(); // 断开主从监听、停止带外采集
m_power_supply.stop();
m_power_supply_control.stop();
m_power_supply_metrics.stop();
m_power_supply_status.stop();
m_power_supply_oem.stop();
m_power_supply_upgrade.stop();
return true;
}
std::string OnePowerXyz::get_active_interface() const
{
return PhysicalInterface.get_value().as<std::string>(); // 例:"pmbus_xyz"
}
} // namespace dev说明
init_protocol()由OnePowerBase实现:按协议名查get_protocol_map()创建协议对象,并用RefChip关联的芯片完成总线访问准备;reinit_protocol()可在运行期按“厂商+型号”切换具体协议实现;setup_oob_management()/evaluate_oob_management()由OnePowerBase提供:无主从(ControlOwner=0)时协议就绪即开始采集;有主从(ControlOwner=1)时仅主 BMC 的 Active 态采集;- 协议就绪后的首次采集入口是虚函数
start_oob_management()(协议驱动实现),见下。
数据采集(start_oob_management 与周期读取)
协议就绪后的带外数据采集入口为 start_oob_management()(协议驱动实现),示意如下:
// 启动带外数据采集:首次带重试读取产品信息 + 启动周期采集(示意,参考 OnePowerPmbus)
bool OnePowerXyz::start_oob_management()
{
if (!is_protocol_ready()) {
ilog("OnePowerXyz protocol not ready, skip data collection");
return false;
}
// 1. 启动时带重试读取产品信息,保证设备树尽早出现非零关键值
get_dynamic_data("firmware_version", 3);
get_dynamic_data("manufacturer", 3);
get_dynamic_data("model", 3);
get_dynamic_data("serial_number", 3);
get_dynamic_data("rated_power_watts", 3);
// ...
get_dynamic_data("input_voltage", 5);
get_dynamic_data("output_voltage", 5);
// 2. 启动周期采集(500ms 定时器,由 OnePowerBase::start_monitor 管理)
start_monitor(mc::make_shared<mc::timer>(this));
reinit_protocol();
return true;
}
// 周期回调(示意):500ms 高频 + 分帧读取,参考 OnePowerPmbus::read_frequency_property()
void OnePowerXyz::read_frequency_property()
{
get_dynamic_data("input_power_watts", 1);
// 健康事件:告警状态位直接分发给 Status 接口
auto [ev, health_failed] = m_ps_protocol->get_health_event();
if (!health_failed) {
update_property("vout", ev.output_voltage_fault);
update_property("iout", ev.output_current_fault);
update_property("temperature", ev.temper_fault);
update_property("input", ev.input_voltage_fault);
update_property("fans_1_2", ev.fan_fault);
update_property("cml", ev.cml_fault);
update_property("mfr_specific", ev.mfr_specific_status);
update_property("other", ev.other_fault);
}
// 每 6 次(约 3s)读取一组动态指标:电压/电流/温度/风扇转速等
static uint8_t cnt = 0;
if (++cnt >= 6) {
get_dynamic_data("output_power_watts", 1);
get_dynamic_data("input_current_amps", 1);
get_dynamic_data("output_current_amps", 1);
get_dynamic_data("env_temperature_celsius", 1);
get_dynamic_data("primary_chip_temperature_celsius", 1);
get_dynamic_data("psu_fan_speed_rpm", 1);
cnt = 0;
}
}
// 慢速属性:每约 5s 读取控制数据/设备模式等(示意)
void OnePowerXyz::read_slow_property()
{
auto control = m_ps_protocol->get_ps_control_data();
if (control.has_value()) {
m_power_supply_control.SleepMode = control->sleep_mode;
m_power_supply_control.WorkMode = control->work_mode;
update_property("normal_and_redundancy_supported",
mc::variant(control->normal_and_redundancy_supported ? 1 : 0));
}
get_dynamic_data("equipment_mode", 1);
get_dynamic_data("rated_power_watts", 1);
}说明
get_dynamic_data(属性名, 重试次数)由OnePowerBase实现:按“属性名”调用协议库对应方法,成功后经update_property()分发给在start()中注册的各接口实现(接口侧再把snake_case属性名映射到自身属性,见 3.5)。大多数属性无需协议驱动自己写 if-else 分发;- 控制类方法(
set_power_work_mode/reset/set_sleep_mode/风扇最小 PWM 等)已在OnePowerBase中统一转发给协议库,主对象内无需重复实现,除非新协议需要特殊处理; - 轮询节奏建议与 PMBus 驱动保持一致(500ms 高频、约 3s/5s 分帧慢速),新协议可覆写
start_monitor()/stop_monitor()调整。
生命周期小结
| 阶段 | 关键动作 | 实现位置 |
|---|---|---|
init() | from_variant 装载 .sr 静态属性并解析 RefChip | 协议驱动主对象 |
start() | init_protocol() 创建协议对象 → 接口 init/start(注册更新回调)→ setup_oob_management()/evaluate_oob_management() | 协议驱动主对象 + OnePowerBase |
| 采集 | start_oob_management():首次读取 + start_monitor() 周期采集 | 协议驱动覆写 + OnePowerBase |
stop() | teardown_oob_management() → 接口 stop()(注销回调) | 协议驱动主对象 |
启动流程:
from_variant()装载.sr静态属性(槽位、协议、接口静态配置),解析RefChip引用init_protocol():按.sr的PhysicalInterface从协议工厂表创建协议对象并绑定芯片- 各接口对象
init(base)、start():向OnePowerBase注册“属性名→属性”更新回调 setup_oob_management():无主从直接放行;有主从则监听 BMC Active/Standby 状态evaluate_oob_management()→ 协议就绪时调用start_oob_management()开始采集
3.5 复用与补充接口实现
核心要点
电源各接口实现类位于 drivers/psu/common/interface/(IPowerSupply、IPowerSupply_Control/Metrics/Status/LogCollection/Oem/Upgrade、IPowerSupply_SharedManagement 等),所有协议电源共享,通常无需为新协议重写。每个接口实现类的职责是:继承对应的 dev::gen 基类,向 OnePowerBase 注册更新回调,把“协议返回的 snake_case 属性名”映射为接口自身的属性。
以 IPowerSupply_Metrics 为例(摘自仓库实际代码):
// interface/power_supply/i_metrics.h
class IPowerSupply_Metrics : public mc::engine::interface<IPowerSupply_Metrics, gen::PowerSupply_Metrics> {
public:
void init(OnePowerBase* base_ptr); // 保存 base 指针
void start(); // 注册指标更新回调
void stop(); // 注销回调
bool update_metrics(std::string_view property, const mc::variant& info);
private:
OnePowerBase* m_base;
};
// interface/power_supply/i_metrics.cpp
namespace dev {
void IPowerSupply_Metrics::start()
{
register_metrics_handler();
}
void IPowerSupply_Metrics::init(OnePowerBase* base_ptr)
{
m_base = base_ptr;
}
void IPowerSupply_Metrics::register_metrics_handler()
{
if (!m_base) {
elog("register_metrics_handler failed, OnePowerBase is null");
return;
}
// 向公共基类注册更新回调:属性名 -> 更新本接口属性
m_base->register_update_interface(this, [this](std::string_view property, const mc::variant& info) -> bool {
return update_metrics(property, info);
});
}
bool IPowerSupply_Metrics::update_metrics(std::string_view property, const mc::variant& info)
{
if (property == "input_power_watts") {
InputPowerWatts = info.as<double>();
return true;
}
if (property == "input_voltage") {
InputVoltage = info.as<double>();
return true;
}
// output_power_watts / output_voltage / env_temperature_celsius / psu_fan_speed_rpm ...
return false;
}
} // namespace dev
MC_REFLECT(dev::IPowerSupply_Metrics) // 单参数反射注册,无需列出 gen 基类关键要点:
- 接口实现类用双模板参数继承:
mc::engine::interface<Impl, gen::Base>,不要直接class Impl : public gen::Base; MC_REFLECT(dev::Xxx)写在实现.cpp末尾(单参数形式);- 属性名采用
snake_case字符串(如input_power_watts),与协议/基类get_dynamic_data()使用的名字一致;更新属性后框架自动对外发信号,无需手动通知上层; - 如果某协议对接口方法有特殊处理(如控制方法差异大),在协议库实现类中覆写对应虚方法即可,接口层不变。
补充新属性/新能力
适配中若需要暴露新属性,正确路径是:
- 先在
dmc/契约中定义:在dmc/intf/bmc/dev/PowerSupply*.json增加属性/方法(改契约先改 dmc); - 同步
gen/:人工在gen/include/device_tree/interface/增加/修改对应基类属性(并同步gen/src/),与dmc契约保持一致——当前仓库没有自动生成工具,不会自动重新生成; - 在对应接口实现中映射:
update_xxx()增加属性名分支(或控制方法覆写); - 配置下发:静态值写进型号
.sr对应接口段,动态值在协议库/驱动采集逻辑中读取。
说明
厂商私有/未标准化属性优先放到 PowerSupply.Oem 相关能力中,避免污染通用接口;风扇部件(非 PSU 内置风扇)的接口实现见 drivers/fan/huawei/interface/(IFan、IFans、IFan_Control、IFan_PWMChannel 等),实现方式与本节的电源接口一致。
3.6 实现 ABI 导出
ABI(Application Binary Interface)是驱动 .so 与 devmon 交互的标准接口:每个驱动 .so 导出一个 register_device_driver() 符号,返回 device_driver_t 数组。结构体与函数类型定义见 include/devmon/driver_abi.h。
代码示例:psu_abi.cpp(XYZ 协议电源)
#include <devmon/driver_abi.h>
#include <mc/log.h>
#include "one_power.h"
#ifndef DEVICE_DRIVER_NAME
#define DEVICE_DRIVER_NAME "psu_xyz" // 与 .sr 的 Compatible、协议名对应
#endif
using namespace dev;
extern "C" {
driver_handle_t create_one_power(void* service, const char* name)
{
try {
if (service == nullptr || name == nullptr) {
return nullptr;
}
auto* device = new OnePowerXyz();
device->set_service(static_cast<mc::engine::service*>(service));
device->set_object_name(name);
return device;
} catch (const std::exception& e) {
elog("create_one_power failed: ${error}", ("error", e.what()));
return nullptr;
}
}
status_t init_one_power(driver_handle_t device, void* csr_object, void* connector)
{
if (device == nullptr || csr_object == nullptr || connector == nullptr) {
return STATUS_ERROR;
}
auto* device_object = static_cast<OnePowerXyz*>(device);
mc::mutable_dict* csr_object_ptr = static_cast<mc::mutable_dict*>(csr_object);
mc::dict* connector_ptr = static_cast<mc::dict*>(connector);
return device_object->init(*csr_object_ptr, *connector_ptr) ? STATUS_OK : STATUS_ERROR;
}
status_t start_one_power(driver_handle_t device)
{
if (device == nullptr) {
return STATUS_ERROR;
}
return static_cast<OnePowerXyz*>(device)->start() ? STATUS_OK : STATUS_ERROR;
}
status_t stop_one_power(driver_handle_t device)
{
if (device == nullptr) {
return STATUS_ERROR;
}
static_cast<OnePowerXyz*>(device)->stop();
return STATUS_OK;
}
// device_name 与 .sr 的 Unit.Type(电源为 "PSU")对应
device_driver_t one_power_device_driver = {.device_name = "PSU",
.ctor = create_one_power,
.init = init_one_power,
.start = start_one_power,
.stop = stop_one_power};
device_driver_t psu_device_driver[] = {one_power_device_driver};
status_t register_device_driver(device_driver_t** device_driver, uint8_t* count)
{
*device_driver = psu_device_driver;
*count = sizeof(psu_device_driver) / sizeof(psu_device_driver[0]);
return STATUS_OK;
}
}关键说明
- 所有 ABI 函数必须用
extern "C"包装,确保符号可被 devmon 按 C 方式加载; - 每个设备类型需要 4 个函数:
create、init、start、stop(device_driver_t还支持可选的dump); device_name与.sr的Unit.Type对应(电源为"PSU",风扇部件为"Fans"/"Fan"/"FanType"),DEVICE_DRIVER_NAME与.sr的Compatible、协议工厂表名称对应;- 一个
.so可注册多个设备类型:风扇驱动fan_abi.cpp一次注册Fans/Fan/FanType3 个条目,register_device_driver返回count = 3; register_device_driver是框架调用的唯一入口符号。
3.7 配置构建编译
电源驱动的构建沿用仓库 meson 体系(drivers/psu/meson.build 先编 common 公共库,再编各协议驱动)。以 pmbus 为例(drivers/psu/pmbus/meson.build):
# 公共库(drivers/psu/common/meson.build):one_power_base + 全部接口实现编入 libPsu_common
libpsu_common = static_library('Psu_common', psu_common_sources, ...)
# 协议驱动:先静态库,再 link_whole 公共库打成动态库
libpsu_pmbus_static = static_library(
'psu_pmbus',
files('one_power.cpp', 'psu_abi.cpp'),
include_directories: include_dirs,
dependencies: [dev_deps, libpsu_common_dep, libpmbus_dep],
install: false,
)
libpsu_pmbus = shared_library(
'psu_pmbus',
include_directories: include_dirs,
name_prefix: 'lib',
name_suffix: 'so',
install: true,
install_dir: drivers_install_dir, # 驱动安装目录(框架定义)
link_whole: [libpsu_common, libpsu_pmbus_static],
dependencies: [dev_deps, libpsu_common_dep, libpmbus_dep],
)若新增协议驱动目录 drivers/psu/xyz,还需在 drivers/psu/meson.build 增加 subdir('xyz');若配套新增协议库 libraries/xyz,需在 libraries/meson.build 注册并让驱动依赖它。
关键说明
- 库名即驱动 SO 文件名(
libpsu_pmbus.so),与.sr的Compatible/DEVICE_DRIVER_NAME对齐; install_dir使用drivers_install_dir变量(框架定义);build_tests开启时仓库会把驱动拷贝到该目录(custom_target的cp逻辑),便于本地联调;- 公共库
libPsu_common通过link_whole全量打入协议驱动,保证各接口实现符号随.so一起导出; - 依赖关系要明确列出(
dev_deps、libpsu_common_dep、协议库依赖等),避免链接错误。
4. 最佳实践建议
4.1 代码规范
命名规范
| 类型 | 规范 | 示例 |
|---|---|---|
| 对象类名 | 对象类型/协议相关 | OnePowerPmbus、OnePowerXyz(主对象);IPowerSupply_Metrics(接口实现) |
| 文件名 | 与对象对应 | one_power.h/.cpp、psu_abi.cpp、one_power_base.h/.cpp |
| 成员变量 | m_ 前缀 | m_ps_protocol、m_property_query_timer |
| 协议库类名 | 协议/型号相关 | pmbus、pmbus_FP1420、pmbus_qb900 |
| 常量 | 大写+下划线 | PSU_CONTROL_OWNER_YES、BMC_ROLE_ACTIVE |
日志规范
- 启动/停止:使用
ilog - 错误:使用
elog - 调试信息:使用
dlog - 日志要包含上下文信息
ilog("OnePowerXyz ${slot} started, protocol=${protocol}",
("slot", m_power_supply.SlotNumber)("protocol", get_active_interface()));错误处理
- 所有可能失败的操作都要检查返回值
- 使用
try-catch捕获异常 - 记录详细的错误信息
try {
auto value = m_ps_protocol->get_input_voltage();
if (!value.has_value()) {
elog("Failed to get input voltage");
return false;
}
} catch (const std::exception& e) {
elog("Exception caught: ${error}", ("error", e.what()));
return false;
}4.2 性能优化
定时任务间隔
- 根据数据变化频率设置不同的更新间隔,避免不必要的通信开销
- 快速变化数据:0.5秒(500ms) - 电压、电流、功率、温度、状态
- 慢速变化数据:5秒(5000ms) - 产品信息、配置信息
实际应用示例(OnePowerBase::start_monitor 定时回调示意):
// 500ms 定时器,回调内区分快慢属性(参考 OnePowerPmbus 实现)
m_property_query_timer->timeout.connect([this]() {
read_frequency_property(); // 每次 500ms:功率、健康事件等
static uint8_t slow_cnt = 0; // 慢属性:约每 10 次(5s)读取
if (++slow_cnt >= 10) {
read_slow_property();
slow_cnt = 0;
}
});
m_property_query_timer->set_single_shot(false);
m_property_query_timer->start(mc::milliseconds(500));内存管理
- 优先使用智能指针(
unique_ptr、shared_ptr) - 及时清理定时器等资源
定时器资源管理:
// 清理定时器
if (m_property_query_timer) {
m_property_query_timer->stop();
m_property_query_timer.reset();
}协议优化
- 批量请求减少通信次数
- 缓存不变的数据(如产品信息)
- 失败时使用重试机制(带指数退避)
4.3 测试建议
单元测试
- 测试每个接口的功能
- 测试异常情况处理
- 使用 mock 对象模拟协议
// 1) 接口映射单测:验证“snake_case 属性名 -> 接口属性”的分发逻辑(无需硬件)
TEST(i_power_supply_metrics, update_metrics_mapping) {
dev::IPowerSupply_Metrics metrics;
EXPECT_TRUE(metrics.update_metrics("input_power_watts", mc::variant(100.0)));
EXPECT_TRUE(metrics.update_metrics("input_voltage", mc::variant(220.0)));
// 校验 InputPowerWatts / InputVoltage 属性已更新...
}
// 2) 协议层单测:mock 底层总线(参考 tests/libraries/smbus/chip_mock.*),
// 直接验证协议类(如厂商 PMBus 变种)的属性读取/命令解析
TEST(pmbus_variant, read_input_voltage) {
MockChip chip; // 打桩 I2C/SMBus 读写
mc::dict data{{"chip", ...}};
pmbus_xyz proto(data); // 厂商变种协议对象
auto v = proto.get_input_voltage();
EXPECT_TRUE(v.has_value());
}说明
- 电源主对象
init()依赖.sr下发并解析RefChip引用,完整生命周期(create→init→start→stop)联调建议用AddDevice加载型号.sr验证(见《快速上手》); - 风扇/电源控制类方法最终落到协议库,单测时在协议层打桩即可,不必构造真实对象。
集成测试
- 测试完整的生命周期:
create→init→start→stop - 测试与实际硬件的通信
- 测试长时间运行的稳定性
风扇相关测试重点:
- 测试风扇转速监控的准确性
- 测试风扇转速控制的响应时间
- 测试风扇故障检测和告警
- 测试多风扇同时控制
- 测试风扇转速与温度的联动
回归测试
- 每次修改后重新测试基本功能
- 检查是否影响其他电源驱动
- 验证 Redfish 接口数据正确
4.4 风扇管理最佳实践
风扇管理分两类场景,实现位置不同:
- PSU 内置风扇:BMC 侧通常只设置最小 PWM 下限(
SetFanMinPWM),实时调速由电源固件按温度完成;转速/状态经Metrics.FanSpeedRPM、Status.Fans_1_2/Fans_3_4上报;- 风扇部件(风扇框/模块):对象路径与接口见 2.2.2,由
drivers/fan/huawei的Fans/Fan对象配合上层散热策略实现 PWM 通道与转速闭环,接口实现方式与电源接口一致。
PSU 内置风扇控制要点
- 通过
PowerSupply.Control设置风扇最小 PWM(占空比),取值范围与含义以dmc契约和厂商规格书为准,不要在驱动里猜测单位或做 rpm 换算; - 若确需直接调速(测试/调试),使用协议库
set_fan_speed_percent();生产场景建议仅用“最小 PWM”+固件自动调速; - 读取风扇状态时以健康事件 + Status 接口为准,不要在驱动对象里自行拼读寄存器位(厂商私有解析放协议库子类)。
温度联动调速
- PSU 内置风扇的温度联动由电源固件完成;驱动侧保证温度指标(
EnvTemperatureCelsius等)与FanSpeedRPM采集及时、.sr静态配置正确即可; - 风扇框/风扇模块的温度联动属于上层散热策略,通过
Fans对象(如SetPWM)下发目标占空比,不在单驱动内做闭环。
风扇故障处理
周期回调中健康事件已把风扇故障位分发给 Status 接口(例如 update_property("fans_1_2", ev.fan_fault) → Status.Fans_1_2),驱动只需保证:
// 周期回调(示意):健康事件 -> Status 接口,含风扇故障位
auto [ev, health_failed] = m_ps_protocol->get_health_event();
if (!health_failed) {
update_property("fans_1_2", ev.fan_fault); // 驱动把故障位交给 Status 接口
update_property("vout", ev.output_voltage_fault);
// ...
}- 告警判定与防抖(连续 N 次才置位)由
IPowerSupply_Status内部使用mc::debounce完成,无需驱动重复实现; - 风扇故障后的“告警/日志”由上层(日志与告警接口)消费,驱动不直接处理自愈策略。
风扇监控频率建议
| 监控项 | 建议频率 | 说明 |
|---|---|---|
| 健康事件(含风扇故障位) | 500ms | 随快采周期,风扇故障能及时反映到 Status.Fans_1_2 |
| 风扇转速 FanSpeedRPM | 约 3s | 随指标分帧读取(read_frequency_property 内分帧) |
| 产品信息/慢速属性 | 约 5s 或启动时读取 | 避免频繁占用总线 |
说明
监控频率只是建议值。实际以各协议驱动的采集实现为准,且要避免在慢速 I2C 总线上对多路电源同时高频采集造成拥塞(必要时错峰)。
PMBus 命令注意事项
1. 命令兼容性与厂商变种
不同厂商/型号的 PMBus 实现存在差异,不要在主对象里直接读写寄存器;差异点收敛到 libraries/pmbus 的协议子类中,通过覆写虚方法表达,再注册进协议工厂表:
// libraries/pmbus/pmbus_xyz.h(示意):厂商变种协议子类
class pmbus_xyz : public pmbus {
public:
explicit pmbus_xyz(mc::dict& data) : pmbus(data) {}
// 厂商差异点:覆写风扇转速/私有属性读取等
std::optional<uint16_t> get_psu_fan_speed_rpm() override;
std::optional<std::string> get_firmware_version() override;
};
// drivers/psu/pmbus/one_power.cpp:注册进协议工厂表
// DEFINE_PS_PROTOCOL("psu_xyz", pmbus_xyz), ...2. 数据格式处理
PMBus 的 LINEAR11/LINEAR16 等格式解析已封装在 libraries/pmbus(如 get_linear_11()/get_linear_16()),驱动层直接拿到物理量,无需重复实现;个别厂商编码不同时在协议子类内处理。
3. 错误重试机制
属性读取带重试能力由 OnePowerBase::get_dynamic_data(属性名, 重试次数) 提供:
// 读取失败自动重试(例如启动阶段读产品信息,失败率高的场景给 3-5 次)
bool ok = get_dynamic_data("psu_fan_speed_rpm", 3);总线级读写失败、超时等由 chip/smbus 层处理并上报日志;驱动无需自行 sleep 重试。若某个属性需要更精细的失败处理,可在协议子类中覆写对应方法并返回 std::nullopt,由调用侧按空值处理。
结语
南向风扇电源驱动适配是一项系统性工作,需要对硬件特性、PMBus 协议、软件架构都有深入理解。本文档提供了一套完整的适配流程和代码示例,涵盖了电源管理和内置风扇控制的方方面面。
关键要点回顾:
电源管理:
- 契约先行:接口/对象路径以
dmc/(intf+path)为准;gen/下的dev::gen基类当前需人工随 dmc 同步修改(仓库内无自动生成工具链) - 驱动按协议族组织(PMBus/CANbus/SMC…),公共能力(
OnePowerBase、IPowerSupply_*)下沉drivers/psu/common供各协议复用 - 支持电压、电流、功率、温度、风扇转速等指标的周期采集,以及工作/休眠模式、复位、风扇最小 PWM、固件升级等控制
- 状态/告警经
PowerSupply.Status上报(含防抖),共享电源场景支持 BMC 主从(Active/Standby)下的带外管理启停
风扇管理:
- PSU 内置风扇:
Metrics.FanSpeedRPM+Status.Fans_1_2/Fans_3_4+Control.SetFanMinPWM,调速策略由固件完成 - 风扇部件(风扇框/模块):由
drivers/fan/huawei的Fans/Fan/FanType对象提供(PWM 通道/转速/状态等接口) - 厂商差异(命令/数据格式)收敛到
libraries/pmbus等协议库子类,避免散落在驱动对象中
开发建议:
- 适配新电源型号先判断适配形态:已有协议通常只需新增
.sr/.dds与协议变种,无需新建驱动目录 - 仔细阅读厂商提供的 PMBus 命令规格书,注意不同型号扩展命令差异
- 依托
get_dynamic_data()重试与接口更新回调,实现完善的错误处理 - 采用合理的监控频率(快慢分帧),避免 I2C 总线拥塞
- 定期进行长时间稳定性测试与回归测试
实际适配中还会遇到各种具体问题,欢迎大家积极在社区沟通交流和贡献。