4. 根节点与 aliases 节点:根节点属性、model/compatible、aliases 的用途与配置

设备树的根节点(/)是整个硬件描述树的起点,它定义了系统最顶层的属性,并为所有子节点提供命名空间。本章将深入解析根节点的核心属性、model 与 compatible 的语义,以及 aliases 节点在驱动开发中的实际用途与配置方法。

4.1 根节点概述

在设备树源文件(.dts)中,根节点使用 / 表示,所有其他节点都是它的子节点或后代节点。根节点本身不描述具体的硬件设备,而是提供系统级的标识、兼容性信息和全局别名映射。

关键概念: 根节点是设备树的唯一入口,其属性直接影响内核如何识别和初始化整个平台。

4.2 根节点核心属性

根节点通常包含以下标准属性,这些属性由 ePAPR(Embedded Power Architecture Platform Requirements)规范定义:

属性名 类型 描述 是否必须
model 字符串 描述具体硬件平台型号,如 "TI AM335x BeagleBone Black" 是
compatible 字符串列表 指定平台兼容的驱动列表,从最具体到最通用 是
#address-cells u32 定义子节点中 reg 属性地址字段的 32 位字数量 是
#size-cells u32 定义子节点中 reg 属性大小字段的 32 位字数量 是
aliases 子节点 为其他节点提供简短的别名引用 否
chosen 子节点 传递引导参数,如 bootargs、stdout-path 否

4.3 model 属性:硬件型号标识

model 属性是一个字符串,用于精确标识开发板或系统的型号。内核在启动早期会读取该属性,用于日志输出和平台匹配。

// 示例:根节点 model 属性
/ {
    model = "Acme Cortex-A9 Development Board Rev 2.0";
    compatible = "acme,cortex-a9-board", "acme,cortex-a9";
};
最佳实践: model 字符串应包含制造商、产品名和版本号,格式建议为 "Manufacturer,Product Revision"。这有助于调试和固件识别。

4.4 compatible 属性:驱动匹配核心

compatible 属性是一个字符串列表,按优先级从高到低排列。内核使用该属性与驱动中的 of_match_table 进行匹配,决定哪个驱动绑定到该设备。

匹配规则:

  • 内核遍历 compatible 列表中的每个字符串。
  • 驱动注册时提供 of_device_id 表,包含其支持的兼容字符串。
  • 当设备树的 compatible 字符串与驱动表中的某个条目完全匹配时,驱动被绑定。
  • 列表顺序决定优先级:第一个匹配的驱动优先使用。
// 设备树根节点 compatible 示例
/ {
    compatible = "ti,am335x-boneblack", "ti,am335x", "ti,omap3";
};

// 内核驱动匹配表示例(伪代码)
static const struct of_device_id my_driver_of_match[] = {
    { .compatible = "ti,am335x-boneblack", },
    { .compatible = "ti,am335x", },
    { /* sentinel */ }
};
注意: 根节点的 compatible 用于匹配平台级驱动(如 machine_desc),而设备节点的 compatible 用于匹配具体外设驱动。两者机制相同,但作用域不同。

4.5 aliases 节点:简化设备引用

aliases 节点是根节点的子节点,用于为其他节点定义简短的别名。别名在以下场景中非常有用:

  • Bootloader 传递参数: 通过 stdout-path 引用串口别名。
  • 驱动查找设备: 使用 of_find_node_by_alias() 快速定位节点。
  • 减少路径长度: 避免书写冗长的设备路径。
// aliases 节点配置示例
/ {
    aliases {
        serial0 = &uart1;
        serial1 = &uart2;
        mmc0 = &mmc1;
        ethernet0 = &mac0;
        i2c0 = &i2c1;
    };

    uart1: serial@10010000 {
        compatible = "ns16550";
        reg = <0x10010000 0x1000>;
    };

    uart2: serial@10020000 {
        compatible = "ns16550";
        reg = <0x10020000 0x1000>;
    };
};

4.6 aliases 的用途与配置

别名通过 &label 语法引用设备节点中的标签(label)。内核提供以下 API 操作别名:

API 函数 用途
of_find_node_by_alias(alias_name) 根据别名名称查找对应的设备节点
of_alias_get_id(np, stem) 获取设备节点在指定别名前缀下的编号
of_alias_get_highest_id(stem) 获取指定前缀别名的最大编号
// 内核驱动中使用别名查找节点
#include <linux/of.h>

