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" 必须用双引号
整数 0x10004096 支持十进制和十六进制
布尔 truefalse 小写,没有引号
数组 [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配置是怎么被解析成驱动对象的。到时候你会发现,今天学的这些配置语法,其实都是在为驱动框架「喂数据」。