1)实验平台:正点原子Linux开发板

2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南

关注官方微信号公众号,获取更多资料:正点原子

前面章节中我们多次提到“设备树”这个概念,因为时机未到,所以当时并没有详细的讲解什么是“设备树”,本章我们就来详细的谈一谈设备树。掌握设备树是Linux驱动开发人员必备的技能!因为在新版本的Linux中,ARM相关的驱动全部采用了设备树(也有支持老式驱动的,比较少),最新出的CPU其驱动开发也基本都是基于设备树的,比如ST新出的STM32MP157、NXP的I.MX8系列等。我们所使用的Linux版本为4.1.15,其支持设备树,所以正点原子I.MX6U-ALPHA开发板的所有Linux驱动都是基于设备树的。本章我们就来了解一下设备树的起源、重点学习一下设备树语法。

43.1 什么是设备树?

设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做DTS(Device Tree Source),这个DTS文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU数量、内存基地址、IIC接口上接了哪些设备、SPI接口上接了哪些设备等等,如图43.1.1所示:

图43.1.1 设备树结构示意图

在图43.1.1中,树的主干就是系统总线,IIC控制器、GPIO控制器、SPI控制器等都是接到系统主线上的分支。IIC控制器有分为IIC1和IIC2两种,其中IIC1上接了FT5206和AT24C02这两个IIC设备,IIC2上只接了MPU6050这个设备。DTS文件的主要功能就是按照图43.1.1所示的结构来描述板子上的设备信息,DTS文件描述设备信息是有相应的语法规则要求的,稍后我们会详细的讲解DTS语法规则。

在3.x版本(具体哪个版本笔者也无从考证)以前的Linux内核中ARM架构并没有采用设备树。在没有设备树的时候Linux是如何描述ARM架构中的板级信息呢?在Linux内核源码中大量的arch/arm/mach-xxx和arch/arm/plat-xxx文件夹,这些文件夹里面的文件就是对应平台下的板级信息。比如在arch/arm/mach-smdk2440.c中有如下内容(有缩减):

示例代码43.1.1 mach-smdk2440.c文件代码段

90staticstruct s3c2410fb_display smdk2440_lcd_cfg __initdata ={

91

92.lcdcon5 = S3C2410_LCDCON5_FRM565 |

93 S3C2410_LCDCON5_INVVLINE |

94 S3C2410_LCDCON5_INVVFRAME |

95 S3C2410_LCDCON5_PWREN |

96 S3C2410_LCDCON5_HWSWP,

......

113};

114

115staticstruct s3c2410fb_mach_info smdk2440_fb_info __initdata ={

116.displays =&smdk2440_lcd_cfg,

117.num_displays =1,

118.default_display =0,

......

133};

134

135staticstruct platform_device *smdk2440_devices[] __initdata ={

136 &s3c_device_ohci,

137&s3c_device_lcd,

138&s3c_device_wdt,

139&s3c_device_i2c0,

140&s3c_device_iis,

141};

上述代码中的结构体变量smdk2440_fb_info就是描述SMDK2440这个开发板上的LCD信息的,结构体指针数组smdk2440_devices描述的SMDK2440这个开发板上的所有平台相关信息。这个仅仅是使用2440这个芯片的SMDK2440开发板下的LCD信息,SMDK2440开发板还有很多的其他外设硬件和平台硬件信息。使用2440这个芯片的板子有很多,每个板子都有描述相应板级信息的文件,这仅仅只是一个2440。随着智能手机的发展,每年新出的ARM架构芯片少说都在数十、数百款,Linux内核下板级信息文件将会成指数级增长!这些板级信息文件都是.c或.h文件,都会被硬编码进Linux内核中,导致Linux内核“虚胖”。就好比你喜欢吃自助餐,然后花了100多到一家宣传看着很不错的自助餐厅,结果你想吃的牛排、海鲜、烤肉基本没多少,全都是一些凉菜、炒面、西瓜、饮料等小吃,相信你此时肯定会脱口而出一句“F*k!”、“骗子!”。同样的,当Linux之父linus看到ARM社区向Linux内核添加了大量“无用”、冗余的板级信息文件,不禁的发出了一句“This whole ARM thing is a f*cking pain in the ass”。从此以后ARM社区就引入了PowerPC等架构已经采用的设备树(Flattened Device Tree),将这些描述板级硬件信息的内容都从Linux内中分离开来,用一个专属的文件格式来描述,这个专属的文件就叫做设备树,文件扩展名为.dts。一个SOC可以作出很多不同的板子,这些不同的板子肯定是有共同的信息,将这些共同的信息提取出来作为一个通用的文件,其他的.dts文件直接引用这个通用文件即可,这个通用文件就是.dtsi文件,类似于C语言中的头文件。一般.dts描述板级信息(也就是开发板上有哪些IIC设备、SPI设备等),.dtsi描述SOC级信息(也就是SOC有几个CPU、主频是多少、各个外设控制器信息等)。

这个就是设备树的由来,简而言之就是,Linux内核中ARM架构下有太多的冗余的垃圾板级信息文件,导致linus震怒,然后ARM社区引入了设备树。

43.2 DTS、DTB和DTC

