嵌入式Linux--设备树(DeviceTree fdt)
目录
- 最前面:重要目录
- 前言:什么是设备树(Device Tree)
- 1、什么是设备树(device tree)
- 2、设备树的相关名词
- 3、DTS 文件格式
- 4、编译后的dtb 格式
- Device Tree简介
- 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)来编译。
还没有评论,来说两句吧...