3、HDF驱动模型基础:HDF驱动对象、HDF设备对象、HDF服务对象、驱动入口函数与加载流程
好,咱们今天来聊聊HDF驱动模型的核心骨架。说白了,就是搞清楚驱动在OpenHarmony里到底长什么样、怎么被系统找到、又是怎么跑起来的。
我刚开始接触HDF的时候,其实挺懵的。以前做Linux驱动,一个module_init就完事了。到了HDF这里,突然冒出来三个对象:驱动对象、设备对象、服务对象。我当时就想,搞这么复杂干嘛?后来踩了几个坑才明白——这恰恰是HDF的精髓所在。
3.1 HDF驱动对象:驱动的“身份证”
每个HDF驱动,都必须有一个驱动对象。你可以把它理解成驱动的“身份证”。系统要加载你,首先得找到这张身份证。
驱动对象用结构体 HdfDriverEntry 来描述。我习惯叫它“驱动入口描述符”。
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); // 释放资源
};
这里面最重要的,就是三个函数指针:Bind、Init、Release。它们构成了驱动的生命周期。
关键点:moduleName 必须和配置文件里的名字一致。我曾经因为大小写写错,折腾了一下午才找到问题。嗯,血的教训。
3.2 HDF设备对象:驱动的“工作台”
设备对象 HdfDeviceObject 是驱动和硬件之间的桥梁。你可以把它想象成你的工作台——上面放着各种工具(资源)、图纸(配置信息)。
struct HdfDeviceObject {
struct IDeviceIoService *service; // 服务接口
struct HdfDeviceObject *parent; // 父设备
void *priv; // 私有数据
struct HdfDeviceResource *resource; // 硬件资源
// ... 还有其他字段
};
我个人习惯把硬件相关的信息,比如GPIO号、I2C地址、中断号,都塞到 priv 里。这样代码结构清晰,也方便调试。
小技巧:在Bind阶段,你可以把设备对象保存到全局变量里。这样Init和Release阶段就能直接用了。我早期没这么做,结果每次都要从参数里取,代码写得很啰嗦。
3.3 HDF服务对象:驱动的“对外窗口”
服务对象 IDeviceIoService 是驱动暴露给上层的能力。上层应用或者框架层,通过这个对象来调用你的驱动。
说白了,这就是驱动的API接口。你提供什么功能,全看你怎么设计这个服务对象。
struct SensorService {
struct IDeviceIoService ioService; // 必须继承这个
int32_t (*Open)(void);
int32_t (*Close)(void);
int32_t (*ReadData)(struct SensorData *data);
};
我做过一个温湿度传感器驱动,服务对象里就三个接口:打开、读取、关闭。简单明了。你想想看,上层调用方根本不需要知道底层I2C怎么操作,它只需要调用 ReadData 就行了。
3.4 驱动入口函数与加载流程
好,现在我们把三个对象串起来,看看驱动到底是怎么加载的。
加载流程分三步走:
- Bind阶段:系统调用你的Bind函数。这时候你要做的是:创建设备对象、绑定服务接口。说白了,就是搭架子。
- Init阶段:系统调用Init函数。这时候你要做的是:初始化硬件、注册中断、分配内存。说白了,就是真正干活。
- Release阶段:系统卸载驱动时调用。你要做的是:释放资源、关闭硬件。说白了,就是打扫战场。
注意:Bind和Init是分开的。Bind失败,Init不会执行。Init失败,Release会被调用。这个顺序一定要记清楚。我曾经在Init里分配了内存,结果Bind失败后忘了释放,导致内存泄漏。后来加了个标志位才解决。
下面这张图,是我自己画的加载流程。你看一眼就明白了。
你看,流程其实不复杂。关键是每个阶段该做什么,心里要有数。
3.5 实战:一个简单的GPIO驱动骨架
光说不练假把式。我给你写个最简单的GPIO驱动骨架,你感受一下。
// gpio_driver.c
#include "hdf_log.h"
#include "hdf_device_desc.h"
// 服务对象
struct GpioService {
struct IDeviceIoService ioService;
int gpioNum;
};
// Bind:绑定服务
static int32_t GpioBind(struct HdfDeviceObject *device)
{
struct GpioService *service = NULL;
service = (struct GpioService *)OsalMemCalloc(sizeof(struct GpioService));
if (service == NULL) {
HDF_LOGE("GpioBind: malloc failed");
return HDF_FAILURE;
}
service->gpioNum = 123; // 假设GPIO号
device->service = &service->ioService;
HDF_LOGI("GpioBind: success");
return HDF_SUCCESS;
}
// Init:初始化硬件
static int32_t GpioInit(struct HdfDeviceObject *device)
{
struct GpioService *service = (struct GpioService *)device->service;
// 这里做真正的GPIO初始化
HDF_LOGI("GpioInit: gpio %d initialized", service->gpioNum);
return HDF_SUCCESS;
}
// Release:释放资源
static void GpioRelease(struct HdfDeviceObject *device)
{
struct GpioService *service = (struct GpioService *)device->service;
if (service != NULL) {
OsalMemFree(service);
}
HDF_LOGI("GpioRelease: done");
}
// 驱动入口
struct HdfDriverEntry g_gpioDriverEntry = {
.moduleVersion = 1,
.moduleName = "HDF_GPIO_DEMO",
.Bind = GpioBind,
.Init = GpioInit,
.Release = GpioRelease,
};
// 注册驱动
HDF_INIT(g_gpioDriverEntry);
个人经验:我建议你在Bind阶段只做内存分配和服务绑定,别碰硬件。硬件初始化放到Init里。为什么?因为Bind失败时,系统不会调用Release。如果你在Bind里操作了硬件,万一失败,硬件状态就乱了。
3.6 避坑指南
最后,分享几个我踩过的坑:
- moduleName重复:系统里不能有两个同名的驱动。我曾经复制粘贴代码,忘了改名字,结果两个驱动互相打架。
- Bind里别做耗时操作:系统加载驱动是有超时机制的。你在Bind里sleep 5秒,系统直接把你踢了。
- Release要幂等:Release可能被调用多次。你释放过的资源,再释放一次不能崩溃。加个标志位判断一下。
- 日志要打全:每个阶段都打日志。出问题的时候,看日志就知道卡在哪一步了。我习惯在Bind、Init、Release的入口和出口都打日志。
好了,HDF驱动模型的基础就这些。你把这个骨架跑通了,后面写传感器驱动就顺了。
公众号:蓝海资料掘金营,微信deep3321