设备树简介

ARM 164浏览


一 arm-linux内核设备树来源

在过去的arm-linux内核源码树中arch/arm/plat-xxx和arch/arm/mach-xxx 等目录下充斥着大量的垃圾代码,相当多数的代码只是在描述板级细节信息,而这些板级细节信息对于内核来说都是垃圾代码,比如板上的 platform_device、 resource、 i2c_board_info、 spi_board_info 以及各种硬件的 platform_data。

为了改变这种局面,ARM 社区开始使用 PowerPC 等其他体系架构下已经使用的Flattened Device Tree(FDT)。Device Tree 是一种描述硬件的数据结构,它起源于OpenFirmware(OF)。采用 Device Tree后,许多板级细节信息可以直接通过Device Tree传递给 linux内核,而不需要在arch/arm/plat-xxx和arch/arm/mach-xxx中进行大量的冗余编码,内核启动时会展开Device Tree并创建和注册相关的设备(比如platform_device,i2c_client,spi_device),同时驱动程序也会以新的方式和Devie Tree中定义的设备结点进行匹配。Device Tree的主要优势:对于使用相同主芯片的不同板卡,只需更换设备树文件dtb即可实现不同板卡的无差异支持。

二 arm-linux内核设备树简介

Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点,所谓属性就是成对出现的name和value。 在Device Tree中可描述的信息包括:

system runtime parameter,比如:bootargs

cpu数量和类别

memory基地址和大小

总线控制器和桥

总线外设

内核基础设施及其使用情况,比如:

中断控制器和中断使用情况

DMA控制器和DMA使用情况

GPIO控制器和GPIO使用情况

CLOCK控制器和ClOCK使用情况

PINCTRL控制器和PINCTRL使用情况

从上述信息可知Device Tree并不能描述所有硬件信息,一般可以动态识别的设备,比如USB设备,PCI/PCIe设备是不需要进行描述的,它们在设备热插拔时,由内核进行探测

设备树包含DTC(device tree compiler)、 DTS(device tree source) 和 DTB(device tree binary),其对应关系如下图1所示:

               图1

下面简单描述 dts文件,dtb文件和DTC

dts文件是一种ASCII文本文件,放置在arch/arm/boot/dts目录。由于每个主芯片可能存在多个电路板,每个电路板都有一个 dts文件,这些 dts文件势必会存在很多公用代码,linux 内核为了简化,把主芯片公用的部分或者多个machine 公用的部分保存到 dtsi文件,供不同的dts文件引用。示例vexpress-v2a.dts引用了vexpress-v2.dtsi,此时vexpress-v2a.dts中会有如下行:#include “vexpress-v2.dtsi”,告诉编译器vexpress-v2a.dts需要引用vexpress-v2.dtsi,类似于C语言的头文件,dtsi 也可以 include 其他的dtsi,示例几乎所有ARM芯片的dtsi 文件都引用了 skeleton.dtsi

DTC为编译工具,源码位于scripts/dtc目录,它可以将dts文件编译成dtb文件,下面为dts文件的Makefile:

dtb-$(CONFIG_ARCH_VEXPRESS) += vexpress-v2a.dtb

vexpress-v2b.dtb

vexpress-v2c.dtb

vexpress-v2d.dtb

从上述Makefile可知,若选择CONFIG_ARCH_VEXPRESS,

vexpress-v2a.dts、vexpress-v2b.dts、vexpress-v2c.dts、vexpress-v2d.dts

都会被编译成相应的dtb文件,bootloader在引导内核时,会预先读取dtb文件到内存,等待内核启动时进行解析

三 arm-linux内核设备树语法

下面以最简单的machine 进行讲解,假设此 machine 的配置如下:

双核ARM Cortex-A9处理器

2 个串口(分别位于0x101F1000和0x101F2000)

GPIO 控制器(位于0x101F3000)

SPI 控制器(位于0x101F4000)

I2C 控制器(位于0x101F5000)

I2C 控制器连接了maxim ds1338 实时钟(i2c地址为0x58)

中断控制器(位于0x10100000)

图2

上图2即一个简单的设备树,下面着重分析相关概念

compatible属性 dts文件的每个设备结点,都有一个compatible属性,该属性用来决定device_driver和device的相互匹配。compatible是一个字符串列表,列表的第一个字符串表征了结点所代表的确切设备,该字符串的组织形式为:”<制造商>,<型号>”,其后的字符串则表示与它兼容的设备。如上图2所示,串口设备的compatible属性为:

“ti,omap4-uart”,表示该设备节点是omap4芯片的串口设备。

再比如飞思卡尔mpc8349芯片拥有一个兼容美国国家半导体nsl16550的串口设备,那么该串口设备的compatible属性应该是:compatible = “fsl,mpc8349-uart”,”nsl16550”,其中mpc8349-uart指定了确切的设备,而nsl16550则说明与美国国家半导体的nsl16550 UART保持了寄存器兼容

