LV06-10-设备树-01-设备树基础

什么是设备树,怎么来的?若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
PC端开发环境 Windows Windows11
Ubuntu Ubuntu20.04.2的64位版本
VMware® Workstation 17 Pro 17.6.0 build-24238078
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Win32DiskImager Win32DiskImager v1.0
Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04
linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
nxp-imx/linux-imx/releases/tag/v4.19.71 NXP linux内核仓库tags中的v4.19.71
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
Source Code https://elixir.bootlin.com/linux/latest/source linux kernel源码
kernel/git/stable/linux.git - Linux kernel stable tree linux kernel源码(官网,tag 4.19.71)
https://elixir.bootlin.com/u-boot/latest/source uboot源码

一、什么是设备树?

设备树(Device Tree) 是一种硬件描述机制, 用于在嵌入式系统和操作系统中描述硬件设备的特性、 连接关系和配置信息。 它提供了一种与平台无关的方式来描述硬件, 使得内核与硬件之间的耦合度降低, 提高了系统的可移植性和可维护性。

image-20250217154258419

大概就上图这样,像一棵树一样。树的主干就是系统总线, IIC 控制器、 GPIO 控制器、 SPI 控制器等都是接到系统主线上的分支。IIC 控制器有分为 IIC1 和 IIC2 两种,其中 IIC1 上接了 FT5206 和 AT24C02这两个 IIC 设备, IIC2 上只接了 MPU6050 这个设备。

二、设备树的由来

前面,我们学习了平台设备驱动,我们使用 struct platform_device 结构体来对硬件设备进行描述, 这是一种传统的平台总线设备描述方式。 每个 struct platform_device 结构表示一个特定的硬件设备, 并通过注册到平台总线上来使得内核能够与该设备进行通信和交互。 该结构包含设备的名称、 资源(如内存地址、 中断号等) 、 设备驱动程序等信息。

然而, 随着时间的推移, Linux 内核中的 ARM 部分存在着大量的平台相关配置代码, 这些代码通常是杂乱而重复的, 导致了维护的困难和工作量的增加。 在 2011 年 3 月 17 日, Linux的创始人 Linus Torvalds 在 ARM Linux 邮件列表中发表了一封帖子, 他表达了对 ARM 架构配置方式的不满, 并宣称”Gaah. Guys, this whole ARM thing is a f*cking pain in the ass”。 这引起了广泛的讨论和反思。 ARM 社区中的开发者们开始认识到, 传统的平台相关配置方式已经变得不可持续, 需要一种更加先进和可扩展的方法来解决这个问题。

为了应对这一挑战, ARM 社区开始探索新的硬件描述机制, 就引入了 PowerPC 等架构已经采用的设备树(Flattened Device Tree),将这些描述板级硬件信息的内容都从 Linux 内中分离开来,用一个专属的文件格式来描述,这个专属的文件就叫做设备树,文件扩展名为.dts。

设备树提供了一种更加灵活和可移植的描述硬件的机制, 将设备的描述信息转移到设备树中。设备树使用一种结构化的数据格式, 通过描述设备节点、 属性和连接关系等信息, 使得硬件的描述与具体的平台无关, 同时允许多个平台共享相同的设备树描述。

一个 SOC 可以作出很多不同的板子,这些不同的板子肯定是有共同的信息, 将这些共同的信息提取出来作为一个通用的文件,其他的.dts 文件直接引用这个通用文件即可,这个通用文件就是.dtsi 文件,类似于 C 语言中的头文件。一般.dts 描述板级信息(也就是开发板上有哪些 IIC 设备、 SPI 设备等), .dtsi 描述 SOC 级信息(也就是 SOC 有几个 CPU、主频是多少、各个外设控制器信息等)。

这个就是设备树的由来,简而言之就是, Linux 内核中 ARM 架构下有太多的冗余的垃圾板级信息文件,导致 linus 震怒,然后 ARM 社区引入了设备树。

随着时间的推移, 设备树逐渐成为了嵌入式系统和 Linux 内核中描述硬件的标准方式。 它不仅在 ARM 架构上得到了广泛应用, 也被扩展到其他架构和平台上。

