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 知识结构图
下面这张图把五种数据类型的关系画清楚了。你可以看到,它们最终都服务于驱动开发中的具体场景。
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 反编译看看,确认类型对不对。