注意:nsl16550没有制造商前缀,这是历史原因造成的,这种做法可以使现有驱动程序能够兼容新的设备,并仍然能够表征确切的设备。

记住以后compatible属性都应该使用这种形式:”<制造商>,<型号>”

root结点compatible = “ti,omap4430”,它定义了整个板卡的名称。

在实际项目中根结点compatible属性一般包含两个或者两个以上的字符串。第一个字符串表示整个板卡的名称,第二个字符串表示主芯片的名称,第三个字符串表示主芯片所属系列的名称,比如:

compatible = “ti,omap4-panda”, “ti,omap4430”, “ti,omap4”;

再比如:

compatible = “ti,omap4-sdp”, “ti,omap4430”, “ti,omap4”;

结点名称 每个结点必须有”name@0x10000000”形式的名称,其中name是一个不超过31位的ASCII字符串,0x10000000是设备地址,用来访问设备的主地址。结点名称应该描述设备类型而不是特定的型号,比如3com公司的ethernet适配器对应的结点名称应该是ethernet而不是3com509。同级结点名称必须是唯一的,当然设备地址不同,多个结点也可以使用相同的名称,比如serial@0x101f1000和serial@0x101f2000,设备地址是用来访问设备的主地址,并且该地址也在结点的reg属性中列出。

label 用来唯一标识一个结点,定义label使得引用结点变得简单,只要以&label的形式就可以访问该结点,而不需要使用全路径的形式访问该结点。

             图3

上图3定义了两个label,如下所示

&mmc0表示结点mmc@101f6000

&mmc1表示结点mmc@101f7000

通过&mmc0访问结点mmc@101f6000,将其status属性置为disabled,

使得mmc@101f6000设备处于disable状态

通过&mmc1访问结点mmc@101f7000,将其status属性置为okay,

使得mmc@101f7000设备处于enable状态

注意:

label习惯以<设备类型> 方式进行命名

address-cells和#size-cells

address-cells和#size-cells 用来决定子结点reg属性address字段cells个数和length字段cells个数。上图2中root结点的#address-cells = <1>,#size-cells = <1>,决定了serial、gpio、spi、i2c等子结点的address字段cells个数为1,length字段cells个数为1。

cpus结点的#address = <1>,#size-cells = <0>,决定了2个cpu子结点的address字段cells个数为1,而length字段为空。

reg属性

reg属性的组织形式如下所示:

reg =

3.1 关于skeleton.dtsi

skeleton.dtsi是所有ARM 芯片公用的dtsi文件,它定义了各ARM vendor共用的一些硬件信息,详细内容如下图4所示

                      图4

/ 表示整个设备树root结点

address-cells = <1> 表示root结点的子结点中,reg属性存在一个address值

size-cells = <1> 表示root结点的子结点中,reg属性存在一个length值(地址偏移),由上可知父结点#address-cells属性和#size-cells属性决定了子结点address字段cells个数和length字段cells个数

chosen 该结点主要描述system runtime parameter(但不会描述设备结点信息),比如原先通过uboot传递的bootargs参数,现在可以通过chosen结点来传递。需要注意的是,如果存在chosen结点,其父结点必须是根结点。

下图5即chosen结点实例信息

                      图5

aliases

当板卡存在多个同类设备时,需要使用aliases结点来进行编号,

aliases结点向内核提供识别符号,使得驱动程序可以获得硬件设备的id编号,进而指定其生成的设备文件id编号(ttyS0还是ttyS1)。

          图6

上图6:2个uart串口,compatible属性相同, 如果没有定义aliases结点,驱动程序探测时,无法知道当前设备id(0还是1)。通过定义aliases结点,串口驱动程序可以调用of_alias_get_id()来获得对应设备的id编号,进而指定其生成的设备文件为ttyS0还是ttyS1

          图7

上图7通过aliases结点告诉内核mcp_rtc为rtc0,tps659038_rtc为rtc1。

memory 顾名思义为内存结点

下图8即memory结点实例信息

                        图8

3.2 注意事项

1 在 dtb文件中,有且仅有一个root结点,因为编译器DTC在编译 dts和dtsi 文件时,会对结点进行合并,使得最终生成的 dtb文件有且仅有一个root 结点

2 除了root结点外,每个结点有且仅有一个父结点

3 某些设备结点,不仅仅需要添加设备本身的属性,还需要添加内核基础设施使用情况(比如interrupt、gpio、dma、clock、regulator、pinctrl)及其子结点信息。

4 添加设备结点的属性,一般在公用的dtsi文件中添加不变属性(比如reg属性,中断号,dma通道),在具体板卡的 dts文件中添加可变属性(比如gpio使用情况,引脚复用情况)。这样使得dtsi文件和dts文件解耦