2. 设备树基础语法:节点、属性、标签、包含文件与 dts/dtsi 文件结构

设备树(Device Tree)是一种描述硬件资源的数据结构,它使用树形节点和键值对属性来描述系统中所有可寻址设备。在 Linux 内核中,设备树是 Platform 驱动与硬件解耦的核心媒介。掌握设备树的基础语法,是进行驱动移植的第一步。

2.1 节点(Node)与根节点

设备树由一系列节点组成,每个节点代表一个硬件设备或总线。节点从根节点 / 开始,形成树状层级结构。每个节点可以包含子节点和属性。

节点命名规则: 节点名称格式为 node-name@unit-address,其中 unit-address 通常为设备在总线上的起始地址或寄存器基址,用于区分同名节点。
/ {
    #address-cells = <1>;
    #size-cells = <1>;
    model = "TI AM335x BeagleBone Black";
    compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "arm,cortex-a8";
            device_type = "cpu";
            reg = <0x0>;
        };
    };

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

上述示例中:

  • / 是根节点,包含整个系统的描述。
  • cpus 节点描述 CPU 拓扑,其子节点 cpu@0 描述具体 CPU 核心。
  • memory@80000000 节点描述物理内存区域。

2.2 属性(Property)

属性是节点的键值对,用于描述设备的特性。属性值可以是字符串、整数数组、字节数组或布尔值。以下是驱动开发中最常用的属性:

属性名 值类型 说明 示例
compatible 字符串列表 设备兼容性标识,驱动匹配的关键字段 "ti,am335x-uart", "ns16550"
reg 整数数组 设备地址空间(基址 + 长度) <0x48020000 0x1000>
interrupts 整数数组 中断号与触发类型 <0x1c 0x4>
clocks phandle 数组 时钟源引用 <&clk_uart1>
status 字符串 设备状态:"okay" 或 "disabled" "okay"
#address-cells 整数 子节点 reg 地址字段的 32 位字数量 <1>
#size-cells 整数 子节点 reg 长度字段的 32 位字数量 <1>
关键提示: compatible 属性是驱动与设备匹配的“身份证”。内核通过 of_match_table 中的 compatible 字符串与设备树节点的 compatible 属性进行比对,匹配成功则调用驱动的 probe 函数。

2.3 标签(Label)与引用

标签(Label)是节点的别名,用于在设备树的其他位置引用该节点。标签以冒号结尾,放在节点名称之前。通过 &label 语法可以引用标签,实现节点内容的追加或覆盖。

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

/* 在其他 dtsi 文件中通过标签引用并修改 */
&uart0 {
    status = "okay";
    pinctrl-0 = <&uart0_pins>;
    pinctrl-names = "default";
};

标签机制的优势:

  • 解耦: SoC 级 dtsi 定义节点骨架,板级 dts 通过标签覆盖具体配置。
  • 复用: 同一 SoC 的不同板卡可以共享 dtsi,仅通过标签差异化配置。
  • 可读性: 避免重复编写完整节点路径,减少错误。

2.4 包含文件(Include)与 dts/dtsi 文件结构

设备树源文件分为两种扩展名:

  • .dts(Device Tree Source):板级设备树文件,通常包含具体板卡的硬件描述。
  • .dtsi(Device Tree Source Include):通用设备树包含文件,通常描述 SoC 内部外设、CPU 架构等公共部分。

通过 #include 预处理指令,可以将 dtsi 文件包含到 dts 中,形成层次化的文件结构。

/* am335x-boneblack.dts */
#include "am33xx.dtsi"
#include "am335x-bone-common.dtsi"

&mmc1 {
    vmmc-supply = <&vmmc_reg>;
    status = "okay";
};

&am33xx_pinmux {
    uart0_pins: pinmux_uart0_pins {
        pinctrl-single,pins = <
            0x170 (PIN_INPUT | MUX_MODE0)  /* uart0_rxd */
            0x174 (PIN_OUTPUT | MUX_MODE0) /* uart0_txd */
        >;
    };
};

典型的设备树文件层级结构如下:

设备树文件层级结构 板级 .dts 文件 #include "soc.dtsi" — SoC 级通用描述 #include "board-common.dtsi" — 板级公共描述 节点定义与标签引用 &uart0 { status = "okay"; }; &mmc1 { vmmc-supply = ... }; pinmux_uart0_pins { ... }; 通过标签引用,板级 dts 覆盖或追加 SoC dtsi 中的节点属性

2.5 节点地址编码:reg、address-cells 与 size-cells

设备树使用 reg 属性描述设备的地址空间。其格式由父节点的 #address-cells 和 #size-cells 决定:

  • #address-cells:表示 reg 中地址字段占用的 32 位整数个数。
  • #size-cells:表示 reg 中长度字段占用的 32 位整数个数。
/* 32 位地址空间,1 个地址 + 1 个长度 */
bus@10000000 {
    compatible = "simple-bus";
    #address-cells = <1>;
    #size-cells = <1>;
    reg = <0x10000000 0x10000>;  /* 基址 0x10000000,长度 0x10000 */

    device@1000 {
        reg = <0x1000 0x200>;     /* 偏移 0x1000,长度 0x200 */
    };
};