上一小节说了,设备树源文件扩展名为.dts,但是我们在前面移植Linux的时候却一直在使用.dtb文件,那么DTS和DTB这两个文件是什么关系呢?DTS是设备树源码文件,DTB是将DTS编译以后得到的二进制文件。将.c文件编译为.o需要用到gcc编译器,那么将.dts编译为.dtb需要什么工具呢?需要用到DTC工具!DTC工具源码在Linux内核的scripts/dtc目录下,scripts/dtc/Makefile文件内容如下:

示例代码43.2.1 scripts/dtc/Makefile文件代码段

1 hostprogs-y := dtc

2 always :=$(hostprogs-y)

3

4 dtc-objs:= dtc.o flattree.o fstree.o data.o livetree.o treesource.o

5 srcpos.o checks.o util.o

6 dtc-objs += dtc-lexer.lex.o dtc-parser.tab.o

......

可以看出,DTC工具依赖于dtc.c、flattree.c、fstree.c等文件,最终编译并链接出DTC这个主机文件。如果要编译DTS文件的话只需要进入到Linux源码根目录下,然后执行如下命令:

makeall

或者:

makedtbs

“makeall”命令是编译Linux源码中的所有东西,包括zImage,.ko驱动模块以及设备树,如果只是编译设备树的话建议使用“makedtbs”命令。

基于ARM架构的SOC有很多种,一种SOC又可以制作出很多款板子,每个板子都有一个对应的DTS文件,那么如何确定编译哪一个DTS文件呢?我们就以I.MX6ULL这款芯片对应的板子为例来看一下,打开arch/arm/boot/dts/Makefile,有如下内容:

示例代码43.2.2 arch/arm/boot/dts/Makefile文件代码段

381 dtb-$(CONFIG_SOC_IMX6UL) +=

382 imx6ul-14x14-ddr3-arm2.dtb

383 imx6ul-14x14-ddr3-arm2-emmc.dtb

......

400 dtb-$(CONFIG_SOC_IMX6ULL) +=

401 imx6ull-14x14-ddr3-arm2.dtb

402 imx6ull-14x14-ddr3-arm2-adc.dtb

403 imx6ull-14x14-ddr3-arm2-cs42888.dtb

404 imx6ull-14x14-ddr3-arm2-ecspi.dtb

405 imx6ull-14x14-ddr3-arm2-emmc.dtb

406 imx6ull-14x14-ddr3-arm2-epdc.dtb

407 imx6ull-14x14-ddr3-arm2-flexcan2.dtb

408 imx6ull-14x14-ddr3-arm2-gpmi-weim.dtb

409 imx6ull-14x14-ddr3-arm2-lcdif.dtb

410 imx6ull-14x14-ddr3-arm2-ldo.dtb

411 imx6ull-14x14-ddr3-arm2-qspi.dtb

412 imx6ull-14x14-ddr3-arm2-qspi-all.dtb

413 imx6ull-14x14-ddr3-arm2-tsc.dtb

414 imx6ull-14x14-ddr3-arm2-uart2.dtb

415 imx6ull-14x14-ddr3-arm2-usb.dtb

416 imx6ull-14x14-ddr3-arm2-wm8958.dtb

417 imx6ull-14x14-evk.dtb

418 imx6ull-14x14-evk-btwifi.dtb

419 imx6ull-14x14-evk-emmc.dtb

420 imx6ull-14x14-evk-gpmi-weim.dtb

421 imx6ull-14x14-evk-usb-certi.dtb

422 imx6ull-alientek-emmc.dtb

423 imx6ull-alientek-nand.dtb

424 imx6ull-9x9-evk.dtb

425 imx6ull-9x9-evk-btwifi.dtb

426 imx6ull-9x9-evk-ldo.dtb

427 dtb-$(CONFIG_SOC_IMX6SLL) +=

428 imx6sll-lpddr2-arm2.dtb

429 imx6sll-lpddr3-arm2.dtb

......

可以看出,当选中I.MX6ULL这个SOC以后(CONFIG_SOC_IMX6ULL=y),所有使用到I.MX6ULL这个SOC的板子对应的.dts文件都会被编译为.dtb。如果我们使用I.MX6ULL新做了一个板子,只需要新建一个此板子对应的.dts文件,然后将对应的.dtb文件名添加到dtb-$(CONFIG_SOC_IMX6ULL)下,这样在编译设备树的时候就会将对应的.dts编译为二进制的.dtb文件。

示例代码43.2.2中第422和423行就是我们在给正点原子的I.MX6U-ALPHA开发板移植Linux系统的时候添加的设备树。关于.dtb文件怎么使用这里就不多说了,前面讲解Uboot移植、Linux内核移植的时候已经无数次的提到如何使用.dtb文件了(uboot中使用bootz或bootm命令向Linux内核传递二进制设备树文件(.dtb))。

43.3 DTS语法

虽然我们基本上不会从头到尾重写一个.dts文件,大多时候是直接在SOC厂商提供的.dts文件上进行修改。但是DTS文件语法我们还是需要详细的学习一遍,因为我们肯定需要修改.dts文件。大家不要看到要学习新的语法就觉得会很复杂,DTS语法非常的人性化,是一种ASCII文本文件,不管是阅读还是修改都很方便。

