4、驱动入口与初始化:驱动入口函数实现、Bind/Init/Release接口、驱动加载流程

驱动入口,说白了就是操作系统认识你这个驱动的第一道门。鸿蒙的HDF框架里,这个门长什么样?怎么开?开完之后干什么?今天咱们就把这事彻底聊透。

我个人习惯,写驱动之前先想清楚三件事:什么时候加载?加载后做什么?卸载时怎么打扫干净? 这三个问题对应到HDF里,就是Bind、Init、Release三个接口。嗯,咱们一个一个来看。

4.1 驱动入口函数:从哪开始?

每个HDF驱动都必须有一个入口描述结构。这个结构告诉框架:你是谁,你提供什么能力,你的生命周期回调在哪。

先看一个最简的入口定义:

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

// 必须用这个宏注册
HDF_INIT(g_sampleDriverEntry);

这里有个关键点——HDF_INIT 宏。它会把你的驱动入口放到特定的段里,HDF框架启动时会扫描这个段,找到所有驱动。我曾经见过有人忘了加这个宏,结果驱动死活加载不上,查了两天才发现是这问题。

核心要点: 每个HDF驱动必须有且仅有一个 HdfDriverEntry 结构,并且必须用 HDF_INIT 注册。这是框架识别驱动的唯一入口。

4.2 Bind接口:驱动与设备的绑定

Bind是驱动加载的第一个回调。它的职责很简单:把你的驱动对象挂到设备对象上。

说白了,就是牵线搭桥。驱动是个服务提供者,设备是个硬件实体,Bind就是把它们俩绑在一起。

