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框架会自动加载你的驱动。如果一切顺利,你就能在日志里看到那句"Hello, HDF!"。
4.8 常见问题与排查
我整理了几个新手最容易踩的坑:
- 驱动没编译进去:检查BUILD.gn中的
sources路径是否正确,文件名有没有拼错。 - 配置文件没生效:确认.hcs文件被包含在vendor的配置目录中,并且语法正确。可以用
hcs2c工具检查语法。 - 日志看不到:检查内核日志级别,有时候INFO级别的日志被过滤了。可以用
dmesg -n 7设置日志级别。 - 驱动加载顺序问题:如果驱动依赖其他模块,记得在配置文件中设置合适的
priority值。
重要提醒:千万不要在生产环境的板子上直接测试新驱动。我见过有人把开发板上的系统搞崩溃,只能重新烧录。建议用单独的测试板,或者至少做好系统备份。
好了,到这里你已经完成了第一个HDF驱动的编写、编译、烧录和验证。虽然它只是打印了几行日志,但背后的流程——从代码到框架加载——是每个HDF驱动都必须经历的。把这个流程跑通了,后面写复杂的驱动就心里有底了。
记住,驱动开发没有捷径,多写多试才是王道。遇到问题别怕,日志是最好的老师。
公众号:蓝海资料掘金营,微信deep3321