3. 设备树数据类型:字符串、整数、数组、布尔值、phandle

设备树里的数据类型,说白了就五种。刚接触时觉得简单,但真写起来,坑不少。我最早做板级移植时,就因为一个数组长度写错,折腾了两天。今天咱们把这五种类型彻底捋清楚。

3.1 字符串(string)

字符串是最常用的类型。写法也直白:用双引号包起来。

compatible = "vendor,device";
model = "MyBoard v1.0";
status = "okay";

注意一点:字符串里不能有换行。如果值太长,可以拆成多个字符串,内核会自动拼接。比如:

compatible = "ti,am335x", "ti,am33xx";

这其实是字符串数组,但驱动读的时候会逐个匹配。我习惯把最具体的型号放前面,通用的放后面。这样驱动匹配时能优先找到最精确的。

小技巧: 字符串里不要加多余空格。比如 "okay " 和 "okay" 是两个不同的值,驱动可能不认。

3.2 整数(u32)

整数在设备树里默认是32位无符号。写法就是直接写数字,可以用十进制或十六进制。

reg = <0x10000000 0x1000>;
clock-frequency = <50000000>;
interrupts = <0 31 4>;

这里有个容易踩的坑:<> 里面每个数字都是32位。如果你要表示64位地址,得用两个32位拼起来。比如:

reg = <0x0 0x10000000 0x0 0x1000>;  // 64位地址 + 64位长度

我曾经在写一个64位ARM平台时,忘了把地址拆成两个32位,结果驱动读到的地址全是错的。嗯,从那以后我每次写reg都会多看一眼位数。

3.3 数组(数组)

数组其实就是多个整数用空格隔开,放在 <> 里。但要注意,设备树里没有“数组长度”这个概念,长度由驱动自己决定。

gpios = <&gpio0 15 0 &gpio1 20 1>;
pinctrl-0 = <&uart0_pins &mmc0_pins>;

数组的每个元素可以是整数,也可以是phandle(后面会讲)。驱动读的时候,按顺序一个个取。我建议你在写数组时,加个注释说明每个元素的含义:

gpios = <&gpio0 15 0>;  // phandle, pin编号, 标志位

这样别人看你的dts,一眼就明白。

数据类型 写法 典型用途
字符串 "string" compatible, model, status
整数 <value> reg, interrupts, clock-frequency
数组 <v1 v2 v3> gpios, pinctrl, dmas
布尔值 存在即true status, 功能开关
phandle &label 引用其他节点

3.4 布尔值(boolean)

设备树里没有 true 或 false 关键字。布尔值的判断逻辑很简单:属性存在就是真,不存在就是假。

// 使能某个功能
enable-wakeup;

// 禁用某个功能
// 直接不写这个属性就行

驱动里用 of_property_read_bool() 来读。比如:

if (of_property_read_bool(np, "enable-wakeup")) {
    // 使能唤醒功能
}

你想想看,这种设计其实挺巧妙的。不需要给属性赋值,省去了很多麻烦。但要注意:千万别写成 enable-wakeup = <1>;,那样就成了整数,驱动读布尔值的API会返回false。

避坑: 布尔属性不要赋值。写成 enable-wakeup; 就行,不要画蛇添足加 = <1> 或 = "true"。

3.5 phandle(指针)

phandle 是设备树的“指针”。它用来引用另一个节点。写法是用 & 加上节点标签。

// 定义节点
uart0: serial@10000000 {
    compatible = "ns16550";
    reg = <0x10000000 0x1000>;
};

// 引用节点
chosen {
    stdout-path = &uart0;
};

phandle 可以出现在数组里,也可以单独用。驱动里用 of_parse_phandle() 来解析。比如:

struct device_node *np;
np = of_parse_phandle(dev->of_node, "clocks", 0);
if (np) {
    // 拿到引用的节点,可以继续读它的属性
}

我刚开始写驱动时,总搞不清 phandle 和字符串的区别。后来想明白了:字符串是“名字”,phandle 是“地址”。名字可能重复,地址是唯一的。

3.6 知识结构图

下面这张图把五种数据类型的关系画清楚了。你可以看到,它们最终都服务于驱动开发中的具体场景。

设备树数据类型总览 设备树数据类型 字符串 整数 (u32) 数组 布尔值 phandle 使用建议: • 字符串:用于 compatible、model 等标识性属性 • 整数:用于 reg、interrupts 等硬件资源描述 • 数组:用于 gpios、dmas 等多值属性 • 布尔值:用于功能开关,存在即 true • phandle:用于节点间引用,相当于指针

3.7 混合使用与实战案例

实际项目中,这五种类型经常混在一起。比如一个典型的GPIO按键节点:

gpio-keys {
    compatible = "gpio-keys";
    #address-cells = <1>;
    #size-cells = <0>;
    
    button@0 {
        label = "Power";           // 字符串
        gpios = <&gpio0 15 0>;    // phandle + 整数数组
        linux,code = <116>;        // 整数
        wakeup-source;             // 布尔值
    };
};

你看,一个节点里五种类型全用上了。驱动读的时候,每种类型用不同的API:

// 读字符串
of_property_read_string(np, "label", &str);

// 读整数
of_property_read_u32(np, "linux,code", &code);

// 读phandle + 数组
gpio = of_get_named_gpio(np, "gpios", 0);

// 读布尔值
if (of_property_read_bool(np, "wakeup-source")) {
    // 使能唤醒
}
核心要点: 写设备树时,先想清楚每个属性该用哪种类型。类型选错了,驱动读出来的值就是错的。我建议你写完后,用 dtc -I dtb -O dts 反编译看看,确认类型对不对。