参考博客

最前面:重要目录

  • 原来硬编码进内核的板级描述文件目录: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-xxxarch/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)来编译。

Logo

更多推荐