本节我们就以imx6ull-alientek-emmc.dts这个文件为例来讲解一下DTS语法。关于设备树详细的语法规则请参考《Devicetree SpecificationV0.2.pdf》和《Power_ePAPR_APPROVED_v1.12.pdf》这两份文档,此两份文档已经放到了开发板光盘中,路径为:4、参考资料->Devicetree SpecificationV0.2.pdf、4、参考资料->Power_ePAPR_APPROVED_v1.12.pdf

43.3.1 .dtsi头文件

和C语言一样,设备树也支持头文件,设备树的头文件扩展名为.dtsi。在imx6ull-alientek-emmc.dts中有如下所示内容:

示例代码43.3.1.1 imx6ull-alientek-emmc.dts文件代码段

12 #include <dt-bindings/input/input.h>

13 #include "imx6ull.dtsi"

第12行,使用“#include”来引用“input.h”这个.h头文件。

第13行,使用“#include”来引用“imx6ull.dtsi”这个.dtsi头文件。

看到这里,大家可能会疑惑,不是说设备树的扩展名是.dtsi吗?为什么也可以直接引用C语言中的.h头文件呢?这里并没有错,.dts文件引用C语言中的.h文件,甚至也可以引用.dts文件,打开imx6ull-14x14-evk-gpmi-weim.dts这个文件,此文件中有如下内容:

示例代码43.3.1.2 imx6ull-14x14-evk-gpmi-weim.dts文件代码段

9 #include "imx6ull-14x14-evk.dts"

可以看出,示例代码43.3.1.2中直接引用了.dts文件,因此在.dts设备树文件中,可以通过“#include”来引用.h、.dtsi和.dts文件。只是,我们在编写设备树头文件的时候最好选择.dtsi后缀。

一般.dtsi文件用于描述SOC的内部外设信息,比如CPU架构、主频、外设寄存器地址范围,比如UART、IIC等等。比如imx6ull.dtsi就是描述I.MX6ULL这颗SOC内部外设情况信息的,内容如下:

示例代码43.3.1.3 imx6ull.dtsi文件代码段

10 #include <dt-bindings/clock/imx6ul-clock.h>

11 #include <dt-bindings/gpio/gpio.h>

12 #include <dt-bindings/interrupt-controller/arm-gic.h>

13 #include "imx6ull-pinfunc.h"

14 #include "imx6ull-pinfunc-snvs.h"

15 #include "skeleton.dtsi"

16

17/{

18 aliases {

19 can0 =&flexcan1;

......

48};

49

50 cpus {

51 #address-cells =<1>;

52 #size-cells =<0>;

53

54 cpu0: cpu@0 {

55 compatible ="arm,cortex-a7";

56 device_type ="cpu";

......

89};

90};

91

92 intc: interrupt-controller@00a01000 {

93 compatible ="arm,cortex-a7-gic";

94 #interrupt-cells =<3>;

95 interrupt-controller;

96 reg =<0x00a010000x1000>,

97<0x00a020000x100>;

98};

99

100 clocks {

101 #address-cells =<1>;

102 #size-cells =<0>;

103

104 ckil: clock@0 {

105 compatible ="fixed-clock";

106 reg =<0>;

107 #clock-cells =<0>;

108 clock-frequency =<32768>;

109 clock-output-names ="ckil";

110};

......

135};

136

137 soc {

138 #address-cells =<1>;

139 #size-cells =<1>;

140 compatible ="simple-bus";

141 interrupt-parent =gpc>;

142 ranges;

143

144 busfreq {

145 compatible ="fsl,imx_busfreq";

......

162};

197

198 gpmi: gpmi-nand@01806000{

199 compatible ="fsl,imx6ull-gpmi-nand","fsl, imx6ul-gpmi-nand";

200 #address-cells =<1>;

201 #size-cells =<1>;

202 reg =<0x018060000x2000>,<0x018080000x4000>;

......

216};

......

1177};

1178};

示例代码43.3.1.3中第54~89行就是cpu0这个设备节点信息,这个节点信息描述了I.MX6ULL这颗SOC所使用的CPU信息,比如架构是cortex-A7,频率支持996MHz、792MHz、528MHz、396MHz和198MHz等等。在imx6ull.dtsi文件中不仅仅描述了cpu0这一个节点信息,I.MX6ULL这颗SOC所有的外设都描述的清清楚楚,比如ecspi1~4、uart1~8、usbphy1~2、i2c1~4等等,关于这些设备节点信息的具体内容我们稍后在详细的讲解。

43.3.2设备节点

设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。以下是从imx6ull.dtsi文件中缩减出来的设备树文件内容:

示例代码43.3.2.1 设备树模板

1/{

2 aliases {

3 can0 =&flexcan1;

4 };

5

6 cpus {

7 #address-cells =<1>;

8 #size-cells =<0>;

9

10 cpu0: cpu@0 {

11 compatible ="arm,cortex-a7";

12 device_type ="cpu";

13 reg =<0>;

14 };

15 };

16

17 intc: interrupt-controller@00a01000 {

18 compatible ="arm,cortex-a7-gic";

19 #interrupt-cells =<3>;

20 interrupt-controller;

21 reg =<0x00a010000x1000>,

22 <0x00a020000x100>;

23 };

24}

