什么是设备树 设备树全称叫开发固件设备树——Open Firmware Device Tree
- 设备树可以描述的信息包括 CPU的数量和类别、内存基地址和大小、总线和桥、外设连接、中断控制器和中断使用情况、GPIO控制器和GPIO使用情况、Clock控制器和Clock使用情况。
- 设备树信息被保存在一个ASCII文本文件中,适合人类的阅读习惯,类似xml文件,在ARM Linux中,一个.dts文件对应一个ARM的machine放置在内核的arch/arm/boot/dts/目录。
- 设备树是一种数据结构,用于描述设备信息的语言,具体而言,是用于操作系统中描述硬件,使得不需要对设备信息进行硬编码(hard code)。
- Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。
- 设备树源文件dts被编译成dtb二进制文件,在bootloader运行时传递给操作系统,操作系统对其进行解析展开(Flattened),从而产生一个硬件设备的拓扑图,有了这个拓扑图,在编码的过程中可以直接通过系统提供的接口获得设备树中的节点和属性信息。
设备树文件在 arch/arm/boot/dts 目录下(2.6版本以及之前版本的内核没有设备树)
Linux设备树的由来——为什么会有设备树
- 在Linux2.6中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量描述板级细节的垃圾代码,如板上的platform设备,resource、i2c_board_info、spi_board_info以及各种硬件platform_data。常见的s3c2410、s3c6410等板级目录,代码量在数万行。
- Linus Torvalds对此种情况大发雷霆,在2011年的ARM Linux邮件列表宣称”this whole ARM thing is a f*ucking pain in the ass"。
- 随后Linux开发社区就开始整改,设备树最早用于PowerPC等其他体系架构,ARM架构开发社区就开始采用设备树来描述设备的信息。
2.6中在 arch/arm/目录下描述板级信息,例如
static struct clk s3c2440_clk_cam_upll = {
.name = "camif-upll",
.id = -1,
.set_rate = s3c2440_camif_upll_setrate,
.round_rate = s3c2440_camif_upll_round,
};
快速编译设备树——DTC(device tree compiler)
- .dts文本文件会被DTC编译为.dtb二进制文件,二进制文件会被内核使用。
- DTC的源码为于内核的scripts/dtc目录,在Linux内核使能了Device Tree的情况下,编译内核的时候主机工具DTC会被编译出来。
- 在Linux内核的arch/arm/boot/dts/Makefile中,描述了当某种SoC被选中后,哪些.dtb文件会被编译出来,如与EXYNOS对应的.dtb包括
#如果在ARM架构下并且时EXYNOS平台下,那么就会编译出以下这么多目标文件,dtb文件对应同名dts
dtb-$(CONFIG_ARCH_EXYNOS) += exynos4210-origen.dtb \
exynos4210-smdkv310.dtb \
exynos4210-trats.dtb \
exynos4210-universal_c210.dtb \
exynos4412-odroidx.dtb \
exynos4412-origen.dtb \
exynos4412-smdk4412.dtb \
exynos4412-tiny4412.dtb \
exynos4412-trats2.dtb \
exynos5250-arndale.dtb \
exynos5250-smdk5250.dtb \
exynos5250-snow.dtb \
exynos5420-arndale-octa.dtb \
exynos5420-smdk5420.dtb \
exynos5440-sd5v1.dtb \
exynos5440-ssdk5440.dtb
.dtsi相当于.dts文件中的头文件,包含通用的设备信息描述,如要编译自己创建的dts文件,则在Makefile文件中相关平台下添加对应文件名就行。
如何创建自己的dts文件并编译
cp arch/arm/boot/dts/exynos4412-origen.dts arch/arm/boot/dts/exynos4412-fs4412.dts
vim arch/arm/boot/dts/Makefile
在dtb-$(CONFIG_ARCH_EXYNOS) += exynos4210-origen.dtb \ 下添加exynos4412-fs4412.dts\
-编译设备树文件,在内核文件最上层编译
make dtbs
执行后
DTC arch/arm/boot/dts/exynos4412-fs4412.dtb
cp arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpboot/
set bootcmd tftp 0x41000000 uImage\; tftp 0x42000000 exynos4412-fs4412.dtb\; bootm 0x41000000-0x42000000
设备树文件是如何被内核使用的
set bootcmd tftp 0x41000000 uImage\; tftp 0x42000000 exynos4412-fs4412.dtb\; bootm 0x41000000-0x42000000
bootm后,启动内核,内核会自动去0x42000000读取dtb文件,读取后会展开成二叉树 根节点 |_ | | | 子节点 子节点 子节点
节点中有自己定义的信息,时钟、中断…… 启动过程中内核可以看到信息, 驱动中如果想读取某个特定的节点,只要遍历整个节点,找到某个节点的标识符,内核会提供API接口of_api
相关的缩写名词解释
DT:Device Tree //设备树 FDT:Flattened DeviceTree //展开设备树,做成树状结构 OF:Open Firmware //开放式固件 DTS:device tree source DTSI:device tree source include DTB:device tree blob DTC:device tree compiler
设备树的语法
- 节点
- 属性
- 根节点
- compatible属性
- reg属性
- #address-cells和#address-siz属性
- 中断信息属性–interrupts和interrupts
设备树基本语法
设备树模板
/{
node1 {
a-string-property = "A string";
a-string-list-property = "first string", "second string";
a-byte-data-property = [0x01 0x23 0x34 0x56];
child-node1 {
first-child-property;
second-child-property = <1>;
a-string-property = "Hello,world";
};
child-node2{
};
};
node2{
an-empty-property;
a-cell-property = <1 2 3 4>;
child-node1{
};
};
}
节点
节点名称 每个节点必须有一个"<名称>[@<设备地址>]"形式的名字<名称>就是一个不超过31位的简单ascii字符串,中括号中的内容可有可无。
<设备地址>用来访问该设备的主地址,并且该地址也在节点的reg属性中列出,同级节点命名必须是唯一的,但只要地址不同,多个节点也可以使用一样的通用名称,当然设备地址也是可选的。这里的设备地址主要是用来区分的,不做属性使用。
树中每个表示一个设备的节点都需要一个compatible属性,一般是一个字符串 reg属性表示起始地址和地址长度如:reg = <0x0203F000 0x1000>;
属性property
简单的键-值对,它的值可以为空或者包含一个任意字节流。虽然数据类型并没有编码进数据结构,但在设备树源文件中任有几个基本的数据表示形式:
文件字符串(无结束符)可以用双引号表示:
string-property = "a string"
cells是32位无符号整数 用尖括号限定:
cell-property = <0xbeef 123 0xabcd123>
二进制数据用方括号限定:
binary-property = [01 23 45 67];
不同表示形式的数据可以使用逗号连在一起:
mixed-property = "a string",[01 23 45 67],<0x12345678>;
都好也可用于创建字符串列表:
string-list = "read fish", "blue fish";
常见属性–compatible属性
指定了系统的名称,是一个字符串列表,实际在代码中可以用于匹配,当前你选择的是哪个机器,它包含了一个"<制造商>,<型号>"形式的字符串。重要的是要指定一个确切的设备,并且包括制造商的名字,以避免命名空间冲突 不要使用通配符的compatible值,比如"fsl,mpc93xx-uart"或类似情况
/{
compatible = "acme,coyotes-revenge";
}
常见属性–#address-cells和#size-cells
#address-cells = <1>表示address字段的长度为1 #size-cell = <1>;表示length字段的长度为1
external-bus{
#address-cells=<2>
#size-cells=<1>;
ethernet@0,0{
compatible = "smc,smc91c111";
reg = <0 0 0x1000>;
nterrupts = <5 2>;
}
}
常见属性–reg属性
reg的组织形式为reg = <address1 length1 address2 length2 address3 length3…>其中的每一组address length表明了设备使用的一个地址范围
/{
compatible = "acme,coyotes-revenge";
#address-cells = <1>;
#size-cells = <1>;
serial@101f2000{
compatible = "arm,pl011";
reg = <0x101f2000 0x1000>;
interrupts = <2 0>;
};
}
常见属性–中断信息
描述中断连接需要四个属性 interrupt-controller:一个空的属性定义该节点作为一个接收中断信号的设备
#interrupt-cells:这是一个中断控制器节点的属性。它声明了改中断控制器的中断指示符中cell的个数(类似于#address-cells和#size-cells)
interrupt-parent:这是一个设备节点的属性,包含一个指向该设备连接的中断控制器的phandle(指向或者可以引用&)那些没有interrupt-parent的节点则从他们的父节点中继承该属性。
interrupts:一个设备节点属性,包含一个中断指示符的列表,对应于该设备上的每个中断输出信号
常见属性–中断信息
/{
compatible = "acme,coyotes-revenge";
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&intc>;
serial@101f0000{
compatible = "arm,pl011";
reg = <0x101f0000 0x1000>;
interrupts = <1 0>;
};
intc:interrupt-controller@10140000{
compatible = "arm,pl190";
reg = <0x10140000 0x1000>;
interrupt-controller;
#interrupt-cells = <2>;
};
}
对于arm架构,中断标志的具体含义 interrupts = <1st, 2nd, 3rd>
1st: 表示中断的类型 0:表示SPI interrupts 共享中断 1:表示PPI interrupts 个人中断 IPI:inter-processer interrupt 中断号0~15 PPI:per processor interrupts 中断号16~31 SPI:shared processor interrupts 中断号 32 ~32+224 SGI:software generated interrupts (SGI) 2nd: 表示中断是第一个参数里面的第几个 3rd: 中断触发方式 下面是中断类型值 #define IRQ_TYPE_NONE 0 #define IRQ_TYPE_EDGE_RISING 1 #define IRQ_TYPE_EDGE_FALLING 2 #define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING) #define IRQ_TYPE_LEVEL_HIGH 4 #define IRQ_TYPE_LEVEL_LOW 8
|