3、HDF核心数据结构:HdfDriverEntry、HdfDeviceObject、HdfDevice、IUnknown接口
好,咱们今天来啃HDF驱动框架里最核心的几个数据结构。说实话,我刚接触鸿蒙驱动开发那会儿,看着这几个结构体也是一头雾水——名字长得像,功能又重叠,稍不留神就搞混了。
但后来我悟了:这几个结构体,其实就是驱动在HDF世界里的“身份证”、“户口本”和“工作证”。搞懂了它们,你写驱动时就知道该往哪儿填代码了。
3.1 HdfDriverEntry——驱动的“身份证”
每个驱动都得有一个HdfDriverEntry。它告诉HDF框架:你是谁?你从哪来?你要干啥?
说白了,这就是驱动的入口描述符。框架加载驱动时,第一个找的就是它。
struct HdfDriverEntry {
uint32_t moduleVersion; // 驱动版本号
const char *moduleName; // 驱动名字,必须唯一
int32_t (*Bind)(struct HdfDeviceObject *device); // 绑定
int32_t (*Init)(struct HdfDeviceObject *device); // 初始化
void (*Release)(struct HdfDeviceObject *device); // 释放
};
你看,就5个成员。但每个都关键。
| 成员 | 作用 | 我踩过的坑 |
|---|---|---|
| moduleVersion | 版本号,用于兼容性检查 | 曾经忘了赋值,结果框架一直报版本不匹配 |
| moduleName | 驱动名字,HDF用它来匹配设备 | 名字必须和hcs配置里的一致,大小写敏感 |
| Bind | 绑定阶段,把驱动接口挂到设备对象上 | 这个阶段别做资源申请,容易出问题 |
| Init | 初始化阶段,真正干活的地方 | 我习惯在这里做硬件初始化 |
| Release | 卸载时清理资源 | 别忘了释放中断和DMA缓冲区 |
3.2 HdfDeviceObject——驱动的“户口本”
HdfDeviceObject是驱动和HDF框架之间的通信桥梁。每个驱动实例都有一个对应的HdfDeviceObject。
你想想看,驱动要访问硬件资源、要获取配置信息、要跟其他驱动通信——这些信息都存在HdfDeviceObject里。
struct HdfDeviceObject {
struct IDeviceIoService *service; // 服务接口
struct HdfDevice *device; // 指向HdfDevice
struct HdfDriver *driver; // 指向驱动描述
void *priv; // 私有数据
struct HdfObject *object; // 对象基类
...
};
这里我要特别强调priv指针。它是个void*,你可以把驱动的私有数据结构挂上去。我在项目里经常这么用:
// 定义私有数据结构
struct MyGpioDevice {
uint32_t baseAddr;
uint32_t irqNum;
struct OsalMutex mutex;
};
// 在Init里挂载
int32_t MyGpioInit(struct HdfDeviceObject *device) {
struct MyGpioDevice *priv = OsalMemCalloc(sizeof(struct MyGpioDevice));
priv->baseAddr = 0x20000000;
priv->irqNum = 42;
device->priv = priv; // 挂上去
return HDF_SUCCESS;
}
3.3 HdfDevice——驱动的“工作证”
HdfDevice是HDF框架内部管理的设备节点。它比HdfDeviceObject更底层,包含了设备在框架中的完整生命周期信息。
说实话,普通驱动开发者很少直接操作HdfDevice。它更多是框架内部用的。但理解它有助于你理解HDF的设备管理机制。
struct HdfDevice {
struct HdfObject object; // 对象基类
struct HdfDeviceNode *node; // 设备节点
struct HdfDeviceObject *deviceObject; // 关联的HdfDeviceObject
struct HdfDriver *driver; // 驱动描述
struct HdfDevice *parent; // 父设备
struct HdfDevice *child; // 子设备链表
...
};
你看,HdfDevice里包含了HdfDeviceObject的指针。它们的关系是这样的:
- HdfDevice:框架视角的设备节点,管理设备树
- HdfDeviceObject:驱动视角的设备对象,提供接口
- HdfDriverEntry:驱动入口,定义生命周期回调
3.4 IUnknown接口——驱动的“服务窗口”
IUnknown是HDF里实现驱动间通信的核心接口。它本质上是一个多态接口基类,类似于COM里的IUnknown。
为什么要搞这么复杂?你想想看,一个GPIO驱动要给I2C驱动提供服务,总不能直接调用对方函数吧?那样耦合太深了。IUnknown就是解决这个问题的:通过接口查询,动态获取服务。
struct IUnknown {
int32_t (*QueryInterface)(struct IUnknown *unknown, int32_t ver, void **out);
int32_t (*AddRef)(struct IUnknown *unknown);
int32_t (*Release)(struct IUnknown *unknown);
};
三个方法:
- QueryInterface:查询指定版本的接口
- AddRef:增加引用计数
- Release:减少引用计数,减到0就释放
3.5 实战:写一个完整的驱动骨架
光说不练假把式。咱们把上面四个结构体串起来,写一个完整的驱动骨架:
// my_gpio_driver.c
#include "hdf_device_desc.h"
#include "hdf_log.h"
#define HDF_LOG_TAG my_gpio_driver
// 私有数据结构
struct MyGpioDevice {
uint32_t baseAddr;
uint32_t irqNum;
};
// Bind:挂接口
static int32_t MyGpioBind(struct HdfDeviceObject *device) {
HDF_LOGI("MyGpioBind: enter");
// 这里只做接口挂载,不做资源申请
return HDF_SUCCESS;
}
// Init:初始化
static int32_t MyGpioInit(struct HdfDeviceObject *device) {
HDF_LOGI("MyGpioInit: enter");
struct MyGpioDevice *priv = OsalMemCalloc(sizeof(struct MyGpioDevice));
if (priv == NULL) {
return HDF_ERR_MALLOC_FAIL;
}
// 从hcs配置读取硬件信息
priv->baseAddr = 0x20000000;
priv->irqNum = 42;
// 挂到device上
device->priv = priv;
// 硬件初始化
// ...
HDF_LOGI("MyGpioInit: success");
return HDF_SUCCESS;
}
// Release:释放
static void MyGpioRelease(struct HdfDeviceObject *device) {
HDF_LOGI("MyGpioRelease: enter");
if (device->priv != NULL) {
OsalMemFree(device->priv);
device->priv = NULL;
}
HDF_LOGI("MyGpioRelease: done");
}
// 驱动入口
struct HdfDriverEntry g_myGpioDriverEntry = {
.moduleVersion = 1,
.moduleName = "my_gpio_driver",
.Bind = MyGpioBind,
.Init = MyGpioInit,
.Release = MyGpioRelease,
};
// 注册驱动
HDF_INIT(g_myGpioDriverEntry);
3.6 避坑指南
最后,分享几个我踩过的坑:
- moduleName必须唯一:HDF框架用名字来匹配驱动和设备,重名会导致加载失败。我习惯用"厂商_模块_功能"的命名方式。
- Bind里别做资源申请:Bind阶段框架还没准备好,这时候申请资源容易出问题。资源申请放Init里。
- Release里别忘了清理:priv、中断、DMA、定时器——一个都不能少。我吃过亏,后来写了个清理清单,每次对照着检查。
- IUnknown的引用计数要小心:多线程环境下,AddRef和Release必须成对出现,否则要么内存泄漏,要么野指针崩溃。
嗯,这几个数据结构就讲到这里。你把这些搞明白了,HDF驱动开发就算入门了。剩下的就是多写、多调试、多踩坑——经验就是这么积累出来的。