4. HDF驱动模型:Probe/Remove/Bind/Unbind生命周期与驱动入口函数

驱动开发,说白了就是跟内核打交道。HDF的驱动模型,我理解下来,核心就是一套生命周期管理。你写一个驱动,内核什么时候找到你?什么时候初始化你?什么时候卸载你?这些都得有个规矩。

我个人习惯,拿到一个新平台,先不看具体业务逻辑,先把Probe和Remove这两个函数骨架搭好。为什么?因为这是驱动的“生”与“死”,搞不清楚这个,后面全是坑。

4.1 驱动入口函数:HdfDriverEntry

每个HDF驱动,都必须有一个入口。这个入口就是HdfDriverEntry结构体。它告诉HDF框架:我这个驱动叫什么名字,初始化、绑定的函数在哪里。

核心结构体定义:

struct HdfDriverEntry {
    uint16_t    moduleVersion;      // 驱动版本号
    const char  *moduleName;        // 驱动名称,必须唯一
    int32_t     (*Bind)(struct HdfDeviceObject *deviceObject);
    int32_t     (*Init)(struct HdfDeviceObject *deviceObject);
    void        (*Release)(struct HdfDeviceObject *deviceObject);
};

嗯,这里要注意。很多新手会搞混Bind和Init的顺序。我刚开始做HDF移植时,就犯过这个错——把硬件初始化全塞进Bind里,结果设备树还没解析完,寄存器就读不到。

函数 调用时机 典型职责
Bind 驱动与设备匹配成功后,最早调用 解析设备树、创建服务接口、注册设备对象
Init Bind成功返回后调用 硬件初始化、中断注册、资源申请
Release 驱动卸载或设备移除时调用 资源释放、反初始化、注销服务

我的习惯:Bind里只做“轻量级”工作,比如保存设备对象指针、解析设备树属性。Init里才做“重量级”工作,比如申请DMA缓冲区、注册中断。这样万一Bind失败,不会留下脏资源。

4.2 Probe与Remove:驱动的“生”与“死”

在HDF框架里,Probe和Remove并不是直接暴露给驱动开发者的。它们被封装在HDF的核心调度层。你写驱动时,实际上是通过Bind和Release来响应这两个生命周期事件。

为什么会这样?我理解,HDF的设计思路是:把设备匹配、资源管理这些通用逻辑交给框架,驱动开发者只关心业务逻辑。你想想看,如果每个驱动都要自己写设备匹配逻辑,那得重复造多少轮子?

4.2.1 Bind:驱动与设备的“握手”

Bind函数,说白了就是驱动和设备第一次见面。HDF框架会根据设备树或配置文件,找到匹配的驱动,然后调用你的Bind。

static int32_t MyDriverBind(struct HdfDeviceObject *deviceObject)
{
    // 1. 检查入参
    if (deviceObject == NULL || deviceObject->property == NULL) {
        HDF_LOGE("MyDriverBind: invalid param");
        return HDF_ERR_INVALID_PARAM;
    }

    // 2. 解析设备树属性
    const char *regAddr = NULL;
    int32_t ret = DeviceNodeGetAttrValue(deviceObject->property, "reg", ®Addr);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("MyDriverBind: get reg failed");
        return ret;
    }

    // 3. 创建服务接口(可选)
    static struct IDeviceIoService myService = {
        .Dispatch = MyDriverDispatch,
    };
    deviceObject->service = &myService;

    HDF_LOGI("MyDriverBind: success");
    return HDF_SUCCESS;
}

我曾经踩过的坑:在Bind里直接调用malloc申请大块内存。如果设备树解析失败,Bind返回错误,但内存已经申请了,没人帮你释放。后来我改成在Init里申请资源,Bind只做指针赋值和属性解析。

4.2.2 Init:硬件“上电”

Init是Bind成功后的下一步。这时候设备树已经解析完了,你可以放心地操作硬件寄存器了。

static int32_t MyDriverInit(struct HdfDeviceObject *deviceObject)
{
    // 1. 获取私有数据
    struct MyDriverData *data = (struct MyDriverData *)OsalMemAlloc(sizeof(struct MyDriverData));
    if (data == NULL) {
        return HDF_ERR_MALLOC_FAIL;
    }
    (void)memset_s(data, sizeof(struct MyDriverData), 0, sizeof(struct MyDriverData));

    // 2. 映射寄存器地址
    data->regBase = OsalIoRemap(MY_DRIVER_REG_BASE, MY_DRIVER_REG_SIZE);
    if (data->regBase == NULL) {
        OsalMemFree(data);
        return HDF_ERR_IO;
    }

    // 3. 注册中断
    int32_t ret = OsalRegisterIrq(data->irqNum, OSAL_IRQF_TRIGGER_HIGH,
                                  MyDriverIrqHandler, "my_driver", data);
    if (ret != HDF_SUCCESS) {
        OsalIoUnmap(data->regBase);
        OsalMemFree(data);
        return ret;
    }

    // 4. 保存私有数据到设备对象
    deviceObject->priv = (void *)data;

    HDF_LOGI("MyDriverInit: success");
    return HDF_SUCCESS;
}

