目录:
一、加载Linux(有无设备树)
二、设备树入门知识
一、加载Linux(有无设备树)
没有设备树的时候(以ARM架构为例):
- 内核在源码里记录了所有硬件相关的信息(芯片型号、内存大小,各种控制器和外设等);
- booloader只需要加载一个kernel image(例如uImage、zImage等),然后跳转到kernel image的起始地址就可以开始执行内核代码了。
- bootloader在加载kernel image前,会先准备好一些供内核使用的信息,这些信息被称为ATAGS,典型的ATAGS有:启动参数(command line)、内存大小、内存位置。bootloader在跳转到kernel image 前,会把ATAGS的地址放在r2寄存器里,内核启动时通过读r2寄存器获取ATAGS;因为ATAGS很重要(内存的起始地址和大小不确定的话内核寸步难行),内核必须尽早获取ATAGS。
- bootloader在加载kernel image前,会将单板型号(machine type,一个整型数)放在r1寄存器里,内核启动时获取到machine type integer,进而找到用于描述单板的struct machine_desc,struct machine_desc里包含了所有硬件信息:
- 以U-boot为例,U-boot加载内核的命令:bootm <kernel image addr>,没有设备树时加载内核的示意图如下:
为什么要引入设备树?
引入设备树前:
- 所有单板相关的硬件信息都是hard-coded,这导致内核必须收集单板所有的配置,每当有一点点非常小的改动时,就需要生成一个新的kernel image。例如产品里某个I2C设备是可插拔的,那么不管这个I2C设备是否存在,工程师们都会把该I2C设备的描述代码添加到内核中,这样的内核是臃肿的。
- 不同平台需要不同的Kernel image,各Linux发行版维护工作繁重。
引入设备树后:
- 对于芯片厂商:
不再需要编写machine support code(例如ARM架构的芯片的arch/arm/mach-*),只需要关注在设备驱动的开发上,machine_desc由内核自动创建;
- 对于开发板定制商:
1) 适配更容易了,芯片厂提供的多份DTB配置里可能有一份可直接或者简单修改就运行在定制板上;
2) 不再需要为定制板分配一个全局的machine id,取而代之的是在设备树里添加 <vendor>,<boardname>;
3) 许多单板相关的配置都集中在设备树或者设备驱动里了,和machine代码隔离开;
有了设备树后(以ARM架构为例):
- 内核源码里不再包含任何关于某个单板的硬件信息,每个单板的硬件信息都独立地保存在1个二进制文件里:device tree blob(简称DTB文件,一般以.dtb为后缀)。DTB文件由dts文件编译而来,ARM架构的单板的dts文件位于arch/arm/boot/dts
- bootloader启动内核时需要加载2个文件:kernel image和DTB文件。DTB文件的起始地址是通过r2寄存器传递给内核的,之前保存在ATAGS的里信息(command line、memory info)现在都直接包含在DTB文件里了。
- bootloader 不再需要传递machine type给内核了,但是并不是说内核里不再需要machine type integer和struct machine_desc。只是内核现在可以通过解析DTB的方式自行构造出出struct machine_desc,相关代码:
这里的__atags_pointer = r2寄存器 = dt_phys = DTB文件的地址。在没有设备树之前__atags_pointer是用来保存ATAGS的地址的,有了设备树之后,它能同时兼容ATAGS和DTB地址,这是因为 DTB 文件里有magic number(OF_DT_MAGIC),内核通过调用函数__vet_atags()来判断r2寄存器保存的是atags还是dtb的地址;
- 以U-boot为例,U-boot加载内核的命令:bootm <kernel image addr> - <dtb addr>,有设备树后加载内核的示意图如下:
DT booting的兼容模式(目的是为了兼容旧版本的booloader)
- 许多芯片的供货时长会有10年甚至以下,这些旧款芯片的BSP版本非常旧,例如Uboot-1.1.6 + Linux-2.6。而当你想改用新版本的内核(例如 Linux-4.14)又不想丢弃芯片厂商提供的功能齐全的U-boot时,Uboot-1.1.6 是否可以加载 Linux-4.14 的 kernel image 和 DTB 呢?答案是可以的。
- 内核提供了一种兼容机制:
CONFIG_ARM_APPENDED_DTB。该配置项会让内核从kernel image的末尾去寻找DTB,将kernel image和DTB拼接起来的命令如下:
$ cat zImage xxx.dtb > zImage-with-dtb
- 在CONFIG_ARM_APPENDED_DTB配置项之下,还有一个可选的配置项:CONFIG_ARM_ATAG_DTB_COMPAT。此配置项会让内核读取bootloader传递过来的ATAGS,然后更新到DT里。
- 如何判断你当前使用的U-boot是否支持加载DTB?
一般来说查看一下U-boot自带的帮助说明即可:
但是,很多芯片厂商的U-boot是定制过的,有可能没有使用U-boot里自带的标准命令来加载kernel image,这种情况就只能自行分析原厂添加的相关的代码,从源码里才能确定原厂的U-boot是否有加载DTB的能力,并且确定在加载DTB前是否改动过DTB。
二、设备树入门知识
什么是设备树?
设备树是一个易读的硬件描述文件,格式类似JSON,树状结构,树上的每一个node代表硬件描述里的一个 device。一个 node 会包含多个 properties,properties可以没有value(即只有key没有value,足够用来表示bool值),也可以是一对key-value,示意图如下:
设备树入门知识
- DTS 是 Device Tree Source 的缩写,对于ARM架构,DTS文件位于arch/arm/boot/dts,在该目录下,.dts文件用于定义板级的硬件信息,.dtsi文件用于定义芯片级的硬件信息,.dtsi文件会被.dts文件包含,以BeagleBone board的DTS为例:
- 设备树编译器scripts/dtc可将 DTS 文件编译成 DTB文件(Device Tree Blob),编译命令如下:
$ make zImage dtbs ARCH=arm CROSS_COMPILE=arm-linux-
arch/arm/boot/dts/Makefile里包含了所有应该的生成的DTB文件,
- 在目标设备上查看设备树:
- 在目标设备上的打印整个设备树:
$ dtc -I fs /proc/device-tree
$ dtc -I fs /sys/firmware/devicetree/base/
2个命令的效果一样。
一个简单的设备树节点示例:
设备树节点:
设备驱动端:
- 设备树节点和设备驱动是通过compatible属性绑定在一起的。
- 变量of_match_table的类型为struct of_device_id *,指定的是此驱动支持的设备列表;
- 既然一个驱动可支持多个设备,不同设备的初始化过程是不同的,那么驱动端应该有能力知道当前要probe的是哪个设备:调用of_match_device()就可以获取matching device的of_device_id,进而决定读取哪些属性;
- 获取设备树里的属性:
设备树的 bindings
- bindings 用于定义在设备树中如何表示某一类或者某一个特定的设备;
- 设备树节点中的 compatible 属性用于表示该节点下的所有bindings会被哪个设备驱动所使用;
- bindings可以理解为某个节点支持的所有properties,某一个设备可能只需要部分properties就能被完整地描述,即properties分为强制和可选的;
- 每一个内核子系统或者framework都有自己的bindings,所有内核支持的bindings说明文档都位于Documentation/devicetree/bindings,示例如下:
Documentation/devicetree/bindings/serial/snps-dw-apb-uart.txt
到此,设备树的一些基本知识就算描述完毕了,关于设备树还有很多值得挖掘的知识,以后再慢慢完善吧。
你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。如果你也对嵌入式系统开发有兴趣,并且想和更多人互相交流学习的话,请关注我的公众号:ESexpert,一起来学习吧,欢迎各种收藏/转发/批评。
网友评论