第1行,“/”是根节点,每个设备树文件只有一个根节点。细心的同学应该会发现,imx6ull.dtsi和imx6ull-alientek-emmc.dts这两个文件都有一个“/”根节点,这样不会出错吗?不会的,因为这两个“/”根节点的内容会合并成一个根节点。

第2、6和17行,aliases、cpus和intc是三个子节点,在设备树中节点命名格式如下:

node-name@unit-address

其中“node-name”是节点名字,为ASCII字符串,节点名字应该能够清晰的描述出节点的功能,比如“uart1”就表示这个节点是UART1外设。“unit-address”一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话“unit-address”可以不要,比如“cpu@0”、“interrupt-controller@00a01000”。

但是我们在示例代码43.3.2.1中我们看到的节点命名却如下所示:

cpu0:cpu@0

上述命令并不是“node-name@unit-address”这样的格式,而是用“:”隔开成了两部分,“:”前面的是节点标签(label),“:”后面的才是节点名字,格式如下所示:

label: node-name@unit-address

引入label的目的就是为了方便访问节点,可以直接通过&label来访问这个节点,比如通过&cpu0就可以访问“cpu@0”这个节点,而不需要输入完整的节点名字。再比如节点“intc: interrupt-controller@00a01000”,节点label是intc,而节点名字就很长了,为“interrupt-controller@00a01000”。很明显通过&intc来访问“interrupt-controller@00a01000”这个节点要方便很多!

第10行,cpu0也是一个节点,只是cpu0是cpus的子节点。

每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流。设备树源码中常用的几种数据形式如下所示:

①、字符串

compatible = "arm,cortex-a7";

上述代码设置compatible属性的值为字符串“arm,cortex-a7”。

②、32位无符号整数

reg = <0>;

上述代码设置reg属性的值为0,reg的值也可以设置为一组值,比如:

reg = <0 0x123456 100>;

③、字符串列表

属性值也可以为字符串列表,字符串和字符串之间采用“,”隔开,如下所示:

compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";

上述代码设置属性compatible的值为“fsl,imx6ull-gpmi-nand”和“fsl, imx6ul-gpmi-nand”。

43.3.3 标准属性

节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以自定义属性。除了用户自定义属性,有很多属性是标准属性,Linux下的很多外设驱动都会使用这些标准属性,本节我们就来学习一下几个常用的标准属性。

1、compatible属性

compatible属性也叫做“兼容性”属性,这是非常重要的一个属性!compatible属性的值是一个字符串列表,compatible属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序,compatible属性的值格式如下所示:

"manufacturer,model"

其中manufacturer表示厂商,model一般是模块对应的驱动名字。比如imx6ull-alientek-emmc.dts中sound节点是I.MX6U-ALPHA开发板的音频设备节点,I.MX6U-ALPHA开发板上的音频芯片采用的欧胜(WOLFSON)出品的WM8960,sound节点的compatible属性值如下:

compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";

属性值有两个,分别为“fsl,imx6ul-evk-wm8960”和“fsl,imx-audio-wm8960”,其中“fsl”表示厂商是飞思卡尔,“imx6ul-evk-wm8960”和“imx-audio-wm8960”表示驱动模块名字。sound这个设备首先使用第一个兼容值在Linux内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查找,直到找到或者查找完整个Linux内核也没有找到对应的驱动。

一般驱动程序文件都会有一个OF匹配表,此OF匹配表保存着一些compatible值,如果设备节点的compatible属性值和OF匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。比如在文件imx-wm8960.c中有如下内容:

示例代码43.3.3.1 imx-wm8960.c文件代码段

632staticconststruct of_device_id imx_wm8960_dt_ids[]={

633{.compatible ="fsl,imx-audio-wm8960",},

634{/* sentinel */}

635};

636 MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);

637

638staticstruct platform_driver imx_wm8960_driver ={

639.driver ={

640.name ="imx-wm8960",

641.pm =&snd_soc_pm_ops,

642.of_match_table = imx_wm8960_dt_ids,

643},

644.probe = imx_wm8960_probe,

645.remove = imx_wm8960_remove,

646};

第632~635行的数组imx_wm8960_dt_ids就是imx-wm8960.c这个驱动文件的匹配表,此匹配表只有一个匹配值“fsl,imx-audio-wm8960”。如果在设备树中有哪个节点的compatible属性值与此相等,那么这个节点就会使用此驱动文件。

第642行,wm8960采用了platform_driver驱动模式,关于platform_driver驱动后面会讲解。此行设置.of_match_table为imx_wm8960_dt_ids,也就是设置这个platform_driver所使用的OF匹配表。

2、model属性

model属性值也是一个字符串,一般model属性描述设备模块信息,比如名字什么的,比如:

model = "wm8960-audio";

3、status属性

status属性看名字就知道是和设备状态有关的,status属性值也是字符串,字符串是设备的状态信息,可选的状态如表43.3.3.1所示:

表43.3.3.1 status属性值表

4、#address-cells和#size-cells属性

