3. HDF 核心概念:驱动对象、设备对象、服务接口与 HCS 解析

好,咱们进入 HDF 框架的核心地带。说实话,我刚接触 HDF 时,也被这几个概念绕得有点晕——驱动对象、设备对象、服务接口,还有那个 HCS 配置文件。它们到底什么关系?怎么配合?

我个人的理解是:驱动对象是“人”,设备对象是“岗位”,服务接口是“技能”,HCS 是“招聘简章”。你想想看,一个驱动要干活,总得知道自己管哪个设备、提供什么能力、怎么被系统发现吧?HDF 把这四件事拆得清清楚楚。

3.1 驱动对象(DriverObject)——驱动的“身份证”

每个 HDF 驱动在加载时,内核都会给它分配一个 DriverObject。这东西记录了驱动的名字、厂商、版本,以及最重要的——生命周期回调函数。

核心结构体:

struct DriverObject {
    uint32_t      objectId;       // 对象ID,全局唯一
    const char    *driverName;    // 驱动名称,如 "hdf_uart"
    const char    *vendorName;    // 厂商名称
    uint32_t      version;        // 驱动版本号
    int32_t       (*Bind)(struct HdfDeviceObject *device);
    int32_t       (*Init)(struct HdfDeviceObject *device);
    void          (*Release)(struct HdfDeviceObject *device);
    struct DriverObject *next;    // 链表指针,用于挂载多个驱动
};

这里我重点说三个回调:

  • Bind:驱动和设备“配对”。系统读到 HCS 配置后,调用 Bind 把驱动对象绑定到具体的设备对象上。我在项目中遇到过 Bind 返回失败的情况,查了半天发现是 HCS 里的设备ID写错了。
  • Init:真正的初始化。分配内存、注册中断、配置寄存器,都在这里做。
  • Release:卸载时清理。嗯,这个很多人容易忘,我曾经就因为 Release 里没释放 DMA 缓冲区,导致系统反复加载卸载后内存泄漏。

我的习惯:在 Bind 里只做轻量级校验,别搞复杂逻辑。Init 才是干重活的地方。这样如果设备不存在,Bind 快速失败,不会拖慢系统启动。

3.2 设备对象(DeviceObject)——驱动的“工位”

设备对象描述的是硬件实例。一个 UART 控制器、一个 GPIO 引脚组,在 HDF 里都对应一个 HdfDeviceObject。它包含了设备号、资源信息、私有数据指针等。

struct HdfDeviceObject {
    uint32_t          deviceId;       // 设备ID,与HCS中的device_id对应
    uint32_t          deviceClass;    // 设备类别,如 DISPLAY、INPUT、SENSOR
    struct DeviceResourceNode *property; // 指向HCS解析后的属性树
    void             *priv;           // 私有数据,驱动自己用
    struct HdfDeviceObject *next;     // 链表指针
};

注意这个 property 字段——它指向 HCS 配置解析后的属性节点。说白了,你在 HCS 里写的那些 reg_base、irq_num,最终都挂在这里。驱动通过 DeviceResourceNode 接口读取配置,不用自己解析文本。

避坑指南:我曾经在适配一款传感器时,发现 Init 里读到的 property 是空指针。排查后发现 HCS 文件里设备节点的 match_attr 写错了,导致驱动找不到对应的配置块。记住:HCS 里的 match_attr 必须和驱动代码里调用 DeviceResourceGet 时传入的 key 完全一致。

3.3 服务接口(ServiceInterface)——驱动的“对外窗口”

驱动不能只闷头干活,还得给上层提供调用入口。HDF 的服务接口机制,说白了就是把驱动的能力暴露给用户态或其他内核模块。

服务接口的定义很简单:

struct HdfServiceInterface {
    const char *serviceName;   // 服务名称,如 "uart_service_0"
    void       *(*Create)(struct HdfDeviceObject *device);
    int32_t    (*Dispatch)(struct HdfDeviceIoService *service, 
                           int32_t cmd, 
                           struct HdfSBuf *data, 
                           struct HdfSBuf *reply);
};

