2、HDF核心数据结构与对象模型:驱动对象、设备对象、服务对象、节点对象详解与生命周期管理

HDF(Hardware Driver Foundation)是OpenHarmony的驱动基石。很多初学者一上来就啃源码,结果被一堆结构体指针绕晕了。我个人习惯是先搞清楚它的对象模型——说白了,就是HDF怎么抽象硬件、怎么管理驱动的生命周期。

这一章,我们聚焦四个核心对象:驱动对象、设备对象、服务对象、节点对象。你把这四个对象的关系理清了,HDF的骨架也就搭起来了。

2.1 四个核心对象的关系图谱

先上一张图,让你有个整体印象。这张图我画了很多遍,每次给团队新人讲HDF,都是从这张图开始的。

HDF 核心对象关系图 驱动对象 Driver 设备对象 Device 服务对象 Service 节点对象 DeviceNode 说明: 驱动对象绑定设备对象,设备对象对外暴露服务对象。 节点对象是设备在HDF树中的挂载点,一个设备对象可以挂载多个节点。

从上图可以看出:驱动对象负责创建设备对象,设备对象负责注册服务对象,而节点对象则是设备在HDF设备树中的“户口本”。

2.2 驱动对象(Driver)

驱动对象是HDF中最上层的抽象。它描述的是“谁在驱动这个硬件”。每个驱动模块在HDF中对应一个HdfDriverEntry结构体。

struct HdfDriverEntry {
    int32_t moduleVersion;          // 驱动版本号
    const char *moduleName;         // 驱动名称,全局唯一
    int32_t (*Bind)(struct HdfDeviceObject *device);    // 绑定设备
    int32_t (*Init)(struct HdfDeviceObject *device);    // 初始化设备
    void (*Release)(struct HdfDeviceObject *device);    // 释放设备
};

这里有个关键点:Bind、Init、Release三个回调函数,构成了驱动的生命周期。

生命周期三阶段:

  • Bind:驱动与设备绑定,此时可以解析HCS配置、分配资源。
  • Init:设备初始化,此时硬件已经就绪,可以注册中断、创建服务。
  • Release:驱动卸载,释放所有资源。

我记得有一次排查一个驱动加载失败的问题,发现Bind函数里调用了硬件寄存器操作,结果设备还没上电,直接崩了。嗯,这里要注意:Bind阶段不要操作硬件,那是Init的事。

2.3 设备对象(DeviceObject)

设备对象是驱动与硬件的桥梁。每个驱动实例对应一个HdfDeviceObject。它包含了设备属性、服务列表、私有数据等。

struct HdfDeviceObject {
    struct IDeviceIoService *service;   // 设备服务接口
    struct HdfDeviceObject *parent;     // 父设备
    struct HdfDeviceObject *children;   // 子设备链表
    uint32_t deviceId;                  // 设备ID
    const char *deviceName;             // 设备名称
    struct HdfObject *priv;             // 私有数据
};

设备对象最核心的字段是service和priv。service是暴露给上层调用的接口,priv是驱动自己用的上下文数据。

我的习惯:在Bind阶段就把priv分配好,把HCS配置解析后存进去。这样Init阶段直接拿priv里的参数初始化硬件,代码结构清晰很多。

2.4 服务对象(Service)

服务对象是驱动对外提供能力的窗口。上层应用通过HDF框架找到服务,然后调用服务接口操作硬件。

服务对象的核心是IDeviceIoService接口,它定义了一个Dispatch方法:

struct IDeviceIoService {
    struct HdfObject object;
    int32_t (*Dispatch)(struct IDeviceIoService *service,
                        int32_t cmdId,
                        struct HdfSBuf *data,
                        struct HdfSBuf *reply);
};

上层通过cmdId来区分不同的操作命令。比如cmdId=1表示读寄存器,cmdId=2表示写寄存器。数据通过HdfSBuf传递,这是一个序列化缓冲区,支持跨进程通信。

我曾经踩过的坑:Dispatch函数里没有做cmdId的合法性校验,结果上层传了个非法cmdId进来,直接数组越界。从那以后,我每个Dispatch函数第一件事就是校验cmdId范围。

2.5 节点对象(DeviceNode)

节点对象是HDF设备树中的挂载点。它描述了设备在设备树中的位置、匹配信息、优先级等。

