嵌入式Linux--设备树(DeviceTree fdt)

tech2022-08-14  125

目录

最前面:重要目录前言:什么是设备树(Device Tree)1、什么是设备树(device tree)2、设备树的相关名词3、DTS 文件格式4、编译后的dtb 格式 1. Device Tree简介2. Device Tree编译3、设备树启动

参考博客

device tree 简介

最前面:重要目录

原来硬编码进内核的板级描述文件目录:arch/arm/mach-xxx 和 arch/arm/plat-xxx新的设备树文件是放在Linux内核工程目录里面:arch/arm/boot/dts/xxx.dts

前言:什么是设备树(Device Tree)

1、什么是设备树(device tree)

 它是一种描述硬件资源的数据结构,可以通过bootloader将它传给内核,内核(driver)使用它对硬件进行初始化,好处是使得内核和硬件资源描述相对独立,不需要太多的硬编码。

2、设备树的相关名词

1)DTS(device tree source) .dts文件是一种ASCII文本对Device Tree的描述,位于linux-4.10//arch/arm64/boot/dts目录下。

2)DTC(device tree compiler) DTC为编译工具,它可以将.dts文件编译成.dtb文件,DTC的源码位于linux-4.10/scripts/dtc目录下。

3)DTB(device tree blob) DTC编译.dts生成的二进制文件(.dtb),bootloader在加载内核时,也会同时把.dtb加载到内存,后面传递给内核使用。

3、DTS 文件格式

例如 linux-4.10/arch/arm64/boot/dts/arm64-demo.dts

#include "arm64-demo.dtsi" / { model = "arm64-demo Board"; compatible = "arm,arm64-demo"; aliases { serial0 = &uart0; serial1 = &uart1; serial2 = &uart2; serial3 = &uart3; }; memory@40000000 { device_type = "memory"; reg = <0 0x40000000 0 0x1e800000>; }; chosen { stdout-path = "serial0:921600n8"; }; }; &uart0 { status = "okay"; };

 dts目录下并没有 arm64-demo.dts 这样的文件,这里只是为了举例,dts目录下有其他arm芯片厂商的dts 文件可以参考一下

 “/“为root节点,在一个.dts文件中,有且仅有一个root节点,#include “arm64-demo.dtsi”,跟代码中的include 头文件的作用差不多,也就是把rm64-demo.dtsi定义的device tree节点包含到arm64-demo.dts中,虽然arm64-demo.dtsi 文件中也会有一个”/”,但是dtc编译时,会把它们合并成一个。

1)aliases node

aliases { serial0 = &uart0; serial1 = &uart1; serial2 = &uart2; serial3 = &uart3; };

aliases 节点定义了一些别名。为何要定义这个node呢?因为Device tree是树状结构,当要引用一个node的时候要指明相对于root node的full path。例如

linux-4.10/arch/arm64/boot/dts/arm64-demo.dtsi

uart0: serial@11002000 { compatible = "mediatek,mt6795-uart", "mediatek,mt6577-uart"; reg = <0 0x11002000 0 0x400>; interrupts = <GIC_SPI 91 IRQ_TYPE_LEVEL_LOW>; clocks = <&uart_clk>; status = "disabled"; };

serial0 = &uart0; 所以serial0 就是/serial@11002000 的一个别名,uart0 是一个lable,也是/serial@11002000,使用lable需要在前面加上& 。例如

&uart0 { status = "okay"; };

就是把/serial@11002000 节点里面的status 属性改成okay

2)memory node

memory@40000000 { device_type = "memory"; reg = <0 0x40000000 0 0x1e800000>;

对于memory node,device_type必须为memory,memory device node是所有设备树文件的必备节点,它定义了系统物理内存的layout。reg描述了memory-mapped IO register的offset和length。对于memory node,定义了该memory的起始地址和长度,这里的0 0x40000000 是起始地址,0 0x1e800000 是内存的大小(长度)。

linux-4.10/arch/arm64/boot/dts/arm64-demo.dtsi

#address-cells = <2>; #size-cells = <2>;

为什么是0 0x40000000 表示起始地址,因为root 节点 #address-cells = <2>; 表示用两个cell (32位),同样的#size-cells = <2> 表示用两个cell (32位)。

每个node用节点名字(node name)标识,节点名字的格式是node-name@unit-address。如果该node没有reg属性,那么该节点名字中必须不能包括@和unit-address。unit-address的具体格式是和设备挂在那个bus上相关。

所以上面memory 的描述是,起始地址是 0x 80000000

3)chosen node

