2. HDF驱动框架初探:核心概念与加载流程

好,咱们正式开始接触鸿蒙的驱动框架——HDF。说实话,我第一次看HDF的代码时,第一反应是“这玩意儿怎么这么绕”。但后来做多了才发现,它的设计其实很精巧。说白了,HDF就是一套帮你管理驱动生命周期的“管家”,你只需要告诉它“我的驱动长什么样、怎么初始化”,剩下的加载、卸载、消息传递,它都帮你包办了。

这一节,咱们先摸清HDF的三个核心概念,再走一遍驱动的加载流程,最后看看驱动之间怎么互相“喊话”。

2.1 三个核心概念:DeviceObject、DriverEntry、HdfDriverEntry

在HDF里,驱动被抽象成了几个关键结构体。你写驱动,本质上就是在填充这些结构体。我习惯把它们比作“身份证”、“简历”和“入职登记表”。

概念 结构体名 我的比喻 核心作用
DeviceObject DeviceObject 身份证 代表一个具体的设备实例,包含设备号、属性、服务接口
DriverEntry DriverEntry 简历 描述驱动的基本信息,比如名字、所属厂商、版本
HdfDriverEntry HdfDriverEntry 入职登记表 驱动真正的入口,定义了Init、Bind、Release三个回调函数

关键点: HdfDriverEntry 是驱动开发者最常打交道的结构体。你写的驱动代码,最终都要挂到它的三个回调里。

咱们直接看代码。一个典型的传感器驱动入口长这样:

// 传感器驱动的HdfDriverEntry定义
struct HdfDriverEntry g_sensorDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "HDF_SENSOR_DRIVER",
    .Bind = SensorDriverBind,      // 绑定设备服务到HDF框架
    .Init = SensorDriverInit,      // 初始化硬件和资源
    .Release = SensorDriverRelease, // 释放资源
};

// 驱动入口注册宏
HDF_INIT(g_sensorDriverEntry);

嗯,这里要注意:HDF_INIT 这个宏会把你的驱动入口放到特定的段里,HDF框架启动时会自动扫描这个段,找到所有驱动。我曾经在移植一个加速度计驱动时,忘了加这个宏,结果驱动死活加载不上,排查了半天……

2.2 驱动加载流程:从开机到驱动跑起来

驱动是怎么从无到有的?我画了一张流程图,你看完就明白了。

HDF驱动加载流程图 HDF驱动加载流程 系统启动 HDF框架初始化 扫描HDF_INIT段 调用 Bind() 调用 Init() 驱动就绪 说明: Bind() 负责注册设备服务接口 Init() 负责硬件初始化 Release() 在卸载时调用

流程其实不复杂,但有几个坑我得提醒你:

  1. Bind() 里不要做耗时操作——它只是“绑定”,不是“初始化”。我见过有人把I2C初始化写在Bind里,结果导致驱动加载超时。
  2. Init() 失败要回滚——如果Init里申请了内存但后续步骤失败,记得释放。否则会造成内存泄漏。
  3. Release() 一定要幂等——HDF可能会多次调用Release,你的代码要能扛得住重复释放。

曾经踩过的坑: 我在调试一个温湿度传感器驱动时,Init()里调用了GPIO配置,但没检查返回值。结果GPIO被其他驱动占用了,Init返回成功,但后续读写全部失败。后来我养成了一个习惯:所有硬件操作都要检查返回值,并在Init失败时打印明确的错误码。

2.3 驱动消息机制:驱动之间怎么“打电话”

驱动不是孤岛。比如你的传感器驱动需要把数据发给显示驱动,或者上层APP要下发配置。HDF提供了一套消息机制,让驱动之间可以互相通信。

消息机制的核心是 HdfIoService 和 HdfSBuf。前者是服务的“电话线”,后者是传递数据的“信封”。

来看一个实际例子:上层APP通过消息给传感器驱动下发采样率配置。

// 上层发送消息(简化版)
struct HdfIoService *service = HdfIoServiceBind("HDF_SENSOR_DRIVER");
if (service == NULL) {
    printf("服务绑定失败\n");
    return;
}

struct HdfSBuf *data = HdfSBufObtain(1024);
// 把采样率写入SBuf
uint32_t sampleRate = 100; // 100Hz
HdfSBufWriteUint32(data, sampleRate);

// 发送消息,消息ID为 CMD_SET_SAMPLE_RATE
int ret = service->dispatcher->Dispatch(service, CMD_SET_SAMPLE_RATE, data, NULL);
if (ret != 0) {
    printf("消息发送失败, ret=%d\n", ret);
}

HdfSBufRecycle(data);
HdfIoServiceRecycle(service);

而在驱动端,你需要在Bind()里注册一个Dispatch回调:

static int SensorDriverDispatch(struct HdfDeviceIoClient *client,
                                int cmdId, struct HdfSBuf *data, struct HdfSBuf *reply)
{
    switch (cmdId) {
        case CMD_SET_SAMPLE_RATE: {
            uint32_t rate;
            HdfSBufReadUint32(data, &rate);
            // 设置硬件采样率
            SensorSetSampleRate(rate);
            break;
        }
        default:
            break;
    }
    return 0;
}

static int SensorDriverBind(struct HdfDeviceObject *device)
{
    static struct IDeviceIoService service = {
        .Dispatch = SensorDriverDispatch,
    };
    device->service = &service;
    return 0;
}

个人建议: 消息ID最好用枚举定义,并加上注释。我曾经在一个项目里用了魔数(比如0x01、0x02),三个月后自己都忘了0x01是干啥的。后来统一改成枚举,代码可读性好了很多。

消息机制有几个特点值得注意:

  • 同步/异步:Dispatch默认是同步的,如果需要异步,可以用HdfSendEvent。
  • 数据序列化:HdfSBuf支持多种数据类型(Uint32、String、Blob等),但要注意字节序——不同平台可能不一样。
  • 权限控制:HDF框架会检查调用方的权限,不是谁都能随便发消息的。

2.4 小结:HDF框架的“三板斧”

回顾一下,HDF驱动开发的核心就三件事:

  1. 填好HdfDriverEntry——把Bind、Init、Release三个回调写好。
  2. 理解加载流程——从系统启动到驱动就绪,每一步都有明确的职责。
  3. 用好消息机制——通过Dispatch和SBuf实现驱动间通信。