02、图解Kernel Device Tree(设备树)的使用

Device Tree的作用是让操作系统更灵活地适应不同硬件。过去,每个硬件平台都需要单独编译专属的内核版本,因为硬件配置信息直接写死在代码里。现在通过Device Tree,启动加载程序会传递一个包含硬件信息的数据包给内核。
对于ARM架构来说,这种方式特别重要。随着ARM芯片应用到手机、电脑甚至服务器,我们需要像X86架构那样,用同一个内核文件支持多种设备。这时候内核需要三个关键信息:
- 硬件身份标识 - 告知内核当前运行的设备类型
- 运行时配置选项 - 设备特定的参数设置
- 硬件组成结构 - 描述设备上的芯片、接口等组件布局和特性
这样内核就像个智能盒子,通过启动时接收的硬件信息包,就能自动适配不同设备,而不再需要为每个新硬件单独编译内核了。

对于嵌入式系统,在系统启动阶段,bootloader会加载内核并将控制权转交给内核,此外,还需要把上述的三个参数信息传递给kernel如上图,以便kernel可以有较大的灵活性。在linux kernel中,Device Tree的设计目标就是如此。
一、device Tree包含的硬件信息有哪些?(海纳百川?)
Device Tree不能描述所有硬件信息。原因很简单: 系统运行时能自动发现的硬件无需描述,例如USB设备(插拔时系统会自动识别)。 但固定在主板上的核心硬件必须手动写入Device Tree,比如:
- 主板上的USB控制器(无法自动识别,必须明确告诉系统它的存在)
- PCI桥接器(如果系统无法自动探测其位置)
需要明确写入的内容主要包括:
- CPU(处理器核心配置)
- 内存(容量和地址范围)
- 总线(如PCIe、I2C等连接通道)
- 外设接口(如SD卡槽、串口等物理连接)
- 中断控制器(协调硬件中断的逻辑)
- GPIO控制器(管理通用输入输出引脚)
- 时钟控制器(设定硬件部件的工作频率)
总结来说,Device Tree主要描述系统启动时必须预先知道的硬件信息,而运行中可动态发现的设备则不需要。
二、Device Tree示例 (线头)
为了了解Device Tree的结构,我们首先给出一个Device Tree的示例: 最重要的属性 compatible, reg, clocks,interrupts, and status。

三、节点的介绍

3.1、根节点root node
设备树使用一种层次结构, 其中的根节点(Root Node) 是整个设备树的起始点和顶层节点。 根节点由一个以/开头的标识符来表示, 然后使用{}来包含根节点所在的内容, 一个最简单的根节点示例如下所示:
/dts-v1/; //设备树版本信息
/ {
...
};2
3
4
5
6
其中第一行的设备树中的版本信息行 dts-v1 是可选的, 可以根据需要选择是否保留。 这行注释通常用于指定设备树的语法版本。 如果您不需要在设备树中指定版本信息, 可以将其删除。
3.2、子节点
设备树中的子节点是根节点的直接子项, 用于描述具体的硬件设备或设备集合。 子节点采用以下特定的格式来表示:

以下是对这些部分的详细介绍:
(1) 节点标签(Label) (可选)
节点标签是一个可选的标识符, 用于在设备树中引用该节点。 标签允许其他节点直接引用此节点, 以便在设备树中建立引用关系。
(2) 节点名称(Node Name)
节点名称是一个字符串, 用于唯一标识该节点在设备树中的位置。 节点名称通常是硬件设备的名称, 但必须在设备树中是唯一的。
(3) 单元地址(Unit Address) ( 可选)
单元地址用于标识设备的实例。 它可以是一个整数、 一个十六进制值或一个字符串, 具体取决于设备的要求。 单元地址的目的是区分相同类型的设备的不同实例, 例如在下图中名为 cpu 的节点通过它们的单元地址值 0 和 1 来区分, 名称为 ethernet 的节点通过其单元地址值 fe002000 和 fe003000 来区分。
(4) 属性定义( Properties Definitions)
属性定义是一组键值对, 用于描述设备的配置和特性。 属性可以根据设备的需求进行定义, 例如寄存器地址、 中断号、 时钟频率等, 关于这些属性会在后面的小节中进行讲解
(5) 子节点( Child Nodes)
子节点是当前节点的子项, 用于进一步描述硬件设备的子组件或配置。 子节点可以包含自己的属性定义和更深层次的子节点, 形成设备树的层次结构。
3.3、别名节点
aliases 节点定义了一些别名。为何要定义这个node呢?
因为Device tree是树状结构,当要引用一个node的时候要指明相对于root node的full path,例如/node1/child-node1。如果多次引用,每次都要写这么复杂的字符串多少是有些麻烦,因此可以在aliases节点定义一些设备节点full path的缩写。