这里 Dispatch 是核心——上层通过它发送命令码(cmd)和数据(data),驱动处理后返回结果(reply)。我习惯把命令码定义成枚举,比如:

enum UartCmd {
    UART_CMD_OPEN,
    UART_CMD_CLOSE,
    UART_CMD_SET_BAUD,
    UART_CMD_SEND,
    UART_CMD_RECV,
};

这样上层调用时语义清晰,驱动里用 switch-case 分发也方便。

服务注册流程:

  1. 在 Init 里调用 HdfDeviceRegisterService(device, serviceInterface)
  2. 系统把服务名和接口指针注册到全局服务表
  3. 上层通过 HdfDeviceServiceGet("uart_service_0") 获取服务句柄
  4. 调用 service->Dispatch(...) 完成通信

3.4 HCS 配置解析——驱动的“说明书”

HCS(HDF Configuration Source)是 HDF 的专属配置文件格式。它用缩进和键值对描述硬件信息,比 DTS(Device Tree Source)更简洁,而且支持条件编译和宏展开。

一个典型的 HCS 片段长这样:

root {
    uart_config {
        match_attr = "hdf_uart_config";
        device_id = 0;
        reg_base = 0xFE010000;
        reg_size = 0x1000;
        irq_num = 32;
        baud_rate = 115200;
        data_bits = 8;
        stop_bits = 1;
        parity = "none";
    };
};

驱动里怎么读?用 DeviceResourceGet 系列 API:

// 在 Init 中
struct DeviceResourceNode *node = device->property;
uint32_t regBase;
int32_t ret = DeviceResourceGetUint32(node, "reg_base", ®Base, 0);
if (ret != HDF_SUCCESS) {
    HDF_LOGE("Failed to get reg_base");
    return HDF_FAILURE;
}

我建议:把所有 HCS 键名定义成宏,放在一个头文件里。比如 #define HCS_KEY_REG_BASE "reg_base"。这样如果配置项改名,只需要改一处,不会出现字符串散落在代码里到处找的尴尬。

3.5 四者关系全景图

说了这么多,咱们用一张图把关系理清楚:

HDF 核心概念关系图 HDF 核心概念关系图 HCS 配置文件 DriverObject(驱动对象) Bind() DeviceObject(设备对象) RegisterService() ServiceInterface(服务接口) Dispatch() 上层应用 / 用户态服务 读取 property HCS 解析后挂载到 DeviceObject.property,驱动通过 DeviceResourceGet 读取配置

从上图能看明白:HCS 是源头,驱动对象通过 Bind 绑定到设备对象,设备对象持有 HCS 解析后的属性,驱动初始化完成后注册服务接口,上层通过 Dispatch 与驱动交互。环环相扣,缺一不可。

3.6 关键对比:HDF vs 传统 Linux 驱动模型

为了帮你更快理解,我列个对比表:

概念 HDF 传统 Linux 驱动
驱动描述 DriverObject(结构体 + 回调) file_operations + platform_driver
设备描述 HdfDeviceObject(属性树挂载) device_node(DTS 解析)
配置方式 HCS(缩进式,支持宏) DTS(设备树,语法更严格)
服务暴露 ServiceInterface + Dispatch ioctl / sysfs / uevent
生命周期 Bind → Init → Release probe → remove

说白了,HDF 把 Linux 驱动里那些“约定俗成”的东西,用一套标准框架固化下来了。你只要按这个套路走,代码结构自然清晰,跨平台移植也方便。

本章小结:

  • DriverObject 是驱动的“身份证”,包含 Bind/Init/Release 三个核心回调
  • DeviceObject 是硬件实例的抽象,通过 property 持有 HCS 配置
  • ServiceInterface 是驱动的对外窗口,通过 Dispatch 处理上层请求
  • HCS 是配置源头,用缩进和键值对描述硬件信息,驱动通过 DeviceResourceGet 读取