南向风扇电源驱动适配指南

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

章节总览与分册选型见《部件驱动适配指南》。电源接口契约见 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.jsonPowerSupply/Metrics.jsonFan/Control.json),是南向接口契约的单一事实源
对象路径(dmc)dmc/path/bmc/dev/**定义设备树对象挂载路径与接口组合,如 PowerSupply/PowerSupply.jsonFan/{Fan,Fans,FanType}.json
接口基类(gen)gen/include/device_tree/interface/与契约对应的 C++ 接口基类,位于 dev::gen 命名空间,如 gen::PowerSupplygen::PowerSupply_Controlgen::Fan;当前仓库没有自动重新生成的工具链,改动接口时需要人工同步维护
驱动实现(dev)drivers/ 下各驱动目录厂商/协议实现,位于 dev 命名空间,如 dev::OnePowerPmbusdev::IPowerSupply
设备描述(CSR)驱动目录内 csr/*.dds / *.sr.dds 声明设备对象到 D-Bus 路径的映射与接口列表,.sr 是型号级实例配置(总线拓扑、静态属性、Compatible 等)

设备对象

对象类型说明代码示例
电源主对象代表整个电源设备,承载 OnePowerBase 公共逻辑并组合各接口dev::OnePowerPmbusdrivers/psu/pmbus/
电源公共基类协议无关的电源对象基类:协议管理、属性采集、更新回调、升级、BMC 主从带外管理等dev::OnePowerBasedrivers/psu/common/
风扇框/风扇对象风扇部件对象(非 PSU 内置风扇)dev::Fans / dev::Fan / dev::FanTypedrivers/fan/huawei/

接口

  • gen 命名空间(dev::gen:与 dmc 契约对应的接口基类,只声明属性与虚方法,用 MC_INTERFACE("bmc.dev.Xxx") 声明接口名;改动契约时需要人工同步修改
  • dev 命名空间:驱动实现类。接口实现类通过 mc::engine::interface<Impl, gen::Base> 双模板参数继承对应 gen 基类
  • 接口组合:一个设备可实现多个接口(PowerSupplyPowerSupply.ControlPowerSupply.MetricsPowerSupply.StatusPowerSupply.Oem 等),组合关系在 .dds/.srMC_OBJECT 中声明

协议库

  • libraries/protocolpower_supply_protocol_base 电源协议抽象基类(ps_protocol_t
  • libraries/pmbus:PMBus 电源管理总线协议(业界标准),含厂商变种实现(如 pmbus_FP1420pmbus_qb900 等)
  • libraries/smbuslibraries/canbus:底层通信协议封装;不同协议族(PMBus/CANbus/SMC)使用不同的驱动目录
  • 电源驱动按“通信协议”组织而非按厂商,公共部分下沉到 drivers/psu/common

1.4 驱动分层架构

  1. 分层架构:从契约定义到硬件交互的完整体系:契约层(dmc)→ 基类层(gendev::gen 接口基类,需随 dmc 人工同步)→ 实现层(dev,厂商/协议实现)→ 部署层(.so + csr 配置由 devmon 加载并实例化到 D-Bus 设备树);
  2. 设计理念:接口与实现分离。“改契约先改 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/ 目录包含所有硬件设备的具体驱动实现,第一层按部件类型组织(如 psufanbuschippcie_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)定义了 ModelManufacturerSerialNumber 等属性
接口实现类用双模板参数继承接口基类,实现具体逻辑class IPowerSupply : public mc::engine::interface<IPowerSupply, gen::PowerSupply>
命名空间接口基类位于 dev::gen,驱动实现位于 dev驱动代码中写 gen::PowerSupplydev::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/...
使用方式

接口基类的风格如下(以电源接口为例,摘自仓库实际代码):

cpp
// 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 完成反射注册:

cpp
// 厂商接口实现类(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 外,还包含网卡/硬盘等部件使用的 mctppldmncsinvme 等协议库。

目录结构
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-定义电源协议的通用接口(读取/控制/状态/升级),所有电源协议类的基类
pmbusPMBus 电源管理总线协议I2C/SMBus电源管理的主要协议;厂商变种(pmbus_FP1420pmbus_qb900pmbus_eb1000_1 等)以子类形式扩展
smbus系统管理总线协议I2C底层总线通信,PMBus 的物理承载
canbusCANbus 协议(含 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() 按名创建:

cpp
// 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 目录之间的关系

工作流程
  1. 契约定义:在 dmc/ 中维护接口(intf)与对象路径/接口组合(path
  2. 基类维护gen/ 下与契约对应的 C++ 接口/对象基类(dev::gen)需人工同步维护(当前仓库无自动生成工具链)
  3. 接口实现:在 drivers/*/common/interface 等位置用 mc::engine::interface<Impl, gen::Base> 实现接口
  4. 驱动组装:在协议驱动目录(如 drivers/psu/pmbus)组合接口、继承公共基类(OnePowerBase),创建完整驱动对象并导出 ABI
  5. 协议调用:驱动通过 libraries/ 中的协议库与硬件通信
  6. 部署加载:配套 csr 下的 .dds/.sr,由 devmon 在设备树中实例化设备对象

