3、HDF配置管理:HCS配置文件的语法与结构、如何加载配置、配置与驱动的绑定

好,咱们今天来聊聊HDF配置管理。说实话,这部分内容在刚接触OpenHarmony驱动开发时,我一度觉得有点绕。但后来发现,搞懂了HCS(HDF Configuration Source)的语法和加载机制,整个驱动开发的骨架就清晰了。

说白了,HCS就是一套专门给驱动用的配置文件。它不像DTS(Device Tree)那样复杂,也不像普通JSON那样松散。HCS是HDF自己定义的一套键值对描述语言,结构清晰,解析效率高。我个人习惯把HCS看作是驱动和内核之间的“翻译官”——驱动告诉内核“我需要什么资源”,内核通过HCS告诉驱动“你的资源在哪里”。

3.1 HCS配置文件的语法与结构

先看一个最简单的HCS文件长什么样。嗯,这里要注意,HCS文件后缀是.hcs,通常放在vendor/xxx/hdf_config/目录下。

root {
    module = "my_driver";
    device = "my_device";
    policy = 2;
    support = [ "A", "B", "C" ];
    attrs {
        reg_base = 0x20000000;
        reg_size = 0x1000;
        irq_num = 38;
        speed = "high";
    };
};

这个结构其实很直观:

  • root:根节点,每个HCS文件只有一个根节点
  • module:驱动模块名,和驱动代码里的HDF_INIT注册的名字对应
  • device:设备名,用于匹配设备
  • policy:策略值,0表示不对外暴露,1表示只读,2表示可读写
  • support:数组,表示该驱动支持的设备列表
  • attrs:自定义属性节点,里面放硬件相关的寄存器地址、中断号等

我在项目中遇到过一个问题:有个同事把policy写成了police,结果驱动加载后死活拿不到配置。排查了半天才发现是拼写错误。所以啊,HCS的字段名是大小写敏感的,而且不能拼错。

3.1.1 节点与属性

HCS的节点用花括号{}包裹,属性用key = value;的形式。支持的数据类型有:

类型 示例 说明
字符串 name = "uart0"; 双引号包裹
整数 baud = 115200; 十进制或十六进制(0x开头)
数组 pins = [1, 2, 3]; 方括号包裹,逗号分隔
布尔 enable = true; truefalse
节点 attrs { ... }; 嵌套子节点

你想想看,这种结构其实比DTS简单多了。DTS里各种#address-cellsreg的格式,新手看了头大。HCS直接就是键值对,一目了然。

3.1.2 引用与模板

HCS还支持引用和模板,这个功能很实用。比如多个设备有相同的配置,可以定义一个模板:

root {
    template uart_common {
        baud = 115200;
        data_bits = 8;
        stop_bits = 1;
        parity = "none";
    };
    uart0 : uart_common {
        reg_base = 0x20000000;
    };
    uart1 : uart_common {
        reg_base = 0x20001000;
    };
};

这里uart0 : uart_common表示uart0继承uart_common的所有属性,然后可以覆盖或新增。我曾经用这个特性重构过一个项目,把原来200多行的重复配置压缩到了50行,维护起来轻松多了。

3.2 如何加载配置

配置写好了,驱动怎么拿到呢?HDF提供了一套API来加载和解析HCS。核心函数就几个:

// 1. 打开配置节点
const struct DeviceResourceNode *node = NULL;
node = HdfDeviceResourceGetNode(device, "my_device");
if (node == NULL) {
    HDF_LOGE("Failed to get config node");
    return HDF_FAILURE;
}

// 2. 读取属性值
uint32_t regBase;
int32_t ret = HdfDeviceResourceGetUint32(node, "reg_base", ®Base, 0);
if (ret != HDF_SUCCESS) {
    HDF_LOGE("Failed to read reg_base");
    return HDF_FAILURE;
}

// 3. 读取字符串
const char *speed = NULL;
ret = HdfDeviceResourceGetString(node, "speed", &speed);
if (ret != HDF_SUCCESS) {
    HDF_LOGE("Failed to read speed");
    return HDF_FAILURE;
}

这里有个坑,我曾经踩过:HdfDeviceResourceGetUint32的最后一个参数是默认值,如果配置里没有这个字段,就会返回默认值。但如果你不检查返回值,直接用默认值去初始化硬件,很可能出问题。所以我的习惯是:每个配置读取后都检查返回值,宁可报错也不要猜默认值

注意:配置加载的顺序很重要。HDF在驱动初始化时会先加载HCS,然后调用驱动的BindInit接口。如果在Init里读取配置,此时配置已经加载完毕,可以放心使用。

3.3 配置与驱动的绑定

配置和驱动是怎么绑到一起的?这就要说到HDF的设备匹配机制了。每个驱动在注册时,会指定一个deviceId,而HCS里的device字段就是用来匹配的。

看一个完整的驱动注册示例:

// 驱动入口结构体
struct HdfDriverEntry g_myDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "my_driver",      // 对应HCS里的module
    .Bind = MyDriverBind,
    .Init = MyDriverInit,
    .Release = MyDriverRelease,
};

// 注册驱动
HDF_INIT(g_myDriverEntry);

在HCS里:

root {
    module = "my_driver";   // 和moduleName一致
    device = "my_device";   // 设备名
    ...
};

当HDF启动时,会遍历所有HCS配置,找到module字段匹配的驱动,然后调用它的Bind接口。在Bind里,你可以拿到配置节点,然后保存起来供后续使用。

核心逻辑:HCS中的module → 驱动中的moduleName → 通过HDF_INIT注册 → HDF自动匹配并调用Bind → 驱动在Bind中获取配置节点。

我曾经接手过一个项目,驱动死活加载不上。查了半天发现是HCS里的module写成了moduleName,而驱动里注册的是moduleName。这两个字段名不一样,HDF匹配不上,自然就不会调用驱动的Bind。所以啊,字段名一定要和HDF框架定义的完全一致

3.4 知识体系总览

为了让你更直观地理解HCS配置管理的全貌,我画了一张图:

HDF配置管理知识体系 HCS语法与结构 配置加载 配置与驱动绑定 节点、属性、类型 引用与模板 数组、字符串、整数 HdfDeviceResourceGetNode 读取Uint32/String等 返回值检查与错误处理 moduleName匹配 HDF_INIT注册 Bind接口获取配置 核心流程 编写HCS → 驱动注册 → HDF自动匹配 → Bind中加载配置 → Init中使用配置
小技巧:调试配置加载问题时,可以在Bind接口里加打印,把读到的配置值都打出来。我曾经靠这个办法,半小时就定位到了一个寄存器地址写错的问题。另外,HDF也提供了hdf_pt工具,可以在命令行查看配置树,非常方便。

好了,关于HDF配置管理,核心就是这三块:怎么写HCS、怎么加载、怎么绑定。你只要记住module匹配、Bind中获取、Init中使用这个流程,基本就不会跑偏。下次遇到驱动加载失败,先检查HCS的module字段和驱动注册的moduleName是否一致——这个坑我替你先踩过了。


专注资料整理