/* 64 位地址空间,2 个地址 + 1 个长度 */
bus@0 {
    compatible = "simple-bus";
    #address-cells = <2>;
    #size-cells = <1>;
    reg = <0x0 0x40000000 0x10000>;  /* 高32位 0x0,低32位 0x40000000,长度 0x10000 */

    device@0,40001000 {
        reg = <0x0 0x40001000 0x1000>;
    };
};
常见错误: 忘记设置 #address-cells 和 #size-cells 会导致内核解析 reg 属性时出现地址错位。对于 simple-bus 节点,这两个属性必须显式声明。

2.6 中断属性与中断控制器

设备树通过 interrupt-parent、interrupts 和 interrupt-controller 描述中断拓扑:

/ {
    interrupt-parent = <&gic>;  /* 全局默认中断控制器 */

    gic: interrupt-controller@48241000 {
        compatible = "arm,cortex-a15-gic";
        #interrupt-cells = <3>;
        interrupt-controller;
        reg = <0x48241000 0x1000>,
              <0x48242000 0x2000>;
    };

    uart1: serial@44e09000 {
        compatible = "ti,am3352-uart", "ns16550";
        reg = <0x44e09000 0x2000>;
        interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
        interrupt-parent = <&gic>;
    };
};

中断属性关键点:

  • interrupt-controller; 声明该节点为中断控制器。
  • #interrupt-cells 指定 interrupts 属性中每个中断描述占用的单元数(通常为 3:中断类型、中断号、触发标志)。
  • interrupt-parent 指定设备连接的中断控制器,若未指定则继承父节点或根节点的设置。

2.7 设备树编译与反编译

设备树源文件需要通过编译器生成二进制设备树 Blob(.dtb),内核在启动时解析 dtb 并构建 device_node 树。

命令 说明 示例
dtc -I dts -O dtb -o output.dtb input.dts 将 .dts 编译为 .dtb dtc -I dts -O dtb -o am335x-boneblack.dtb am335x-boneblack.dts
dtc -I dtb -O dts -o output.dts input.dtb 将 .dtb 反编译为 .dts dtc -I dtb -O dts -o am335x-boneblack.dts am335x-boneblack.dtb
dtc -I dts -O dtb -@ -o output.dtb input.dts 编译时保留符号(支持插件覆盖) dtc -I dts -O dtb -@ -o output.dtb input.dts
调试技巧: 使用 dtc -I fs -O dts /proc/device-tree 可以实时查看内核当前使用的设备树内容,用于验证驱动解析是否正确。

2.8 设备树语法速查表

设备树语法速查图 节点定义 [label:] name@addr { }; 属性赋值 key = <value>; 或 "string"; 标签引用 &label { 追加属性 }; #include "soc.dtsi" — 文件包含,支持层级复用 地址编码 #address-cells / #size-cells + reg 中断描述 interrupt-parent + interrupts + #interrupt-cells 编译流程:.dts + .dtsi → dtc 编译器 → .dtb → 内核解析 → device_node 树 驱动通过 of_* 系列 API 从 device_node 中读取属性值

2.9 实战案例:解析一个 UART 节点

以下是一个完整的 UART 设备树节点及其对应的驱动解析流程:

/* 设备树节点 */
uart2: serial@48022000 {
    compatible = "ti,am3352-uart", "ns16550";
    reg = <0x48022000 0x2000>;
    interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;
    interrupt-parent = <&gic>;
    clocks = <&uart2_clk>;
    clock-names = "fck";
    status = "okay";
};

驱动中对应的属性解析代码:

#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>

static int my_uart_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    struct resource res;
    int irq;

    /* 1. 解析 reg 属性:获取 IO 内存资源 */
    if (of_address_to_resource(np, 0, &res)) {
        dev_err(dev, "failed to get reg resource\n");
        return -EINVAL;
    }

    /* 2. 解析 interrupts 属性:获取中断号 */
    irq = of_irq_get(np, 0);
    if (irq < 0) {
        dev_err(dev, "failed to get irq\n");
        return irq;
    }

    /* 3. 解析 clocks 属性:获取时钟句柄 */
    struct clk *clk = of_clk_get(np, 0);
    if (IS_ERR(clk)) {
        dev_err(dev, "failed to get clock\n");
        return PTR_ERR(clk);
    }

    /* 4. 解析 status 属性:检查设备是否启用 */
    if (!of_device_is_available(np)) {
        dev_info(dev, "device is disabled\n");
        return -ENODEV;
    }

    dev_info(dev, "UART probe success: reg=0x%llx, irq=%d\n",
             res.start, irq);
    return 0;
}
核心要点: 设备树语法是驱动移植的“硬件描述语言”。节点定义硬件拓扑,属性描述设备参数,标签实现配置复用,dtsi 文件实现 SoC 级抽象。驱动开发者需要熟练掌握 compatible、reg、interrupts、clocks 等核心属性的解析方法,这是后续章节中 Platform 驱动与设备树交互的基础。