2.3 驱动协议架构

以 PMBus 电源为例,驱动、协议库与物理总线的分层关系如下(其他协议族如 CANbus、SMC 结构相同,只是中间协议库不同):

2.4 底层协议确定

仓库中电源驱动按协议族组织:drivers/psu/ 下有 pmbuspmbus_qb900canbuscanbus_tpsusmc 五个协议目录,公共能力(OnePowerBaseIPowerSupply_* 接口实现)统一在 common/

协议类型

协议驱动目录说明适用场景
PMBusdrivers/psu/pmbus业界标准电源管理协议(SMBus/I2C),同一驱动内通过协议工厂表支持 pmbuspmbus_qb900pmbus_FP1420pmbus_eb1000_1 等具体实现标准 PMBus 电源
CANbus / CANbus-TPSUdrivers/psu/canbusdrivers/psu/canbus_tpsuCANbus 通信的电源使用 CANbus 的电源
SMCdrivers/psu/smc通过 SMC 通道访问电源带 SMC 通道的整机电源

协议实现关系

协议库(libraries)关系说明
protocol/power_supply_protocol.h抽象基类定义 power_supply_protocol_base,统一读取/控制/状态/升级等虚方法
pmbus/pmbus.h具体实现pmbus 实现标准 PMBus 命令
pmbus/pmbus_*.h厂商/型号变种在标准 pmbus 之上扩展厂商私有命令(如 pmbus_FP1420pmbus_qb900pmbus_PDC3KD5412_LC 等),由 drivers/psu/pmbus/one_power.cpp 的协议工厂表注册

2.5 电源与风扇支持的能力