struct device_node *np;
np = of_find_node_by_alias("serial0");
if (np) {
    // 成功找到 uart1 节点
    pr_info("Found serial0 alias: %pOF\n", np);
    of_node_put(np);
}

// 获取别名编号
int id = of_alias_get_id(np, "serial");
if (id >= 0) {
    pr_info("Serial device ID: %d\n", id);
}
配置规则:
  1. 别名名称必须是小写字母、数字和连字符的组合。
  2. 一个别名只能指向一个节点,但一个节点可以有多个别名。
  3. 别名在设备树编译时解析为绝对路径,运行时不可更改。
  4. 建议为所有关键外设(串口、MMC、以太网、I2C)定义别名。

4.7 根节点与 aliases 关系图

下图展示了根节点、aliases 节点与设备节点之间的引用关系:

根节点与 aliases 节点引用关系 根节点 / aliases 节点 serial0 = &uart1 mmc0 = &mmc1 ethernet0 = &mac0 设备节点 uart1: serial@10010000 mmc1: mmc@20000000 mac0: ethernet@30000000 说明 aliases 节点通过 &label 引用设备节点,内核使用 of_find_node_by_alias() 解析别名。 虚线表示别名到设备节点的映射关系,实线表示父子层级关系。

4.8 实战配置示例

以下是一个完整的根节点与 aliases 配置实例,基于典型的 ARM Cortex-A9 双核平台:

/dts-v1/;

/ {
    model = "Vendor Cortex-A9 Dual-Core Development Board v1.0";
    compatible = "vendor,cortex-a9-board", "vendor,cortex-a9", "arm,cortex-a9";
    #address-cells = <2>;
    #size-cells = <2>;

    aliases {
        serial0 = &uart0;
        serial1 = &uart1;
        mmc0 = &emmc;
        mmc1 = &sdhci;
        ethernet0 = &gmac;
        i2c0 = &i2c0;
        spi0 = &spi0;
        gpio0 = &gpio0;
    };

    chosen {
        stdout-path = &uart0;
        bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rw";
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x0 0x80000000 0x0 0x40000000>;  // 1GB RAM
    };

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

    uart0: serial@10000000 {
        compatible = "ns16550a";
        reg = <0x0 0x10000000 0x0 0x1000>;
        interrupts = <0 33 4>;
        clock-frequency = <24000000>;
        status = "okay";
    };

    uart1: serial@10001000 {
        compatible = "ns16550a";
        reg = <0x0 0x10001000 0x0 0x1000>;
        interrupts = <0 34 4>;
        clock-frequency = <24000000>;
        status = "disabled";
    };

    emmc: mmc@20000000 {
        compatible = "vendor,emmc-controller";
        reg = <0x0 0x20000000 0x0 0x1000>;
        interrupts = <0 45 4>;
        bus-width = <8>;
        non-removable;
        status = "okay";
    };

    gmac: ethernet@30000000 {
        compatible = "vendor,gmac";
        reg = <0x0 0x30000000 0x0 0x2000>;
        interrupts = <0 50 4>;
        phy-mode = "rgmii";
        status = "okay";
    };
};
调试技巧: 在 Linux 内核启动时,可以通过 printk 或 earlycon 查看设备树解析日志。使用 of_find_node_by_alias() 返回 NULL 时,检查别名名称是否拼写正确,以及设备节点是否定义了对应的标签。

4.9 常见错误与排查

错误场景 原因 解决方案
别名指向不存在的节点 标签未定义或拼写错误 检查 &label 中的标签是否在设备节点中定义
根节点缺少 #address-cells 地址解析失败,导致 reg 属性无法正确读取 根据总线宽度添加 #address-cells 和 #size-cells
compatible 字符串顺序错误 最通用的字符串排在最前,导致驱动匹配错误 将最具体的兼容字符串放在列表首位
别名名称包含大写字母 设备树规范要求别名全小写 将别名名称改为小写字母和连字符组合
重要提示: 设备树编译工具 dtc 不会检查别名引用的有效性。如果别名指向一个不存在的标签,编译不会报错,但内核在运行时 of_find_node_by_alias() 会返回 NULL,导致驱动初始化失败。务必在开发阶段通过 devicetree.dts 的 aliases 节点进行人工审查。