4. HCS配置语法
HCS,全称是 HDF Configuration Source。说白了,它就是一套给驱动写配置的专用语言。我刚开始接触 OpenHarmony 驱动时,第一反应是:为什么不用 JSON 或者 XML?后来踩了几个坑才明白,HCS 的设计目标就是「轻量、可覆盖、适合嵌入式」。今天我们就把它彻底讲透。
4.1 HCS 文件结构:一棵有根的树
HCS 文件本质上是一棵属性树。每个文件只有一个根节点,下面挂子节点,子节点再挂属性或更小的子节点。你想想看,这跟设备树的思路很像,但语法更简洁。
核心结构:
- 根节点:每个 HCS 文件有且只有一个,用
root {}包裹。 - 子节点:用
node_name {}定义,可以无限嵌套。 - 属性:键值对,写在节点内部。
来看一个实际例子。这是我之前在调试 I2C 控制器驱动时写的配置片段:
root {
i2c_config {
i2c0 {
bus_number = 0;
speed = 400000; // 单位 Hz
gpio_scl = 12;
gpio_sda = 13;
};
i2c1 {
bus_number = 1;
speed = 100000;
gpio_scl = 14;
gpio_sda = 15;
};
};
};
嗯,这里要注意:每个属性后面必须跟分号,节点结束也要分号。我见过不少新手漏掉分号,编译直接报错,排查半天。
4.2 节点定义:路径与引用
每个节点都有一个路径。比如上面例子中,i2c0 的完整路径就是 root.i2c_config.i2c0。这个路径在后面的覆盖机制里非常关键。
节点名可以包含字母、数字和下划线,但不能以数字开头。我个人习惯用小写+下划线风格,跟内核代码风格保持一致。
小技巧:如果你想让某个节点可以被外部引用,可以在节点名前加 ref 关键字。比如 ref i2c0 {},这样其他 HCS 文件就能通过路径来覆盖它。
4.3 属性类型:不只是整数和字符串
HCS 支持的属性类型比你想的要多。我整理了一张表,方便你对照:
| 类型 | 关键字 | 示例 | 说明 |
|---|---|---|---|
| 整数 | 直接写数字 | count = 10; |
支持十进制、十六进制(0xFF) |
| 布尔 | true / false |
enable = true; |
注意是小写 |
| 字符串 | 双引号 | compatible = "hisi,i2c"; |
不能换行 |
| 数组 | 方括号 | irqs = [32, 33, 34]; |
元素用逗号分隔 |
| 字节数组 | 单引号 + 十六进制 | mac = [0x00, 0x11, 0x22]; |
常用于 MAC 地址、UUID |
| 节点引用 | & 符号 |
parent = &i2c0; |
指向另一个节点的路径 |
我曾经在配置 DMA 通道时,把数组写成了 channels = 1,2,3; 忘了加方括号,结果驱动读到的永远只有第一个值。这种坑,踩过一次就记住了。
4.4 引用与覆盖机制:HCS 的精髓
这是 HCS 最强大的地方,也是初学者最容易懵的地方。我尽量用大白话讲清楚。
引用(Reference):允许一个节点「继承」另一个节点的内容。语法是 ref 路径 {}。
覆盖(Override):在继承的基础上,修改部分属性。语法是 路径 : 属性名 = 新值;。
来看一个实际场景。假设你有一个默认配置文件 i2c_default.hcs:
root {
i2c_default {
speed = 100000;
mode = "standard";
timeout_ms = 1000;
};
};
然后你的板级配置文件 board_i2c.hcs 想引用并覆盖:
root {
i2c0 : ref i2c_default {
speed = 400000; // 只覆盖速度,其他属性继承
};
};
最终 i2c0 节点的实际配置是:speed=400000、mode="standard"、timeout_ms=1000。看到了吗?你只改了一个值,其他自动继承。
注意:覆盖时,路径必须完全匹配。如果你写 i2c0 : ref i2c_default,但 i2c_default 的路径是 root.i2c_default,那就要写全路径。我曾经因为路径写错,导致覆盖没生效,驱动用了默认值,I2C 速率跑飞,调试了两天。
4.5 编译集成:HCS 如何变成 C 代码
HCS 文件不是直接给驱动用的。它需要经过一个编译工具(hc-gen)转换成 C 语言的头文件和源文件。流程是这样的:
- 编写:在
vendor/xxx/或device/xxx/目录下创建.hcs文件。 - 编译:构建系统自动调用
hc-gen,生成.hcb(二进制)和.h(头文件)。 - 链接:驱动代码通过
#include包含生成的头文件,调用 HDF 提供的 API 读取配置。
在 BUILD.gn 中,你需要这样声明 HCS 文件:
hcs_resources("my_driver_hcs") {
sources = [
"my_driver_config.hcs",
]
}
然后驱动代码里这样读:
#include "hdf_config.h"
// 通过 HDF 提供的接口读取配置树
struct DeviceResourceNode *node = HdfGetConfigNode("root.i2c_config.i2c0");
uint32_t speed;
HdfGetUint32(node, "speed", &speed, 100000); // 默认值 100000
我的习惯:在 HCS 文件里给每个属性都写注释,标明单位、取值范围、默认值。这样半年后回来看代码,不用重新猜。
4.6 知识结构总览
下面这张图把 HCS 的核心知识点串起来了。你可以把它当作本章的「思维导图」:
4.7 避坑指南
最后,分享几个我实际踩过的坑:
- 路径大小写敏感:HCS 的路径是大小写敏感的。我见过有人把
I2C0写成i2c0,结果覆盖不生效。 - 数组索引从 0 开始:覆盖数组元素时,用
array[0] = 10;这种语法。 - 不要用 Tab 缩进:HCS 解析器对 Tab 支持不好,建议统一用空格。我习惯 4 空格缩进。
- 注释只能用
//:HCS 不支持/* */块注释,只能用双斜杠。
我曾经犯过的错:在一个产品项目中,我把覆盖写成了 i2c0 : ref i2c_default,但 i2c_default 节点里有个 pinctrl 子节点。我以为覆盖会自动合并子节点,结果 HCS 是整节点替换——子节点全丢了。后来我改成显式引用子节点才解决。
好了,HCS 配置语法就讲到这里。记住:结构是树,属性是叶,覆盖是魂。把这三点吃透,HCS 对你来说就是小菜一碟。