3、HDF驱动框架入门:核心概念、驱动模型与开发流程
各位同学,欢迎来到HDF驱动框架的第一课。说实话,HDF(Hardware Driver Foundation)是OpenHarmony硬件生态的基石。你想想看,没有一套统一的驱动框架,每个芯片厂商各写各的,那系统还怎么玩?
我刚开始接触HDF时,第一反应是“这不就是Linux驱动那套吗?”后来踩了几个坑才发现——HDF有自己的脾气。它既要兼容多内核(Linux、LiteOS),又要做到硬件抽象层与内核解耦,设计上做了不少取舍。
今天我们就从三个角度把它啃透:核心概念、驱动模型、开发流程。嗯,开始吧。
3.1 HDF核心概念
先搞清楚几个关键术语。这些概念你后面写代码天天都会碰到。
| 概念 | 说明 | 我的一点理解 |
|---|---|---|
| HDF | Hardware Driver Foundation,硬件驱动框架 | 相当于驱动界的“操作系统”,统一管理所有驱动 |
| Driver | 驱动实体,负责硬件操作 | 你写的具体代码,比如点亮一个LED |
| Device | 设备对象,描述硬件资源 | 驱动操作的目标,比如GPIO控制器 |
| Service | 驱动对外暴露的服务接口 | 上层应用通过Service调驱动,不直接碰硬件 |
| HCS | HDF Configuration Source,设备配置源文件 | 用文本描述硬件拓扑,HDF自动解析 |
| HDI | Hardware Device Interface,硬件设备接口 | 定义驱动与上层之间的契约 |
核心要点:HDF把驱动拆成“设备描述”和“驱动实现”两部分。设备描述写在HCS文件里,驱动实现用C代码写。两者通过HDF框架自动绑定。这样做的好处是——换硬件时只改HCS,不用改驱动代码。
我个人习惯把HDF比作“驱动界的微服务架构”。每个驱动是一个独立服务,通过消息机制通信。你想想看,这样设计的好处是什么?解耦、可复用、易扩展。
3.2 驱动模型:分层与对象
HDF的驱动模型分三层,我画了张图帮你理解。
这张图你看懂了吗?从上到下依次是:
- 应用层:你的App或系统服务,通过HDI接口调用驱动。它不关心底层是GPIO还是I2C。
- 驱动服务层:你写的驱动代码。实现HDI接口,管理设备对象,处理业务逻辑。
- HDF核心层:框架的心脏。负责驱动加载、设备管理、消息分发、生命周期管理。这部分你不用改,直接用。
- 内核适配层:封装不同内核的差异。比如Linux用file_operations,LiteOS用另一套。HDF帮你抹平了。
- 硬件层:真实的物理设备。GPIO、UART、SPI……
我的经验:刚开始写HDF驱动时,最容易犯的错是“把所有逻辑都塞到驱动服务层”。其实应该把硬件操作封装到内核适配层,服务层只做业务编排。这样换内核时,你只需要改适配层,服务层纹丝不动。
3.3 驱动开发流程:从零到一
好,概念讲完了,咱们来点实际的。一个HDF驱动的完整开发流程是怎样的?我总结为6步。
- 编写HCS配置文件:描述设备硬件信息,包括设备ID、资源、依赖关系。
- 实现驱动入口:实现Bind、Init、Release三个回调函数。
- 实现设备操作接口:根据硬件功能,实现读写、控制等操作。
- 注册驱动服务:将驱动注册到HDF框架,暴露服务给上层。
- 编译构建:将驱动代码编译进内核或动态加载模块。
- 测试验证:通过HDI接口调用驱动,验证功能正确性。
下面我拿一个GPIO点灯的驱动来演示。这是最经典的入门例子。
3.3.1 编写HCS配置文件
HCS文件是HDF的“硬件说明书”。它告诉框架:有哪些设备、设备用什么驱动、资源怎么分配。
/* gpio_led.hcs */
root {
platform {
gpio_led {
match_attr = "hdf_gpio_led";
gpio_num = 10; // GPIO引脚号
active_level = 1; // 高电平点亮
service_name = "hdf_gpio_led_service";
}
}
}
你看,这个文件很简单。定义了GPIO引脚号、电平极性、服务名。HDF启动时会自动解析这个文件,创建设备对象。
注意:HCS文件里match_attr字段必须和驱动代码里的HDF_INIT宏匹配。我曾经因为拼写错误,驱动死活加载不上,查了两天才发现是“gpio”写成了“gpioo”。嗯,这种低级错误,谁还没犯过呢。
3.3.2 实现驱动入口
每个HDF驱动必须实现三个回调函数:Bind、Init、Release。这是框架管理驱动生命周期的入口。
#include "hdf_device_desc.h"
#include "hdf_log.h"
#define HDF_LOG_TAG gpio_led_driver
// 绑定服务接口
static int32_t GpioLedBind(struct HdfDeviceObject *device) {
HDF_LOGI("GpioLedBind: enter");
// 这里可以初始化服务接口
return HDF_SUCCESS;
}
// 初始化硬件
static int32_t GpioLedInit(struct HdfDeviceObject *device) {
HDF_LOGI("GpioLedInit: enter");
// 从HCS读取配置
struct DeviceResourceNode *node = device->property;
int32_t gpioNum;
if (DeviceResourceGetUint32(node, "gpio_num", &gpioNum, 0) != HDF_SUCCESS) {
HDF_LOGE("get gpio_num failed");
return HDF_FAILURE;
}
// 设置GPIO为输出模式
// ... 调用内核适配层API
HDF_LOGI("GpioLedInit: gpio %d initialized", gpioNum);
return HDF_SUCCESS;
}
// 释放资源
static void GpioLedRelease(struct HdfDeviceObject *device) {
HDF_LOGI("GpioLedRelease: enter");
// 清理GPIO资源
}
// 注册驱动
HDF_INIT(GpioLedBind, GpioLedInit, GpioLedRelease);
这段代码你看着可能有点懵,别急。核心逻辑就三件事:
- Bind:绑定服务接口,告诉上层“我能提供什么功能”。
- Init:初始化硬件,从HCS读配置,设置GPIO模式。
- Release:卸载时清理资源,防止内存泄漏。
避坑指南:我曾经在Init里申请了内存,但Release里忘了释放。结果驱动反复加载卸载几次后,系统内存被吃光。嗯,从那以后我养成了习惯——每次写Init,先写Release。
3.3.3 实现设备操作接口
驱动不能光初始化,还得干活。下面实现点灯和灭灯的操作。
// 点亮LED
int32_t GpioLedOn(struct HdfDeviceObject *device) {
int32_t gpioNum = GetGpioNumFromDevice(device);
// 调用内核适配层API设置GPIO高电平
return GpioWrite(gpioNum, GPIO_VAL_HIGH);
}
// 熄灭LED
int32_t GpioLedOff(struct HdfDeviceObject *device) {
int32_t gpioNum = GetGpioNumFromDevice(device);
return GpioWrite(gpioNum, GPIO_VAL_LOW);
}
你看,驱动代码其实不复杂。复杂的是怎么把驱动注册成服务,让上层能调。这就是下一步。
3.3.4 注册驱动服务
HDF用“服务”的概念把驱动暴露给上层。上层通过服务名找到驱动,然后调用接口。
// 在Bind函数中注册服务
static int32_t GpioLedBind(struct HdfDeviceObject *device) {
static struct IHdfGpioLedService service = {
.On = GpioLedOn,
.Off = GpioLedOff,
};
device->service = &service;
return HDF_SUCCESS;
}
上层调用时,通过HdfDeviceObject拿到服务指针,然后调用On和Off方法。整个过程对上层透明——它不知道底层是GPIO还是别的什么。
3.3.5 编译构建
HDF驱动可以编译进内核,也可以作为动态模块加载。我建议初学者先用动态模块,调试方便。
# BUILD.gn
import("//build/ohos.gni")
ohos_shared_library("gpio_led_driver") {
sources = [ "gpio_led_driver.c" ]
include_dirs = [
"//drivers/hdf_core/framework/include",
"//drivers/hdf_core/adapter/khdf/linux/include",
]
deps = [ "//drivers/hdf_core/framework:libhdf" ]
}
编译命令:
./build.sh --product-name rk3568 --build-target gpio_led_driver
编译成功后,把生成的.ko文件推到开发板,用hdf_install命令加载。
3.3.6 测试验证
写个简单的测试程序,验证驱动是否正常工作。
#include "hdf_io_service.h"
int main() {
struct HdfIoService *service = HdfIoServiceBind("hdf_gpio_led_service");
if (service == NULL) {
printf("bind service failed\n");
return -1;
}
// 点亮LED
service->dispatcher->Dispatch(service, CMD_LED_ON, NULL, NULL);
sleep(1);
// 熄灭LED
service->dispatcher->Dispatch(service, CMD_LED_OFF, NULL, NULL);
HdfIoServiceRecycle(service);
return 0;
}
如果LED按预期亮灭,恭喜你,第一个HDF驱动跑通了!
3.4 本章小结
今天我们干了三件事:
- 搞清楚了HDF的核心概念:Driver、Device、Service、HCS、HDI。
- 理解了驱动模型的分层架构:应用层→服务层→核心层→适配层→硬件层。
- 走了一遍完整的开发流程:从HCS配置到驱动注册,再到编译测试。
说实话,HDF的门槛并不高。它把很多底层细节封装好了,你只需要关注业务逻辑。但有一点要记住——HCS配置是HDF的灵魂。配置写错了,驱动写得再好也跑不起来。
我的建议:初学者先别急着写复杂驱动。拿一个GPIO点灯练手,把HCS配置、驱动入口、服务注册这套流程跑通。后面再学I2C、SPI、UART就水到渠成了。
嗯,今天就到这里。代码都在上面了,自己动手试试。有问题随时交流。