这两个属性的值都是无符号32位整形,#address-cells和#size-cells这两个属性可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。#address-cells属性值决定了子节点reg属性中地址信息所占用的字长(32位),#size-cells属性值决定了子节点reg属性中长度信息所占的字长(32位)。#address-cells和#size-cells表明了子节点应该如何编写reg属性值,一般reg属性都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度,reg属性的格式一为:

reg =

每个“addresslength”组合表示一个地址范围,其中address是起始地址,length是地址长度,#address-cells表明address这个数据所占用的字长,#size-cells表明length这个数据所占用的字长,比如:

示例代码43.3.3.2 #address-cells和#size-cells属性

1 spi4 {

2 compatible ="spi-gpio";

3 #address-cells =<1>;

4 #size-cells =<0>;

5

6 gpio_spi: gpio_spi@0 {

7 compatible ="fairchild,74hc595";

8 reg =<0>;

9 };

10};

11

12 aips3: aips-bus@02200000 {

13 compatible ="fsl,aips-bus","simple-bus";

14 #address-cells =<1>;

15 #size-cells =<1>;

16

17 dcp: dcp@02280000 {

18 compatible ="fsl,imx6sl-dcp";

19 reg =<0x022800000x4000>;

20 };

21};

第2,3行,节点spi4的#address-cells = <1>,#size-cells = <0>,说明spi4的子节点reg属性中起始地址所占用的字长为1,地址长度所占用的字长为0。

第8行,子节点gpio_spi: gpio_spi@0的reg 属性值为<0>,因为父节点设置了#address-cells = <1>,#size-cells = <0>,因此addres=0,没有length的值,相当于设置了起始地址,而没有设置地址长度。

第14,15行,设置aips3: aips-bus@02200000节点#address-cells = <1>,#size-cells = <1>,说明aips3: aips-bus@02200000节点起始地址长度所占用的字长为1,地址长度所占用的字长也为1。

第19行,子节点dcp: dcp@02280000的reg属性值为<0x02280000 0x4000>,因为父节点设置了#address-cells = <1>,#size-cells = <1>,address= 0x02280000,length= 0x4000,相当于设置了起始地址为0x02280000,地址长度为0x40000。

5、reg属性

reg属性前面已经提到过了,reg属性的值一般是(address,length)对。reg属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息,比如在imx6ull.dtsi中有如下内容:

示例代码43.3.3.3 uart1节点信息

323 uart1: serial@02020000 {

324 compatible ="fsl,imx6ul-uart",

325"fsl,imx6q-uart","fsl,imx21-uart";

326 reg =<0x020200000x4000>;

327 interrupts =<GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;

328 clocks =clks IMX6UL_CLK_UART1_IPG>,

329clks IMX6UL_CLK_UART1_SERIAL>;

330 clock-names ="ipg","per";

331 status ="disabled";

332};

上述代码是节点uart1,uart1节点描述了I.MX6ULL的UART1相关信息,重点是第326行的reg属性。其中uart1的父节点aips1: aips-bus@02000000设置了#address-cells = <1>、#size-cells = <1>,因此reg属性中address=0x02020000,length=0x4000。查阅《I.MX6ULL参考手册》可知,I.MX6ULL的UART1寄存器首地址为0x02020000,但是UART1的地址长度(范围)并没有0x4000这么多,这里我们重点是获取UART1寄存器首地址。

6、ranges属性

ranges属性值可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵,ranges是一个地址映射/转换表,ranges属性每个项目由子地址、父地址和地址空间长度这三部分组成:

child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells确定此物理地址所占用的字长。

parent-bus-address:父总线地址空间的物理地址,同样由父节点的#address-cells确定此物理地址所占用的字长。

length:子地址空间的长度,由父节点的#size-cells确定此地址长度所占用的字长。

如果ranges属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换,对于我们所使用的I.MX6ULL来说,子地址空间和父地址空间完全相同,因此会在imx6ull.dtsi中找到大量的值为空的ranges属性,如下所示:

示例代码43.3.3.4 imx6ull.dtsi文件代码段

137 soc {

138 #address-cells =<1>;

139 #size-cells =<1>;

140 compatible ="simple-bus";

141 interrupt-parent =gpc>;

142 ranges;

......

1177}

第142行定义了ranges属性,但是ranges属性值为空。

ranges属性不为空的示例代码如下所示:

示例代码43.3.3.5 ranges属性不为空

1 soc {

2 compatible ="simple-bus";

3 #address-cells =<1>;

4 #size-cells =<1>;

5 ranges =<0x00xe00000000x00100000>;

6

7 serial {

8 device_type ="serial";

9 compatible ="ns16550";

10 reg =<0x46000x100>;

11 clock-frequency =<0>;

12 interrupts =<0xA0x8>;

13 interrupt-parent =ipic>;

14 };

15};

第5行,节点soc定义的ranges属性,值为<0x0 0xe0000000 0x00100000>,此属性值指定了一个1024KB(0x00100000)的地址范围,子地址空间的物理起始地址为0x0,父地址空间的物理起始地址为0xe0000000。

第6行,serial是串口设备节点,reg属性定义了serial设备寄存器的起始地址为0x4600,寄存器长度为0x100。经过地址转换,serial设备可以从0xe0004600开始进行读写操作,0xe0004600=0x4600+0xe0000000。

7、name属性

