第1章:设备树语法入门——节点、属性、标签、路径的基本规则
各位同学好,我是老李。做嵌入式Linux驱动开发十几年了,设备树这东西,我刚开始接触时也是一头雾水。说实话,它看起来像XML,又有点像JSON,但又不完全是。今天咱们就把设备树最基础的语法掰开揉碎了讲清楚。
设备树,说白了就是描述硬件信息的“说明书”。CPU有几个核?外设挂在哪条总线上?中断号是多少?这些信息都写在设备树里。内核启动时读这份说明书,就知道怎么跟硬件打交道了。
核心概念一句话:设备树是用树形结构描述硬件资源的数据格式,驱动通过它找到自己对应的硬件。
1.1 节点(Node)——设备树的骨架
节点是设备树最基本的组成单元。每个节点代表一个硬件设备或总线。我习惯把节点想象成“文件夹”,里面可以放子节点,也可以放属性。
看个最简单的例子:
/dts-v1/;
/ {
model = "MyBoard";
compatible = "vendor,myboard";
cpu@0 {
compatible = "arm,cortex-a7";
reg = <0>;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>;
};
};
这里 / 是根节点,下面挂了 cpu@0 和 memory@80000000 两个子节点。节点名后面的 @0 和 @80000000 是地址,用来区分同类型设备。
我的经验:节点名尽量用通用名,比如 serial 比 uart1 好。因为内核会通过节点名匹配驱动,名字太随意容易出问题。我曾经见过有人把串口节点命名为 my_uart_test,结果驱动死活加载不上……
1.2 属性(Property)——节点的“身份证”
属性就是键值对,描述节点的具体特征。常见的属性类型有:
| 属性类型 | 写法示例 | 说明 |
|---|---|---|
| 字符串 | compatible = "vendor,device"; |
驱动匹配的关键 |
| 32位整数 | reg = <0x1000 0x100>; |
地址和大小 |
| 64位整数 | reg = <0x0 0x80000000 0x0 0x40000000>; |
高地址+低地址 |
| 布尔值 | status = "disabled"; |
存在即true |
| 字符串列表 | interrupt-names = "rx", "tx"; |
用空格分隔 |
这里重点说下 compatible 属性。它是驱动和设备树之间的“暗号”。内核启动时,会遍历所有节点,找到 compatible 值匹配的驱动。格式一般是 "厂商名,设备名"。
注意:compatible 的顺序很重要!内核会按列表顺序依次尝试匹配。我遇到过把 fallback 兼容写在前面的情况,结果驱动匹配到了错误的版本,折腾了两天才发现。
1.3 标签(Label)——给节点起个“外号”
标签就是节点的别名。为什么要用标签?你想想看,如果节点路径特别深,比如 /soc/bus@40000000/i2c@1000/rtc@68,每次引用都要写这么长,多累啊。
标签的写法很简单,在节点名前加个名字和冒号:
&i2c1: i2c@1000 {
compatible = "vendor,i2c";
reg = <0x1000 0x100>;
rtc@68 {
compatible = "maxim,ds3231";
reg = <0x68>;
};
};
这样在其他地方就可以用 &i2c1 来引用这个节点了。比如在另一个节点里引用它:
&i2c1 {
status = "okay";
clock-frequency = <400000>;
};
我的习惯:所有重要的外设节点都加上标签。标签名用 <总线名><序号> 的格式,比如 &i2c1、&spi2、&uart0。这样在 .dtsi 文件里覆盖配置时特别方便。
1.4 路径(Path)——设备树的“地址”
路径就是从根节点到目标节点的完整路线。用斜杠分隔,比如:
/soc/bus@40000000/i2c@1000/rtc@68
路径在 devicetree 的 API 里经常用到。比如在驱动里通过 of_find_node_by_path() 函数查找节点:
struct device_node *np;
np = of_find_node_by_path("/soc/bus@40000000/i2c@1000/rtc@68");
if (np) {
// 找到节点,可以读取属性了
}
路径和标签的区别?说白了,路径是绝对地址,标签是相对引用。路径写死了,标签可以灵活覆盖。
1.5 知识体系总览
下面这张图是我画的设备树语法结构图,帮你理清这几个概念的关系:
1.6 避坑指南
做设备树开发这些年,我踩过不少坑。分享几个最常见的:
- 分号别漏了——每个属性后面都要加分号。我曾经漏了一个分号,编译报错找了半小时。
- 标签名别重复——整个设备树里标签必须唯一。重复了编译不会报错,但引用时会混乱。
- 路径大小写敏感——Linux是大小写敏感的系统,
/SOC和/soc是两个不同的路径。 - 地址别写错进制——
0x开头是十六进制,不加是十进制。我见过有人把0x1000写成1000,结果地址对不上。
特别提醒:设备树编译(dtc)不会检查语义错误。它只检查语法。也就是说,你写个不存在的寄存器地址,编译也能通过。但内核启动时就会挂掉。所以写完设备树,一定要在目标板上实际测试。
好了,设备树的基本语法就这些。节点是骨架,属性是血肉,标签是别名,路径是地址。把这四个概念搞明白,后面学中断、时钟、GPIO映射就轻松多了。
记住一句话:设备树就是硬件的“身份证”,驱动通过它找到对应的硬件。写设备树时,把自己想象成硬件工程师,把每个外设的细节都描述清楚,驱动开发就事半功倍了。
公众号:蓝海资料掘金营,微信deep3321