3. HDF配置管理:HCS配置文件的语法与结构
好,咱们今天聊聊HDF的配置管理。说实话,这部分内容在鸿蒙驱动开发里属于「看着简单,坑却不少」的典型。我刚开始接触HCS(HDF Configuration Source)时,觉得不就是个配置文件嘛,能有多复杂?结果第一次编译就报错,找了一下午才发现是缩进问题……嗯,咱们今天就把这块彻底讲透。
3.1 HCS文件长什么样?
HCS文件说白了就是鸿蒙自己定义的一种配置描述语言。它不像XML那么啰嗦,也不像JSON那么松散。我个人觉得它更像是一种「结构化文本」——有层次、有类型、有继承。
来看一个最简单的例子:
root {
module = "my_driver";
version = 1;
device0 {
name = "uart0";
reg = 0x11002000;
irq = 38;
status = "okay";
};
};
嗯,是不是有点像C语言的结构体?没错,HCS的设计哲学就是「让嵌入式工程师看着眼熟」。每个花括号 {} 定义了一个节点,节点里可以放属性(key-value对)或者子节点。
核心要点:HCS文件必须从 root {} 开始,这是整个配置树的根节点。我曾经见过有人把root拼成"Root"或者"ROOT",结果编译直接报错——大小写敏感,没得商量。
3.2 基本语法规则
咱们一条一条说,别嫌啰嗦,这些都是我踩过的坑。
3.2.1 注释
HCS支持两种注释方式:
// 这是单行注释,和C语言一样
/* 这是多行注释
可以跨行写 */
我个人习惯用双斜杠 //,因为写起来快。但要注意,注释不能嵌套,比如 /* /* */ */ 这种写法会报错。
3.2.2 数据类型
HCS支持的数据类型不多,但够用:
| 类型 | 示例 | 说明 |
|---|---|---|
| 字符串 | "hello" |
必须用双引号 |
| 整数 | 0x1000 或 4096 |
支持十进制和十六进制 |
| 布尔 | true 或 false |
小写,没有引号 |
| 数组 | [1, 2, 3] |
用方括号包裹 |
小技巧:整数类型里,我建议寄存器地址用十六进制,计数类用十进制。这样别人看你的配置时,一眼就能区分「这是地址」还是「这是数量」。
3.2.3 节点与属性
节点可以嵌套,属性就是键值对。看这个例子:
i2c_controller {
compatible = "hisi,i2c";
bus_num = 2;
speed = 400000; // 单位Hz
device1 {
addr = 0x50;
reg_len = 1;
};
};
这里 i2c_controller 是一个节点,它下面有3个属性和1个子节点 device1。你想想看,这种结构是不是天然适合描述硬件拓扑?
3.3 高级特性:模板与继承
这部分是HCS的精华。我在项目中遇到过一种情况:有8个一模一样的GPIO控制器,每个就差个基地址。如果一个个写,那代码量……嗯,你懂的。
HCS提供了模板机制:
template gpio_controller {
compatible = "hisi,gpio";
irq = 42;
gpio_count = 16;
};
gpio0 : gpio_controller {
reg = 0x12000000;
};
gpio1 : gpio_controller {
reg = 0x12001000;
};
看到那个冒号 : 了吗?它表示「继承自」。gpio0 继承了 gpio_controller 的所有属性,然后只需要覆盖 reg 就行了。这样写,既清晰又省事。
注意:模板本身不会生成配置项,只有用冒号继承后才会生效。我曾经犯过这个错——定义了一堆模板,结果驱动加载时发现啥都没有……
3.4 如何编写一个简单的设备配置
好,理论说完了,咱们动手写一个。假设我们要配置一个LED驱动,它挂在GPIO上,需要知道引脚号和亮灭电平。
第一步:创建HCS文件,比如 led_config.hcs
root {
module = "led_driver";
version = 1;
led_device {
gpio_pin = 12; // GPIO12
active_level = 1; // 高电平点亮
blink_interval = 500; // 闪烁间隔500ms
status = "okay";
};
};
第二步:在驱动代码中读取配置
HDF提供了API来解析HCS。大致流程是这样的:
// 伪代码示意
DevHandle handle = HdfOpenNode("led_device");
int32_t pin = HdfGetInt32(handle, "gpio_pin", -1);
int32_t level = HdfGetInt32(handle, "active_level", 0);
HdfCloseNode(handle);
嗯,这里要注意:HdfGetInt32 的第三个参数是默认值。如果配置里没找到这个属性,就返回默认值。我建议所有读取操作都带上默认值,这样即使配置写漏了,驱动也不会直接崩溃。
3.5 避坑指南
这部分是我用时间换来的经验,你记一下:
- 缩进必须一致:HCS用空格缩进,不能用Tab。我习惯用4个空格。混用Tab和空格?编译报错没商量。
- 属性名不能重复:同一个节点下,两个同名属性会报错。但子节点可以跟父节点属性重名,这不算冲突。
- 字符串别忘引号:
status = okay;是错的,必须写成status = "okay";。我刚开始老忘。 - 路径引用:可以用
root.module.device这种路径来引用节点,但路径里不能有空格。
总结一下:HCS配置管理,说白了就是「用结构化的方式描述硬件」。你只要记住三点——节点表示设备、属性表示参数、冒号表示继承。剩下的就是多写多练。我刚开始写HCS时,每写一个文件都要编译验证一遍,后来熟练了,基本一次过。
下一章咱们聊聊HDF驱动框架的加载流程,看看HCS配置是怎么被解析成驱动对象的。到时候你会发现,今天学的这些配置语法,其实都是在为驱动框架「喂数据」。