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);
}
配置规则:
- 别名名称必须是小写字母、数字和连字符的组合。
- 一个别名只能指向一个节点,但一个节点可以有多个别名。
- 别名在设备树编译时解析为绝对路径,运行时不可更改。
- 建议为所有关键外设(串口、MMC、以太网、I2C)定义别名。
4.7 根节点与 aliases 关系图
下图展示了根节点、aliases 节点与设备节点之间的引用关系:
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 节点进行人工审查。