您的位置首页>企业动态>

学会Linux设备树dts移植

导读大家好,我是极客范的本期栏目编辑小友,现在为大家讲解学会Linux设备树dts移植问题。该设备的引入减少了内核支持新硬件所需的改动,提高了

大家好,我是极客范的本期栏目编辑小友,现在为大家讲解学会Linux设备树dts移植问题。

该设备的引入减少了内核支持新硬件所需的改动,提高了代码复用,加快了Linux支持包的开发,使得单个内核镜像可以支持多个系统。作为U-Boot与Linux内核的动态接口,阐述了设备树的数据存储格式和源代码描述语法,进而分析了U-Boot对平面设备树的支持设置以及Linux内核对设备树的分析过程。

固件(一种嵌入在硬件设备中提供软硬件接口的程序)最初被IBM、Sun等厂商的服务器采用,用来初始化系统配置,提供操作系统软硬件接口,启动运行系统。后来为了标准化和兼容性,IBM、Sun等联合推出了固件接口的IEEE 1275标准,使得他们的服务器如IBM PowerPC pSeries、Apple PowerPC、Sun等都采用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]。某些信息,如缓存大小、中断路由等。均由设备树直接提供,而其他信息,如eTSEC MAC地址、频率、PCI总线号等。在运行时由U-Boot修改。U-Boot使用扁平设备树代替bd_t,不再保证bd_t的向后兼容性。

2设备树概念

简单来说,设备树是描述硬件配置的树形数据结构,只有一个根节点[4]。它包含有关中央处理器、物理内存、总线、串行端口、PHY和其他外围设备的信息。此树继承了OpenFirmware IEEE 1275设备树的定义。操作系统可以在启动时解析这个结构来配置内核并加载相应的驱动程序。

3设备树存储格式

U-Boot需要将设备树的内存地址传递给内核。树主要由三部分组成:头、结构块和字符串块。内存中树的存储布局如图1所示:

图1设备树存储格式图

图1数据块的布局

3.1标题

头部主要描述设备树的基本信息,如设备树的幻数标志、设备树块的大小、结构块的偏移地址等。它的具体结构boot_param_header如下。这种结构中的值都以大端模式表示,偏移地址是相对于设备树头的起始地址计算的。

3.2结构块(结构块)

扁平设备树结构块是线性化的树结构,它与字符串块一起构成设备树的主体,并以节点的形式存储目标板的设备信息。在结构块中,节点开始标志为常量宏OF_DT_BEGIN_NODE,节点结束标志为宏of _ dt _ end _ node子节点在节点的结束标记之前定义。一个节点可以概括为以OF_DT_BEGIN_NODE开头的序列,包括节点路径、属性列表、子节点列表,以OF_DT_END_NODE结尾。每个子节点本身都有类似的结构。

3.3字符串块

为了节省空间,一些属性名,特别是那些重复出现和冗余出现的属性名,被单独提取并存储在字符串块中。这个块包含许多带有结束标志的属性名字符串。这些字符串的偏移地址存储在设备树的结构块中,因此可以很容易地找到属性名字符串。字符串块的引入节省了嵌入式系统紧张的存储空间。

4设备树源DTS表示

设备树源文件(。dts)以可读可编辑的文本形式描述系统硬件配置设备树,支持C/C标注。该结构有一个唯一的根节点“/”,每个节点都有自己的名称,并且可以包含多个子节点。树的数据格式符合开放固件IEEE标准1275。本文只简单描述了设备树的数据布局和语法,Linux板级支持包的开发者应该详细参考IEEE 1275标准[5]等文献[2] [4]。为了说明,首先

先给出基于PowerPC MPC8349E 处理器的最小系统的设备树源码示例。可以看到,这个设备树中有很多节点,每个节点都指定了节点单元名称。每一个属性后面都给出相应的值。以双引号引出的内容为ASCII 字符串,以尖括号给出的是32 位的16 进制值。这个树结构是启动Linux 内核所需节点和属性简化后的集合,包括了根节点的基本模式信息、CPU 和物理内存布局,它还包括通过/chosen 节点传递给内核的命令行参数信息。/ {model ="MPC8349EMITX";compatible ="MPC8349EMITX","MPC834xMITX","MPC83xxMITX";#address-cells = ; #size-cells = ; cpus {#address-cells = ;#size-cells = ;PowerPC,8349@0 {device_type ="cpu";reg = ;d-cache-line-size = ; i-cache-line-size = ;d-cache-size = ; i-cache-size = ;TImebase-frequency = ; bus-frequency = ;clock-frequency = ;};};memory {device_type ="memory";reg = ; };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 = ; reg = ;bus-frequency = ; {device_type ="watchdog";compaTIble ="mpc83xx_wdt";reg = ; };};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.dtb6 U-Boot 相关设置说明为使 U-Boot 支持设备树,需要在板子配置头文件中设置一系列宏变量。如本文在MPC8349E 处理器目标板中移植的U-Boot 配置如下:#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) { /** 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);}#endifarch/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 blob8 结论本文介绍了设备树的起源及其优点,进而阐述了设备树的数据存储格式以及源码描述语法,给出了设备树的编译方法,最后引出了移植过程中的U-Boot 相关设置说明及内核的解析过程分析。设备树为嵌入式系统向Linux 内核传递参数的动态接口,本文以MPC8349E处理器目标板上的DTS 移植经历作总结,希望对嵌入式PowerPC Linux 开发者具有一定的参考价值,可以加快嵌入式PowerPC Linux 开发中的设备树DTS 移植过程。 

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。