三、设备树基础知识

1. 设备树的文件格式

当描述设备树(Device Tree) 时, 通常会涉及到以下几个关键术语: DTS、 DTSI、 DTB 和DTC。

1.1 DTS( Device Tree Source)

DTS 是设备树的源文件, 采用一种类似于文本的语法来描述硬件设备的结构、 属性和连接关系。 DTS 文件以.dts 为扩展名, 通常由开发人员编写。 它是人类可读的形式, 用于描述设备树的层次结构和属性信息。

1.2 DTSI( Device Tree Source Include)

DTSI 文件是设备树源文件的包含文件。 它扩展了 DTS文件的功能, 用于定义可重用的设备树片段。 DTSI 文件以.dtsi 为扩展名, 可以在多个 DTS 文件中包含和共享。 通过使用 DTSI, 可以提高设备树的可重用性和可维护性( 和 C 语言中头文件的作用相同) 。

1.3 DTB(Device Tree Blob)

DTB 是设备树的二进制表示形式。 DTB 文件是通过将 DTS 或 DTSI文件编译而成的二进制文件, 以.dtb 为扩展名。 DTB 文件包含了设备树的结构、 属性和连接信息, 被操作系统加载和解析。 在运行时, 操作系统使用 DTB 文件来动态识别和管理硬件设备。

1.4 DTC( Device Tree Compiler)

DTC 是设备树的编译器。 它是一个命令行工具, 用于将 DTS和 DTSI 文件编译成 DTB 文件。 DTC 将文本格式的设备树源代码转换为二进制的设备树表示形式, 以便操作系统能够加载和解析。 DTC 是设备树开发中一个重要的工具。

这个工具的源码位于:dtc - scripts/dtc - Linux source code (v4.19.71),我们编译设备树或者内核的时候只要用到这个工具,它就会被编译出来。

2. DTS、 DTSI、 DTB 和 DTC的关系

( 1) 开发人员使用文本编辑器编写 DTS 和 DTSI 文件, 描述硬件设备的层次结构、 属性和连接关系。

( 2) DTSI 文件可以在多个 DTS 文件中包含和共享, 以提高设备树的可重用性和可维护性。

( 3) 使用 DTC 编译器, 开发人员将 DTS 和 DTSI 文件编译成二进制的 DTB 文件, 如下图 :

image-20250217193941724

( 4) 操作系统在启动过程中加载和解析 DTB 文件, 以识别和管理硬件设备。

3. 设备树文件在哪?

ARM 体系结构下的设备树源文件通常存放在 dts - arch/arm/boot/dts - Linux source code (v4.19.71)目录中。例如我手里的imx6ull-14x14-evk板的设备树文件:imx6ull-14x14-evk.dts

image-20250217194504874

ARM64 体系结构下的设备树源文件通常存放在 dts - arch/arm64/boot/dts - Linux source code (v4.19.71) 目录及其子目录中。 该目录也是设备树源文件的根目录, 并包含了针对不同 ARM64 平台和设备的子目录:

image-20250217194604712

子目录结构: 在 ARM64 的子目录中, 同样会按照硬件平台、 设备类型或制造商进行组织和分类。 这些子目录的命名可能与特定芯片厂商(如 Qualcomm、 NVIDIA、 Samsung) 有关,另外每个子目录中可能包含多个设备树文件, 用于描述不同的硬件配置和设备类型 。

4. 设备树的编译

4.1 DTC工具

设备树的编译是将设备树源文件(如上述的.dts 文件)转换为二进制的设备树表示形式(.dtb文件) 的过程。 编译器通常被称为 DTC(Device Tree Compiler) 。

在 Linux 内核源码中, DTC( Device Tree Compiler) 的源代码和相关工具通常存放在 dtc - scripts/dtc - Linux source code (v4.19.71) 目录中:

image-20250217195009844

在编译完源码之后 dtc 设备树编译器会默认生成, 如果没有生成相应的 dtc 可执行文件,可以查看在内核默认配置文件中 CONFIG_DTC 是否使能。

