3. 设备树基础语法:DTS文件结构、节点与属性、标准属性、设备树包含文件

好,咱们正式开始啃设备树的语法。说实话,我刚接触设备树那会儿,看着满屏的dts文件,心里直犯嘀咕——这玩意儿跟C语言长得有点像,但又完全不是一回事。后来做项目多了,才慢慢摸清它的脾气。今天我就把这些年积累的经验,掰开了揉碎了讲给你听。

3.1 DTS文件结构:一棵倒着长的树

设备树,名字里带个「树」字,它真就是一棵树。根节点叫/,下面挂CPU、内存、外设,一层套一层。我习惯把设备树想象成公司组织架构图——CEO在最顶上,下面分技术部、市场部,每个部门再往下分小组。设备树也是这个逻辑。

来看一个最简单的DTS文件骨架:

/dts-v1/;

/ {
    model = "MyBoard";
    compatible = "vendor,myboard", "vendor,baseboard";

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;

        cpu@0 {
            compatible = "arm,cortex-a9";
            reg = <0>;
        };
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x40000000>;
    };

    serial@101f1000 {
        compatible = "ns16550";
        reg = <0x101f1000 0x1000>;
        interrupts = <0 12 4>;
    };
};

看到没?根节点/下面挂了三个子节点:cpus、memory、serial。每个节点又可以继续挂子节点,比如cpus下面又挂了cpu@0。这就是设备树的层级结构——说白了,就是一个嵌套的键值对集合。

核心要点:设备树的每个节点代表一个硬件设备或总线,节点名通常写成名称@地址的形式。地址部分用来区分同类型设备,比如两个串口可以叫serial@101f1000和serial@101f2000。

3.2 节点与属性:设备树的细胞和DNA

节点是设备树的「细胞」,属性是节点的「DNA」。每个节点用一对花括号{ }包裹,里面可以放属性,也可以放子节点。属性就是键值对,写法很灵活。

我总结了几种常见的属性写法:

  • 字符串:compatible = "vendor,device";
  • 32位整数:reg = <0x1000 0x100>;
  • 整数数组:interrupts = <0 12 4>;
  • 布尔值:status = "okay"; 或 status = "disabled";
  • 字节数组:local-mac-address = [00 11 22 33 44 55];

嗯,这里要注意:属性值要么用双引号括起来(字符串),要么用尖括号括起来(整数),要么用方括号括起来(字节)。混着用?不行,编译器会报错。我曾经在项目里把reg的值写成了字符串格式,结果内核死活认不出内存范围,排查了半天才发现是这种低级错误。

3.3 标准属性:compatible、reg、interrupts

这三个属性是设备树的「三驾马车」,几乎每个设备节点都会用到。咱们一个一个说。

3.4.1 compatible:设备的身分证

compatible属性告诉内核:这个设备是什么型号,用哪个驱动来管它。格式是"厂商,设备型号"。比如:

compatible = "ti,am335x-uart", "ns16550";

内核匹配驱动时,会从左到右依次尝试。第一个匹配不上,就试第二个。我建议你写的时候,把最具体的型号放前面,通用的放后面。这样既保证精确匹配,又有兜底方案。

避坑指南:我曾经见过有人把compatible写反了——通用型号在前,具体型号在后。结果内核匹配到了通用驱动,导致某些特殊功能无法启用。记住:越具体越靠前。

3.4.2 reg:设备的地址空间

reg属性描述设备在总线上的地址范围和大小。格式是<地址 长度>,地址和长度都是32位整数。如果地址或长度超过32位,就需要配合#address-cells和#size-cells来调整。

// 32位地址,32位长度
reg = <0x101f1000 0x1000>;

// 64位地址,32位长度(需要父节点设置#address-cells=<2>)
reg = <0x0 0x80000000 0x1000>;

说白了,#address-cells决定地址占几个32位整数,#size-cells决定长度占几个。默认都是1。我刚开始学的时候,老搞混这两个值,后来记住一句话:父节点定义规则,子节点遵守规则。

3.4.3 interrupts:设备的中断信号

