2. Linux 设备树基础:设备树(DT)的起源与作用、DTS/DTC/DTB 文件格式、设备树的基本语法

2.1 设备树的起源与核心作用

在早期的 Linux 内核中,板级硬件信息(如内存基址、中断号、外设寄存器地址)被硬编码在 arch/arm/mach-xxx 目录下的 C 语言板文件中。每更换一款硬件平台,就需要重新编译内核,导致内核镜像与硬件高度耦合,维护成本急剧上升。

设备树(Device Tree,DT)的引入正是为了解决这一痛点。它是一套用于描述硬件资源的数据结构,将硬件配置从内核源码中剥离出来,以独立文件的形式提供给内核。其核心作用包括:

  • 硬件描述标准化:使用统一的节点/属性模型描述 CPU、内存、总线、外设及其连接关系。
  • 内核与硬件解耦:同一内核镜像可通过更换设备树文件适配不同硬件平台,无需重新编译。
  • 驱动匹配机制:驱动通过 compatible 属性与设备树节点匹配,实现自动加载。
  • 资源动态传递:中断、DMA、时钟、GPIO 等资源通过设备树传递给驱动,避免硬编码。
关键理解:设备树不是驱动代码,而是硬件拓扑的“数据清单”。驱动读取这份清单来知道“我在哪、用什么资源、怎么配置”。
设备树解耦前后对比 旧模式:硬编码板文件 内核源码 板文件(.c) 编译 → 单一内核镜像 硬件变更 → 重新编译内核 新模式:设备树解耦 内核镜像 设备树(.dtb) Bootloader 加载 DTB 到内存 硬件变更 → 仅更换 DTB 文件

2.2 DTS / DTC / DTB 文件格式与编译流程

设备树涉及三种文件格式,它们构成了从源码到二进制再到内核解析的完整链路:

文件类型 扩展名 格式 作用
DTS .dts 文本(ASCII) 设备树源文件,由开发者编写,描述硬件拓扑
DTSI .dtsi 文本(ASCII) 设备树包含文件,类似 C 语言的 .h,用于复用公共定义
DTC — 可执行程序 设备树编译器(Device Tree Compiler),将 DTS 编译为 DTB
DTB .dtb 二进制(FDT 格式) 设备树二进制 Blob,Bootloader 传递给内核解析
编译命令示例:
# 将 .dts 编译为 .dtb
dtc -I dts -O dtb -o my_board.dtb my_board.dts

# 反编译 .dtb 为 .dts(调试用)
dtc -I dtb -O dts -o my_board_decompiled.dts my_board.dtb
设备树编译流程 .dts 源文件 .dtsi 包含文件 DTC 编译器 .dtb 二进制 #include 输入 输出 Bootloader(U-Boot)读取 .dtb → 传递给内核 → 驱动解析节点

2.3 设备树基本语法:节点、属性、标签

设备树采用树形层级结构,根节点为 /,每个节点代表一个硬件设备或总线,节点内包含若干属性描述其配置信息。以下是核心语法要素:

2.3.1 节点(Node)

节点是设备树的基本单元,格式为 节点名@地址 { ... };。节点名应体现设备类型,地址部分通常为寄存器基址。

/dts-v1/;

/ {
    model = "TI AM335x EVM";
    compatible = "ti,am335x-evm", "ti,am335x";

    /* 根节点下的子节点 */
    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "arm,cortex-a8";
            reg = <0x0>;
        };
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  /* 512MB RAM */
    };

    /* 外设节点 */
    &uart0 {
        status = "okay";
    };
};

2.3.2 属性(Property)

属性是节点内的键值对,用于描述硬件特性。常见属性类型如下:

属性类型 语法示例 说明
字符串 compatible = "ti,am335x-evm"; 驱动匹配的关键标识,格式为“厂商,型号”
32 位整数 reg = <0x48020000 0x1000>; 地址/长度对,由 #address-cells 和 #size-cells 决定
布尔值 status = "okay"; 或 status = "disabled"; 控制设备启用/禁用
字符串列表 compatible = "ti,am335x-evm", "ti,am335x"; 多个字符串用逗号分隔,内核按顺序匹配
二进制数据 local-mac-address = [00 11 22 33 44 55]; 用方括号包裹十六进制字节

2.3.3 标签(Label)与引用

标签为节点提供一个别名,便于在其他位置引用或覆盖节点属性。标签定义在节点名之前,使用 &标签名 引用。

/* 定义标签 */
uart0: serial@44e09000 {
    compatible = "ti,am3352-uart", "ti,omap3-uart";
    reg = <0x44e09000 0x2000>;
    interrupts = <72>;
    status = "disabled";
};

/* 在其他 .dts 文件中通过标签引用并修改属性 */
&uart0 {
    status = "okay";
    pinctrl-0 = <&uart0_pins>;
};
常见错误:标签引用时不要忘记 & 符号;reg 属性中的地址和长度必须与 #address-cells、#size-cells 的数值一致,否则内核解析会出错。
设备树节点层级结构示例 / (根节点) cpus memory@80000000 chosen serial@44e09000 cpu@0 cpu@1 节点关键属性 compatible = "ti,am3352-uart" → 驱动匹配 reg = <0x44e09000 0x2000> → 寄存器基址+长度

2.4 设备树在 CAN 驱动集成中的关键作用

在网关驱动部署实践中,设备树是 CAN 控制器与内核驱动之间的桥梁。以 TI AM335x 平台为例,D_CAN 控制器在设备树中的典型配置如下:

&dcan0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&dcan0_pins>;
};

&dcan1 {
    status = "okay";
    pinctrl-0 = <&dcan1_pins>;
};

驱动通过 of_match_table 匹配 compatible 属性,自动获取 reg(寄存器地址)、interrupts(中断号)、clocks(时钟源)等资源,无需在驱动代码中硬编码任何硬件地址。

实践要点:在网关项目中,设备树不仅描述 CAN 控制器,还需配置 CAN 收发器的 GPIO 控制引脚(如 STB/EN 引脚)、CAN 时钟频率以及总线波特率参数。这些信息全部通过设备树属性传递给驱动,实现“一套驱动,多板适配”。