3. HDF核心概念详解
驱动框架这东西,说白了就是一套标准化的“插拔”规则。你写驱动,本质上是在跟三个核心概念打交道:驱动对象、设备对象、服务接口。再加上一个贯穿始终的消息机制,这四个东西构成了HDF的骨架。
我记得刚接触HDF时,光看头文件里的结构体定义,感觉每个都差不多。后来踩了几个坑才明白——它们各管一摊,分工极其明确。今天我就把这四个概念掰开揉碎了讲给你听。
3.1 驱动对象(HdfDriverEntry)——驱动的“身份证”
每个驱动在HDF里注册时,都需要一个HdfDriverEntry结构体。它告诉框架:你是谁、你从哪来、你要干什么。
struct HdfDriverEntry {
uint16_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。它们按顺序执行,构成了驱动的生命周期。
生命周期三阶段:
- Bind:驱动与设备对象建立关联。此时驱动还没真正“跑起来”,只是告诉框架“这个设备归我管”。
- Init:真正的初始化工作。分配内存、注册中断、配置寄存器,都在这里做。
- Release:驱动卸载时的清理工作。释放资源、注销服务,一个都不能少。
我个人习惯在Init里做大部分初始化,Bind只做最轻量的关联。为什么?因为Bind失败时框架会重试,如果里面放了太重的工作,重试成本就高了。
3.2 设备对象(HdfDeviceObject)——驱动的“工作台”
HdfDeviceObject是驱动与HDF框架交互的桥梁。每个驱动实例都对应一个设备对象,它承载了驱动的所有上下文信息。
struct HdfDeviceObject {
struct IDeviceIoService *service; // 服务接口
struct HdfDeviceObject *parent; // 父设备
struct HdfDeviceObject *children; // 子设备链表
uint32_t deviceId; // 设备ID
const char *deviceName; // 设备名称
struct HdfObject *object; // 私有数据指针
};
这里有个关键字段——object。它是个void *类型的指针,你可以把驱动的私有数据挂在这里。比如你的驱动里有个结构体存了寄存器基址、中断号、缓冲区,统统挂到object上。
我的经验: 在Init里用HdfDeviceObject的object字段挂载私有数据,然后在Bind里通过HdfDeviceObject访问它。这样代码结构清晰,也方便调试。
3.3 服务接口(HdfIoService)——驱动的“对外窗口”
驱动写好了,上层应用怎么调用它?答案就是HdfIoService。它封装了驱动对外暴露的服务接口,用户态程序通过它来与内核态驱动通信。
struct HdfIoService {
struct IDeviceIoService base; // 基础服务接口
struct HdfSession *session; // 会话对象
void *priv; // 私有数据
};
用户态获取服务的方式很简单:
struct HdfIoService *serv = HdfIoServiceBind("HDF_PLATFORM_I2C_0");
if (serv == NULL) {
// 服务获取失败,处理错误
return -1;
}
// 使用服务
serv->base.Dispatch(&serv->base, cmd, data, reply);
// 释放服务
HdfIoServiceRecycle(serv);
你想想看,这个Dispatch接口就是驱动的“命令分发器”。上层传一个命令码和数据下来,驱动根据命令码执行不同操作。我在项目中遇到过一个问题:多个线程同时调用Dispatch,导致驱动内部数据错乱。后来加了个自旋锁才解决。
注意: HdfIoServiceBind返回的服务对象,用完后必须调用HdfIoServiceRecycle释放。否则会造成资源泄漏,长时间运行的系统尤其要小心。
3.4 消息机制——驱动的“神经传导”
HDF的消息机制,说白了就是驱动之间、驱动与用户态之间传递数据的一套标准协议。它基于HdfMessage结构体实现。
struct HdfMessage {
uint32_t code; // 消息码
uint32_t flags; // 标志位
uint32_t dataSize; // 数据大小
const void *data; // 数据指针
uint32_t replySize; // 回复数据大小
void *replyData; // 回复数据指针
};
消息传递的典型流程是这样的:
- 发送方构造
HdfMessage,填充消息码和数据。 - 调用
HdfSendMessage发送消息。 - 接收方在
Dispatch回调中解析消息码,执行对应操作。 - 如果需要回复,填充
replyData和replySize。
嗯,这里要注意:消息机制支持同步和异步两种模式。同步模式下,发送方会阻塞等待回复;异步模式下,发送方发完就走,回复通过回调通知。我建议在驱动初始化阶段用同步模式,确保配置正确;在正常运行阶段用异步模式,避免阻塞影响性能。
3.5 核心关系一览
这四个概念的关系,我用一张图来总结:
3.6 实战对比:四个概念的区别
| 概念 | 核心作用 | 生命周期 | 谁创建 | 典型场景 |
|---|---|---|---|---|
| HdfDriverEntry | 驱动注册与生命周期 | 驱动加载到卸载 | 驱动开发者 | 驱动初始化、资源释放 |
| HdfDeviceObject | 设备上下文与数据挂载 | 设备创建到销毁 | HDF框架 | 存储私有数据、设备树 |
| HdfIoService | 用户态与内核态通信 | 服务绑定到释放 | 用户态程序 | 调用驱动功能、获取数据 |
| HdfMessage | 消息传递与命令分发 | 消息发送到回复 | 调用方 | 驱动间通信、命令交互 |
我曾经在一个项目中,把HdfDeviceObject和HdfDriverEntry搞混了,在Bind里试图访问HdfDeviceObject的service字段,结果发现它还是空的。后来才意识到——Bind阶段服务还没注册呢,得等到Init之后才能用。
避坑指南: 记住这个顺序——Bind建立关联,Init初始化资源,Release清理资源。服务接口在Init之后才可用。别在Bind里做需要服务接口的事情。
这四个概念,你掌握了,HDF驱动框架的骨架就搭起来了。后面写驱动时,你会反复跟它们打交道。多写几个demo,自然就熟了。