name属性值为字符串,name属性用于记录节点名字,name属性已经被弃用,不推荐使用name属性,一些老的设备树文件可能会使用此属性。

8、device_type属性

device_type属性值为字符串,IEEE 1275会用到此属性,用于描述设备的FCode,但是设备树没有FCode,所以此属性也被抛弃了。此属性只能用于cpu节点或者memory节点。imx6ull.dtsi的cpu0节点用到了此属性,内容如下所示:

示例代码43.3.3.6 imx6ull.dtsi文件代码段

54 cpu0: cpu@0 {

55 compatible ="arm,cortex-a7";

56 device_type ="cpu";

57 reg =<0>;

......

89};

关于标准属性就讲解这么多,其他的比如中断、IIC、SPI等使用的标准属性等到具体的例程在讲解。

43.3.4 根节点compatible属性

每个节点都有compatible属性,根节点“/”也不例外,imx6ull-alientek-emmc.dts文件中根节点的compatible属性内容如下所示:

示例代码43.3.4.1 imx6ull-alientek-emmc.dts根节点compatible属性

14/{

15 model ="Freescale i.MX6 ULL 14x14 EVK Board";

16 compatible ="fsl,imx6ull-14x14-evk","fsl,imx6ull";

......

148}

可以看出,compatible有两个值:“fsl,imx6ull-14x14-evk”和“fsl,imx6ull”。前面我们说了,设备节点的compatible属性值是为了匹配Linux内核中的驱动程序,那么根节点中的compatible属性是为了做什么工作的?通过根节点的compatible属性可以知道我们所使用的设备,一般第一个值描述了所使用的硬件设备名字,比如这里使用的是“imx6ull-14x14-evk”这个设备,第二个值描述了设备所使用的SOC,比如这里使用的是“imx6ull”这颗SOC。Linux内核会通过根节点的compoatible属性查看是否支持此设备,如果支持的话设备就会启动Linux内核。接下来我们就来学习一下Linux内核在使用设备树前后是如何判断是否支持某款设备的。

1、使用设备树之前设备匹配方法

在没有使用设备树以前,uboot会向Linux内核传递一个叫做machine id的值,machine id也就是设备ID,告诉Linux内核自己是个什么设备,看看Linux内核是否支持。Linux内核是支持很多设备的,针对每一个设备(板子),Linux内核都用MACHINE_START和MACHINE_END来定义一个machine_desc结构体来描述这个设备,比如在文件arch/arm/mach-imx/mach-mx35_3ds.c中有如下定义:

示例代码43.3.4.2 MX35_3DS设备

613 MACHINE_START(MX35_3DS,"Freescale MX35PDK")

614/* Maintainer: Freescale Semiconductor, Inc */

615.atag_offset =0x100,

616.map_io = mx35_map_io,

617.init_early = imx35_init_early,

618.init_irq = mx35_init_irq,

619.init_time = mx35pdk_timer_init,

620.init_machine = mx35_3ds_init,

621.reserve = mx35_3ds_reserve,

622.restart = mxc_restart,

623 MACHINE_END

上述代码就是定义了“Freescale MX35PDK”这个设备,其中MACHINE_START和MACHINE_END定义在文件arch/arm/include/asm/mach/arch.h中,内容如下:

示例代码43.3.4.3 MACHINE_START和MACHINE_END宏定义

#define MACHINE_START(_type,_name)

static const struct machine_desc __mach_desc_##_type

__used

__attribute__((__section__(".arch.info.init"))) = {

.nr = MACH_TYPE_##_type,

.name = _name,

#define MACHINE_END

};

根据MACHINE_START和MACHINE_END的宏定义,将示例代码43.3.4.2展开后如下所示:

示例代码43.3.4.3 展开以后

1staticconststruct machine_desc __mach_desc_MX35_3DS

2 __used

3 __attribute__((__section__(".arch.info.init")))={

4 .nr = MACH_TYPE_MX35_3DS,

5 .name ="Freescale MX35PDK",

6 /* Maintainer: Freescale Semiconductor, Inc */

7 .atag_offset =0x100,

8 .map_io = mx35_map_io,

9 .init_early = imx35_init_early,

10 .init_irq = mx35_init_irq,

11 .init_time = mx35pdk_timer_init,

12 .init_machine = mx35_3ds_init,

13 .reserve = mx35_3ds_reserve,

14 .restart = mxc_restart,

15}

从示例代码43.3.4.3中可以看出,这里定义了一个machine_desc类型的结构体变量__mach_desc_MX35_3DS,这个变量存储在“.arch.info.init”段中。第4行的MACH_TYPE_MX35_3DS就是“Freescale MX35PDK”这个板子的machineid。MACH_TYPE_MX35_3DS定义在文件include/generated/mach-types.h中,此文件定义了大量的machineid,内容如下所示:

示例代码43.3.4.3 mach-types.h文件中的machine id

15 #define MACH_TYPE_EBSA110 0

16 #define MACH_TYPE_RISCPC 1

17 #define MACH_TYPE_EBSA285 4

18 #define MACH_TYPE_NETWINDER 5

19 #define MACH_TYPE_CATS 6

20 #define MACH_TYPE_SHARK 15

21 #define MACH_TYPE_BRUTUS 16

