本文在网络上引用甚广,详细的讲述了新版内核dts架构,这里引来详细拜读,本人对原文格式略作调整,原文地址:
嵌入式PowerPC上的扁平设备树FDT
摘 要:设备树的引入减少了内核为支持新硬件而需要的改变,提高代码重用,加速了Linux支持包的开发,使得单个内核镜像能支持多个系统。作为U-Boot和Linux内核之间的动态接口,本文阐述了设备树的数据存储格式以及源码描述语法,进而分析了U-Boot对扁平设备树的支持设置,Linux内核对设备树的解析流程。
关键词:扁平设备树 DTS PowerPC Linux
在嵌入式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的后向兼容。
U-Boot需要将扁平设备树在内存地址传给内核。该树主要由三大部分组成:头(Header)、结构块(Structure block)、字符串块(Strings block)。在内存中分配图如下:
2.1 头(header)
头主要描述设备树的基本信息,如设备树魔数标志、设备树块大小、结构块的偏移地址等,其具体结构boot_param_header如下。这个结构中的值都是以大端模式表示,并且偏移地址是相对于设备树头的起始地址计算的。
struct boot_param_header { u32 magic; /* magic word OF_DT_HEADER */ u32 totalsize; /* total size of DT block */ u32 off_dt_struct; /* offset to structure */ u32 off_dt_strings; /* offset to strings */ u32 off_mem_rsvmap; /* offset to memory reserve map */ u32 version; /* format version */ u32 last_comp_version; /* last compatible version */ /* version 2 fields below */ u32 boot_cpuid_phys; /* Which physical CPU id we're booting on */ /* version 3 fields below */ u32 size_dt_strings; /* size of the strings block */ /* version 17 fields below */ u32 size_dt_struct; /* size of the DT structure block */ };2.2 结构块(structure block)
1. 节点起始标志OF_DT_BEGIN_NODE(即0x0000_0001); 2. 节点路径或者节点单元名(version < 3以及节点路径表示,version > 16时以节点单元名表示); 3. 填充字节保证四字节对齐; 4. 节点属性。每个属性以常值宏OF_DT_PROP 开始,后面依次为属性值的字节长度、属性名在在字符串块中的偏移值、属性值及字节对齐填充段; 5. 如果存在子节点,则定义子节点。 6. 节点结束标志OF_DT_END_NODE(即0x0000_0002)。
2.3 字符串块(Strings block)
为了节省空间,对于那些属性名,尤其是很多属性名是重复冗余出现的,提取出来单独存放到字符串块。这个块中包含了很多有结束标志的属性名字符串。在设备树的结构块中存储了这些字符串的偏移地址,因为可以很容易的查找到属性名字符串。字符串块的引入节省嵌入式系统较为紧张的存储空间。
3 设备树源码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 = <1>; /* 32bit address */ #size-cells = <1>; /* 4GB size */ cpus { #address-cells = <1>; #size-cells = <0>; PowerPC,8349@0 { device_type = "cpu"; reg = <0>; d-cache-line-size = <20>; /* 32 Bytes */ i-cache-line-size = <20>; d-cache-size = <8000>; /* L1 dcache, 32K */ i-cache-size = <8000>; timebase-frequency = <0>; /* From bootloader */ bus-frequency = <0>; clock-frequency = <0>; }; }; memory { device_type = "memory"; reg = <00000000 10000000>; /* 256MB */ }; chosen { /* add by starby */ name = "chosen"; bootargs = "root=/dev/ram rw console=ttyS0,115200"; linux,stdout-path = "/soc8349@e0000000/serial@4500"; }; };3.1 根节点
3.2 CPU节点
/cpus节点是根节点的子节点,对于系统中的每一个CPU,都有相应的节点。/cpus节点没有必须指明的属性,但指明#address-cells = <1>和 #size-cells = <0>是个好习惯,这同时指明了每个CPU节点的reg属性格式,方便为物理CPU编号。
此节点应包含板上每个CPU的属性。CPU名称一般写作PowerPC,<name>,例如Freescale会使用PowerPC,8349来描述本文的MPC8349E处理器。CPU节点的单元名应该是cpu@0的格式,此节点一般要指定device_type(固定为"cpu"),一级数据/指令缓存的表项大小,一级数据/指令缓存的大小,核心、总线时钟频率等。在上面的示例中通过系统引导代码动态填写时钟频率相关项。
3.3 系统内存节点
此节点用于描述目标板上物理内存范围,一般称作/memory节点,可以有一个或多个。当有多个节点时,需要后跟单元地址予以区分;只有一个单元地址时,可以不写单元地址,默认为0。
此节点包含板上物理内存的属性,一般要指定device_type(固定为"memory")和reg属性。其中reg的属性值以<起始地址 空间大小>的形式给出,如上示例中目标板内存起始地址为0,大小为256M字节。
3.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启动)等相关信息。
U-Boot源码common/cmd_bootm.c的如下代码,显示了在执行内核代码前将调用ft_setup函数填写设备树。
#if defined(CONFIG_OF_FLAT_TREE) /* * Create the /chosen node and modify the blob with board specific * values as needed. */ ft_setup(of_flat_tree, kbd, initrd_start, initrd_end); /* ft_dump_blob(of_flat_tree); */ #endif
3.5 片上系统SOC节点
此节点用来描述片上系统SOC,如果处理器是SOC,则此节点必须存在。顶级SOC节点包含的信息对此SOC上的所有设备可见。节点名应该包含此SOC的单元地址,即此SOC内存映射寄存器的基址。SOC节点名以/soc<SOCname>的形式命名,例如MPC8349的SOC节点是"soc8349"。soc8349@e0000000 { #address-cells = <1>; #size-cells = <1>; device_type = "soc"; compatible = "simple-bus"; ranges = <0 e0000000 100000>; /* 1MB size */ reg = <e0000000 00000200>; bus-frequency = <0>; /* From bootloader */ wdt@200 { device_type = "watchdog"; compatible = "mpc83xx_wdt"; reg = <200 100>; /* Offset: 0x200 */ };
$git clone http://www.gdl.com/projects/dtc.git
dtc [-I <input_fomat>] [-O <ouput_fomat>] [-o output_filename] [-V output_version] input_filename input_format可以使用以下三个参数: dtb: 表示输入文件为dtb文件; dts: 表示输入文件为dts文件; fs: 表示输入文件为与/proc/device-tree文件的格式相同。 output_format可以使用以下三个参数: dtb: 表示输出文件为dtb文件; dts: 表示输出文件为dts文件; asm: 表示输出文件为汇编语言文件;
$ make ARCH=powerpc canyonlands.dtb
$ dtc -f -I dts -O dtb -R 8 -S 0x3000 test.dts > mpc836x_mds.dtb $ mkimage -A ppc -O Linux -T flat_dt -C none -a 0x300000 -e 0 -d mpc836x_mds.dtb mpc836x_mds.dtu
5 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
6 Linux内核对设备的解析
扁平设备树描述了目标板平台中的设备树信息。每个设备都有一个节点来描述其信息,每个节点又可以有子节点及其相应的属性。内核源码中include/linux/of.h及drivers/of/base.c等文件中提供了一些Open Firmware API,通过这些API,内核及设备驱动可以查找到相应的设备节点,读取其属性值,利用这些信息正确地初始化和驱动硬件。
分析Linux内核的源码,可以看到其对扁平设备树的解析流程[7 待详细分析]如下:
(1)首先在内核入口处将从u-boot传递过来的镜像基地址和dtb文件映像基地址保存通用寄存器r30和r31。 (2)通过调用machine_init()、early_init_devtree()函数来获取内核前期初始化所需的bootargs,cmd_line等系统引导参数。 (3)根据bootargs,cmd_line等系统引导参数进入start_kernel()函数,进行内核的第二阶段初始化。 (4)调用start_kernel()、setup_arch()、unflatten_device_tree()函数来解析dtb文件,构建一个由device_node结构连接而成的单项链表,并使用全局变量allnodes 指针来保存这个链表的头指针。 (5)内核调用OF 提供的API函数获取allnodes链表信息来初始化内核其他子系统、设备等。
8 结束语
本文介绍了设备树的起源及其优点,进而阐述了设备树的数据存储格式以及源码描述语法,给出了设备树的编译方法,最后引出了移植过程中的U-Boot相关设置说明及内核的解析过程分析。设备树为嵌入式系统向Linux内核传递参数的动态接口,希望本文对嵌入式PowerPC Linux开发者具有一定的参考价值,加快嵌入式PowerPC Linux开发中的设备树DTS移植过程。
参考文献
[1] Matt Tyrlik. Booting Linux on Embedded PowerPCTM Systems [R]. Embedded Linux Developers Conference, 2007
[2] Benjamin Herrenschmidt, Becky Bruce, et al. Booting the Linux/ppc kernel without Open Firmware [Z]. bootint-without-of.txt from the Linux kernel source tree , 2006
[3] DENX. Flattened Device Tree Blob [EB/OL]. http://www.denx.de/wiki/view/DULG/LinuxFDTBlob, 2009
[4] Grant Likely, Josh Boyer. A Symphony of Flavours: Using the device tree to describe embedded hardware [R]. Ottawa, Canada: Linux Symposium, 2008
[5] SUN. The Open Firmware Home Page [EB/OL]. http://playground.sun.com/1275/home.html, 2005
[6] David Gibson, Benjamin Herrenschmidt, OzLabs. Device trees everywhere [R]. 2006
[7] 邱文华,邱珍珍. 基于扁平设备树的Linux内核启动方式[J]. 现代计算机,2009年第3期,115-118. 2009.3