第3章 第一个HDF驱动:hello_world

说实话,学驱动开发最怕什么?就是环境搭好了,却不知道第一个程序怎么写。我当年刚接触HDF时也懵,文档翻来翻去,就是找不到一个能直接跑起来的例子。所以这一章,咱们直接动手,写一个最简单的HDF驱动——hello_world。

这个驱动不干别的,就是加载时打印一句"Hello, HDF!",卸载时再打印一句"Goodbye, HDF!"。别看它简单,它把HDF驱动的骨架全走了一遍:入口函数、绑定、初始化、释放。你把这个跑通了,后面写任何复杂驱动心里都有底。

3.1 HDF驱动的“三件套”

每个HDF驱动都必须实现三个函数:Bind、Init、Release。我习惯叫它“三件套”。

函数 调用时机 主要职责
Bind 驱动加载时,最早被调用 绑定服务接口,注册设备对象
Init Bind成功后调用 初始化硬件、分配资源、配置参数
Release 驱动卸载时调用 释放资源、反初始化、清理现场

调用顺序是固定的:Bind → Init → Release。你想想看,这跟Linux驱动的probe/remove是不是很像?但HDF把Bind单独拎出来,就是为了让服务注册和硬件初始化解耦。我在项目中遇到过一个问题:某个传感器驱动在Init里申请了中断,但Bind还没注册完服务,结果中断来了找不到处理函数。后来我把服务注册放Bind里,硬件初始化放Init里,问题就解决了。

关键点: Bind只做服务注册,不做硬件操作;Init才操作硬件。这个原则能避免很多竞态问题。

3.2 编写hello_world驱动源码

好,咱们直接上代码。新建一个文件 hello_world_driver.c,放在 drivers/hdf_core/adapter/khdf/linux/platform/ 下(或者你自己建的驱动目录里)。

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

#define HDF_LOG_TAG hello_world_driver

// Bind函数:注册服务接口
static int32_t HelloWorldBind(struct HdfDeviceObject *deviceObject)
{
    if (deviceObject == NULL) {
        HDF_LOGE("HelloWorldBind: deviceObject is NULL");
        return HDF_ERR_INVALID_PARAM;
    }
    HDF_LOGI("HelloWorldBind: enter");
    // 这里可以注册服务,比如 deviceObject->service = &g_helloService;
    return HDF_SUCCESS;
}

// Init函数:初始化驱动
static int32_t HelloWorldInit(struct HdfDeviceObject *deviceObject)
{
    if (deviceObject == NULL) {
        HDF_LOGE("HelloWorldInit: deviceObject is NULL");
        return HDF_ERR_INVALID_PARAM;
    }
    HDF_LOGI("HelloWorldInit: Hello, HDF!");
    return HDF_SUCCESS;
}

// Release函数:释放驱动
static void HelloWorldRelease(struct HdfDeviceObject *deviceObject)
{
    if (deviceObject == NULL) {
        HDF_LOGE("HelloWorldRelease: deviceObject is NULL");
        return;
    }
    HDF_LOGI("HelloWorldRelease: Goodbye, HDF!");
}

// 驱动入口结构体
struct HdfDriverEntry g_helloWorldEntry = {
    .moduleVersion = 1,
    .moduleName = "hello_world",
    .Bind = HelloWorldBind,
    .Init = HelloWorldInit,
    .Release = HelloWorldRelease,
};

// 注册驱动
HDF_INIT(g_helloWorldEntry);

代码不长,我带你捋一遍:

  • Bind:检查参数,打印日志。实际项目中这里会调用 HdfDeviceObjectRegisterService 注册服务。
  • Init:打印"Hello, HDF!"。这就是咱们驱动的“第一声啼哭”。
  • Release:打印"Goodbye, HDF!"。卸载时清理用。
  • HdfDriverEntry:驱动入口结构体,告诉HDF框架你的驱动叫什么名字、三个函数在哪。
  • HDF_INIT:宏,把驱动注册到HDF框架里。
小技巧: HDF_LOGI 打印的日志可以通过 hilog 命令查看。调试时多用日志,比猜变量值靠谱多了。

3.3 配置驱动:HCS文件

源码写完了,但HDF框架还不知道你的驱动存在。你需要写一个HCS(HDF Configuration Source)配置文件,告诉框架:有个叫"hello_world"的驱动,它应该在哪个设备上加载。