22 #define MACH_TYPE_PERSONAL_SERVER 17

......

287 #define MACH_TYPE_MX35_3DS 1645

......

1000 #define MACH_TYPE_PFLA03 4575

第287行就是MACH_TYPE_MX35_3DS的值,为1645。

前面说了,uboot会给Linux内核传递machineid这个参数,Linux内核会检查这个machineid,其实就是将machineid与示例代码43.3.4.3中的这些MACH_TYPE_XXX宏进行对比,看看有没有相等的,如果相等的话就表示Linux内核支持这个设备,如果不支持的话那么这个设备就没法启动Linux内核。

2、使用设备树以后的设备匹配方法

当Linux内核引入设备树以后就不再使用MACHINE_START了,而是换为了DT_MACHINE_START。DT_MACHINE_START也定义在文件arch/arm/include/asm/mach/arch.h里面,定义如下:

示例代码43.3.4.4 DT_MACHINE_START宏

#define DT_MACHINE_START(_name, _namestr)

static const struct machine_desc __mach_desc_##_name

__used

__attribute__((__section__(".arch.info.init"))) = {

.nr = ~0,

.name = _namestr,

可以看出,DT_MACHINE_START和MACHINE_START基本相同,只是.nr的设置不同,在DT_MACHINE_START里面直接将.nr设置为~0。说明引入设备树以后不会再根据machineid来检查Linux内核是否支持某个设备了。

打开文件arch/arm/mach-imx/mach-imx6ul.c,有如下所示内容:

示例代码43.3.4.5 imx6ull设备

208staticconstchar*imx6ul_dt_compat[] __initconst ={

209"fsl,imx6ul",

210"fsl,imx6ull",

211NULL,

212};

213

214 DT_MACHINE_START(IMX6UL,"Freescale i.MX6 Ultralite (Device Tree)")

215.map_io = imx6ul_map_io,

216.init_irq = imx6ul_init_irq,

217.init_machine = imx6ul_init_machine,

218.init_late = imx6ul_init_late,

219.dt_compat = imx6ul_dt_compat,

220 MACHINE_END

machine_desc结构体中有个.dt_compat成员变量,此成员变量保存着本设备兼容属性,示例代码43.3.4.5中设置.dt_compat = imx6ul_dt_compat,imx6ul_dt_compat表里面有"fsl,imx6ul"和"fsl,imx6ull"这两个兼容值。只要某个设备(板子)根节点“/”的compatible属性值与imx6ul_dt_compat表中的任何一个值相等,那么就表示Linux内核支持此设备。imx6ull-alientek-emmc.dts中根节点的compatible属性值如下:

compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

其中“fsl,imx6ull”与imx6ul_dt_compat中的“fsl,imx6ull”匹配,因此I.MX6U-ALPHA开发板可以正常启动Linux内核。如果将imx6ull-alientek-emmc.dts根节点的compatible属性改为其他的值,比如:

compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ullll"

重新编译DTS,并用新的DTS启动Linux内核,结果如图43.3.4.1所示的错误提示:

图43.3.4.1 系统启动信息

当我们修改了根节点compatible属性内容以后,因为Linux内核找不到对应的设备,因此Linux内核无法启动。在uboot输出Startingkernel…以后就再也没有其他信息输出了。

接下来我们简单看一下Linux内核是如何根据设备树根节点的compatible属性来匹配出对应的machine_desc,Linux内核调用start_kernel函数来启动内核,start_kernel函数会调用setup_arch函数来匹配machine_desc,setup_arch函数定义在文件arch/arm/kernel/setup.c中,函数内容如下(有缩减):

示例代码43.3.4.6 setup_arch函数内容

913void __init setup_arch(char**cmdline_p)

914{

915conststruct machine_desc *mdesc;

916

917 setup_processor();

918 mdesc = setup_machine_fdt(__atags_pointer);

919if(!mdesc)

920 mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);

921 machine_desc = mdesc;

922 machine_name = mdesc->name;

......

986}

第918行,调用setup_machine_fdt函数来获取匹配的machine_desc,参数就是atags的首地址,也就是uboot传递给Linux内核的dtb文件首地址,setup_machine_fdt函数的返回值就是找到的最匹配的machine_desc。

函数setup_machine_fdt定义在文件arch/arm/kernel/devtree.c中,内容如下(有缩减):

示例代码43.3.4.7 setup_machine_fdt函数内容

204conststruct machine_desc * __init setup_machine_fdt(unsignedint dt_phys)

205{

206conststruct machine_desc *mdesc,*mdesc_best =NULL;

......

214

215if(!dt_phys ||!early_init_dt_verify(phys_to_virt(dt_phys)))

216returnNULL;

217

218 mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);

219

......

247 __machine_arch_type = mdesc->nr;

248

249return mdesc;

250}

第218行,调用函数of_flat_dt_match_machine来获取匹配的machine_desc,参数mdesc_best是默认的machine_desc,参数arch_get_next_mach是个函数,此函数定义在定义在arch/arm/kernel/devtree.c文件中。找到匹配的machine_desc的过程就是用设备树根节点的compatible属性值和Linux内核中保存的所以machine_desc结构的. dt_compat中的值比较,看看那个相等,如果相等的话就表示找到匹配的machine_desc,arch_get_next_mach函数的工作就是获取Linux内核中下一个machine_desc结构体。

