2、HDF驱动模型核心概念:驱动对象、设备对象、服务对象、驱动入口与出口函数
好,咱们今天聊聊HDF驱动模型里最核心的几个概念。说实话,我刚接触HDF的时候,也被这些对象绕得有点晕。驱动对象、设备对象、服务对象,听起来都差不多,到底有什么区别?
我个人的理解是:驱动对象是“代码”,设备对象是“硬件”,服务对象是“接口”。你想想看,一个驱动要跑起来,总得有代码吧?代码得绑定到具体的硬件上吧?绑定之后还得给上层提供调用接口吧?嗯,这三个对象就是干这三件事的。
2.1 驱动对象(Driver Object)
驱动对象,说白了就是驱动程序的“身份证”。它告诉HDF框架:我这个驱动叫什么名字、能管理哪些设备、初始化函数在哪里、卸载函数在哪里。
在HDF里,驱动对象用 HdfDriverEntry 结构体表示。我刚开始写驱动时,总觉得这个结构体字段太多,后来发现其实核心就三个:moduleName、Bind、Init、Release。
struct HdfDriverEntry g_myDriverEntry = {
.moduleVersion = 1,
.moduleName = "my_driver",
.Bind = MyDriverBind,
.Init = MyDriverInit,
.Release = MyDriverRelease,
};
这里要注意,moduleName 必须和HCS配置里的名字一致。我曾经因为大小写不匹配,折腾了一下午才找到问题。嗯,这种坑踩过一次就记住了。
核心要点:驱动对象是静态定义的,编译时就确定了。它不负责具体硬件操作,只负责告诉框架“我来了,我该怎么被调用”。
2.2 设备对象(Device Object)
设备对象代表的是真实的硬件实体。比如你板子上挂了一个I2C传感器,那这个传感器就是一个设备对象。
设备对象由 DeviceResourceIface 和 HdfDeviceObject 共同描述。前者从HCS配置里读取硬件资源(比如中断号、寄存器地址),后者是运行时设备实例。
我个人习惯把设备对象理解成“硬件资源的容器”。它里面装着:
- 设备属性(从HCS解析来的键值对)
- 服务接口(如果有的话)
- 私有数据(驱动自己用的结构体)
struct HdfDeviceObject {
uint32_t deviceId;
const char *serviceName;
struct DeviceResourceIface *resource;
void *priv; // 驱动私有数据
};
这里有个坑:priv 指针是驱动自己管理的,HDF框架不会帮你释放。我记得有一次写GPIO驱动,忘记在卸载时释放 priv 指向的内存,结果每次insmod/rmmod都泄漏几KB。虽然不大,但积累多了系统就卡了。
避坑指南:设备对象的生命周期由HDF框架管理,但 priv 指向的内存必须由驱动在 Release 函数中手动释放。我曾经因为这个问题导致内存泄漏,排查了很久才发现。
2.3 服务对象(Service Object)
服务对象是驱动暴露给上层应用的接口。说白了,用户态程序不能直接操作硬件,得通过服务对象来间接访问。
在HDF里,服务对象通过 IDeviceIoService 基类实现。驱动可以继承这个基类,添加自己的接口方法。
struct MyDriverService {
struct IDeviceIoService base;
int32_t (*ReadData)(struct MyDriverService *service, uint8_t *buf, uint32_t len);
int32_t (*WriteData)(struct MyDriverService *service, uint8_t *buf, uint32_t len);
};
你想想看,为什么要有服务对象?直接让上层调用驱动函数不行吗?嗯,不行。因为内核态和用户态是隔离的,不能直接调用。服务对象就是这座“桥”。
个人经验:设计服务接口时,尽量保持简单。我见过有人把几十个功能都塞到一个服务对象里,结果接口文档比驱动代码还长。其实大部分场景下,读、写、控制(ioctl)三个接口就够了。
2.4 驱动入口与出口函数
每个HDF驱动都有三个关键函数:Bind、Init、Release。它们就是驱动的“生老病死”。
| 函数 | 调用时机 | 主要职责 |
|---|---|---|
| Bind | 驱动加载时,最先调用 | 将驱动对象绑定到设备对象,创建服务对象 |
| Init | Bind成功后调用 | 初始化硬件、申请资源、注册中断 |
| Release | 驱动卸载时调用 | 释放资源、注销中断、清理内存 |
这三个函数的调用顺序是固定的:Bind → Init → Release。如果Bind失败,Init不会被调用。如果Init失败,Release会被调用来做清理。
static int32_t MyDriverBind(struct HdfDeviceObject *deviceObject)
{
// 创建服务对象并挂到deviceObject上
struct MyDriverService *service = OsalMemAlloc(sizeof(*service));
if (service == NULL) return HDF_ERR_MALLOC_FAIL;
service->ReadData = MyDriverRead;
service->WriteData = MyDriverWrite;
deviceObject->service = &service->base;
return HDF_SUCCESS;
}
static int32_t MyDriverInit(struct HdfDeviceObject *deviceObject)
{
// 初始化硬件,比如配置GPIO、使能时钟
// 从HCS读取资源
struct DeviceResourceIface *resource = deviceObject->resource;
uint32_t gpioNum;
resource->GetUint32(resource, "gpio_num", &gpioNum, 0);
// 注册中断等操作
return HDF_SUCCESS;
}
static void MyDriverRelease(struct HdfDeviceObject *deviceObject)
{
// 释放服务对象
struct MyDriverService *service = (struct MyDriverService *)deviceObject->service;
if (service != NULL) {
OsalMemFree(service);
deviceObject->service = NULL;
}
}
关键点:Bind里只做“轻量级”工作,比如分配内存、创建服务对象。Init里做“重量级”工作,比如操作硬件寄存器。这样设计的好处是:如果硬件初始化失败,Bind已经分配的资源可以在Release里统一释放,不会遗漏。
2.5 三者关系图
为了让你更直观地理解这三个对象的关系,我画了一张图:
从这张图可以看得很清楚:驱动对象通过Bind关联到设备对象,设备对象持有服务对象的指针,用户态程序通过服务对象来操作硬件。三者各司其职,缺一不可。
2.6 小结
嗯,这一章的内容就这些。总结一下:
- 驱动对象:驱动的“身份证”,告诉框架我是谁
- 设备对象:硬件的“代言人”,装着资源和私有数据
- 服务对象:驱动的“窗口”,让上层能调用底层功能
- 入口/出口函数:驱动的生命周期管理,Bind→Init→Release
我个人觉得,理解这三个对象的关系,是写好HDF驱动的第一步。刚开始可能会觉得抽象,但多写几个驱动就自然明白了。就像学开车,一开始觉得离合、油门、刹车配合好难,开多了就变成肌肉记忆了。
小技巧:调试时可以在Bind和Init里加打印日志,观察调用顺序。我经常用 HDF_LOGI("Bind called for %s", deviceObject->serviceName); 来确认流程是否正确。
公众号:蓝海资料掘金营,微信deep3321