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的驱动模型分三层,我画了张图帮你理解。

HDF驱动模型分层架构 应用层(App / Framework) 通过HDI接口调用驱动服务 驱动服务层(Driver Service) 实现HDI接口,管理设备对象 HDF核心层(HDF Core) 驱动加载、设备管理、消息分发、生命周期 内核适配层(Kernel Adapter) 封装Linux/LiteOS内核接口,提供统一API 硬件层(Hardware) 用户态 内核态

这张图你看懂了吗?从上到下依次是:

  • 应用层:你的App或系统服务,通过HDI接口调用驱动。它不关心底层是GPIO还是I2C。
  • 驱动服务层:你写的驱动代码。实现HDI接口,管理设备对象,处理业务逻辑。
  • HDF核心层:框架的心脏。负责驱动加载、设备管理、消息分发、生命周期管理。这部分你不用改,直接用。
  • 内核适配层:封装不同内核的差异。比如Linux用file_operations,LiteOS用另一套。HDF帮你抹平了。
  • 硬件层:真实的物理设备。GPIO、UART、SPI……

我的经验:刚开始写HDF驱动时,最容易犯的错是“把所有逻辑都塞到驱动服务层”。其实应该把硬件操作封装到内核适配层,服务层只做业务编排。这样换内核时,你只需要改适配层,服务层纹丝不动。

3.3 驱动开发流程:从零到一

好,概念讲完了,咱们来点实际的。一个HDF驱动的完整开发流程是怎样的?我总结为6步。

  1. 编写HCS配置文件:描述设备硬件信息,包括设备ID、资源、依赖关系。
  2. 实现驱动入口:实现Bind、Init、Release三个回调函数。
  3. 实现设备操作接口:根据硬件功能,实现读写、控制等操作。
  4. 注册驱动服务:将驱动注册到HDF框架,暴露服务给上层。
  5. 编译构建:将驱动代码编译进内核或动态加载模块。
  6. 测试验证:通过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就水到渠成了。

嗯,今天就到这里。代码都在上面了,自己动手试试。有问题随时交流。