3. HDF驱动框架概述
好,咱们今天来啃HDF驱动模型的四个核心概念。说实话,我刚接触HDF的时候,也被这些术语绕得有点晕。驱动实体、设备对象、服务接口、配置描述——这四个东西到底是什么关系?
我打个比方你就明白了。如果把一个驱动比作一家公司:驱动实体就是公司的营业执照,设备对象是公司的办公场地,服务接口是公司的业务窗口,而配置描述就是公司的规章制度。缺一个,这公司都转不起来。
一句话总结:驱动实体负责注册,设备对象负责实例化,服务接口负责对外交互,配置描述负责参数化。四者协同,构成了HDF驱动的完整生命周期。
3.1 驱动实体(DriverEntry)——驱动的“身份证”
每个HDF驱动,都必须有一个驱动实体。说白了,它就是驱动在HDF框架中的“户口本”。没有它,HDF根本不知道你这个驱动存在。
驱动实体是一个结构体,定义在 hdf_device_desc.h 里。我直接给你看代码,比说一百句都管用:
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); // 释放资源
};
这里我特别想强调一点:moduleName 必须全局唯一。我在项目中遇到过两次驱动加载失败,查了半天,最后发现是两个模块的 moduleName 重名了。HDF 是按名字索引的,重名就直接覆盖,后加载的那个永远起不来。
驱动实体的生命周期是这样的:
- Bind:HDF框架先调用 Bind,把驱动实体和设备对象“绑”在一起。这时候驱动还没真正启动,只是建立关联。
- Init:绑定成功后,框架调用 Init。这里才是真正的初始化——申请内存、注册中断、配置寄存器,都在这一步做。
- Release:驱动卸载时调用。释放资源、注销中断、归还内存。嗯,这一步很多人容易忘,导致内存泄漏。
我曾经踩过的坑:在 Init 里申请了DMA缓冲区,但在 Release 里忘了释放。结果驱动反复加载卸载几次后,系统内存直接耗尽。从那以后,我写 Release 函数时都会对照 Init 逐项检查,确保“有借有还”。
3.2 设备对象(DeviceObject)——驱动的“实体”
驱动实体是“身份”,设备对象就是“实体”。每个驱动在运行时,都会对应一个 HdfDeviceObject 实例。你可以把它理解成驱动在内存中的“代言人”。
struct HdfDeviceObject {
struct IDeviceIoService *service; // 服务接口指针
struct HdfDeviceObject *parent; // 父设备对象
struct HdfDeviceObject *child; // 子设备对象链表
uint32_t deviceId; // 设备ID
const char *deviceName; // 设备名称
struct HdfObject *object; // 私有数据指针
};
设备对象有几个关键字段,我挑重要的说:
| 字段 | 作用 | 我的经验 |
|---|---|---|
service |
指向服务接口,供上层调用 | 如果为NULL,上层无法访问这个设备 |
parent/child |
构建设备树层级关系 | I2C控制器和挂载的传感器就是父子关系 |
object |
指向驱动的私有数据 | 我习惯在这里存放硬件寄存器基址、中断号等 |
你想想看,驱动实体是“类”,设备对象就是“实例”。一个驱动实体可以被多个设备对象引用——比如同一个GPIO控制器驱动,管理着多个GPIO引脚,每个引脚就是一个设备对象。
3.3 服务接口(Service)——驱动的“窗口”
驱动写好了,上层应用怎么用?靠服务接口。服务接口是驱动暴露给上层调用的函数集合,说白了就是驱动的API。
HDF 定义了一个基础接口 IDeviceIoService,但实际开发中我们通常要自己定义子类:
// 自定义传感器服务接口
struct SensorService {
struct IDeviceIoService ioService; // 继承基础服务
int32_t (*ReadData)(struct SensorService *self, uint8_t *buf, uint32_t len);
int32_t (*SetConfig)(struct SensorService *self, uint32_t cmd, void *arg);
int32_t (*Enable)(struct SensorService *self, bool enable);
};
服务接口的注册时机很关键。我个人习惯在 Bind 阶段就把服务接口挂到设备对象上:
static int32_t SensorBind(struct HdfDeviceObject *device) {
static struct SensorService service = {
.ioService.Dispatch = SensorDispatch, // 统一分发函数
.ReadData = SensorReadData,
.SetConfig = SensorSetConfig,
.Enable = SensorEnable,
};
device->service = &service.ioService; // 挂载服务
return HDF_SUCCESS;
}
小技巧:服务接口的 Dispatch 函数是统一入口,上层通过 IoService->Dispatch(cmd, data) 调用。我建议把所有命令码定义成枚举,switch-case 分发,这样扩展新功能时不用改接口签名。
3.4 配置描述(HCS)——驱动的“说明书”
最后说 HCS。HCS 是 HDF Configuration Source 的缩写,说白了就是驱动配置文件。为什么需要它?因为驱动里很多参数——比如I2C地址、中断号、采样频率——如果硬编码在代码里,换一个硬件平台就得改代码,太蠢了。
HCS 文件长这样:
root {
sensor_config {
imu6050 {
i2cConfig {
busId = 3; // I2C总线号
regAddr = 0x68; // 设备地址
freq = 400000; // 时钟频率 400KHz
}
interruptConfig {
gpioNum = 47; // 中断引脚
irqMode = 1; // 上升沿触发
}
sampleRate = 100; // 采样率 100Hz
}
}
}
HCS 文件经过编译后生成 HCB(二进制)文件,HDF 框架在启动时解析 HCB,把配置值注入到驱动中。驱动里通过 HdfGetDeviceConfig() 读取:
static int32_t SensorInit(struct HdfDeviceObject *device) {
const struct DeviceResourceNode *node = device->property;
if (node == NULL) return HDF_ERR_INVALID_PARAM;
// 读取I2C配置
uint16_t busId;
HdfGetUint16(node, "i2cConfig.busId", &busId, 0);
// 读取中断配置
uint16_t gpioNum;
HdfGetUint16(node, "interruptConfig.gpioNum", &gpioNum, 0);
// 初始化硬件...
return HDF_SUCCESS;
}
注意:HCS 的层级路径必须和代码中读取的路径完全一致,多一个点或少一个点都读不到。我曾经因为路径写错,排查了整整一个下午,最后发现是 "i2cConfig" 写成了 "i2c_config"——HCS 里用的是驼峰命名。
3.5 四者关系全景图
说了这么多,咱们用一张图把四个概念串起来。这张图我画了很多遍,每次给团队新人讲HDF都会拿出来用:
从这张图你能看到:驱动实体通过 Bind 创建设备对象,设备对象挂载服务接口供上层调用,而配置描述则在初始化阶段为设备对象提供参数。四者环环相扣,缺一不可。
3.6 实战中的常见问题
最后,我总结几个实际开发中容易踩的坑,你遇到了可以少走弯路:
| 问题 | 现象 | 根因 | 解决方案 |
|---|---|---|---|
| 驱动加载失败 | HDF 报 "Bind failed" | moduleName 重复或 HCS 节点名不匹配 | 检查 moduleName 唯一性,核对 HCS 路径 |
| 上层无法访问驱动 | IoService 返回 NULL | Bind 阶段未挂载 service | 在 Bind 中设置 device->service |
| 配置值读不到 | HdfGetUint16 返回默认值 | HCS 路径写错或节点层级不对 | 用 hdf-config 工具验证 HCB 内容 |
| 内存泄漏 | 反复加载卸载后内存不足 | Release 未释放 Init 申请的资源 | 对照 Init 逐项释放 |
我的调试习惯:在 Init 和 Release 函数开头加 HDF_LOGI 打印,加载卸载时看日志就能知道流程是否完整。如果 Init 打印了但 Release 没打印,那肯定有资源没释放。
好了,HDF 驱动模型的四个核心概念就讲到这里。你把这些搞清楚了,后面写具体驱动的时候就会顺手很多。记住:驱动实体是身份,设备对象是实体,服务接口是窗口,配置描述是说明书——四者配合,才能写出一个健壮的 HDF 驱动。