2. 设备树语法入门
好,咱们正式开始动手写设备树了。说实话,我第一次接触设备树语法时,觉得它长得有点像 JSON,又有点像 C 语言的结构体。但真正上手之后才发现——它其实就是一个描述硬件的树形结构。你只要搞懂四个核心概念:节点、属性、标签、包含,剩下的就是多写多练。
1. 节点(node)—— 树上的每一个分叉
设备树的核心就是「树」。树的每个分叉点,就是一个节点。节点可以嵌套,就像文件夹套文件夹一样。
举个例子,一个简单的设备树长这样:
/dts-v1/;
/ {
model = "STM32MP157 Development Board";
compatible = "st,stm32mp157";
cpu0: cpu@0 {
compatible = "arm,cortex-a7";
reg = <0>;
};
memory@c0000000 {
device_type = "memory";
reg = <0xc0000000 0x20000000>;
};
};
看到没?根节点是 /,下面挂了 cpu@0 和 memory@c0000000 两个子节点。每个节点用花括号 { } 包裹,最后用分号结束。嗯,这里要注意——分号千万别漏,我刚开始写的时候经常丢分号,编译报错找半天。
节点命名规则:
- 节点名一般写成
名称@地址的形式,比如uart@40011000 - 名称表示设备类型,地址表示它的基地址或编号
- 同一个父节点下,节点名必须唯一
2. 属性(property)—— 节点的身份证
节点光有个名字还不够,你得告诉内核这个设备是什么、怎么用。属性就是干这个的。
属性说白了就是键值对。常见的属性类型有几种:
| 属性类型 | 写法示例 | 说明 |
|---|---|---|
| 字符串 | compatible = "st,stm32mp157"; |
用于匹配驱动,格式一般是"厂商,设备名" |
| 32位整数 | reg = <0x40011000 0x400>; |
尖括号包起来,多个值用空格隔开 |
| 布尔值 | status = "okay"; 或 "disabled" |
用字符串表示,没有 true/false 关键字 |
| 字节数组 | local-mac-address = [00 11 22 33 44 55]; |
方括号包起来,每个字节用十六进制 |
我个人习惯把 compatible 属性放在每个节点的第一行,这样一眼就能看出这个节点对应什么驱动。我在项目中遇到过好几次,因为 compatible 写错了,驱动死活加载不上——后来发现是少了个逗号,或者厂商名拼错了。
3. 标签(label)—— 给节点起个外号
你有没有想过,如果节点名很长,比如 i2c@40012000,每次引用它都要写全名,多麻烦?标签就是解决这个问题的。
看上面那个例子里的 cpu0: cpu@0,cpu0 就是标签。标签写在节点名前面,用冒号隔开。之后你想引用这个节点,直接用 &cpu0 就行了。
&cpu0 {
clock-frequency = <650000000>;
};
这招在修改或扩展节点时特别有用。比如你有一个基础设备树,想给某个节点加点东西,不用把整个节点重写一遍,直接用标签引用,然后追加属性就行。
我的小技巧:标签命名尽量简短且有规律。比如 UART 节点统一叫 uart1、uart2,I2C 节点叫 i2c1、i2c2。这样在 &uart1 的时候,一看就懂。
4. 包含(include)—— 像 C 语言一样复用代码
设备树支持 #include 预处理指令。没错,就是 C 语言那个 #include。你可以把通用的定义放在一个 .dtsi 文件里,然后在具体板子的 .dts 文件里包含它。
举个例子:
// stm32mp157.dtsi —— 芯片级通用定义
/ {
cpu0: cpu@0 {
compatible = "arm,cortex-a7";
reg = <0>;
};
uart4: serial@40010000 {
compatible = "st,stm32-uart";
reg = <0x40010000 0x400>;
status = "disabled";
};
};
// my-board.dts —— 具体板子
/dts-v1/;
#include "stm32mp157.dtsi"
/ {
model = "My Custom Board";
};
&uart4 {
status = "okay";
clock-frequency = <64000000>;
};
你看,芯片级的定义放在 .dtsi 里,板子级的配置放在 .dts 里。这样同一个芯片的不同板子,只需要改 .dts 文件就行了。我曾经在一个项目里,用这种方式同时维护了 5 块不同的板子,每块板子只需要写几十行差异配置,省了不少事。
避坑指南:我曾经犯过一个错误——在 .dtsi 文件里把某个节点的 status 设成了 "okay",然后在 .dts 里又用 &节点 把它设成 "disabled"。结果编译时发现,两个地方都生效了,但后加载的覆盖了先加载的。所以建议:.dtsi 里统一设 "disabled",具体板子需要哪个就开哪个,这样逻辑更清晰。
5. 知识体系总览
说了这么多,咱们用一张图把整个语法体系串起来:
这张图把四个核心概念的关系理清楚了。节点是骨架,属性是血肉,标签是快捷方式,包含是复用利器。你写设备树的时候,脑子里时刻想着这四样东西,基本就不会跑偏。
6. 实战小练习
光看不练假把式。我给你留个小练习:
- 创建一个根节点,里面包含一个
uart7节点,基地址 0x40018000,长度 0x400 - 给 uart7 加上标签
uart7 - 在另一个文件里用
#include包含它,然后把status设为"okay"
试试看,写完了对照我前面给的例子检查一下。嗯,语法这东西,写三遍就熟了。