static int32_t SampleDriverBind(struct HdfDeviceObject *deviceObject)
{
    // 驱动私有数据结构
    struct SampleDriverData *driverData = NULL;

    if (deviceObject == NULL) {
        HDF_LOGE("SampleDriverBind: deviceObject is NULL");
        return HDF_ERR_INVALID_PARAM;
    }

    // 分配驱动私有数据
    driverData = (struct SampleDriverData *)OsalMemAlloc(sizeof(struct SampleDriverData));
    if (driverData == NULL) {
        HDF_LOGE("SampleDriverBind: failed to alloc driver data");
        return HDF_ERR_MALLOC_FAIL;
    }

    // 初始化驱动数据
    driverData->deviceIndex = 0;
    driverData->regBase = 0x20000000;
    driverData->irqNum = 42;

    // 挂到设备对象上
    deviceObject->service = driverData;

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

注意看最后一行——deviceObject->service = driverData。这就是绑定的本质。以后框架或者其他服务想访问你的驱动,通过这个service指针就能找到你的私有数据。

我的经验: Bind里只做资源分配和数据结构初始化,千万别在这里操作硬件。硬件操作放到Init里。为什么?因为Bind阶段设备可能还没上电,你操作硬件就是给自己挖坑。

4.3 Init接口:驱动真正的初始化

Bind做完之后,框架会调用Init。这才是驱动真正开始干活的地方。

Init里通常做三件事:

  1. 硬件初始化——配置寄存器、使能时钟、设置中断
  2. 注册服务——把驱动能力暴露给上层
  3. 创建设备节点——让用户态能访问
static int32_t SampleDriverInit(struct HdfDeviceObject *deviceObject)
{
    struct SampleDriverData *driverData = NULL;

    if (deviceObject == NULL || deviceObject->service == NULL) {
        HDF_LOGE("SampleDriverInit: invalid param");
        return HDF_ERR_INVALID_PARAM;
    }

    driverData = (struct SampleDriverData *)deviceObject->service;

    // 1. 硬件初始化:映射寄存器地址
    driverData->regBaseVir = OsalIoRemap(driverData->regBase, 0x1000);
    if (driverData->regBaseVir == NULL) {
        HDF_LOGE("SampleDriverInit: IoRemap failed");
        return HDF_ERR_IO;
    }

    // 2. 配置硬件寄存器(示例:使能设备)
    uint32_t regVal = OSAL_READL(driverData->regBaseVir);
    regVal |= 0x01;  // 使能位
    OSAL_WRITEL(regVal, driverData->regBaseVir);

    // 3. 注册中断
    int32_t ret = OsalRegisterIrq(driverData->irqNum, OSAL_IRQF_TRIGGER_HIGH,
                                  SampleDriverIrqHandler, "sample_driver", driverData);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("SampleDriverInit: register irq failed");
        return ret;
    }

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

这里有个细节——deviceObject->service 在Bind里已经被赋值了,Init里直接拿过来用就行。你想想看,如果Bind没做好,Init里拿到的service就是NULL,那整个驱动就崩了。所以Bind和Init的配合很重要。

避坑指南: 我曾经在一个项目里,Init函数返回了成功,但实际上硬件初始化失败了。结果上层应用调用驱动时直接死机。后来我养成了一个习惯:Init里每一步都要检查返回值,任何一个失败都应该返回错误码,让框架知道驱动没准备好。

4.4 Release接口:优雅地退出

驱动卸载时,Release会被调用。它的任务就是把Bind和Init里分配的资源全部释放。

static void SampleDriverRelease(struct HdfDeviceObject *deviceObject)
{
    struct SampleDriverData *driverData = NULL;

    if (deviceObject == NULL || deviceObject->service == NULL) {
        HDF_LOGE("SampleDriverRelease: invalid param");
        return;
    }

    driverData = (struct SampleDriverData *)deviceObject->service;

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

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

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

    HDF_LOGI("SampleDriverRelease: success");
}

Release里释放的顺序要和Init里分配的顺序相反。这是基本原则——后分配的先释放,避免出现悬空指针。

我的习惯: 每次写完Release,我都会对照Bind和Init逐行检查:每个malloc有没有对应的free?每个IoRemap有没有对应的IoUnmap?每个注册的中断有没有注销?漏掉一个,系统就可能内存泄漏或者崩溃。

4.5 驱动加载流程:从入口到就绪

现在我们把三个接口串起来,看看HDF框架加载驱动的完整流程。

HDF驱动加载完整流程 系统启动 HDF框架初始化 扫描HDF_INIT注册的驱动 调用 Bind 接口 调用 Init 接口 驱动就绪,提供服务 关键说明 • Bind失败 → 不调用Init • Init失败 → 驱动不可用 • Release在卸载时调用

这个流程其实很清晰。我总结一下关键点:

阶段 调用接口 主要工作 失败后果
1. 框架启动 无 扫描HDF_INIT段,收集所有驱动入口 驱动不被识别
2. 绑定阶段 Bind 分配私有数据,挂接到设备对象 驱动加载终止
3. 初始化阶段 Init 硬件初始化,注册中断和服务 驱动不可用,但已加载
4. 运行阶段 无 提供驱动能力给上层 —
5. 卸载阶段 Release 释放所有资源 资源泄漏
核心原则: Bind只做数据准备,Init做硬件操作,Release做资源清理。三个接口各司其职,不要越界。我见过有人把硬件初始化写在Bind里,结果设备还没上电就操作寄存器,直接导致系统挂死。

4.6 实战中的几个坑

最后分享几个我踩过的坑,希望能帮你少走弯路。

  • Bind返回成功但Init失败: 这种情况下驱动已经被加载了,但不可用。上层调用时可能拿到一个半残的对象。我的做法是在Init失败时,主动调用Release清理资源,然后返回错误。
  • Release里忘记置空指针: 释放内存后,记得把指针置NULL。否则如果其他地方还持有这个指针,就成了悬空指针,随时可能崩溃。
  • 多个驱动共用一个入口: 有些场景下,一个驱动要管理多个同类型设备。这时候Bind里要根据设备ID分配不同的私有数据。我习惯在Bind里加一个设备索引判断,避免数据错乱。
曾经有一次: 我在Release里忘了调用OsalIoUnmap,结果驱动卸载后寄存器地址映射还在。下次加载时,映射失败,驱动直接罢工。查了两天才发现是资源没释放干净。从那以后,我写Release都是对照Bind和Init逐行检查,一个都不能漏。

驱动入口和初始化,说白了就是三板斧:Bind搭架子,Init填内容,Release拆干净。把这套流程吃透了,HDF驱动开发就算入门了。