struct HdfDeviceNode {
    struct HdfDeviceObject *device;     // 关联的设备对象
    struct HdfDeviceNode *parent;       // 父节点
    struct HdfDeviceNode *sibling;      // 兄弟节点
    uint32_t priority;                  // 加载优先级
    const char *moduleName;             // 匹配的驱动名称
    const char *deviceName;             // 设备名称
};

节点对象最重要的作用是驱动匹配。HDF启动时,会遍历设备树中的所有节点,根据moduleName找到对应的驱动,然后调用驱动的Bind和Init。

对象类型 核心作用 生命周期 关键回调/方法
驱动对象 描述驱动模块 Bind → Init → Release Bind, Init, Release
设备对象 驱动与硬件的桥梁 随驱动创建/销毁 service, priv
服务对象 对外提供能力 Init时注册,Release时注销 Dispatch
节点对象 设备树挂载点 系统启动时创建 moduleName匹配

2.6 生命周期管理实战

光讲理论不够,我们来看一个实际的驱动模板。这是我写驱动时常用的骨架:

// my_driver.c
#include "hdf_device_desc.h"
#include "hdf_log.h"

#define HDF_LOG_TAG my_driver

// 私有数据结构
struct MyDriverPriv {
    uint32_t regBase;
    uint32_t irqNum;
};

// Bind:解析配置,分配私有数据
static int32_t MyDriverBind(struct HdfDeviceObject *device)
{
    struct MyDriverPriv *priv = NULL;

    // 解析HCS配置
    // ... 从device->property中读取regBase和irqNum

    priv = (struct MyDriverPriv *)OsalMemCalloc(sizeof(struct MyDriverPriv));
    if (priv == NULL) {
        HDF_LOGE("MyDriverBind: alloc priv failed");
        return HDF_ERR_MALLOC_FAIL;
    }

    device->priv = (struct HdfObject *)priv;
    HDF_LOGI("MyDriverBind: success");
    return HDF_SUCCESS;
}

// Init:初始化硬件,注册服务
static int32_t MyDriverInit(struct HdfDeviceObject *device)
{
    struct MyDriverPriv *priv = (struct MyDriverPriv *)device->priv;

    // 初始化硬件寄存器
    // ...

    // 注册服务
    device->service = &g_myService;  // 全局服务对象
    HDF_LOGI("MyDriverInit: success");
    return HDF_SUCCESS;
}

// Release:释放资源
static void MyDriverRelease(struct HdfDeviceObject *device)
{
    struct MyDriverPriv *priv = (struct MyDriverPriv *)device->priv;

    if (priv != NULL) {
        OsalMemFree(priv);
        device->priv = NULL;
    }

    device->service = NULL;
    HDF_LOGI("MyDriverRelease: success");
}

// 驱动入口
struct HdfDriverEntry g_myDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "my_driver",
    .Bind = MyDriverBind,
    .Init = MyDriverInit,
    .Release = MyDriverRelease,
};

HDF_INIT(g_myDriverEntry);

生命周期管理要点:

  • Bind:只做资源分配和配置解析,不操作硬件。
  • Init:操作硬件、注册中断、创建服务。
  • Release:逆序释放Init和Bind分配的资源,防止内存泄漏。

2.7 避坑指南

做驱动开发这些年,我在生命周期管理上栽过不少跟头。分享几个典型的坑:

  1. Bind里操作硬件:设备还没上电,直接读写寄存器导致系统挂死。
  2. Release里忘记释放priv:驱动卸载后内存泄漏,反复加载卸载几次后系统OOM。
  3. Init失败后没有清理Bind分配的资源:导致部分资源泄漏。
  4. 多个设备共用一个驱动对象:没有区分device->priv,导致数据错乱。

我曾经遇到一个最隐蔽的bug:驱动Init成功,但上层调用Dispatch时发现service指针是空的。查了两天才发现,是HCS配置里设备节点和驱动名称不匹配,导致驱动虽然加载了,但设备对象没有正确关联到服务。从那以后,我每次写驱动都会在Init最后加一句日志打印service地址,确认服务注册成功。

2.8 小结

四个对象的关系,说白了就是一句话:驱动对象生设备,设备对象挂服务,节点对象管匹配。你把这个关系刻在脑子里,HDF的代码就不再是一团乱麻了。