4.2 在内核中编译设备树

  • 在内核源码中编译设备树
1
2
3
4
5
# 1. 编译所有设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs -j16

# 编译指定的设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx6ull-14x14-evk.dtb -j16
  • 在源码外编译设备树
1
2
3
4
5
# 在源码目录外编译所有设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(CURRENT_PATH) dtbs -j16

# 在源码目录外编译指定设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(CURRENT_PATH) imx6ull-14x14-evk.dtb -j16

上面两种,都是在内核中编译设备树文件,他们生成的DTB文件在 arch/arm/boot/dts

image-20250217200106746

4.3 单独使用DTC工具

4.3.1 使用说明

其实设备树的编译并不依赖于内核,只是内核中提供了一些规则来编译设备树文件,当内核编译出dtc工具后,我们完全可以将dtc工具拷贝出来,在其他的地方直接使用dtc工具编译设备树文件。直接使用它的话, 包含其他文件时不能使用“ #include”,而必须使用“ /incldue”,但是我们不能每个都去改,所以我们可以使用gcc工具,完成设备树的预编译,然后使用dtc工具完成设备树的最终编译,其实内核中也是这么做的,我们可以看打印信息:

image-20250217200649504

整理一下就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
make -f ./scripts/Makefile.build obj=arch/arm/boot/dts MACHINE= arch/arm/boot/dts/imx6ull-14x14-evk.dtb
mkdir -p arch/arm/boot/dts/ ;
arm-linux-gnueabihf-gcc -E
-Wp,-MD,arch/arm/boot/dts/.imx6ull-14x14-evk.dtb.d.pre.tmp -nostdinc
-I./scripts/dtc/include-prefixes
-undef -D__DTS__ -x assembler-with-cpp
-o arch/arm/boot/dts/.imx6ull-14x14-evk.dtb.dts.tmp
arch/arm/boot/dts/imx6ull-14x14-evk.dts ;

./scripts/dtc/dtc -O dtb -o arch/arm/boot/dts/imx6ull-14x14-evk.dtb
-b 0 -iarch/arm/boot/dts/
-i./scripts/dtc/include-prefixes
-Wno-unit_address_vs_reg
-Wno-unit_address_format
-Wno-avoid_unnecessary_addr_size
-Wno-alias_paths
-Wno-graph_child_address
-Wno-graph_port
-Wno-unique_unit_address
-Wno-pci_device_reg
-d arch/arm/boot/dts/.imx6ull-14x14-evk.dtb.d.dtc.tmp arch/arm/boot/dts/.imx6ull-14x14-evk.dtb.dts.tmp ;

cat arch/arm/boot/dts/.imx6ull-14x14-evk.dtb.d.pre.tmp arch/arm/boot/dts/.imx6ull-14x14-evk.dtb.d.dtc.tmp > arch/arm/boot/dts/.imx6ull-14x14-evk.dtb.d

注意:有的linux主机中可能自带的有dtc工具,使用起来可能会有一堆的警告,我们要使用内核编译出来的dtc的时候,一定要加上 ./ 或者 绝对路径,确保我们使用的是内核源码编译出来的。

4.3.2 编译设备树

在 Linux 环境中, 可以使用以下命令将设备树源文件编译为二进制设备树文件 :

1
dtc -I dts -O dtb -o output.dtb input.dts

其中, input.dts是输入的设备树源文件, output.dtb是编译后的二进制设备树文件。编译器会验证设备树源文件的语法和语义, 生成与硬件描述相对应的设备树表示形式。

4.3.3 反编译设备树

设备树的反编译是将二进制设备树文件转换回设备树源文件的过程, 以便进行查看、 编辑或修改。 反编译器通常也是 DTC。在 Linux 环境中, 可以使用以下命令将二进制设备树文件反编译为设备树源文件:

1
dtc -I dtb -O dts -o output.dts input.dtb