本节以 drivers/psu/pmbusOnePowerPmbus(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电源状态信息在位 PresenceVout/Iout/Temperature/Input/CML/Mfr_Specific/Other 告警状态、风扇状态 Fans_1_2/Fans_3_4、通信状态 CommunicationStatus、设备模式、输入/输出状态
日志接口PowerSupply.LogCollection电源故障日志黑匣子信息导出
OEM 接口PowerSupply.Oem厂商私有能力私有寄存器读写(get_register_value)等
升级接口Upgradebmc.dev.Upgrade固件升级/激活升级触发、进度/状态上报、激活(PMBus 升级接口 pmbus_upgrade
共享管理SharedManagementbmc.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::optionalRPM
风扇占空比get_fan_speed_percent()读取风扇 PWM 占空比(0-100)%
风扇转速控制

通过 PowerSupply.Control 接口及协议库控制风扇:

控制项接口/方法说明
风扇最小 PWMIPowerSupply_Control::SetFanMinPWM()(属性 Control.FanMinPWM设置风扇最小 PWM 占空比;取值范围/含义以 dmc 契约与厂商规格书为准
直接调速协议库 set_fan_speed_percent()直接按占空比控制风扇
自动调速电源固件自动控制固件根据温度自动调节风扇转速

说明

  1. 电源内置风扇通常由电源固件按温度自动调速,BMC 侧一般只设置下限(最小 PWM)
  2. 风扇部件(风扇框/风扇模块)的调速不在电源驱动内,而是由 drivers/fan/huaweiFans/Fan 对象提供(PWM 通道、Fan.Control 等接口),见 2.2.2

控制示例:

cpp
// 通过 Control 接口设置风扇最小 PWM(示例:40%)
m_power_supply_control.SetFanMinPWM(40);

// 通过协议直接控制风扇占空比
m_ps_protocol->set_fan_speed_percent(40);
风扇管理流程

能力获取方式

OnePowerPmbus(PMBus)为例,数据获取分为“上电读取”与“周期采集”两个阶段:

阶段获取方式内容说明
上电/带外启动启动时带重试读取厂商、型号、序列号、部件号、生产日期、固件版本、电源类型、输入电压类型、硬件版本、额定功率,以及输入/输出电压初始值保证设备树尽早出现非零的关键值
周期采集(快)500ms 定时器输入功率、健康事件(health_eventvout/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/寄存器数据,通常只在启动与低速周期读取,避免占用总线;
  • 监测任务统一由 OnePowerBasestart_monitor()/restart_monitor() 管理,500ms 是其基类默认值,子类可覆写。

3. 适配流程详解

场景假设:仓库需要支持一款新电源“XYZ”(PMBus 协议族),或使用全新通信协议的新电源。下面先判断适配形态,再按步骤落地。文中示例以 drivers/psu/pmbusOnePowerPmbus 为蓝本。

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)、UnitType/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

json
{
    "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

json
{
    "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": { "...": "..." }
        }
    }
}

配置要点:

  1. Unit 匹配关系要对齐.srUnit.Type(电源为 PSU)与驱动 ABI 的 device_name 对应;Compatible 与驱动加载/协议工厂表使用的名称(如 psu_pmbus)对应,保证 devmon 能匹配到正确的 .so
  2. RefChip 与拓扑要完整ManagementTopology 声明电源所在总线与关联芯片(如 EEPROM),Objects.<对象>.RefChip 引用该芯片,主对象 init() 据此解析出硬件访问通道;
  3. 接口静态属性写在对应接口段SlotNumberProtocol 等由 .sr 静态下发(经 from_variant 加载),运行期通过接口属性读取;
  4. 风扇调速需求段(可选):需要“风扇随温度调速”的场景可在 .sr 中追加冷却/风扇需求(如 CoolingRequirement_*),由上层协同使用。

3.3 创建电源主对象(协议驱动)

电源主对象是每个协议驱动的核心:它组合各接口实现类,并承载协议相关的初始化和数据采集逻辑。公共逻辑(协议创建、属性读取分发、升级、BMC 主从带外管理、监控定时器管理等)已在 OnePowerBase 中实现,协议驱动只需完成少量协议相关的工作,参考对象:drivers/psu/pmbus/one_power.hOnePowerPmbus)。

代码示例:one_power.h(XYZ 协议电源主对象)

cpp
#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.* 接口名:

cpp
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.MetricsEnvTemperatureCelsius 等),告警/风扇状态位在 PowerSupply.Status,见 2.5

关键说明

