3、HDF核心概念:驱动对象、设备对象、服务接口
好,咱们今天来聊聊HDF框架里最核心的三个概念。说实话,我刚接触HDF那会儿,也被这三个东西绕得有点晕——驱动对象、设备对象、服务接口,听起来都差不多,到底谁管谁?
我个人的理解是:驱动对象是“人”,设备对象是“工具”,服务接口是“操作手册”。你想想看,一个工人(驱动)要操作一台机器(设备),总得知道怎么按按钮(接口)吧?HDF就是帮你把这套逻辑理清楚。
3.1 驱动对象(DriverObject)——驱动的“身份证”
驱动对象,说白了就是驱动在HDF框架里的“户口本”。每个驱动加载到系统里,都会对应一个DriverObject结构体。它记录了驱动的名字、初始化函数、绑定函数、以及它要管理哪些设备。
我记得第一次写HDF驱动时,上来就写Bind和Init,结果忘了注册驱动对象,系统根本找不到我的驱动。嗯,这里要注意:没有DriverObject,驱动就是个“黑户”。
核心结构体定义(简化版):
struct DriverObject {
const char *driverName; // 驱动名称,必须唯一
int32_t (*Bind)(struct HdfDeviceObject *device); // 绑定设备
int32_t (*Init)(struct HdfDeviceObject *device); // 初始化设备
void (*Release)(struct HdfDeviceObject *device); // 释放设备
struct DeviceObject **deviceObjects; // 管理的设备对象列表
uint32_t deviceCount; // 设备数量
};
你看,这个结构体里最关键的就是三个函数指针:Bind、Init、Release。它们构成了驱动的生命周期。
- Bind:驱动和设备“配对”的地方。我习惯在这里做资源预分配。
- Init:真正的初始化工作,比如注册中断、申请内存。
- Release:卸载时清理,千万别漏了,否则内存泄漏等着你。
避坑指南:我曾经在Init里申请了DMA缓冲区,但在Release里忘了释放。结果驱动反复加载卸载几次后,系统直接OOM。所以我的习惯是:在写Init的同时,就把Release的对应清理代码写好,成对出现,不容易漏。
3.2 设备对象(DeviceObject)——硬件的“影子”
设备对象,就是硬件设备在软件层的“影子”。每个物理设备(比如一个I2C传感器、一个GPIO按键)在HDF里都对应一个DeviceObject。
你可能会问:驱动对象和设备对象,到底谁是谁?我打个比方:
- 驱动对象 = 一个万能遥控器(可以控制多种设备)
- 设备对象 = 你家里的电视机、空调、音响(每个设备有自己的属性)
一个驱动对象可以管理多个设备对象。比如一个“通用GPIO驱动”可以控制板子上的十几个GPIO引脚,每个引脚就是一个设备对象。
设备对象结构体:
struct DeviceObject {
struct DriverObject *driver; // 所属的驱动对象
struct HdfDeviceObject *property; // 设备属性(来自HCS配置)
uint32_t deviceId; // 设备ID,用于区分同类型设备
void *priv; // 私有数据,我通常放硬件寄存器基址
};
这里有个关键点:priv指针。它是个万能口袋,你可以把任何硬件相关的数据塞进去。我一般用它来存放:
- 寄存器基地址
- 中断号
- 设备状态标志
- 自定义的锁或信号量
注意:设备对象的创建和销毁,通常由HDF框架自动完成。你只需要在HCS配置文件中描述好设备树,框架会帮你生成对应的DeviceObject。千万别手动去new一个DeviceObject,否则框架管理会乱套。
3.3 服务接口(ServiceInterface)——驱动的“API手册”
服务接口,就是驱动对外暴露的功能函数。上层应用(比如一个APP)想控制硬件,不能直接操作寄存器,必须通过服务接口。
我刚开始做驱动时,总觉得服务接口就是“把寄存器读写封装一下”。后来踩了坑才发现,服务接口的设计直接决定了驱动的易用性和安全性。
HDF里定义服务接口的方式很灵活,我常用的是通过HdfDeviceObject的service指针来暴露:
// 定义服务接口结构体
struct GpioService {
int32_t (*SetDir)(uint32_t gpio, uint16_t dir);
int32_t (*Write)(uint32_t gpio, uint16_t val);
int32_t (*Read)(uint32_t gpio, uint16_t *val);
};
// 在Bind函数中注册服务
static int32_t GpioBind(struct HdfDeviceObject *device) {
static struct GpioService service = {
.SetDir = GpioSetDirImpl,
.Write = GpioWriteImpl,
.Read = GpioReadImpl,
};
device->service = &service; // 关键:把服务挂到设备对象上
return HDF_SUCCESS;
}
上层调用时,只需要通过设备对象拿到service指针,就能调用驱动功能了。这种设计的好处是:接口与实现分离。你换硬件了?只要接口不变,上层代码一行都不用改。
我的经验:设计服务接口时,尽量做到“原子操作”。比如GPIO驱动,我提供SetDir、Write、Read三个接口就够了。别搞什么SetDirAndWrite这种组合接口,上层自己会组合。接口越简单,越不容易出错。
3.4 三者关系——一张图说清楚
说了这么多,咱们用一张图来总结三者的关系。我画了个简单的结构图,你看完应该就全明白了:
从这张图可以清楚看到:
- DriverObject 在最上层,它负责管理一个或多个 DeviceObject。
- 每个 DeviceObject 对应一个具体的硬件设备,它内部通过 ServiceInterface 向上层提供功能。
- 上层应用只跟 ServiceInterface 打交道,不关心底层是哪个驱动、哪个设备。
3.5 实战中的一点体会
最后,分享一个我实际项目中的例子。当时做一款智能家居网关,需要同时控制温湿度传感器、继电器、LED指示灯。这三个硬件完全不同,但我只写了一个“通用IO驱动”。
怎么做到的?
- 驱动对象只有一个:
IoDriverObject。 - 设备对象有三个:
TempSensorDevice、RelayDevice、LedDevice。 - 每个设备对象暴露的服务接口不同:传感器提供
ReadTemp、继电器提供SwitchOn/Off、LED提供SetBrightness。
你看,这就是HDF的灵活之处。驱动对象复用,设备对象独立,服务接口按需定制。代码结构清晰,后期维护也方便。
总结一句话:驱动对象是“管理者”,设备对象是“被管理者”,服务接口是“管理工具”。三者各司其职,共同构成了HDF驱动模型的核心骨架。
公众号:蓝海资料掘金营,微信deep3321