3. HDF驱动框架概述

好,咱们今天来啃HDF驱动模型的四个核心概念。说实话,我刚接触HDF的时候,也被这些术语绕得有点晕。驱动实体、设备对象、服务接口、配置描述——这四个东西到底是什么关系?

我打个比方你就明白了。如果把一个驱动比作一家公司:驱动实体就是公司的营业执照,设备对象是公司的办公场地,服务接口是公司的业务窗口,而配置描述就是公司的规章制度。缺一个,这公司都转不起来。

一句话总结:驱动实体负责注册,设备对象负责实例化,服务接口负责对外交互,配置描述负责参数化。四者协同,构成了HDF驱动的完整生命周期。

3.1 驱动实体(DriverEntry)——驱动的“身份证”

每个HDF驱动,都必须有一个驱动实体。说白了,它就是驱动在HDF框架中的“户口本”。没有它,HDF根本不知道你这个驱动存在。

驱动实体是一个结构体,定义在 hdf_device_desc.h 里。我直接给你看代码,比说一百句都管用:

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

这里我特别想强调一点:moduleName 必须全局唯一。我在项目中遇到过两次驱动加载失败,查了半天,最后发现是两个模块的 moduleName 重名了。HDF 是按名字索引的,重名就直接覆盖,后加载的那个永远起不来。

驱动实体的生命周期是这样的:

  1. Bind:HDF框架先调用 Bind,把驱动实体和设备对象“绑”在一起。这时候驱动还没真正启动,只是建立关联。
  2. Init:绑定成功后,框架调用 Init。这里才是真正的初始化——申请内存、注册中断、配置寄存器,都在这一步做。
  3. Release:驱动卸载时调用。释放资源、注销中断、归还内存。嗯,这一步很多人容易忘,导致内存泄漏。

我曾经踩过的坑:在 Init 里申请了DMA缓冲区,但在 Release 里忘了释放。结果驱动反复加载卸载几次后,系统内存直接耗尽。从那以后,我写 Release 函数时都会对照 Init 逐项检查,确保“有借有还”。

3.2 设备对象(DeviceObject)——驱动的“实体”

驱动实体是“身份”,设备对象就是“实体”。每个驱动在运行时,都会对应一个 HdfDeviceObject 实例。你可以把它理解成驱动在内存中的“代言人”。

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

设备对象有几个关键字段,我挑重要的说:

字段 作用 我的经验
service 指向服务接口,供上层调用 如果为NULL,上层无法访问这个设备
parent/child 构建设备树层级关系 I2C控制器和挂载的传感器就是父子关系
object 指向驱动的私有数据 我习惯在这里存放硬件寄存器基址、中断号等

你想想看,驱动实体是“类”,设备对象就是“实例”。一个驱动实体可以被多个设备对象引用——比如同一个GPIO控制器驱动,管理着多个GPIO引脚,每个引脚就是一个设备对象。

3.3 服务接口(Service)——驱动的“窗口”

驱动写好了,上层应用怎么用?靠服务接口。服务接口是驱动暴露给上层调用的函数集合,说白了就是驱动的API。

HDF 定义了一个基础接口 IDeviceIoService,但实际开发中我们通常要自己定义子类:

// 自定义传感器服务接口
struct SensorService {
    struct IDeviceIoService ioService;  // 继承基础服务
    int32_t (*ReadData)(struct SensorService *self, uint8_t *buf, uint32_t len);
    int32_t (*SetConfig)(struct SensorService *self, uint32_t cmd, void *arg);
    int32_t (*Enable)(struct SensorService *self, bool enable);
};

服务接口的注册时机很关键。我个人习惯在 Bind 阶段就把服务接口挂到设备对象上:

static int32_t SensorBind(struct HdfDeviceObject *device) {
    static struct SensorService service = {
        .ioService.Dispatch = SensorDispatch,  // 统一分发函数
        .ReadData = SensorReadData,
        .SetConfig = SensorSetConfig,
        .Enable = SensorEnable,
    };
    device->service = &service.ioService;  // 挂载服务
    return HDF_SUCCESS;
}

