4. 第一个HDF驱动:编写最简单的HDF驱动代码、编译与烧录、验证驱动加载

好,咱们今天来点真格的。

前面几章讲了HDF的架构、核心概念,说白了都是理论。我知道你肯定手痒了,想看看真正的HDF驱动长什么样。我自己当年学驱动开发时也是这样,看文档看得昏昏欲睡,一上手写代码反而精神了。

这一章,我们就从零开始,写一个最简单的HDF驱动。它不干别的,就在加载时打印一句"Hello, HDF!",然后卸载时再打印一句"Goodbye, HDF!"。别小看这个Hello World,它能帮你验证整个开发环境、编译链、烧录流程和驱动加载机制是否正常。我在项目中遇到过好几次,新板子回来第一件事就是跑这个,能快速排除环境问题。

4.1 驱动代码结构概览

一个完整的HDF驱动,至少需要三个文件:

  • 驱动实现文件(.c):写真正的逻辑
  • 驱动配置文件(.hcs):告诉HDF框架这个驱动的信息
  • 编译配置文件(BUILD.gn):告诉编译系统怎么编译

你想想看,HDF框架怎么知道你的驱动存在?就是靠配置文件。它就像驱动的"身份证",告诉框架:我叫什么名字、我属于哪个设备、我的入口函数在哪里。

核心要点:HDF驱动的生命周期由框架管理。你只需要实现三个回调函数:Bind、Init、Release。框架会在合适的时机调用它们。

4.2 编写最简单的HDF驱动代码

我们先创建一个目录,比如在drivers/hdf_core/adapter/khdf/linux/hdf_test/下面。我个人习惯把测试驱动单独放一个目录,方便管理。

创建文件 hdf_test_driver.c

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

#define HDF_LOG_TAG hdf_test_driver

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

// 驱动启动函数
static int32_t HdfTestDriverInit(struct HdfDeviceObject *deviceObject)
{
    if (deviceObject == NULL) {
        HDF_LOGE("HdfTestDriverInit: deviceObject is NULL");
        return HDF_ERR_INVALID_PARAM;
    }
    HDF_LOGI("HdfTestDriverInit: Driver initialized successfully");
    return HDF_SUCCESS;
}

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

// 驱动入口结构体
struct HdfDriverEntry g_hdfTestDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "HDF_TEST_DRIVER",
    .Bind = HdfTestDriverBind,
    .Init = HdfTestDriverInit,
    .Release = HdfTestDriverRelease,
};

// 注册驱动到HDF框架
HDF_INIT(g_hdfTestDriverEntry);

嗯,这里要注意几个细节:

  • HDF_LOGI是HDF的日志宏,对应INFO级别。还有HDF_LOGE(错误)、HDF_LOGW(警告)。
  • HDF_INIT宏会把驱动入口结构体注册到框架中。它本质上是把结构体放到一个特定的段里,框架启动时会扫描这个段。
  • moduleName必须和配置文件中的名字一致,否则框架找不到你的驱动。

避坑指南:我曾经因为moduleName写了个下划线,配置文件中用的是中划线,结果驱动死活加载不上。查了两天才发现是名字不匹配。所以,命名时最好统一风格,我建议全用大写字母加下划线。

4.3 编写驱动配置文件(.hcs)

HDF使用HCS(HDF Configuration Source)格式的配置文件。它有点像设备树,但语法更简洁。

创建文件 hdf_test_config.hcs

root {
    module = "hdf_test";
    device_test :: device {
        device0 :: deviceNode {
            policy = 0;
            priority = 100;
            preload = 0;
            permission = 0664;
            moduleName = "HDF_TEST_DRIVER";
            serviceName = "HDF_TEST_SERVICE";
            deviceMatchAttr = "hdf_test_driver_attr";
        }
    }
}

解释一下关键字段:

字段 含义 我的建议
policy 服务发布策略,0表示不对外发布服务 测试驱动用0就行
priority 驱动加载优先级,数值越小越先加载 100是默认值,够用
preload 预加载策略,0表示按需加载 测试驱动建议用0
moduleName 必须和驱动代码中的moduleName一致 大小写敏感,仔细核对
serviceName 服务名称,其他程序可以通过它找到你的驱动 命名要有意义

4.4 编写编译配置文件(BUILD.gn)