其中, input.dtb 是输入的二进制设备树文件, output.dts 是反编译后的设备树源文件。反编译器会将二进制设备树文件解析并还原为文本形式的设备树源文件, 使其可读性更好。

4.3.4 使用实例

我们新建一个简单的设备树文件:

1
2
3
4
/dts-v1/;
/ {

};

这个设备树很简单, 只包含了根节点/, 而根节点中没有任何子节点或属性。 这个示例并没有描述任何具体的硬件设备或连接关系, 它只是一个最基本的设备树框架, 在这里只是为了测试设备树的编译和反编译。 然后使用以下命令进行设备树的编译, 编译完成如下图:

1
./dtc -I dts -O dtb -o imx6ull-demo.dtb imx6ull-demo.dts
image-20250217202009555

可以看到 imx6ull-demo.dtb 就生成了, 然后继续使用以下命令对 imx6ull-demo.dtb 进行反编译, 反编译完成如下图 :

1
./dtc -I dtb -O dts -o imx6ull-demo-dtb2dts.dts imx6ull-demo.dtb
image-20250217202255227

4.4 设备树编译demo

11_device_tree/01_dtb_compile · 苏木/imx6ull-driver-demo - 码云 - 开源中国

5. 设备树在内核的表现

Linux 内核启动的时候会解析设备树中各个节点的信息,并在根文件系统中体现出来。

  • /proc/device-tree 目录
1
2
cd /proc/device-tree
ls
image-20250218212607840
  • /sys/firmware/devicetree 目录
1
2
cd /sys/firmware/devicetree
ls
image-20250218212721195

/sys/firmware/devicetree 和 /proc/device-tree 目录下是以目录结构程现的 dtb 文件, 根节点对应 base 目录, 每一个节点对应一个目录, 每一个属性对应一个文件。这些属性的值如果是字符串,可以使用 cat 命令把它打印出来;对于数值,可以用 hexdump 把它打印出来。

还可以看到/sys/firmware/fdt 文件,它就是 dtb 格式的设备树文件,可以把它复制出来放到 ubuntu 上,执行下面的命令反编译出来(-I dtb:输入格式是 dtb, -O dts:输出格式是 dts):

1
2
cd kernel-dir
./scripts/dtc/dtc -I dtb -O dts <fdt文件> -o tmp.dts

5.1 根节点“/”各个属性

根节点属性属性表现为一个个的文件,例如“#address-cells”、“#size-cells”、“compatible”、“model”和“name”这 5 个文件,它们在设
备树中就是根节点的 5个属性。既然是文件那么肯定可以查看其内容,输入cat 命令来查看 model 和 compatible 这两个文件的内容,结果如图:

image-20250218213025820

可以发现与我们的设备树中的一致:

1
2
3
4
5
/ {
model = "Freescale i.MX6 UlltraLite 14x14 EVK Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
};

5.2 根节点“/”各子节点

子节点表现为一个个的目录,比如“aliases”、“ backlight”、“ chosen”和“ clocks”等等,可以查看一下 imx6ul-14x14-evk.dtsi ,看看根节点的子节点都有哪些,会发现他们都会在 /proc/device-tree 或者 /sys/firmware/devicetree/base中有对应的目录。

/proc/device-tree 目录就是设备树在根文件系统中的体现,同样是按照树形结构组织的,进入/proc/device-tree/soc 目录中就可以看到 soc 节点的所有子节点 :

image-20250218213443333

和根节点“/”一样,图中的所有文件分别为 soc 节点的属性文件和子节点文件夹。

四、参考资料

可以下载到 《devicetree-specification-v0.4.pdf》

在这里我们可以看到相关的绑定文档 bindings - Documentation/devicetree/bindings,这些文档会告诉我们如何添加节点。比如我们现在要想在 I.MX6ULL 这颗 SOC 的 I2C 下添加一个节点,那么就可以查看i2c-imx.txt - Documentation/devicetree/bindings/i2c/i2c-imx.txt 。有时候使用的一些芯片在 这个目录下找不到对应的文档,这个时候就要咨询芯片的提供商,让他们给我们提供参考的设备树文件。