chosen { stdout-path = "serial0:921600n8"; };

 chosen node 主要用来描述由系统指定的runtime parameter,它并没有描述任何硬件设备节点信息。原先通过tag list传递的一些linux kernel运行的参数,可以通过chosen节点来传递。如command line可以通过bootargs这个property来传递。如果存在chosen node,它的parent节点必须为“/”根节点。

4、编译后的dtb 格式

1)fdt_header

 定义在 linux-4.10/scripts/dtc/libfdt/fdt.h

struct fdt_header { fdt32_t magic; /* magic word FDT_MAGIC */ fdt32_t totalsize; /* total size of DT block */ fdt32_t off_dt_struct; /* offset to structure */ fdt32_t off_dt_strings; /* offset to strings */ fdt32_t off_mem_rsvmap; /* offset to memory reserve map */ fdt32_t version; /* format version */ fdt32_t last_comp_version; /* last compatible version */ /* version 2 fields below */ fdt32_t boot_cpuid_phys; /* Which physical CPU id we're booting on */ /* version 3 fields below */ fdt32_t size_dt_strings; /* size of the strings block */ /* version 17 fields below */ fdt32_t size_dt_struct; /* size of the structure block */ };

 off_dt_struct 是到dt_struct 结构块的偏移量(相对于文件起始位置),off_dt_strings 是到dt_strings字符串块的偏移量(相对于文件起始位置),off_mem_rsvmap 是到memory reserve map 区域的偏移量(相对于文件起始位置)。

2)memory reserve map

 该区域保存的数据会4字节对齐

3)dt_struct

 结构块里面保存了dts 里面描述的设备信息,和dts 里面写的内容一致,只不过转成了另一种数据格式。节点的开始和结束,属性的开始用下面定义的标识。

linux-4.10/scripts/dtc/libfdt/fdt.h

#define FDT_BEGIN_NODE 0x1 /* Start node: full name */ #define FDT_END_NODE 0x2 /* End node */ #define FDT_PROP 0x3 /* Property: name off,size, content */ #define FDT_NOP 0x4 /* nop */ #define FDT_END 0x9

4)off_dt_strings

 字符串块 保存的是dts中属性的名字,因为在不同的节点中会用到相同的属性名,为了减少保存重复的属性名字符串,所以把它们放在字符串块中,每个字符串是以\0为结束标识。 详细的dtb格式如上图,我们以memory 节点为例,由dts转成dtb 是怎么样的

memory { device_type = "memory"; reg = <0 0x40000000 0 0x1e800000>; }; 00 00 00 01 6d 65 6d 6f 72 79 00 00 00 00 00 03 00 00 00 07 00 00 00 10 6d 65 6d 6f 72 79 00 00 00 00 00 03 00 00 00 10 00 00 00 20 00 00 00 00 40 00 00 00 00 00 00 00 00 1e 80 00 00 00 00 02

 第一行的00 00 00 01 就是 FDT_BEGIN_NODE,紧接着的6d 65 6d 6f 72 79 00 00 就是节点的名称 memory(也会做4字节对齐),在往后面00 00 00 03 就是FDT_PROP,标志这属性,第二行的00 00 00 07 指示了属性值的大小,后面的 00 00 00 10 是属性名称在dt_strings中的偏移,这里我是随便写的,device_type = “memory”; 属性值memory 的大小明明是6,为什么是7呢,因为要加上\0,所以是7,后面还有00 是为了保证4字节对齐,接下来第三行又是00 00 00 03,又是一个属性的开始,也就是reg = <0 0x40000000 0 0x1e800000>; 接着一样是属性值的大小,0 0x40000000 0 0x1e800000 会占用16个字节,所以是 0x00000010,后面就是对应的值,最后的00 00 00 02 就是FDT_END_NODE,标识一个节点结束。


