PowerPC扁平设备树FDT(转)

IBM、Sun等厂家的服务器最初都采用了Firmware,用于初始化系统配置,提供操作系统软件和硬件之间的接口,启动和运行系统。后来为了标准化和兼容性,IBM、Sun等联合推出了固件接口IEEE 1275标准,让他们的服务器如IBM PowerPC pSeries,Apple PowerPC,Sun SPARC等均采用Open Firmware,在运行时构建系统硬件的设备树信息传递给内核,进行系统的启动运行。这样做的好处有,减少内核对系统硬件的严重依赖,利于加速支持包的开发,降低硬件带来的变化需求和成本,降低对内核设计和编译的要求。

       随着Linux/ppc64内核的发展,内核代码从原来的arch/ppc32和arch/ppc64逐渐迁移到统一的arch/powerpc目录,并在内核代码引入Open Firmware API以使用标准固件接口。Linux内核在运行时,需要知道硬件的一些相关信息。对于使用ARCH=powerpc参数编译的内核镜像,这个信息需要基于Open Firmware规范,以设备树的形式存在。这样内核在启动时读取扫描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(二进制大对象,是一个可以存储二进制文件的容器)存储传递给内核的参数。一些确定信息,例如cache大小、中断路由等直接由设备树提供,而其他的信息,例如eTSEC的MAC地址、频率、PCI总线数目等由U-Boot在运行时修改。U-Boot使用扁平设备树取代了bd_t,而且也不再保证对bd_t的后向兼容。


FDT设备树结构

      简单的说设备树是一种描述硬件配置信息的数据结构,包括CPU、内存、总线及相关外设。内核启动时可以解析这些信息,以此决定如何配置内核及加载那些驱动。该数据结构有一个单一的根节点“/”。每个节点有个名字并可以包含多个子节点。数据的格式遵循IEEE standard 1275。该树主要由三大部分组成:头(Header)、结构块(Structure block)、字符串块(Strings block),在内存中分配图如图2所示:

图2 设备树内存分配图

头(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 */
};

结构块(structure block)
       扁平设备树结构块是线性化的树形结构,和字符串块一起组成了设备树的主体,以节点形式保存目标板的设备信息。在结构块中,节点起始标志为32位常值宏OF_DT_BEGIN_NODE,节点结束标志为宏OF_DT_END_NODE;子节点定义在节点结束标志前。一个节点的基本结构如下所示:
 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)。
 归纳起来,一个节点可以概括为以OF_DT_BEGIN_NODE开始,节点路径、属性列表、子节点列表以及OF_DT_END_NODE结束的序列,每一个子节点自身也是类似的结构。

字符串块(Strings block)
       为了节省空间,对于那些属性名,尤其是很多属性名是重复冗余出现的,提取出来单独存放到字符串块。这个块中包含了很多有结束标志的属性名字符串。在设备树的结构块中存储了这些字符串的偏移地址,因为可以很容易的查找到属性名字符串。字符串块的引入节省嵌入式系统较为紧张的存储空间。


设备树源码DTS表示

       Device tree source (.dts)采用一种易编辑的文本方式来表达设备树,device tree compiler tool (dtc)将.dts转换成binary device tree blob(.dtb)。设备树并不是控制系统设备的唯一方法,比如内核对USB和PCI已经有非常方便的检测机制。

       为了说明,首先给出基于PowerPC P4080处理器的最小系统的设备树源码示例。可以看到,这个设备树中有很多节点,每个节点都指定了节点单元名称。每一个属性后面都给出相应的值。以双引号引出的内容为ASCII字符串,以尖括号给出的是32位的16进制值。这个树结构是启动Linux内核所需节点和属性简化后的集合,包括了根节点的基本模式信息、CPU和物理内存布局,它还包括通过/chosen节点传递给内核的命令行参数信息。

/ {

       model = "fsl,P4080DS";

       compatible = "fsl,P4080DS";

       #address-cells = <0x2>;

       #size-cells = <0x2>;

       cpus {

              #address-cells = <0x1>;

              #size-cells = <0x0>;

 

              PowerPC,4080@0 {

                     device_type = "cpu";

                     reg = <0x0>;

                     next-level-cache = <0x1>;

                     linux,phandle = <0xa>;

 

                     l2-cache {

                            next-level-cache = <0x2>;

                            linux,phandle = <0x1>;

                     };

              };

 

              PowerPC,4080@1 {

                     device_type = "cpu";

                     reg = <0x1>;

                     next-level-cache = <0x3>;

                     linux,phandle = <0xc>;

 

                     l2-cache {

                            next-level-cache = <0x2>;

                            linux,phandle = <0x3>;

                     };

              };

              ……………….(P4080有8个核,其余的省略)

       }

       memory {

              device_type = "memory";

              reg = <00000000 10000000>;        /* 256MB */

       };

       /*为了举例,chosen节点从网上摘录*/

    chosen {          
        name = "chosen";
        bootargs = "root=/dev/ram rw console=ttyS0,115200";
        linux,stdout-path = "/soc8349@e0000000/serial@4500";
    };

}

 