1. MC_OBJECT 宏的参数:

  • 第 1 个:类名(如 OnePowerXyz
  • 第 2 个:对象类型名(电源为 "OnePower",与 .ddsObjects 键名/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

协议工厂表与生命周期

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()(协议驱动实现),示意如下:

cpp
// 启动带外数据采集:首次带重试读取产品信息 + 启动周期采集(示意,参考 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()(注销回调)协议驱动主对象

启动流程:

  1. from_variant() 装载 .sr 静态属性(槽位、协议、接口静态配置),解析 RefChip 引用
  2. init_protocol():按 .srPhysicalInterface 从协议工厂表创建协议对象并绑定芯片
  3. 各接口对象 init(base)start():向 OnePowerBase 注册“属性名→属性”更新回调
  4. setup_oob_management():无主从直接放行;有主从则监听 BMC Active/Standby 状态
  5. evaluate_oob_management() → 协议就绪时调用 start_oob_management() 开始采集

3.5 复用与补充接口实现

核心要点

电源各接口实现类位于 drivers/psu/common/interface/IPowerSupplyIPowerSupply_Control/Metrics/Status/LogCollection/Oem/UpgradeIPowerSupply_SharedManagement 等),所有协议电源共享,通常无需为新协议重写。每个接口实现类的职责是:继承对应的 dev::gen 基类,向 OnePowerBase 注册更新回调,把“协议返回的 snake_case 属性名”映射为接口自身的属性。

IPowerSupply_Metrics 为例(摘自仓库实际代码):

cpp
// 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() 使用的名字一致;更新属性后框架自动对外发信号,无需手动通知上层;
  • 如果某协议对接口方法有特殊处理(如控制方法差异大),在协议库实现类中覆写对应虚方法即可,接口层不变。

补充新属性/新能力

适配中若需要暴露新属性,正确路径是:

  1. 先在 dmc/ 契约中定义:在 dmc/intf/bmc/dev/PowerSupply*.json 增加属性/方法(改契约先改 dmc);
  2. 同步 gen/:人工在 gen/include/device_tree/interface/ 增加/修改对应基类属性(并同步 gen/src/),与 dmc 契约保持一致——当前仓库没有自动生成工具,不会自动重新生成;
  3. 在对应接口实现中映射update_xxx() 增加属性名分支(或控制方法覆写);
  4. 配置下发:静态值写进型号 .sr 对应接口段,动态值在协议库/驱动采集逻辑中读取。

说明

厂商私有/未标准化属性优先放到 PowerSupply.Oem 相关能力中,避免污染通用接口;风扇部件(非 PSU 内置风扇)的接口实现见 drivers/fan/huawei/interface/IFanIFansIFan_ControlIFan_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 协议电源)

cpp
#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 个函数:createinitstartstopdevice_driver_t 还支持可选的 dump);
  • device_name.srUnit.Type 对应(电源为 "PSU",风扇部件为 "Fans"/"Fan"/"FanType"),DEVICE_DRIVER_NAME.srCompatible、协议工厂表名称对应;
  • 一个 .so 可注册多个设备类型:风扇驱动 fan_abi.cpp 一次注册 Fans/Fan/FanType 3 个条目,register_device_driver 返回 count = 3
  • register_device_driver 是框架调用的唯一入口符号。

3.7 配置构建编译

电源驱动的构建沿用仓库 meson 体系(drivers/psu/meson.build 先编 common 公共库,再编各协议驱动)。以 pmbus 为例(drivers/psu/pmbus/meson.build):

meson
# 公共库(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),与 .srCompatible/DEVICE_DRIVER_NAME 对齐;
  • install_dir 使用 drivers_install_dir 变量(框架定义);build_tests 开启时仓库会把驱动拷贝到该目录(custom_targetcp 逻辑),便于本地联调;
  • 公共库 libPsu_common 通过 link_whole 全量打入协议驱动,保证各接口实现符号随 .so 一起导出;
  • 依赖关系要明确列出(dev_depslibpsu_common_dep、协议库依赖等),避免链接错误。

4. 最佳实践建议

4.1 代码规范

命名规范

