1. HDF框架初探:鸿蒙南向生态概览、HDF框架设计哲学、HDF与Linux驱动的区别、第一个HDF驱动HelloWorld

1.1 鸿蒙南向生态:不只是换个内核

做嵌入式这么多年,我见过太多「换皮」方案了。鸿蒙南向不一样。它不是为了替代Linux而生的,而是为了解决一个更棘手的问题——碎片化。

你想想看,一个智能家居网关里,可能同时跑着Wi-Fi模组、蓝牙芯片、ZigBee协调器、传感器Hub。每个芯片都有自己的驱动,每个驱动都跟内核紧耦合。换一颗Wi-Fi芯片?好,整个驱动栈重写。这就是传统嵌入式开发的痛点。

鸿蒙南向生态的核心,是「硬件能力抽象化」。它不关心你用的是哪家的GPIO、哪家的I2C控制器,它只关心「这个设备能干什么」。说白了,就是把硬件接口标准化,让上层应用和中间件不再跟具体芯片绑定。

南向生态三要素:

  • 硬件厂商:提供芯片、模组、外设,按照HDF规范实现驱动
  • 驱动开发者:我们这些人,负责把硬件能力映射到HDF框架
  • 系统集成商:把不同厂商的驱动拼装成完整系统

我记得刚接触鸿蒙时,有个项目需要同时适配海思和瑞芯微的板子。按以前的做法,得维护两套驱动代码。但用HDF框架,我只写了一套驱动描述文件,换平台时改几行配置就行。嗯,这就是南向生态的价值。

1.2 HDF框架设计哲学:分层、解耦、可迁移

HDF(Hardware Driver Foundation)的设计哲学,我总结成三个词:分层、解耦、可迁移。

先看这张图,你就明白HDF在系统里的位置了:

HDF框架在鸿蒙系统中的位置 应用层(App / Service) 系统服务层(SAM / Bundle Manager) HDF 硬件驱动框架 驱动管理 | 设备发现 | 消息总线 | 配置解析 内核层(LiteOS / Linux Kernel) 硬件层(SoC / 外设 / 总线) 说明: HDF框架位于系统服务层与内核层之间,向上提供统一驱动接口,向下屏蔽硬件差异。

看到红色那层了吗?HDF框架就像个「驱动路由器」。它不直接操作硬件,而是管理驱动对象、分发消息、解析配置。具体来说:

  • 分层:驱动被拆成平台驱动、设备驱动、总线驱动三层,各司其职
  • 解耦:驱动之间通过HDF消息总线通信,不直接调用函数
  • 可迁移:驱动描述用HCS配置文件,换平台只需改配置,不动代码

个人经验: 我刚开始用HDF时,总觉得分层太多,写个GPIO驱动要过好几层。后来遇到一个项目,需要把同一套驱动从Hi3516移植到RK3568,只改了HCS配置文件和几个平台宏定义,两天搞定。要是用传统Linux驱动,至少两周。

1.3 HDF vs Linux驱动:到底差在哪?

很多朋友问我:「HDF不就是Linux驱动的鸿蒙版吗?」还真不是。我列个表,你一看就明白:

对比维度 Linux驱动 HDF驱动
驱动模型 基于设备树 + platform总线 基于HDF设备对象 + 消息总线
配置方式 设备树(DTS),编译时绑定 HCS配置文件,运行时加载
跨平台能力 弱,换SoC要改DTS和驱动代码 强,换SoC只需改HCS配置
驱动通信 函数直接调用 / IOCTL HDF消息总线,支持同步/异步
生命周期 module_init / module_exit HDF驱动入口表,框架统一管理
调试手段 printk / ftrace / procfs HDF log / HDF测试框架 / 模拟器

核心区别在哪?Linux驱动是「内核模块」,跟内核绑死;HDF驱动是「框架组件」,可以独立加载、卸载、替换。我曾经在调试一个USB摄像头驱动时,Linux下每次改驱动都要重新编译内核模块、重启设备。HDF下呢?改完HCS配置,用hdf_install命令热加载,几秒钟搞定。

注意: HDF框架虽然支持热加载,但硬件资源(如中断、DMA)的释放和重新申请需要驱动开发者自行保证。我曾经踩过坑——热加载时没正确释放中断,导致新驱动申请中断失败。嗯,后来在驱动卸载回调里加了资源清理,问题解决。

1.4 第一个HDF驱动:HelloWorld实战

理论说再多,不如跑一个例子。我们来写个最简单的HDF驱动——它不操作硬件,只在内核日志里打印「Hello, HDF!」。

1.4.1 驱动代码

#include "hdf_device_desc.h"
#include "hdf_log.h"

#define HDF_LOG_TAG hello_driver

// 驱动初始化
static int32_t HelloDriverBind(struct HdfDeviceObject *deviceObject)
{
    HDF_LOGI("HelloDriver: Bind called");
    return HDF_SUCCESS;
}

// 驱动启动
static int32_t HelloDriverInit(struct HdfDeviceObject *deviceObject)
{
    HDF_LOGI("HelloDriver: Init called");
    HDF_LOGI("Hello, HDF!");
    return HDF_SUCCESS;
}

// 驱动卸载
static void HelloDriverRelease(struct HdfDeviceObject *deviceObject)
{
    HDF_LOGI("HelloDriver: Release called");
}

// 驱动入口表
struct HdfDriverEntry g_helloDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "hello_driver",
    .Bind = HelloDriverBind,
    .Init = HelloDriverInit,
    .Release = HelloDriverRelease,
};

HDF_INIT(g_helloDriverEntry);

1.4.2 HCS配置文件

驱动写好了,还得告诉HDF框架「这个驱动在哪、叫什么」。在HDF里,这是通过HCS文件配置的:

root {
    platform {
        hello_driver {
            match_attr = "hello_driver_config";
            moduleName = "hello_driver";
            deviceName = "hello_device";
            deviceMatchAttr = "hello_driver_config";
            serviceName = "hello_service";
        }
    }
}

1.4.3 编译与运行

把驱动代码放到 drivers/hdf_core/adapter/khdf/linux/platform/ 目录下,修改 Makefile 添加编译目标。然后:

# 编译内核
./build.sh --product-name rk3568 --ccache

# 烧录后,查看内核日志
dmesg | grep HelloDriver

如果一切顺利,你会看到:

[    0.123456] HelloDriver: Bind called
[    0.123457] HelloDriver: Init called
[    0.123458] Hello, HDF!

避坑指南: 我曾经在写第一个HDF驱动时,忘了在HCS文件里配置 moduleName 和驱动入口表的 moduleName 保持一致。结果驱动加载时一直报「device not found」。查了半天,发现是名字拼写差了一个下划线。嗯,HDF对名字匹配是严格区分大小写的,切记。

1.4.4 驱动生命周期

从上面的代码可以看到,HDF驱动的生命周期有三个阶段:

  1. Bind:驱动与设备绑定,分配资源
  2. Init:驱动初始化,注册服务、申请硬件资源
  3. Release:驱动卸载,释放资源

这个流程跟Linux驱动的 probe / remove 很像,但HDF把绑定和初始化分开了。为什么?我个人理解是:绑定阶段只做资源预留,初始化阶段才真正操作硬件。这样在资源紧张时,可以先绑定但不初始化,等需要时再启动。这种设计在低功耗场景下特别有用。

好了,第一个HDF驱动跑起来了。虽然它什么都没做,但你已经掌握了HDF驱动的基本骨架。后面我们会在这个骨架上,一步步加上GPIO、I2C、SPI等真实硬件操作。