根节点
 设备树的起始点称之为根节点"/"。属性model指明了目标板平台或模块的名称,属性compatible值指明和目标板为同一系列的兼容的开发板名称。对于大多数32位平台,属性#address-cells和#size-cells的值一般为1,这里是2。
CPU节点
 /cpus节点是根节点的子节点,对于系统中的每一个CPU,都有相应的节点。/cpus节点没有必须指明的属性,但指明#address-cells = <1>和 #size-cells = <0>是个好习惯,这同时指明了每个CPU节点的reg属性格式,方便为物理CPU编号。
此节点应包含板上每个CPU的属性。CPU名称一般写作PowerPC,<name>,例如Freescale会使用PowerPC,4080来描述本文的P4080处理器。CPU节点的单元名应该是cpu@0的格式,此节点一般要指定device_type(固定为"cpu"),一级数据/指令缓存的表项大小,一级数据/指令缓存的大小、核心、总线时钟频率等。在上面的示例中通过系统引导代码动态填写时钟频率相关项。

系统内存节点
此节点用于描述目标板上物理内存范围,一般称作/memory节点,可以有一个或多个。当有多个节点时,需要后跟单元地址予以区分;只有一个单元地址时,可以不写单元地址,默认为0。此节点包含板上物理内存的属性,一般要指定device_type(固定为"memory")和reg属性。其中reg的属性值以<起始地址 空间大小>的形式给出,如上示例中目标板内存起始地址为0,大小为256MB(仅仅举例,并不是256MB)。

/chosen节点
 这个节点有一点特殊。通常,这里由Open Firmware存放可变的环境信息,例如参数,默认输入输出设备。这个节点中一般指定bootargs及linux,stdout-path属性值。bootargs属性设置为传递给内核命令行的参数字符串。linux,stdout-path常常为标准终端设备的节点路径名,内核会以此作为默认终端。这里的例子从网上摘录,本项目中没有添加这个节点。


设备树编译

由于内核只识别二进制格式的扁平设备树,因此需要一个特殊的设备树编译器"dtc",将设备树源码文件(.dts)编译二进制文件(.dtb)。dtc编译器会对输入文件进行语法和语义检查,并根据Linux内核的要求检查各节点及属性,将设备树源码文件(.dts)编译二进制文件(.dtb),以保证内核能正常启动。
 dtc编译器的使用方法如下所示:
 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: 表示输出文件为汇编语言文件;
 如果output_format为“dtb”是,ouput_version用来规定生成的dtb文件的版本号,目前dtb文件可用的版本号为1,2,3,16或17,output_format的缺省值为17。-S指定的是生成的dtb文件的大小,需要适当地扩大以供u-boot 创建/chose节点时使用。input_filename和output_filename分别为输入和输出文件名。从dtc编译器的使用方法中发现,dtc编译器不仅可以实现dts文件到dtb文件的转换,也可以实现dtb文件到dts文件的转换。

 Linux源码的arch/powerpc/boot/dts/目录下存放了很多dts文件,可以作为参考文件。另外dtc编译器在内核源码2.6.25版本之后已经被包含进去。在2.6.26版本之后,生成blob的简单规则已经加入makefile,如下命令:
 $ make ARCH=powerpc canyonlands.dtb

 也可以根据自己的硬件修改好dts文件后,用下面类似命令生成dtb文件。
 $ dtc -f -I dts -O dtb -R 8 -S 0x3000 p4080_bcem.dts  > p4080_bcem.dtb
 $ mkimage -A ppc -O Linux -T flat_dt -C none -a 0x300000 -e 0 -d p4080_bcem.dtb p4080_bcem.dtu

 注:最新的U-Boot使用dtb镜像文件。而freescale的U-Boot需要如上使用mkimage为dtb添加镜像信息


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的映射,即虚拟地址和物理地址是相同的。


Linux内核对设备的解析

      扁平设备树描述了目标板平台中的设备树信息。每个设备都有一个节点来描述其信息,每个节点又可以有子节点及其相应的属性。内核源码中include/linux/of.h及drivers/of/base.c等文件中提供了一些Open Firmware API,通过这些API,内核及设备驱动可以查找到相应的设备节点,读取其属性值,利用这些信息正确地初始化和驱动硬件。

分析Linux内核的源码,可以看到其对扁平设备树的解析流程如下:
(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链表信息来初始化内核其他子系统、设备等。

以上是对PowerPC设备树的介绍,后面会根据项目,对设备树进行实例化的介绍。

(以上介绍来自于http://blog.chinaunix.net/u2/76349/showart_2258729.html)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值