第一章:设备树基础

1.1 什么是设备树?

设备树,英文叫 Device Tree。说白了,它就是一个描述硬件的数据结构。

我刚开始做嵌入式时,内核里全是板级文件。换一个板子,就要改一堆 C 代码。后来 ARM Linux 社区受不了了,干脆搞了个设备树出来。嗯,这玩意儿就是用来替代那些硬编码的板级信息的。

设备树用树形结构来描述硬件。CPU、内存、中断控制器、GPIO 控制器、I2C 设备……统统挂在树上。每个硬件节点都有属性,比如地址、中断号、时钟频率等。

核心思想:把硬件描述从内核代码中剥离出来,变成独立的数据文件。内核启动时解析这个文件,就知道板子上有什么硬件了。

1.2 为什么需要设备树?

你想想看,以前没有设备树的时候,内核是怎么知道硬件的?

每个板子都要写一个 board.c 文件。里面注册平台设备、定义资源、配置中断……代码量巨大。而且不同厂商的板子,代码风格还不一样。维护起来简直是噩梦。

我记得有一次,帮客户移植内核。他们换了颗 Flash 芯片,结果要改三个文件:板级文件、NAND 驱动、分区表。改完还要重新编译整个内核。折腾了两天。

设备树解决了三个核心问题:

  • 硬件描述标准化:所有硬件都用同一种语法描述,不再有五花八门的板级文件
  • 内核与硬件解耦:换硬件只需要改 dts 文件,不用改内核代码
  • 启动参数灵活:内存大小、串口波特率、MAC 地址等,都可以在设备树里配

我的经验:做产品开发时,我习惯把设备树和内核分开管理。内核用主线版本,设备树自己维护。这样升级内核时,只需要改少量设备树节点,省心很多。

1.3 设备树文件结构

设备树涉及三种文件:dts、dtsi、dtb。很多人一开始搞混,我简单说说它们的区别。

文件类型 扩展名 作用 谁在用
源文件 .dts 描述具体板子的硬件 开发者编写
头文件 .dtsi 存放通用硬件描述,可被多个 dts 引用 SoC 厂商提供
二进制文件 .dtb 编译后的设备树,内核直接使用 Bootloader 加载

dtsi 就像 C 语言的 .h 头文件。SoC 厂商会把 CPU、中断控制器、GPIO 控制器等通用部分放在 dtsi 里。板级开发者只需要 include 进来,再添加自己板子特有的外设即可。

举个例子:

// 这是 SoC 厂商提供的 .dtsi 文件
// 描述了一个通用的 ARM 处理器

/ {
    #address-cells = <1>;
    #size-cells = <1>;
    
    cpu@0 {
        compatible = "arm,cortex-a7";
        reg = <0x0>;
    };
    
    interrupt-controller@1e00000 {
        compatible = "arm,gic-400";
        reg = <0x1e00000 0x1000>;
        interrupt-controller;
        #interrupt-cells = <3>;
    };
    
    gpio@1c00000 {
        compatible = "snps,dw-apb-gpio";
        reg = <0x1c00000 0x1000>;
        gpio-controller;
        #gpio-cells = <2>;
    };
};

然后板级 dts 文件这样写:

// 这是具体板子的 .dts 文件
// 引用了 SoC 的 dtsi,再添加板级外设

#include "soc.dtsi"

/ {
    model = "MyCustomBoard v1.0";
    compatible = "mycompany,myboard";
    
    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  // 512MB
    };
    
    &uart0 {
        status = "okay";
        clock-frequency = <115200>;
    };
    
    &i2c0 {
        status = "okay";
        
        eeprom@50 {
            compatible = "atmel,24c02";
            reg = <0x50>;
        };
    };
};

编译时,dtc(Device Tree Compiler)会把 dts 和 dtsi 合并,生成一个 dtb 文件。Bootloader 把 dtb 加载到内存,内核启动时解析它。

注意:我曾经遇到过一个坑——dtsi 里定义了某个外设,但板级 dts 里忘了设置 status = "okay"。结果内核根本不初始化这个设备。查了两天才发现是 status 默认是 "disabled"。所以,添加外设后一定要检查 status 属性。

1.4 设备树的核心语法

设备树的语法其实很简单。我总结几个关键点:

  • 节点:用 {} 表示,节点名格式为 name@address
  • 属性:key = value; 的形式,value 可以是字符串、数字、数组
  • 标签:用 & 引用已有节点,方便追加属性
  • 包含:用 #include 引入 dtsi 文件

这里有个小技巧:属性值里的数字,默认是 32 位。如果地址是 64 位,需要设置 #address-cells = <2>。我见过有人把 64 位地址截断了,结果内存只认到 4GB。

1.5 设备树与中断控制器、GPIO 的关系

设备树里,中断控制器和 GPIO 控制器是两个非常重要的节点。它们负责描述硬件的中断路由和 GPIO 映射。

中断控制器节点需要声明:

  • interrupt-controller:表明这是一个中断控制器
  • #interrupt-cells:描述中断描述符需要几个 cell

GPIO 控制器节点需要声明:

  • gpio-controller:表明这是一个 GPIO 控制器
  • #gpio-cells:描述 GPIO 描述符需要几个 cell

外设节点通过 interrupts 和 gpios 属性来引用这些资源。这部分内容,我们会在后面的章节详细展开。

我的建议:刚开始学设备树,不要急着看复杂的 dts 文件。先找一块简单的开发板,读它的 dts 文件。对照原理图,一个一个节点看。看懂了,再自己写一个最小系统。实践出真知。

1.6 本章知识体系

下面这张图,概括了设备树的核心概念和文件结构:

设备树知识体系 .dts 源文件 具体板级描述 .dtsi 头文件 SoC通用描述 .dtb 二进制 编译后内核使用 dtc 编译器 设备树核心概念 节点 (Node) name@address 属性 (Property) key = value 标签 (Label) &引用节点 包含 (Include) #include 设备树应用场景 中断控制器映射 GPIO 映射 外设资源描述

这张图展示了设备树的完整知识体系。从源文件到二进制文件,从核心概念到应用场景。后面的章节,我们会逐一深入每个细节。


好了,第一章就到这里。设备树的基础概念,说白了就是:用树形结构描述硬件,把硬件信息从内核代码中分离出来。dts、dtsi、dtb 三种文件各司其职,共同构成了设备树的完整生态。

下一章,我们会深入设备树的语法细节。包括节点命名规则、属性类型、地址编码等。这些都是写设备树必须掌握的基本功。


公众号:蓝海资料掘金营,微信deep3321