4.2.3 Release:优雅地“善后”

Release对应的是驱动的卸载。你想想看,如果Release写得不干净,下次加载驱动时,上次残留的中断、内存、寄存器映射都会出问题。

static void MyDriverRelease(struct HdfDeviceObject *deviceObject)
{
    if (deviceObject == NULL || deviceObject->priv == NULL) {
        return;
    }

    struct MyDriverData *data = (struct MyDriverData *)deviceObject->priv;

    // 1. 注销中断
    OsalUnregisterIrq(data->irqNum, data);

    // 2. 取消寄存器映射
    if (data->regBase != NULL) {
        OsalIoUnmap(data->regBase);
    }

    // 3. 释放私有数据
    OsalMemFree(data);
    deviceObject->priv = NULL;

    HDF_LOGI("MyDriverRelease: success");
}

我的建议:Release里一定要把deviceObject->priv置NULL。我曾经遇到过驱动卸载后,另一个模块还持有这个指针,结果访问了野指针,系统直接panic。这种问题很难查,因为崩溃现场离错误源头已经很远了。

4.3 生命周期流程图

下面这张图,是我自己画的生命周期流转图。每次带新人时,我都会先让他们看这张图,再动手写代码。

HDF驱动生命周期流程图 HDF驱动生命周期流转图 驱动加载 Bind(设备匹配) 成功? 失败 卸载驱动 成功 Init(硬件初始化) 驱动运行 卸载事件 Release(资源释放) 驱动卸载完成 说明: Bind失败后,框架不会调用Init和Release,驱动直接卸载。 Init失败后,框架会调用Release清理Bind阶段已分配的资源。

4.4 完整驱动入口示例

把上面几块拼起来,就是一个完整的HDF驱动入口。我个人习惯把Bind、Init、Release都定义成static,只在HdfDriverEntry里暴露指针。

/* my_driver.c */
#include "hdf_log.h"
#include "hdf_device_desc.h"
#include "osal_mem.h"
#include "osal_io.h"
#include "osal_irq.h"

#define HDF_LOG_TAG my_driver

static int32_t MyDriverBind(struct HdfDeviceObject *deviceObject)
{
    // 解析设备树、创建服务
    return HDF_SUCCESS;
}

static int32_t MyDriverInit(struct HdfDeviceObject *deviceObject)
{
    // 硬件初始化、中断注册
    return HDF_SUCCESS;
}

static void MyDriverRelease(struct HdfDeviceObject *deviceObject)
{
    // 资源释放
}

/* 驱动入口,HDF框架通过这个结构体找到你的驱动 */
struct HdfDriverEntry g_myDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "my_driver",
    .Bind = MyDriverBind,
    .Init = MyDriverInit,
    .Release = MyDriverRelease,
};

/* 必须导出这个符号,HDF框架才能链接到 */
HDF_INIT(g_myDriverEntry);

注意:HDF_INIT宏一定要写在源文件最外层,不能写在函数里。我见过有人把它写在main函数里,结果链接时找不到入口,编译报错还一脸懵。

4.5 避坑指南

  • Bind里不要做耗时操作。HDF框架在Bind阶段是串行执行的,你卡住了,其他驱动都得等着。我遇到过有人在Bind里做msleep,结果系统启动慢了3秒。
  • Release里要处理重入。如果驱动被多次卸载,Release可能被调用多次。加个标志位判断,防止重复释放。
  • Init失败时,记得回滚Bind的资源。框架不会帮你做这件事。你在Bind里申请了内存,Init失败了,Release里要把内存释放掉。
  • moduleName要唯一。如果两个驱动的moduleName重名,HDF框架只会加载第一个。我曾经因为这个排查了半天,最后发现是名字跟别人的驱动冲突了。

我的调试小技巧:在Bind、Init、Release的入口和出口都加上HDF_LOGI打印。这样通过查看内核日志,就能清楚地看到驱动生命周期走到了哪一步。出问题时,看日志就能定位是Bind失败还是Init失败。