新建 hello_world_config.hcs,放在 vendor/你的厂商/你的产品/hdf_config/ 下:

root {
    module = "hello_world";
    moduleConfig {
        policy = 2;  // 服务发布策略:0不发布,1发布到内核态,2发布到用户态
        priority = 100;  // 加载优先级,数值越小越优先
        preload = 0;  // 0表示按需加载,1表示系统启动时加载
        deviceList = list {
            device0 {
                deviceName = "hello_world_device";
                deviceNodeName = "HDF_PLATFORM";
                deviceMatchAttr = "hello_world_config";
                serviceName = "hello_world_service";
            };
        };
    };
}

这里有几个字段我解释一下:

  • policy:服务发布策略。咱们这个demo用2,表示用户态也能访问。
  • priority:加载优先级。100是中间值,一般够用。
  • preload:是否预加载。0表示按需加载,系统启动时不自动加载。
  • deviceMatchAttr:匹配属性,HDF框架通过这个字段把配置和驱动代码关联起来。

嗯,这里要注意:moduleName 必须和驱动源码里 HdfDriverEntry 的 moduleName 一致。我见过有人把名字写错了,结果驱动死活加载不上,查了半天才发现是大小写问题。

3.4 编译与加载验证

配置写好了,接下来编译。在源码根目录执行:

hb build -f

编译成功后,把镜像烧录到开发板。然后通过串口或SSH登录,执行以下命令加载驱动:

# 加载驱动
hdf_loader load hello_world

# 查看驱动是否加载成功
hdf_loader list

# 卸载驱动
hdf_loader unload hello_world

加载成功后,用 hilog 查看日志:

hilog | grep hello_world

你应该能看到类似这样的输出:

01-01 00:00:01.123   456   789 I 00100/hello_world_driver: HelloWorldBind: enter
01-01 00:00:01.124   456   789 I 00100/hello_world_driver: HelloWorldInit: Hello, HDF!

卸载时:

01-01 00:00:02.456   456   789 I 00100/hello_world_driver: HelloWorldRelease: Goodbye, HDF!
避坑指南: 我曾经在调试时发现驱动加载后日志没打印,折腾了半天,最后发现是 HDF_LOG_TAG 定义错了。这个宏必须放在所有 HDF_LOGI 之前,而且名字不能有空格。另外,如果 hilog 过滤不到日志,试试不加 grep,先确认 hilog 本身能正常工作。

3.5 驱动生命周期流程图

为了让你更直观地理解HDF驱动的加载到卸载全过程,我画了一张流程图:

HDF驱动生命周期流程图 系统启动 HDF框架初始化 加载驱动 Bind() Init() Release() 说明 • Bind:注册服务接口 • Init:初始化硬件资源 • Release:释放资源 调用顺序 系统启动 → HDF初始化 → Bind → Init → Release

从图上可以清楚看到:系统启动后,HDF框架先初始化,然后加载驱动,依次调用Bind和Init。卸载时调用Release。这个流程是固定的,你写任何HDF驱动都得遵循。

3.6 常见问题与排查

第一次跑这个demo,大概率会遇到问题。我把常见的坑列出来:

现象 可能原因 解决方法
驱动加载失败,提示"module not found" HCS配置中的moduleName与源码不一致 检查 moduleName 是否匹配
日志不打印 HDF_LOG_TAG 定义错误或hilog未开启 确认宏定义位置,检查hilog服务状态
驱动加载后立即崩溃 Bind或Init中访问了空指针 加空指针检查,用 HDF_LOGE 定位
卸载时卡死 Release中未正确释放资源 检查是否有未释放的锁或内存
我的经验: 调试驱动时,我习惯先在Init里只打印日志,什么都不做。确认框架跑通了,再一步步加硬件操作。这样出了问题,能快速定位是框架问题还是硬件问题。

好了,到这里你已经完成了第一个HDF驱动的编写、配置、编译和验证。别看它简单,这个hello_world驱动麻雀虽小五脏俱全。后面你要写的GPIO驱动、I2C驱动、SPI驱动,都是在这个骨架基础上加硬件操作而已。把这个跑通了,HDF驱动开发的大门就算正式打开了。