1. Device Tree简介

 Linus Torvalds在2011年3月17日的ARM Linux邮件列表宣称“this whole ARM thing is a fucking pain in the ass”,引发ARM Linux社区的地震,随后ARM社区进行了一系列的重大修正。在过去的ARM Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,相当多数的代码只是在描述板级细节(因为一块CPU芯片可以有很多块不同类型的开发板/应用板,而每一块上面的资源外设细节是各不相同,于是就造成了在该目录下有大量的冗余代码文件…),而这些板级细节对于内核来讲,不过是垃圾,如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的platform_data。

 社区必须改变这种局面,于是PowerPC等其他体系架构下已经使用的Flattened Device Tree(FDT)进入ARM社区的视野。通过引入“Flattened Device Tree”将这些描述板级硬件信息的内容都从Linux内核中分离出来,用一个专属的文件格式来描述,这个专属的文件就叫做设备树,文件扩展名为.dts。一个SOC芯片可以做出很多不同的板子,这些不同的板子肯定是有共同的信息,将这些共同信息提取出来作为一个通用的文件,而其他的.dts文件直接引用这个通用文件即可,这个通用文件就是.dtsi文件,类似于C语言中的头文件 一般的.dts描述板级信息(也就是开发板上有哪些IIC设备、SPI设备等),.dtsi文件描述SOC芯片级的信息(也就是SOC芯片有几个CPU、主频是多少、各个外设控制器信息等) 。

 Device Tree是一种描述硬件的数据结构,它起源于OpenFirmware(OF)。在Linux2.6中,ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx,采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。

 设备树文件一般放在arch/arm/boot/dts(限ARM架构)

 Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。在Device Tree中,可描述的信息包括(原先这些信息大多被硬编码到kernel中):

CPU的数量和类别内存基地址和大小总线和桥外设连接中断控制器和中断使用情况GPIO控制器和GPIO使用情况

 它基本上就是画一棵电路板上CPU、总线、设备组成的树,Bootloader会将这棵树传递给内核,然后内核可以识别这棵树,并根据它展开出Linux内核中的platform_device、i2c_client、spi_device等设备。这些设备用到的内存、IRQ等资源,也被传递给了kernel,kernel会将这些资源绑定给展开的相应的设备。

2. Device Tree编译

编译的地方:在Linux内核工程的目录下(顶目录)输入命令:make imx6ull-alientek-emmc.dtb

然后在目录:arch/arm/boot/dts 下就可以看到 .dtb文件了

名词简介:

DTS:设备树源码文件(.dts)-- 目录是:arch/arm/boot/dts/ (ARM架构)DTB:是DTS编译后得到的二进制文件(.dtb)DTC:将DTS编译成DTB的编译工具 – 目录是:scripts/dtc

 Device Tree文件的格式为dts,包含的头文件格式为dtsi,dts文件是一种人可以看懂的编码格式。但是uboot和linux不能直接识别,他们只能识别二进制文件,所以需要把dts文件编译成dtb文件。dtb文件是一种可以被kernel和uboot识别的二进制文件。把dts编译成dtb文件的工具是dtc。Linux源码目录下scripts/dtc目录包含dtc工具的源码。在Linux的scripts/dtc目录下除了提供dtc工具外,也可以自己安装dtc工具,linux下执行:sudo apt-get install device-tree-compiler安装dtc工具。其中还提供了一个fdtdump的工具,可以反编译dtb文件。dts和dtb文件的转换如图1所示。

 dtc工具的使用方法是:dtc –I dts –O dtb –o xxx.dtb xxx.dts,即可生成dts文件对应的dtb文件了。

正点原子(已经写好了脚本):

编译所有的dts文件:make dtbs编译指定的dts:make imx6ull-alientek-emmc.dtb

3、设备树启动

 Linux-3.x之后的内核统一启用Device Tree机制之后,所有的设备硬件信息描述都会放到 arch/arm/boot/dts/ 路径下的 xxx.dts文件中描述。这些dts(Device Tree Source)文件并不是C代码,而是具有相应语法格式的源文件。在编译内核时,我们可以使用 make dtbs 命令编译生成相应开发板的dtb(Device Tree Blob)文件。因为这些源文件并不是C程序,所以不是用gcc来编译,而是由其相应的编译工具dtc(Device Tree Compiler)来编译。

最新回复(0)