类型规范示例
对象类名对象类型/协议相关OnePowerPmbusOnePowerXyz(主对象);IPowerSupply_Metrics(接口实现)
文件名与对象对应one_power.h/.cpppsu_abi.cppone_power_base.h/.cpp
成员变量m_ 前缀m_ps_protocolm_property_query_timer
协议库类名协议/型号相关pmbuspmbus_FP1420pmbus_qb900
常量大写+下划线PSU_CONTROL_OWNER_YESBMC_ROLE_ACTIVE

日志规范

  • 启动/停止:使用 ilog
  • 错误:使用 elog
  • 调试信息:使用 dlog
  • 日志要包含上下文信息
cpp
ilog("OnePowerXyz ${slot} started, protocol=${protocol}",
     ("slot", m_power_supply.SlotNumber)("protocol", get_active_interface()));

错误处理

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

cpp
// 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_ptrshared_ptr
  • 及时清理定时器等资源

定时器资源管理:

cpp
// 清理定时器
if (m_property_query_timer) {
    m_property_query_timer->stop();
    m_property_query_timer.reset();
}

协议优化

  • 批量请求减少通信次数
  • 缓存不变的数据(如产品信息)
  • 失败时使用重试机制(带指数退避)

4.3 测试建议

单元测试

  • 测试每个接口的功能
  • 测试异常情况处理
  • 使用 mock 对象模拟协议
cpp
// 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 验证(见《快速上手》);
  • 风扇/电源控制类方法最终落到协议库,单测时在协议层打桩即可,不必构造真实对象。

集成测试

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

风扇相关测试重点:

  • 测试风扇转速监控的准确性
  • 测试风扇转速控制的响应时间
  • 测试风扇故障检测和告警
  • 测试多风扇同时控制
  • 测试风扇转速与温度的联动

回归测试

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

4.4 风扇管理最佳实践

风扇管理分两类场景,实现位置不同:

  1. PSU 内置风扇:BMC 侧通常只设置最小 PWM 下限SetFanMinPWM),实时调速由电源固件按温度完成;转速/状态经 Metrics.FanSpeedRPMStatus.Fans_1_2/Fans_3_4 上报;
  2. 风扇部件(风扇框/模块):对象路径与接口见 2.2.2,由 drivers/fan/huaweiFans/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),驱动只需保证:

cpp
// 周期回调(示意):健康事件 -> 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 的协议子类中,通过覆写虚方法表达,再注册进协议工厂表:

cpp
// 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(属性名, 重试次数) 提供:

cpp
// 读取失败自动重试(例如启动阶段读产品信息,失败率高的场景给 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…),公共能力(OnePowerBaseIPowerSupply_*)下沉 drivers/psu/common 供各协议复用
  • 支持电压、电流、功率、温度、风扇转速等指标的周期采集,以及工作/休眠模式、复位、风扇最小 PWM、固件升级等控制
  • 状态/告警经 PowerSupply.Status 上报(含防抖),共享电源场景支持 BMC 主从(Active/Standby)下的带外管理启停

风扇管理:

  • PSU 内置风扇:Metrics.FanSpeedRPM + Status.Fans_1_2/Fans_3_4 + Control.SetFanMinPWM,调速策略由固件完成
  • 风扇部件(风扇框/模块):由 drivers/fan/huaweiFans/Fan/FanType 对象提供(PWM 通道/转速/状态等接口)
  • 厂商差异(命令/数据格式)收敛到 libraries/pmbus 等协议库子类,避免散落在驱动对象中

开发建议:

  • 适配新电源型号先判断适配形态:已有协议通常只需新增 .sr/.dds 与协议变种,无需新建驱动目录
  • 仔细阅读厂商提供的 PMBus 命令规格书,注意不同型号扩展命令差异
  • 依托 get_dynamic_data() 重试与接口更新回调,实现完善的错误处理
  • 采用合理的监控频率(快慢分帧),避免 I2C 总线拥塞
  • 定期进行长时间稳定性测试与回归测试

实际适配中还会遇到各种具体问题,欢迎大家积极在社区沟通交流和贡献。