设备树的基本概念和功能
在内核源代码中,有很多代码描述了板子的细节。这些代码被淹没在 /arch/arm/plat-xxx 和 /arch/arm/mach-xxx 目录中,这些设备中的绝大多数、、、以及各种硬件都是内核的纯冗余代码。为了解决这个问题,ARM 内核版本 3.x 后来引入了 Tree,它已经在 Power PC 等其他架构中使用。
“通过 - , 传递到 Linux 的数据。”开源文档中对设备树的描述是一种描述硬件资源的数据结构,它通过将硬件资源传递给内核(即 *.dtb 文件读入内存,然后由内核解析),使得内核和硬件资源的描述相对独立。
本质上,Tree 改变了原来以代码的形式将硬件配置信息嵌入到内核代码中的方法,转而传递一个 DB。对于嵌入式系统,在启动阶段,加载内核并将控制权转移给内核,此外,需要传递上述三个参数信息,以提供更大的灵活性。在 linux 中,Tree 就是为了这个而设计的。
在 Devie 树中,描述性信息包括:
1. CPU 的数量和类别
2. 内存基址和大小
3. 公交车和桥梁
4. 外围连接
5. 中断控制器和中断使用
6. GPIO 控制器和 GPIO 使用
7. 时钟控制器和时钟使用
此外,设备树没有具体描述热插拔热备,只描述了用于控制热插拔设备的控制器。
设备树的主要优点:对于同一 SOC 的不同主板,您只需替换设备树文件 .dtb 即可实现对不同主板的无差别支持,而无需更改内核文件。
它基本上是一棵由电路板上的 CPU、总线和设备组成的树,它会把这棵树传递给内核,然后内核会识别这棵树,并根据它扩展 Linux 内核中的设备,这些设备使用的内存、IRQ 等资源也会传递给内核, 内核会将这些资源绑定到部署的相应设备上
设备树由 DTC(树)、DTS(树)和 DTB(树 blob)组成。对应关系如下图所示:
DTS 和 DTSI
一个
.dts 文件是位于内核的 /arch/arm/boot/dts 目录中的树的 ASCII 文本描述。一般来说,一个 .dts 文件对应一个 ARM。
.dtsi 文件的作用:由于 SOC 可能有多个不同的板,因此每个板都有一个 .dts。这些 DT 必然会有很多 part,为了减少代码的冗余,设备树将这些 part 细化成一个 .dtsi 文件,供不同的 DTS 一起使用。dtsi 的使用类似于 C 中的头文件,需要在 dts 文件中制作 *.dtsi 文件。当然,DTSI 本身也支持另一个 DTSI 文件。
DTC
DTC 是一种编译工具,用于将 .dts 文件编译成 .dtb 文件。DTC 的源码位于内核的 /dtc 目录下,当选择内核时,会在编译内核时编译主机可执行的 DTC。即 /dtc/
hostprogs-y := dtc
always := $(hostprogs-y)
在内核的 arch/arm/boot/dts/ 中,如果选中了一个 SOC,则所有与它关联的 dtb 文件都会被编译。在 Linux 下,make dtbs 可以单独编译。以下是 TEGRA 平台的摘录。
ifeq ($(CONFIG_OF),y)
dtb-$(CONFIG_ARCH_TEGRA) += tegra20-harmony.dtb \
tegra30-beaver.dtb \
tegra114-dalmore.dtb \
tegra124-ardbeg.dtb
DTB
DTC 编译生成的二进制文件 (.dtb) *.dts 会在启动内核时将 .dtb 预读到内存中,然后被内核解析。
您需要将设备树的内存地址传递给内核。在 ARM 中,它由 bootm 或 bootz 命令传递。bootm[][][],其中 是内核镜像的地址,是地址,也是 DTB 所在的地址。如果为空,请将其替换为 “-”。
设备树中 DTS 和 DTSI 文件的基本语法基本语法DTS 的基本结构
这
下图是 DTS 的基本语法示例。
它由一系列节点以及描述节点的属性组成。
“/” 表示根节点。在一个 .dts 文件中,只有一个根节点,根节点下有 “node1”、“node2” 子节点,根节点称为 “node1” 和 “node2”,每个节点除了根节点外只有一个节点。子节点 node1 下还有子节点 “child-” 和 “child-node2” 。
注意:如果你看到内核 /arch/arm/boot/dts 目录,你可能有问题。每个 .dsti 和 .dts 中都会有一个 “/” 根节点,因此,如果设备树文件中有一个 .dtsi 文件,那么将有多个 “/” 根节点。其实,当编译器 DTC 编译 .dts 生成 dtb 时,会合并节点,最终生成的 dtb 只有一个根节点。DTC 将执行合并操作,该操作也可以从属性中验证。稍后会详细介绍。
在节点的 {} 中是描述节点的属性 (),即设备的特性。它的值是多种多样的:
1. 可以是字符串,如 (1);它也可以是 -list 的数组,如 (2)。
2. 也可以是 32 位的,比如 cell(8),用 表示
3. 它也可以是数据,如 (3) 所示,十六进制用 [] 表示。
4. 它也可以是空的,如 (7) 所示。
在 /arch/arm/boot/dts/ 目录中,有一个 .dtsi 文件,它定义了所有 ARM 通用的一些硬件。以下是 .dtsi 的全部内容。
/ {
#address-cells = <1>;
#size-cells = <1>;
chosen { };
aliases { };
memory { device_type = “memory”; reg = <0 0>; };
};
如上所示,属性 # -cells 的值为 1,这意味着根节点为 “/” 的子节点中的 reg 属性中有一个值。#size-cells 的值为 1,这表示根节点为 “\” 的子节点的 reg 属性中存在一个 size 值。也就是说,父节点的 # -cells 和 #size-cells 确定子节点的总和大小的长度;Reg 以 reg= 的形式进行组织
由于其他设备节点由属性描述,因此它们具有类似的形式。以下各节主要分析各种属性的含义和功能,并结合相关示例进行解释。
在 node 中,reg 是 -IO 的总和。子节点的 reg 属性和长度取决于父节点的 #-cells 和 #size-cells 的值。例:
在上述 AIPS 节点中,有一个子节点 SPDA。在 SPDA 中,reg 是 ,即 和 大小。如图 3-1 所示。
这里要补充的一点是,设备节点的名称格式为 node-name@unit-,节点名称由 node-name 唯一标识,node-name 是一个 ASCII 字符串。其中 @unit- 是可选的,可以不进行描述。unit- 的具体格式与设备安装的总线有关。例如,CPU 的单位从 0 开始寻址,以便加 1;在此示例中,AIPS 是。
在 (1) 中,属性为 list,用于将设备与对应的驱动程序进行匹配,优先级从左到右。在此示例中,SPBA 的驱动程序优先考虑 “FSL, AIPS-BUS” 驱动程序;如果没有 “fsl, aips-bus” 驱动程序,请使用字符串 “-bus” 继续寻找合适的驱动程序。即实现了原内核版本 3.x 中 .name 的功能,至于具体的实现,本文后面会解释。
注意: 对于 “/” 根节点,它还具有与类型匹配的属性。具体说明将在后面给出。
设备节点通过 - 指定它所连接的中断控制器,当节点未指定 - 时,设备节点将从该节点继承。在上面的示例中,根节点的 -=.此处使用引用,即 mic 引用 (2) 中的 -@;根节点的子节点没有指定,例如 AHB 和 FAB,它们都使用从根节点继承的 MIC,即它所在的中断控制器。
如果子节点使用中断(中断编号、触发方法等),则需要通过属性来指定,属性的值长度由中断控制器中的 #- 值(3)控制,即属性中值的数量为 #- 的值;在本例中为 #-=,因此 (4) 中的值采用形式,每个值的含义由驱动程序实现确定。
属性
该属性是地址转换表,在 PCIe 中很常见,表示设备在节点中使用的地址映射关系。格式长度由当前节点 #-cell、节点 #-cells 和当前节点 #size-cell 控制。顺序为 =,该函数的具体实现可以在 /arch/arm//.c 中找到。
node 结构体
记录节点信息的结构体。.dtb 在解析后将节点信息存储为列表。
属性结构
结构中描述节点属性信息的成员结构。
uboot
首先,我们来看看 uboot 用来记录 os、fdt 信息的数据结构,定义在 //image.h 中,这里是其中一小部分与 dtb 相关的。
指向 DTB 设备树镜像的标头。
LMB 是 UBOOT 下的内存管理机制,全称是 。用于管理映像的内存。LMB 记录的内存信息最终会被传递。此处不描述 LMB。LMB 的接口和定义在 //lmb.h 和 /lib/lmb.c 中描述。有兴趣的读者可以查看其中的少量代码。
DTB 加载和解析过程
从 uboot 开始,根据前面的描述,内存中 DTB 的地址是通过 bootm 命令传递的。在 bootm 中,它会根据传入的 DTB 地址,对 DTB 所在的内存进行一系列的操作,为内核解析 DTB 提供了保障。对应的函数调用图如上图所示。
在 中,调用 main 函数,第四个参数是 bootm 将要处理的阶段和状态。
在 中,LMB 将被初始化,LMB 管理的物理内存块可以通过三种方式获得。起始地址,优先级从上到下:
环境变量 ''
宏 E(中间)。
gd->bd->[0].start
大小:
环境变量 ''
gd->bd->[0].大小
初始化后,此内存归 LMB 管辖。然后,调用 image 相关操作,这里不再详述。
记得我们讲 bootm 的三个参数时,第一个参数 已经处理完毕,接下来的两个参数将会作进去。
首先,根据第二个参数找到地址,得到图片;然后根据第三个参数获取 DTB 镜像,和检查镜像一样,检查 DTB 镜像也会进行一系列的验证工作,如果验证错误,内核将无法正常启动。此外,uboot 确认 DTB 镜像正确后,会将地址保存在环境变量 “” 中。
然后,uboot 会对 DTB 镜像进行一次镜像,使得 DTB 镜像所在的物理内存由 lmb 管理:(1) 它会设置原始内存 DTB 镜像所在的内存,确保内存不会被其他人非法使用,并确保下一个数据正确无误;(2) 申请该区域未使用的内存,然后将 DTB 图片内容复制到该区域(即 lmb 管理的区域)。
注意:如果在环境变量中指定了 “” 参数,则会调用函数根据该值分配 DTB 镜像的地址空间。如果分配失败,则 bootm 操作将停止。因此,不建议设置参数。
接下来,根据内核的类型调用相应的启动函数。与 linux 对应的是。
(1)
启动后的准备参数
(2)
从上面的代码片段代码中可以看出,如果使用 DTB,则原来用于存储 ATAG 的寄存器 R2 将用于存储 .dtb 图像地址。
最后,它将被调用,将 .dtb 镜像地址传递给内核。
我们来看一下 的 部分:
在 arch/arm//head 中。S,有这样一段:
在 /arch/arm//head- 中定义。S 中,它主要对 DTB 镜像进行简单的检查。
这
dbt 的实际解析过程的开始是 ->。这部分处理在第 V 部分中提到。
如图所示,是 中的解析过程。
节点初始化对。
解析根节点的 {size,} 将初始化为 ,。它为其他节点(如以后的解析)提供了基础。
解析该节点,该节点中描述的内存将被添加到 bank 中。为后续内存初始化提供条件。
这
解析设备树在函数 e 中完成,该函数将 .dtb 解析为一个结构(其定义在 第 5 部分 中),并形成一个项链列表,供 OF 的 API 接口使用。
下面主要结合代码分析: //fdt.c
总的总结是:
(1) 从入口处的 uboot 获取 .dtb 镜像的基址
(2) 使用 () 函数获取初始化所需的 sum 等系统启动参数。
(3) 调用 e 函数解析 dtb 文件,构造一个由结构体组成的单向链表,并使用全局变量保存该链表的头指针。
(4) 内核调用 OF 的 API 接口获取链表信息,初始化内核的其他子系统和设备。
扫一扫在手机端查看
-
Tags : armlinux编译内核_linux内核机制之设备树
我们凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求,请立即点击咨询我们或拨打咨询热线: 13761152229,我们会详细为你一一解答你心中的疑难。