4、HDF驱动模型:驱动入口、驱动绑定、驱动初始化、驱动释放

好,咱们今天来聊聊HDF驱动模型里最核心的四个生命周期函数。说白了,一个驱动从加载到卸载,就靠这四个函数撑着。我刚开始接触HDF的时候,觉得这玩意儿不就是注册一下嘛,后来踩了几个坑才明白——顺序错了、参数没对齐,系统直接罢工给你看。

4.1 驱动入口:一切从这里开始

驱动入口,就是系统加载驱动时第一个调用的函数。在HDF里,它叫HdfDriverEntry。你想想看,系统怎么知道你的驱动存在?就是靠这个入口。

核心要点:每个HDF驱动必须实现一个HdfDriverEntry结构体,并导出为全局符号。

// 驱动入口结构体定义
struct HdfDriverEntry g_sampleDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "sample_driver",
    .Bind = SampleDriverBind,
    .Init = SampleDriverInit,
    .Release = SampleDriverRelease,
};

// 导出符号,让HDF框架能找到
HDF_INIT(g_sampleDriverEntry);

这里有个细节我特别想强调——moduleName必须和你在HCS配置里的名字完全一致。我在项目中遇到过,就因为大小写差了一个字母,驱动死活加载不上,查了半天才发现是名字没对齐。

个人习惯:我一般把moduleName定义成宏,这样HCS和代码里用同一个宏,避免手写不一致。

4.2 驱动绑定:把驱动和设备牵上线

驱动绑定,说白了就是告诉系统:我这个驱动能管哪些设备。HDF里通过Bind函数来实现。

为什么会需要这一步?因为一个驱动可能对应多个设备实例。比如你写一个I2C控制器驱动,系统里可能有三个I2C控制器,每个都需要绑定到同一个驱动上。

// 驱动绑定函数示例
static int32_t SampleDriverBind(struct HdfDeviceObject *deviceObject)
{
    if (deviceObject == NULL) {
        HDF_LOGE("SampleDriverBind: deviceObject is NULL");
        return HDF_ERR_INVALID_PARAM;
    }

    // 创建设备服务接口
    static struct ISampleDriverService sampleService = {
        .ioService = {
            .object = { .GetObject = NULL, .Release = NULL },
            .Dispatch = SampleDispatch,
        },
        .WriteData = SampleWriteData,
        .ReadData = SampleReadData,
    };

    // 将服务接口绑定到设备对象
    deviceObject->service = &sampleService.ioService;
    HDF_LOGI("SampleDriverBind: success");
    return HDF_SUCCESS;
}

嗯,这里要注意——Bind函数里不要做太重的初始化工作。我曾经犯过这个错,在Bind里申请了一大块内存,结果设备还没准备好,系统就崩了。Bind只负责建立连接,真正的资源分配放到Init里去做。

避坑指南:我曾经在Bind里调用了HdfDeviceRegisterService,结果导致设备节点重复注册。记住,Bind阶段只做服务接口的赋值,不要主动注册。

4.3 驱动初始化:真正干活的阶段

Init函数,才是驱动真正开始干活的地方。系统在Bind成功之后,会调用Init来完成驱动的初始化工作。

// 驱动初始化函数示例
static int32_t SampleDriverInit(struct HdfDeviceObject *deviceObject)
{
    if (deviceObject == NULL) {
        HDF_LOGE("SampleDriverInit: deviceObject is NULL");
        return HDF_ERR_INVALID_PARAM;
    }

    // 解析HCS配置
    struct DeviceResourceNode *node = deviceObject->property;
    if (node == NULL) {
        HDF_LOGE("SampleDriverInit: property node is NULL");
        return HDF_ERR_INVALID_PARAM;
    }

    // 读取配置参数
    uint32_t regBase;
    int32_t ret = DeviceResourceGetUint32(node, "reg_base", &regBase, 0);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("SampleDriverInit: get reg_base failed");
        return ret;
    }

    // 映射寄存器地址
    deviceObject->priv = (void *)OsalIoRemap(regBase, 0x1000);
    if (deviceObject->priv == NULL) {
        HDF_LOGE("SampleDriverInit: remap register failed");
        return HDF_ERR_MALLOC_FAIL;
    }

    HDF_LOGI("SampleDriverInit: success, regBase=0x%x", regBase);
    return HDF_SUCCESS;
}

你想想看,Init里一般做哪些事?我总结了一下:

  • 资源分配:内存、中断、DMA缓冲区等
  • 硬件初始化:寄存器配置、时钟使能、复位释放
  • 配置解析:从HCS里读取设备参数
  • 服务注册:把驱动提供的服务注册到系统服务框架

重要提醒:Init函数执行时,系统还没有完全启动。不要在这里依赖其他驱动或者用户态服务,否则会死锁。我见过有人直接在Init里调用sleep等待其他驱动,结果整个系统卡死。

4.4 驱动释放:优雅地退出

Release函数,是驱动卸载时的清理工作。很多新手容易忽略这个函数,觉得驱动卸载就卸载呗,管它呢。但实际项目中,不做好释放工作,内存泄漏、资源冲突都是轻的,严重时会导致系统重启。

// 驱动释放函数示例
static void SampleDriverRelease(struct HdfDeviceObject *deviceObject)
{
    if (deviceObject == NULL) {
        HDF_LOGE("SampleDriverRelease: deviceObject is NULL");
        return;
    }

    // 释放寄存器映射
    if (deviceObject->priv != NULL) {
        OsalIoUnmap((void *)deviceObject->priv, 0x1000);
        deviceObject->priv = NULL;
    }

    // 释放服务接口
    deviceObject->service = NULL;

    HDF_LOGI("SampleDriverRelease: success");
}

Release里必须做和Init相反的操作:

  1. 先释放中断、DMA等硬件资源
  2. 再释放内存、取消映射
  3. 最后清空服务接口指针

避坑指南:我曾经在Release里忘记检查指针是否为NULL,结果在卸载时直接空指针异常。记住,Release可能在任何状态下被调用,包括Init失败后。所以每个释放操作前都要判空。

4.5 四个函数的调用顺序与关系

这四个函数的调用顺序是固定的:Bind → Init → Release。但有个特殊情况——如果Init失败了,系统会回滚调用Release来清理Bind阶段分配的资源。

我画了一张流程图,帮你理清这个关系:

HDF驱动生命周期流程图 驱动加载 Bind() 驱动绑定 Bind成功? 失败 返回错误码 Init() 驱动初始化 驱动运行中... 卸载 Release() 驱动释放 注:如果Init失败,系统会自动调用Release清理Bind阶段分配的资源

4.6 实际项目中的经验总结

最后,我把自己在实际项目中积累的一些经验分享给你:

函数 职责 常见错误 我的建议
Bind 建立驱动与设备的连接 在Bind里做资源分配 只赋值服务接口,不做重操作
Init 完成驱动初始化 依赖其他未就绪的驱动 做好错误处理,失败要回滚
Release 清理驱动资源 忘记判空导致崩溃 每个释放操作前都判空

个人习惯:我写驱动时,会先在Init里把所有资源分配好,然后统一在Release里释放。如果Init中途失败,我会在失败点直接跳转到Release的清理逻辑。这样代码结构清晰,不容易遗漏。

嗯,关于HDF驱动模型的四个核心函数,今天就聊到这儿。说白了,掌握了Bind、Init、Release这三个函数,你就掌握了HDF驱动的骨架。剩下的就是在骨架里填充具体的业务逻辑了。


公众号:蓝海资料掘金营,微信deep3321