中断描述稍微复杂一点,因为它涉及中断控制器。一个典型的中断描述包含三部分:中断类型(SPI/P PI)、中断号、触发方式。

interrupts = <0 12 4>;

这里的三个数字分别代表:

  • 0:SPI中断(共享外设中断)
  • 12:中断号(硬件线号)
  • 4:触发方式(高电平触发)

触发方式的常用值:1=上升沿触发,2=下降沿触发,4=高电平触发,8=低电平触发。嗯,这个数字是位掩码,可以组合使用。

注意:中断号的编号方式因芯片而异。有些芯片从0开始编号,有些从32开始。我在做TI AM335x项目时,就踩过这个坑——手册上写的中断号是32,但设备树里要写0。为什么?因为中断控制器内部做了偏移。所以,一定要看芯片手册里的「中断映射表」,别想当然。

3.4 设备树包含文件:.dtsi 的妙用

一个复杂的板子,外设可能有几十个。如果所有内容都写在一个.dts文件里,那文件会变得又臭又长。这时候.dtsi文件就派上用场了——它相当于C语言里的头文件,用来存放通用的、可复用的设备树片段。

比如,TI AM335x芯片的通用定义放在am335x.dtsi里:

// am335x.dtsi
#include <dt-bindings/interrupt-controller/irq.h>

/ {
    compatible = "ti,am335x";

    ocp {
        #address-cells = <1>;
        #size-cells = <1>;

        uart0: serial@44e09000 {
            compatible = "ti,am335x-uart", "ns16550";
            reg = <0x44e09000 0x2000>;
            interrupts = <72>;
            status = "disabled";
        };

        i2c0: i2c@44e0b000 {
            compatible = "ti,am335x-i2c";
            reg = <0x44e0b000 0x1000>;
            interrupts = <70>;
            #address-cells = <1>;
            #size-cells = <0>;
            status = "disabled";
        };
    };
};

然后具体的板子文件myboard.dts只需要包含它,再覆盖或添加板级特有的内容:

// myboard.dts
/dts-v1/;
#include "am335x.dtsi"

/ {
    model = "MyBoard";
    compatible = "mycompany,myboard", "ti,am335x";

    &uart0 {
        status = "okay";
        clock-frequency = <48000000>;
    };

    &i2c0 {
        status = "okay";
        eeprom@50 {
            compatible = "atmel,24c02";
            reg = <0x50>;
        };
    };
};

看到&uart0这种写法了吗?这叫「引用标签」。在.dtsi里用uart0:给节点打上标签,在.dts里用&uart0引用它,然后添加或覆盖属性。这样既复用了芯片的通用定义,又保留了板级的灵活性。

我的习惯:我会把芯片相关的所有外设都定义在.dtsi里,并且默认把status设为disabled。然后在板级.dts里,只把用到的外设打开。这样既清晰又安全——万一哪个外设没用到,也不会意外占用资源。

3.5 知识体系总览

为了让你对本章内容有个整体把握,我画了一张图:

设备树基础语法知识体系 DTS文件结构 节点与属性 标准属性 .dtsi包含文件 节点命名规则 属性值类型 层级嵌套 compatible reg interrupts 通用定义复用 标签引用 属性覆盖 核心思想:用树形结构描述硬件拓扑 节点 = 设备,属性 = 设备参数,.dtsi = 复用模板

这张图把本章的知识点串起来了。你想想看,设备树其实就三件事:节点怎么组织、属性怎么写、公共部分怎么复用。掌握了这三块,大部分DTS文件你都能看懂,也能自己动手写了。

最后说一句:设备树语法不难,难的是把硬件手册里的信息准确翻译成设备树。我刚开始做的时候,经常对着几百页的芯片手册发呆。后来慢慢摸索出一个方法——先找到芯片厂商提供的.dtsi文件,照着它的风格写,遇到不确定的就去查手册。这样效率高很多。

好,这一章就到这里。下一章咱们聊聊设备树的编译和调试,到时候我会分享一些我踩过的坑和调试技巧。


可可精品资料库