创建文件 BUILD.gn

import("//drivers/hdf_core/adapter/khdf/linux/hdf.gni")

hdf_driver("hdf_test_driver") {
    sources = [
        "hdf_test_driver.c"
    ]
    
    include_dirs = [
        "//drivers/hdf_core/framework/include/core",
        "//drivers/hdf_core/framework/include/utils",
    ]
    
    deps = [
        "//drivers/hdf_core/framework/core/hdf:libhdf_core",
    ]
}

这里用到了HDF提供的模板hdf_driver,它会自动处理驱动注册相关的链接工作。你只需要告诉它源文件、头文件路径和依赖库就行。

注意:include_dirs一定要包含HDF框架的核心头文件路径,否则编译时会报找不到hdf_device_desc.h。我刚开始学的时候漏了这个,编译报错一脸懵。

4.5 编译与烧录

编译命令很简单,在源码根目录执行:

hb build -f -T hdf_test_driver

参数说明:

  • -f:强制重新编译
  • -T:指定编译目标,这里就是我们的驱动模块名

编译成功后,会生成一个.ko文件(Linux内核模块)。把它烧录到开发板上。烧录方式取决于你的硬件平台,一般用USB烧录工具或者网络烧录。

烧录完成后,启动开发板,进入系统。

4.6 验证驱动加载

验证驱动是否加载成功,有两种方法:

方法一:查看内核日志

# 查看HDF相关的日志
dmesg | grep HDF

# 你应该能看到类似输出:
# [   12.345678] HDF: HdfTestDriverBind: Hello, HDF!
# [   12.345679] HDF: HdfTestDriverInit: Driver initialized successfully

方法二:查看驱动设备节点

# 查看HDF设备目录
ls /dev/hdf/

# 如果看到我们的设备节点,说明驱动加载成功
# 比如:/dev/hdf/HDF_TEST_SERVICE

如果看不到任何输出,别慌。先检查几个地方:

  • 配置文件中的moduleName是否和代码一致?
  • 编译是否成功?有没有报错?
  • 烧录是否正确?文件有没有覆盖?

我的经验:有一次我折腾了半天驱动就是不加载,最后发现是配置文件没被编译进去。HDF的配置文件需要放在特定目录下,并且要在hdf_vendor_config.hcs中引用。你可以检查一下//vendor/你的厂商/你的产品/config/下面有没有包含你的配置文件。

4.7 核心逻辑流程图

下面这张图展示了从驱动代码到加载成功的完整流程,我画出来帮你理清思路:

HDF驱动加载流程 编写驱动代码 hdf_test_driver.c 编写配置文件 hdf_test_config.hcs 编译 hb build -f -T xxx 烧录到开发板 生成 .ko 文件 系统启动 HDF框架扫描驱动 HDF框架加载驱动 调用 Bind → Init → Release(卸载时) 验证驱动加载 dmesg | grep HDF

这张图把整个流程串起来了。你写代码、配配置、编译、烧录,最后系统启动时HDF框架会自动加载你的驱动。如果一切顺利,你就能在日志里看到那句"Hello, HDF!"。

4.8 常见问题与排查

我整理了几个新手最容易踩的坑:

  1. 驱动没编译进去:检查BUILD.gn中的sources路径是否正确,文件名有没有拼错。
  2. 配置文件没生效:确认.hcs文件被包含在vendor的配置目录中,并且语法正确。可以用hcs2c工具检查语法。
  3. 日志看不到:检查内核日志级别,有时候INFO级别的日志被过滤了。可以用dmesg -n 7设置日志级别。
  4. 驱动加载顺序问题:如果驱动依赖其他模块,记得在配置文件中设置合适的priority值。

重要提醒:千万不要在生产环境的板子上直接测试新驱动。我见过有人把开发板上的系统搞崩溃,只能重新烧录。建议用单独的测试板,或者至少做好系统备份。

好了,到这里你已经完成了第一个HDF驱动的编写、编译、烧录和验证。虽然它只是打印了几行日志,但背后的流程——从代码到框架加载——是每个HDF驱动都必须经历的。把这个流程跑通了,后面写复杂的驱动就心里有底了。

记住,驱动开发没有捷径,多写多试才是王道。遇到问题别怕,日志是最好的老师。


公众号:蓝海资料掘金营,微信deep3321