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", ®Base, 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相反的操作:
- 先释放中断、DMA等硬件资源
- 再释放内存、取消映射
- 最后清空服务接口指针
避坑指南:我曾经在Release里忘记检查指针是否为NULL,结果在卸载时直接空指针异常。记住,Release可能在任何状态下被调用,包括Init失败后。所以每个释放操作前都要判空。
4.5 四个函数的调用顺序与关系
这四个函数的调用顺序是固定的:Bind → Init → Release。但有个特殊情况——如果Init失败了,系统会回滚调用Release来清理Bind阶段分配的资源。
我画了一张流程图,帮你理清这个关系:
4.6 实际项目中的经验总结
最后,我把自己在实际项目中积累的一些经验分享给你:
| 函数 | 职责 | 常见错误 | 我的建议 |
|---|---|---|---|
| Bind | 建立驱动与设备的连接 | 在Bind里做资源分配 | 只赋值服务接口,不做重操作 |
| Init | 完成驱动初始化 | 依赖其他未就绪的驱动 | 做好错误处理,失败要回滚 |
| Release | 清理驱动资源 | 忘记判空导致崩溃 | 每个释放操作前都判空 |
个人习惯:我写驱动时,会先在Init里把所有资源分配好,然后统一在Release里释放。如果Init中途失败,我会在失败点直接跳转到Release的清理逻辑。这样代码结构清晰,不容易遗漏。
嗯,关于HDF驱动模型的四个核心函数,今天就聊到这儿。说白了,掌握了Bind、Init、Release这三个函数,你就掌握了HDF驱动的骨架。剩下的就是在骨架里填充具体的业务逻辑了。
公众号:蓝海资料掘金营,微信deep3321