小技巧:服务接口的 Dispatch 函数是统一入口,上层通过 IoService->Dispatch(cmd, data) 调用。我建议把所有命令码定义成枚举,switch-case 分发,这样扩展新功能时不用改接口签名。

3.4 配置描述(HCS)——驱动的“说明书”

最后说 HCS。HCS 是 HDF Configuration Source 的缩写,说白了就是驱动配置文件。为什么需要它?因为驱动里很多参数——比如I2C地址、中断号、采样频率——如果硬编码在代码里,换一个硬件平台就得改代码,太蠢了。

HCS 文件长这样:

root {
    sensor_config {
        imu6050 {
            i2cConfig {
                busId = 3;          // I2C总线号
                regAddr = 0x68;     // 设备地址
                freq = 400000;      // 时钟频率 400KHz
            }
            interruptConfig {
                gpioNum = 47;       // 中断引脚
                irqMode = 1;        // 上升沿触发
            }
            sampleRate = 100;       // 采样率 100Hz
        }
    }
}

HCS 文件经过编译后生成 HCB(二进制)文件,HDF 框架在启动时解析 HCB,把配置值注入到驱动中。驱动里通过 HdfGetDeviceConfig() 读取:

static int32_t SensorInit(struct HdfDeviceObject *device) {
    const struct DeviceResourceNode *node = device->property;
    if (node == NULL) return HDF_ERR_INVALID_PARAM;
    
    // 读取I2C配置
    uint16_t busId;
    HdfGetUint16(node, "i2cConfig.busId", &busId, 0);
    
    // 读取中断配置
    uint16_t gpioNum;
    HdfGetUint16(node, "interruptConfig.gpioNum", &gpioNum, 0);
    
    // 初始化硬件...
    return HDF_SUCCESS;
}

注意:HCS 的层级路径必须和代码中读取的路径完全一致,多一个点或少一个点都读不到。我曾经因为路径写错,排查了整整一个下午,最后发现是 "i2cConfig" 写成了 "i2c_config"——HCS 里用的是驼峰命名。

3.5 四者关系全景图

说了这么多,咱们用一张图把四个概念串起来。这张图我画了很多遍,每次给团队新人讲HDF都会拿出来用:

HDF驱动模型核心概念关系图 驱动实体 DriverEntry Bind / Init / Release 设备对象 DeviceObject service / parent / child 服务接口 IDeviceIoService Dispatch / Read / Write 配置描述 HCS / HCB 参数化配置 Bind 绑定 挂载 service 解析注入配置 生命周期流程 1. HCS 解析配置 2. DriverEntry.Bind() 3. 挂载 Service 4. DriverEntry.Init() 5. 上层通过 Service 访问 关键原则 • 一个驱动实体 可对应多个设备对象 • 设备对象必须 挂载服务接口 • HCS 避免硬编码

从这张图你能看到:驱动实体通过 Bind 创建设备对象,设备对象挂载服务接口供上层调用,而配置描述则在初始化阶段为设备对象提供参数。四者环环相扣,缺一不可。

3.6 实战中的常见问题

最后,我总结几个实际开发中容易踩的坑,你遇到了可以少走弯路:

问题 现象 根因 解决方案
驱动加载失败 HDF 报 "Bind failed" moduleName 重复或 HCS 节点名不匹配 检查 moduleName 唯一性,核对 HCS 路径
上层无法访问驱动 IoService 返回 NULL Bind 阶段未挂载 service 在 Bind 中设置 device->service
配置值读不到 HdfGetUint16 返回默认值 HCS 路径写错或节点层级不对 用 hdf-config 工具验证 HCB 内容
内存泄漏 反复加载卸载后内存不足 Release 未释放 Init 申请的资源 对照 Init 逐项释放

我的调试习惯:在 Init 和 Release 函数开头加 HDF_LOGI 打印,加载卸载时看日志就能知道流程是否完整。如果 Init 打印了但 Release 没打印,那肯定有资源没释放。

好了,HDF 驱动模型的四个核心概念就讲到这里。你把这些搞清楚了,后面写具体驱动的时候就会顺手很多。记住:驱动实体是身份,设备对象是实体,服务接口是窗口,配置描述是说明书——四者配合,才能写出一个健壮的 HDF 驱动。