设备树的引入减少了内核为支持新硬件而需要的改变,提高代码重用,加速了linux支持包的开发,使得单个内核镜像能支持多个系统。作为u-boot 和linux 内核之间的动态接口,本文阐述了设备树的数据存储格式以及源码描述语法,进而分析了u-boot 对扁平设备树的支持设置,linux 内核对设备树的解析流程。
ibm、sun 等厂家的服务器最初都采用了firmware(一种嵌入到硬件设备中的程序,用于提供软件和硬件之间的接口),用于初始化系统配置,提供操作系统软件和硬件之间的接口,启动和运行系统。后来为了标准化和兼容性,ibm、sun 等联合推出了固件接口ieee 1275标准,让他们的服务器如ibm powerpc pseries,apple powerpc,sun sparc 等均采用openfirmware,在运行时构建系统硬件的设备树信息传递给内核,进行系统的启动运行[1]。这样做的好处有,减少内核对系统硬件的严重依赖,利于加速支持包的开发,降低硬件带来的变化需求和成本,降低对内核设计和编译的要求。
随着 linux/ppc64 内核的发展,内核代码从原来的arch/ppc32 和arch/ppc64 逐渐迁移到统一的arch/powerpc 目录,并在内核代码引入open firmware api 以使用标准固件接口[2]。linux 内核在运行时,需要知道硬件的一些相关信息。对于使用arch=powerpc 参数编译的内核镜像,这个信息需要基于open firmware 规范,以设备树的形式存在[3]。这样内核在启动时读取扫描open firmware 提供的设备树,从而获得平台的硬件设备信息,搜索匹配的设备驱动程序并将该驱动程序绑定到设备。
在嵌入式 powerpc 中,一般使用u-boot 之类的系统引导代码,而不采用open firmware。早期的u-boot 使用include/asm-ppc/u-boot.h 中的静态数据结构struct bd_t 将板子基本信息传递给内核,其余的由内核处理。这样的接口不够灵活,硬件发生变化就需要重新定制编译烧写引导代码和内核,而且也不再适应于现在的内核。为了适应内核的发展及嵌入式powerpc平台的千变万化,吸收标准open firmware 的优点,u-boot 引入了扁平设备树fdt 这样的动态接口,使用一个单独的fdt blob(二进制大对象,是一个可以存储二进制文件的容器)存储传递给内核的参数[3]。一些确定信息,例如cache 大小、中断路由等直接由设备树提供,而其他的信息,例如etsec 的mac 地址、频率、pci 总线数目等由u-boot 在运行时修改。u-boot 使用扁平设备树取代了bd_t,而且也不再保证对bd_t 的后向兼容。
2 设备树概念
简单的说,设备树是一种描述硬件配置的树形数据结构,有且仅有一个根节点[4]。它包含了有关cpu、物理内存、总线、串口、phy 以及其他外围设备信息等。该树继承了openfirmware ieee 1275 设备树的定义。操作系统能够在启动时对此结构进行语法分析,以此配置内核,加载相应的驱动。
3 设备树存储格式
u-boot 需要将设备树在内存中的存储地址传给内核。该树主要由三大部分组成:头(header)、结构块(structure block)、字符串块(strings block)。设备树在内存中的存储布局图1 如下:
图1 设备树存储格式图
fig1 the layout of a dt block
3.1 头(header)
头主要描述设备树的基本信息,如设备树魔数标志、设备树块大小、结构块的偏移地址等,其具体结构boot_param_header 如下。这个结构中的值都是以大端模式表示,并且偏移地址是相对于设备树头的起始地址计算的。
3.2 结构块(structure block)
扁平设备树结构块是线性化的树形结构,和字符串块一起组成了设备树的主体,以节点形式保存目标板的设备信息。在结构块中,节点起始标志为常值宏of_dt_begin_node,节点结束标志为宏of_dt_end_node;子节点定义在节点结束标志前。一个节点可以概括为以of_dt_begin_node 开始,包括节点路径、属性列表、子节点列表,最后以of_dt_end_node 结束的序列,每一个子节点自身也是类似的结构。
3.3 字符串块(strings block)
为了节省空间,将一些属性名,尤其是那些重复冗余出现的属性名,提取出来单独存放到字符串块。这个块中包含了很多有结束标志的属性名字符串。在设备树的结构块中存储了这些字符串的偏移地址,这样可以很容易地查找到属性名字符串。字符串块的引入节省了嵌入式系统较为紧张的存储空间。
4 设备树源码dts 表示
设备树源码文件(.dts)以可读可编辑的文本形式描述系统硬件配置设备树,支持c/c++方式的注释,该结构有一个唯一的根节点“/”,每个节点都有自己的名字并可以包含多个子节点。设备树的数据格式遵循了open firmware ieee standard 1275。本文只简述设备树数据布局及语法,linux 板级支持包开发者应该详细参考ieee 1275 标准[5]及其他文献[2] [4]。为了说明,首先给出基于powerpc mpc8349e 处理器的最小系统的设备树源码示例。
可以看到,这个设备树中有很多节点,每个节点都指定了节点单元名称。每一个属性后面都给出相应的值。以双引号引出的内容为ascii 字符串,以尖括号给出的是32 位的16 进制值。这个树结构是启动linux 内核所需节点和属性简化后的集合,包括了根节点的基本模式信息、cpu 和物理内存布局,它还包括通过/chosen 节点传递给内核的命令行参数信息。
/ {
model = mpc8349emitx;
compatible = mpc8349emitx, mpc834xmitx, mpc83xxmitx;
#address-cells = ; /* 32bit address */
#size-cells = ; /* 4gb size */
cpus {
#address-cells = ;
#size-cells = ;
powerpc,8349@0 {
device_type = cpu;
reg = ;
d-cache-line-size = ; /* 32 bytes */
i-cache-line-size = ;
d-cache-size = ; /* l1 dcache, 32k */
i-cache-size = ;
timebase-frequency = ; /* from bootloader */
bus-frequency = ;
clock-frequency = ;
};
};
memory {
device_type = memory;
reg = ; /* 256mb */
};
chosen {
name = chosen;
bootargs = root=/dev/ram rw console=ttys0,115200;
linux,stdout-path = /soc8349@e0000000/serial@4500;
};
};
4.1 根节点
设备树的起始点称之为根节点/。属性model 指明了目标板平台或模块的名称,属性compatible 值指明和目标板为同一系列的兼容的开发板名称。对于大多数32 位平台,属性
#address-cells 和#size-cells 的值一般为1。
4.2 cpu 节点
/cpus 节点是根节点的子节点,对于系统中的每一个cpu,都有相应的节点。/cpus 节点没有必须指明的属性,但指明#address-cells = 和 #size-cells = 是个好习惯,这同时指明了每个cpu 节点的reg 属性格式,方便为物理cpu 编号。
此节点应包含板上每个cpu 的属性。cpu 名称一般写作powerpc,,例如freescale 会使用powerpc,8349 来描述本文的mpc8349e 处理器。cpu 节点的单元名应该是cpu@0 的格式,此节点一般要指定device_type(固定为cpu),一级数据/指令缓存的表项
大小,一级数据/指令缓存的大小,核心、总线时钟频率等。在上面的示例中通过系统引导代码动态填写时钟频率相关项。
4.3 系统内存节点
此节点用于描述目标板上物理内存范围,一般称作/memory 节点,可以有一个或多个。当有多个节点时,需要后跟单元地址予以区分;只有一个单元地址时,可以不写单元地址,默认为0。
此节点包含板上物理内存的属性,一般要指定device_type(固定为memory)和reg属性。其中reg 的属性值以的形式给出,如上示例中目标板内存起始地址为0,大小为256m 字节。
4.4 /chosen 节点
这个节点有一点特殊。通常,这里由open firmware 存放可变的环境信息,例如参数,默认输入输出设备。
这个节点中一般指定bootargs 及linux,stdout-path 属性值。bootargs 属性设置为传递给内核命令行的参数字符串。linux,stdout-path 常常为标准终端设备的节点路径名,内核会以此作为默认终端。
u-boot 在1.3.0 版本后添加了对扁平设备树fdt 的支持,u-boot 加载linux 内核、ramdisk 文件系统(如果使用的话)和设备树二进制镜像到物理内存之后,在启动执行linux内核之前,它会修改设备树二进制文件。它会填充必要的信息到设备树中,例如mac 地址、pci 总线数目等。u-boot 也会填写设备树文件中的“/chosen”节点,包含了诸如串口、根设备(ramdisk、硬盘或nfs 启动)等相关信息。
4.5 片上系统soc 节点
此节点用来描述片上系统soc,如果处理器是soc,则此节点必须存在。顶级soc 节点包含的信息对此soc 上的所有设备可见。节点名应该包含此soc 的单元地址,即此soc内存映射寄存器的基址。soc 节点名以/soc的形式命名,例如mpc8349 的soc
节点是soc8349。
在属性中应该指定device_type(固定为soc)、ranges、bus-frequency 等属性。ranges属性值以的形式指定。soc 节点还包含目标板使用的每个soc 设备子节点,应该在设备树中尽可能详细地描述此soc 上的外围设备。如下给出带有看门狗设备的soc 节点dts 示例。
soc8349@e0000000 {
#address-cells = ;
#size-cells = ;
device_type = soc;
compatible = simple-bus;
ranges = ; /* size 1mb */
reg = ;
bus-frequency = ; /* from bootloader */
{
device_type = watchdog;
compatible = mpc83xx_wdt;
reg = ; /* offset: 0x200 */
};
};
4.6 其他设备节点
分级节点用来描述系统上的总线和设备,类似物理总线拓扑,能很方便的描述设备间的关系。对于系统上的每个总线和设备,在设备树中都有其节点。对于这些设备属性的描述和定义请详细参考ieee 1275 标准及本文参考文献[2]。
设备树的中断系统稍显复杂,设备节点利用interrupt-parent 和interrupts 属性描述到中断控制器的中断连接。其中interrupt-parent 属性值为中断控制器节点的指针,#interrupts 属性值描述可触发的中断信号,其值格式与中断控制器的interrupt-cells 属性值有关。一般
#interrupt-cells 属性值为2,interrupts 属性就对应为一对描述硬件中断号和中断触发方式的十六进制值。
5 扁平设备树编译
根据嵌入式板的设备信息写设备树源码文件(.dts)通常比较简单,但是手写二进制的扁平设备树(.dtb)就显得比较复杂了。设备树编译器dtc 就是用来根据设备树源码的文本文件生成设备树二进制镜像的。dtc 编译器会对输入文件进行语法和语义检查,并根据linux内核的要求检查各节点及属性,将设备树源码文件(.dts)编译二进制文件(.dtb),以保证内核能正常启动。dtc 编译器的使用方法如下所示[6]:dtc [ -i dts ] [ -o dtb ] [ -o opt_file ] [ -v opt_version ] ipt_file2.6.25 版本之后的内核源码已经包含了dtc 编译器。在配置编译内核时选中config_dtc,会自动生成设备树编译器dtc。将编写的目标板设备树文件mpc8349emitx.dts放到内核源码的arch/powerpc/boot/dts/目录下,利用内核makefile 生成blob 的简单规则,使
用以下命令亦可完成设备树的dtc 编译:
$ make mpc8349emitx.dtb
6 u-boot 相关设置说明
为使 u-boot 支持设备树,需要在板子配置头文件中设置一系列宏变量。如本文在
mpc8349e 处理器目标板中移植的u-boot 配置如下:
/* pass open firmware flat tree */
#define config_of_libfdt 1
#undef config_of_flat_tree
#define config_of_board_setup 1
#define config_of_has_bd_t 1
#define config_of_has_uboot_env 1
启动引导代码u-boot 在完成自己的工作之后,会加载linux 内核,并将扁平设备树的
地址传递给内核,其代码形式如下:
#if defined(config_of_flat_tree) || defined(config_of_libfdt)
if (of_flat_tree) { /* device tree; boot new style */
/*
* linux kernel parameters (passing device tree):
* r3: pointer to the fdt, followed by the board info data
* r4: physical pointer to the kernel itself
* r5: null
* r6: null
* r7: null
*/
(*kernel) ((bd_t *)of_flat_tree, (ulong)kernel, 0, 0, 0);
/* does not return */
}
#endif
arch/powerpc 内核的入口有且只有一个,入口点为内核镜像的起始。此入口支持两种调用方式,一种是支持open firmware 启动,另一种对于没有of 的引导代码,需要使用扁平设备树块,如上示例代码。寄存器r3 保存指向设备树的物理地址指针,寄存器r4 保存为内
核在物理内存中的地址,r5 为null。其中的隐含意思为:假设开启了mmu,那么这个mmu的映射关系是1:1 的映射,即虚拟地址和物理地址是相同的。
7 linux 内核对设备树的解析
扁平设备树描述了目标板平台中的设备树信息。每个设备都有一个节点来描述其信息,每个节点又可以有子节点及其相应的属性。内核源码中include/linux/of.h 及drivers/of/base.c等文件中提供了一些open firmware api,通过这些api,内核及设备驱动可以查找到相应
的设备节点,读取其属性值,利用这些信息正确地初始化和驱动硬件。
图2 内核及驱动对扁平设备树的解析
fig2 interaction from kernel and drivers with the fdt blob
8 结论
本文介绍了设备树的起源及其优点,进而阐述了设备树的数据存储格式以及源码描述语法,给出了设备树的编译方法,最后引出了移植过程中的u-boot 相关设置说明及内核的解析过程分析。设备树为嵌入式系统向linux 内核传递参数的动态接口,本文以mpc8349e
处理器目标板上的dts 移植经历作总结,希望对嵌入式powerpc linux 开发者具有一定的参考价值,可以加快嵌入式powerpc linux 开发中的设备树dts 移植过程。
“游艇式”设计的欧陆GT特别版车型官图发布,全新一代欧陆有望于9月正式亮相!
探究超大Transformer语言模型的分布式训练框架
经纬辉开:收购诺思微系统,布局5G射频滤波器产业
AWE 2019火热开启 小豹翻译棒首秀成焦点
生物制药电力监控解决方案
学会Linux设备树dts移植
海康威视:2020年前三季度营业收入420.2亿元,净利润84.4亿元
D触发器的几种表示形式同步复位、同步释放
LED驱动设计需要考虑哪些问题
晶振损坏了,STM32还能正常运行?
发电机运行时的监测和注意事项
华为开发者大会2021日程
国内制造业转型关键技术——机器人
关于码垛机器人的应用,它的工艺流程是怎样的
中国移动和华为在北京联合召开5G-Advanced创新产业峰会
Altera推出业界带宽最大的28nm中端FPGA
码垛机器人尿素行业生产线改造使用优势
三大运营商积极布局5G MEC
实现20nm及更尖端工艺的3D芯片堆叠
Intel宣布正式出货全新Stratix10DXFPGA 支持PCIe 4.0 x16