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 生命周期流程图
下面这张图,是我自己画的生命周期流转图。每次带新人时,我都会先让他们看这张图,再动手写代码。
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失败。