最后在来看一下of_flat_dt_match_machine函数,此函数定义在文件drivers/of/fdt.c中,内容如下(有缩减):

示例代码43.3.4.8 of_flat_dt_match_machine函数内容

705constvoid* __init of_flat_dt_match_machine(constvoid*default_match,

706constvoid*(*get_next_compat)(constchar*const**))

707{

708constvoid*data =NULL;

709constvoid*best_data = default_match;

710constchar*const*compat;

711unsignedlong dt_root;

712unsignedint best_score =~1, score =0;

713

714 dt_root = of_get_flat_dt_root();

715while((data = get_next_compat(&compat))){

716 score = of_flat_dt_match(dt_root, compat);

717if(score >0&& score < best_score){

718 best_data = data;

719 best_score = score;

720}

721}

......

739

740 pr_info("Machine model: %s", of_flat_dt_get_machine_name());

741

742return best_data;

743}

第714行,通过函数of_get_flat_dt_root获取设备树根节点。

第715~720行,此循环就是查找匹配的machine_desc过程,第716行的of_flat_dt_match函数会将根节点compatible属性的值和每个machine_desc结构体中. dt_compat的值进行比较,直至找到匹配的那个machine_desc。

总结一下,Linux内核通过根节点compatible属性找到对应的设备的函数调用过程,如图43.3.4.2所示:

图43.3.4.2 查找匹配设备的过程

韦东山 IMX6ULL和正点原子_「正点原子Linux连载」第四十三章Linux设备树(一)相关推荐

  1. 【正点原子Linux连载】第四十三章 Linux设备树 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  2. 正点原子linux串口驱动下载,「正点原子Linux连载」第六十三章Linux RS232/485/GPS驱动实验...

    1)实验平台:正点原子Linux开发板 2)摘自<正点原子I.MX6U嵌入式Linux驱动开发指南>关注官方微信号公众号,获取更多资料:正点原子 第六十三章Linux RS232/485/ ...

  3. 【正点原子Linux连载】第六十三章 Linux RS232/485/GPS驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  4. linux 信号量 自旋锁 测试 实验,「正点原子Linux连载」第四十八章Linux并发与竞争实验...

    1)实验平台:正点原子Linux开发板 2)摘自<正点原子I.MX6U嵌入式Linux驱动开发指南> 关注官方微信号公众号,获取更多资料:正点原子 第四十八章Linux并发与竞争实验 在上 ...

  5. linux键盘设置的文件在哪个文件夹,「正点原子Linux连载」第十五章按键输入试验...

    原标题:「正点原子Linux连载」第十五章按键输入试验 第十五章按键输入试验 前面几章试验都是讲解如何使用I.MX6U的GPIO输出控制功能,I.MX6U的IO不仅能作为输出,而且也可以作为输入.I. ...

  6. 【正点原子FPGA连载】第四十三章MT9V034摄像头RGB-LCD显示实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

    1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...

  7. 【正点原子Linux连载】第三十七章 Linux内核移植 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  8. 【正点原子Linux连载】第三十三章 U-Boot移植 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  9. 【正点原子Linux连载】第五十三章 异步通知实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

最新文章

  1. 把握数据,驱动未来 | 清华大学大数据研究中心2020年RONG奖学金答辩会成功举办...
  2. charshow技术预研
  3. 【ABAP】BASE64加密及解密
  4. 汉诺塔java程序_Java编写一个汉诺塔的过程
  5. WEB安全基础-SQL相关
  6. MySQL学习笔记_1_MySQL数据库管理系统概述
  7. LeetCode 696. Count Binary Substrings
  8. 打不过 Chrome 的 Firefox,我为什么要选择?
  9. BSCI—7:OSPF的路由汇总
  10. 【C++】二维vector初始化的方法
  11. 直播六脉神剑,练好这几招才能行走江湖
  12. 众筹系统源码 java_以太坊 众筹系统
  13. 计算机的硬盘维修,计算机硬盘的维修方法和技巧
  14. Spring AOP切入点表达式
  15. (十八)自动装配-@Autowired-构造器,参数,方法,属性
  16. java代码输出万年历
  17. 2003计算机应用基础题答案,计算机应用基础(Windows_XP+Office_2003)课后题答案
  18. 图形学进阶——移动端TB(D)R架构基础
  19. matlab绘图去白边
  20. java基础-多态-多态的理解及使用

热门文章

  1. SuperMap 三维性能提问单
  2. 电子招投标中的时间节点汇总
  3. 195本免费在线编程书籍
  4. 小米、华为和智汀智能家居从单品向全屋智能前进
  5. EYEOS WEB操作系统的安装方法
  6. drupal mysql hash密码_【Drupal】如何重置Drupal 7的用户密码
  7. java查看线程使用情况,查看java的线程cpu使用情况
  8. python读取文件夹下的文件名写入excel(高效办公)
  9. addonsmaker怎么制作_addons制作器 v1.14.3 苹果IOS版
  10. 大小写切换c语言程序设计教程课后答案,中国大学MOOC-翁恺-C语言程序设计习题集 05-0 到 06-3...