3.4、CPU节点
对于根节点,必须有一个cpus的child node来描述系统中的CPU信息。
cpus {address-cells = <2;size-cells = <0;
cpu_l0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a53", "arm,armv8";
reg = <0x0 0x0;
enable-method = "psci";cooling-cells = <2; /* min followed by max */
clocks = <&cru ARMCLKL;
dynamic-power-coefficient = <100;};2
3
4
5
6
7
8
3.5、Memory节点
memory device node是所有设备树文件的必备节点,它定义了系统物理内存的layout。device_type属性定义了该node的设备类型,例如cpu、serial等。对于memory node,其device_type必须等于memory。reg属性定义了访问该device node的地址信息,该属性的值被解析成任意长度的(address,size)数组,具体用多长的数据来表示address和size是在其parent node中定义(#address-cells和#size-cells)。对于device node,reg描述了memory-mapped IO register的offset和length。对于memory node,定义了该memory的起始地址和长度。
3.6、可选节点
chosen node主要用来描述由系统firmware指定的runtime parameter。如果存在chosen这个node,其parent node必须是名字是“/”的根节点。原来通过tag list传递的一些linux kernel的运行时参数可以通过Device Tree传递。例如command line可以通过bootargs这个property这个属性传递;initrd的开始地址也可以通过linux,initrd-start这个property这个属性传递。在实际中,建议增加一个bootargs的属性,例如:
chosen { bootargs = "console=ttymxc0,115200"; };我们知道,device tree用于HW platform识别,runtime parameter传递以及硬件设备描述。chosen节点并没有描述任何硬件设备节点的信息,它只是传递了runtime parameter。
四、属性

4.1、Compatible属性
在设备树中, compatible 属性用于描述设备的兼容性信息。 它是设备树中重要的属性之一,用于识别设备节点与驱动程序之间的匹配关系。
compatible 属性的值是一个字符串或字符串列表, 用于指定设备节点与相应的驱动程序或设备描述符兼容的规则。 通常, compatible 属性的值由设备的厂商定义, 并且在设备树中使用。
以下是一些常见的 compatible 属性值的示例:
- 单个字符串值: 例如 "vendor,device", 用于指定设备节点与特定厂商的特定设备兼容。
- 字符串列表: 例如 ["vendor,device1", "vendor,device2"], 用于指定设备节点与多个设备兼容, 通常用于设备节点具有多种变体或配置。
- 通配符匹配: 例如 "vendor,*", 用于指定设备节点与特定厂商的所有设备兼容, 不考虑具体的设备标识。
以下是一个示例, 展示了如何在设备树中使用 compatible 属性:

compatible 属性也可以具有多个匹配值, 用于指定设备节点与多个设备或驱动程序的兼容性规则。 这种情况下, compatible 属性的值是一个字符串列表, 每个字符串表示一个匹配值。
以下是一个示例, 展示了具有多个匹配值的 compatible 属性的用法:

通过使用 compatible 属性, 设备树可以提供设备和驱动程序之间的匹配信息。 当设备树被操作系统或设备管理软件解析时, 会根据设备节点的 compatible 属性值来选择适合的驱动程序进行设备的初始化和配置。
4.2、model属性
在设备树中, model 属性用于描述设备的型号或者名称。 它通常作为设备节点的一个属性,用来提供关于设备的标识信息。 model 属性是可选的, 但在实际应用中经常被使用。
model 属性的值是一个字符串, 可以是设备的型号、 名称、 或者其他标识符, 用来识别设备。 该值通常由设备的厂商定义, 并且在设备树中使用。
以下是一个示例, 展示了如何在设备树中使用 model 属性:

model 属性通常用于标识和区分不同的设备, 特别是当设备节点的 compatible 属性相同或相似时。 通过使用不同的 model 属性值, 可以更加准确地确定所使用的设备类型。
4.3、reg属性
reg 属性用于在设备树中指定设备的寄存器地址和大小, 提供了与设备树中的物理设备之间的寄存器映射关系。
reg 属性可以在设备节点中有单个值格式和列表值格式这两种常见格式, 接下来将对这两种格式进行介绍:
4.3.1、单个值格式
reg = <address size>;这种格式适用于描述单个寄存器的情况。 其中, address 是设备的起始寄存器地址, 可以是一个整数或十六进制值。 size 表示寄存器的大小, 即占用的字节数。

4.3.2、列表值格式
reg = <address1 size1 address2 size1>;当设备具有多个寄存器区域时, 可以使用列表值格式的 reg 属性来描述每个寄存器区域的地址和大小。 通过这种方式, 可以指定多个寄存器的位置和大小, 以描述设备的完整寄存器映射。
例如, 考虑一个设备节点 my_device, 它具有两个寄存器区域, 分别是 8 字节和 4 字节大小的寄存器。 可以使用列表值格式的 reg 属性来描述这种情况:

在这个示例中, my_device 设备节点的 reg 属性值为 <0x1000 0x8 0x2000 0x4>, 表示设备有两个寄存器区域。 第一个寄存器区域从地址 0x1000 开始, 大小为 8 字节; 第二个寄存器区域从地址 0x2000 开始, 大小为 4 字节。
通过使用 reg 属性, 设备树可以提供有关设备寄存器布局和寄存器访问方式的信息。 这对于操作系统的设备驱动程序很重要, 因为它们需要了解设备的寄存器映射以正确地与设备进行交互和配置。
4.4、寻址属性(address-cells 和 size-cells 属性 )
#address-cells 和 #size-cells 属性用于指定在上个小节中要设置的设备树中地址单元和大小单元的位数。 它们提供了设备树解析所需的元数据, 以正确解释设备的地址和大小信息。 下面对两个属性分别进行介绍:
4.4.1、#address-cells 属性
#address-cells 属性是一个位于设备树根节点的特殊属性, 它指定了设备树中地址单元的位数。 地址单元是设备树中用于表示设备地址的单个单位。 它通常是一个整数, 可以是十进制或十六进制值。
#address-cells 属性的值告诉解析设备树的软件在解释设备地址时应该使用多少位来表示一个地址单元。
默认情况下, #address-cells 的值为 2, 表示使用两个单元来表示一个设备地址。 这意味着设备的地址将由两个整数(每个整数使用指定位数的位) 组成。
例如, 对于一个使用两个 32 位(4 字节) 整数表示地址的设备, 可以在设备树的根节点中设置 #address-cells 属性为 <2>。
4.4.2、#size-cells 属性
#size-cells 属性也是一个位于设备树根节点的特殊属性, 它指定了设备树中大小单元的位数。 大小单元是设备树中用于表示设备大小的单个单位。 它通常是一个整数, 可以是十进制或十六进制值。
#size-cells 属性的值告诉解析设备树的软件在解释设备大小时应该使用多少位来表示一个大小单元。
默认情况下, #size-cells 的值为 1, 表示使用一个单元来表示一个设备的大小。 这意味着设备的大小将由一个整数(使用指定位数的位) 表示。
例如, 对于一个使用一个 32 位(4 字节) 整数表示大小的设备, 可以在设备树的根节点中设置 #size-cells 属性为 <1>。
这两个属性的存在是为了使设备树能够灵活地描述各种设备的地址和大小表示方式。 通过在设备树的根节点中设置适当的 #address-cells 和 #size-cells 值, 设备树解析软件能够正确地解释设备节点中的地址和大小信息。
4.4.3、案例
在 spi 节点中,#address-cells 设置为 1,#size-cells 设置为 0。

解释后的地址和大小值如下:
地址部分:
大小部分:
4.5、节点状态
在设备树中, status 属性用于描述设备或节点的状态。 它是设备树中常见的属性之一, 用于表示设备或节点的可用性或操作状态。
- status 属性的值可以是以下几种:
- "okay": 表示设备或节点正常工作, 可用。
- "disabled": 表示设备或节点被禁用, 不可用。
- "reserved": 表示设备或节点已被保留, 暂时不可用。
- "fail": 表示设备或节点初始化或操作失败, 不可用。
以下是一个示例, 展示了如何在设备树中使用 status 属性:

在这个示例中, my_device 节点具有 status 属性, 其值为 "okay"。 这表示设备处于正常工作状态, 可用。
通过使用 status 属性, 设备树可以动态地控制设备的启用和禁用状态。 这对于在系统启动过程中选择性地启用或禁用设备, 或者在运行时根据特定条件调整设备状态非常有用。