fpga怎么产生一组时序(第二十四章Linux设备树)

1)摘自【正点原子】领航者 ZYNQ 之linux驱动开发指南

2)实验平台:正点原子领航者ZYNQ开发板3)平台购买地址:https://item.taobao.com/item.htm?&id=6061601087614)全套实验源码 手册 视频下载:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html5)对正点原子FPGA感兴趣的同学可以加群讨论:8767449006)关注正点原子公众号,获取最新资料

fpga怎么产生一组时序(第二十四章Linux设备树)(1)

第二十四章Linux设备树

前面章节中我们多次提到“设备树”这个概念,因为时机未到,所以当时并没有详细的讲解什么是“设备树”,本章我们就来详细的谈一谈设备树。掌握设备树是Linux驱动开发人员必备的技能!因为在新版本的Linux内核中,设备驱动基本全部采用了设备树(也有支持老式驱动的,比较少)的方式,最新出的CPU其驱动开发也基本都是基于设备树的,我们所使用的Linux版本为4.14.0,肯定是支持设备树的,所以正点原子领航者开发板的所有Linux驱动都是基于设备树的。本章我们就来了解一下设备树的起源、重点学习一下设备树语法。24.1什么是设备树?在旧版本(大概是3.x以前的版本)的linux内核当中,ARM架构的板级硬件设备信息被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx目录下的文件当中,例如板子上的platform设备信息、设备I/O资源resource、板子上的i2c设备的描述信息信息i2c_board_info、板子上spi设备的描述信息spi_board_info以及各种硬件设备的platform_data等,所以就导致在Linux内核源码中大量的arch/arm/mach-xxx和arch/arm/plat-xxx文件夹,这些文件夹里面的文件就描述了对应平台下的板级硬件设备信息。比如在arch/arm/mach-s3c24xx/mach-smdk2440.c文件中有如下内容(有缩减):示例代码24.1.1 mach-smdk2440.c文件代码片段

  1. 90 static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {
  2. 91
  3. 92 .lcdcon5 = S3C2410_LCDCON5_FRM565 |
  4. 93 S3C2410_LCDCON5_INVVLINE |
  5. 94 S3C2410_LCDCON5_INVVFRAME |
  6. 95 S3C2410_LCDCON5_PWREN |
  7. 96 S3C2410_LCDCON5_HWSWP,
  8. ......
  9. 113 };
  10. 114
  11. 115 static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {
  12. 116 .displays = &smdk2440_lcd_cfg,
  13. 117 .num_displays = 1,
  14. 118 .default_display = 0,
  15. ......
  16. 133 };
  17. 134
  18. 135 static struct platform_device *smdk2440_devices[] __initdata = {
  19. 136 &s3c_device_ohci,
  20. 137 &s3c_device_lcd,
  21. 138 &s3c_device_wdt,
  22. 139 &s3c_device_i2c0,
  23. 140 &s3c_device_iis,
  24. 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内核来说只不过是垃圾代码而已,所以当Linux之父linus看到ARM社区向Linux内核添加了大量“无用”、冗余的板级信息文件,不禁的发出了一句“This whole ARM thing is a f*cking pain in the ass”。从此以后ARM社区就开始引入设备树DTS了。DTS即Device Tree Source设备树源码, Device Tree是一种描述硬件的数据结构,它起源于OpenFirmware(OF),用于实现驱动代码与设备信息相分离;在设备树出现以前,所有关于板子上硬件设备的具体都要硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx目录下的文件当中,或者直接硬编码在驱动代码当中,例如我们前面编写的LED驱动就是直接将led的信息(用的哪个管脚、GPIO寄存器的基地址等)直接编码在了驱动源码当中,一旦外围设备变化(例如PS_LED0换成另一个MIO引脚了),驱动代码就要重写。引入了设备树之后,驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,这样,如果只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。使用设备树之后,许多硬件设备信息可以直接通过它传递给Linux,而不需要在内核中堆积大量的冗余代码。设备树,将这个词分开就是“设备”和“树”,描述设备树的文件叫做DTS(Device Tree Source),这个DTS文件采用树形结构描述板级设备,也就是开发板上的硬件设备信息,比如CPU数量、内存基地址、IIC接口上接了哪些设备、SPI接口上接了哪些设备等等,如图 35.1.1所示:

fpga怎么产生一组时序(第二十四章Linux设备树)(2)

图 35.1.1 设备树结构示意图

在图 35.1.1中,树的主干就是系统总线,IIC控制器、GPIO控制器、SPI控制器等都是接到系统主线上的分支。IIC控制器有分为IIC1和IIC2两种,其中IIC1上接了FT5206和AT24C02这两个IIC设备,IIC2上只接了MPU6050这个设备。DTS文件的主要功能就是按照图 35.1.1所示的结构来描述板子上的设备信息,DTS文件描述设备信息是有相应的语法规则要求的,稍后我们会详细的讲解DTS语法规则。设备树文件的扩展名为.dts,一个.dts(device tree source)就文件对应一个开发板,一般放置在内核的"arch/arm/boot/dts/"目录下,比如exynos4412开发板的板级设备树文件就是"arch/arm/boot/dts/exynos4412-origen.dts",再比如I.MX6ULL-EVK开发板的板级设备树文件就是arch/arm/boot/dts/imx6ull-14x14-evk.dts。那本篇驱动开发我们所使用的板级设备树文件就是arch/arm/boot/dts/system-top.dts,这个文件是在第三十一章时候使用hsi命令自动生成的,前面已经跟大家讲过了,除了system-top.dts文件之外,还生成了另外三个文件pl.dtsi、pcw.dtsi以及zynq-7000.dtsi(system-top.dts包含它们三个,后面会说到),并且一并把它们放入了linux内核源码arch/arm/boot/dts目录下了。前面也跟大家讲过,除了内核支持设备树之外,新版的u-boot也是支持设备树的,如果有机会也可以跟大家讲一讲U-Boot的设备树。24.2设备树的基本知识24.2.1dts设备树的源文件的后缀名就是.dts,每一款硬件平台可以单独写一份xxxx.dts,所以在Linux内核源码中存在大量.dts文件,对于arm架构可以在arch/arm/boot/dts找到相应的dts。24.2.2dtsi值得一提的是,对于一些相同的dts配置可以抽象到dtsi文件中,这个dtsi文件其实就类似于C语言当中的.h头文件,可以通过C语言中使用include来包含一个.dtsi文件,例如arch/arm/boot/dts/system-top.dts文件有如下内容:示例代码24.2.2.1 system-top.dts内容片段

  1. 1 /*
  2. 2 * CAUTION: This file is automatically generated by Xilinx.
  3. 3 * Version: HSI
  4. 4 * Today is: Mon Mar 16 02:51:23 2020
  5. 5 */
  6. 6
  7. 7
  8. 8 /dts-v1/;
  9. 9 #include "zynq-7000.dtsi"
  10. 10 #include "pl.dtsi"
  11. 11 #include "pcw.dtsi"
  12. 12 / {
  13. 13 model = "Alientek ZYNQ Development Board";
  14. 14
  15. 15 chosen {
  16. 16 bootargs = "console=ttyPS0,115200 earlyprintk root=/dev/mmcblk0p2 rw rootwait";
  17. 17 stdout-path = "serial0:115200n8";
  18. 18 };
  19. 19 aliases {
  20. 20 ethernet0 = &gem0;
  21. 21 i2c0 = &i2c_2;
  22. 22 i2c1 = &i2c0;
  23. 23 i2c2 = &i2c1;
  24. 24 serial0 = &uart0;
  25. 25 serial1 = &uart1;
  26. 26 spi0 = &qspi;
  27. 27 };
  28. 28 memory {
  29. 29 device_type = "memory";
  30. 30 reg = <0x0 0x20000000>;
  31. 31 };
  32. 32 };

复制代码

第9~11行中,通过#include包含了同目录下的三个.dtsi文件,分别为:zynq-7000.dtsi、pl.dtsi、pcw.dtsi。这里简答地给大家说一下这三个文件的内容有啥不同,首先zynq-7000.dtsi文件中的内容是zynq-7000系列处理器相同的硬件外设配置信息(PS端的),pl.dtsi的内容是我们在vivado当中添加的pl端外设对应的配置信息,而pcw.dtsi则表示我们在vivado当中已经使能的PS外设。那么除此之外,使用#include除了可以包含.dtsi文件之外,还可以包含.dts文件以及C语言当中的.h文件,这些都是可以的,可以这么理解.dtsi和.dts文件语法各方面都是一样的,但是不能直接编译一个.dtsi文件。24.2.3dtcdtc其实就是device-tree-compiler,那就是设备文件.dts的编译器嘛,将.c文件编译为.o文件需要用到gcc编译器,那么将.dts文件编译为相应的二进制文件则需要dtc编译器,dtc工具在Linux内核的scripts/dtc目录下,当然必须要编译了内核源码之后才会生成,如下所示:

fpga怎么产生一组时序(第二十四章Linux设备树)(3)

图 35.2.1 dtc编译器

我们来看看scripts/dtc/Makefile文件,如下所示:示例代码24.2.3.1 scripts/dtc/Makefile文件代码段

  1. 1 hostprogs-y := dtc
  2. 2 always := $(hostprogs-y)
  3. 3
  4. 4 dtc-objs:= dtc.o flattree.o fstree.o data.o livetree.o treesource.o \
  5. 5 srcpos.o checks.o util.o
  6. 6 dtc-objs = dtc-lexer.lex.o dtc-parser.tab.o
  7. ......

复制代码

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

  1. make all

复制代码

或者:

  1. make dtbs

复制代码

“make all”命令是编译Linux源码中的所有东西,包括zImage,.ko驱动模块以及设备树,如果只是编译设备树的话建议使用“make dtbs”命令。在内核源码arch/arm/boot/dts目录下有很多的dts文件,那我们编译的时候如何确定编译的是哪个或者说哪些dts文件的呢?大家可以打开arch/arm/boot/dts/Makefile文件,找到CONFIG_ATCH_ZYNQ宏所在的位置,内容如下所示:示例代码43.2.3.2 arch/arm/boot/dts/Makefile文件部分内容

  1. ......
  2. 1003 dtb-$(CONFIG_ARCH_ZYNQ) = \
  3. 1004 zynq-cc108.dtb \
  4. 1005 zynq-microzed.dtb \
  5. 1006 zynq-parallella.dtb \
  6. 1007 zynq-zc702.dtb \
  7. 1008 zynq-zc706.dtb \
  8. 1009 zynq-zc770-xm010.dtb \
  9. 1010 zynq-zc770-xm011.dtb \
  10. 1011 zynq-zc770-xm012.dtb \
  11. 1012 zynq-zc770-xm013.dtb \
  12. 1013 zynq-zed.dtb \
  13. 1014 zynq-zybo.dtb \
  14. 1015 system-top.dtb
  15. ......

复制代码

由上面可以知道,当CONFIG_ATCH_ZYNQ=y时,对应下面列举的所有.dts文件会被编译进去,也包括我们之前添加上去的system-top.dtb文件;但是CONFIG_ATCH_ZYNQ=y又怎么确定呢?在31.5小节编译内核的时候我们执行了” make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- xilinx_zynq_defconfig”这样一条命令,后面的xilinx_zynq_defconfig(arch/arm/configs/xilinx_zynq_defconfig)文件就是我们zynq平台对应的defconfig配置文件,那么打开它,找到CONFIG_ATCH_ZYNQ,如下:

fpga怎么产生一组时序(第二十四章Linux设备树)(4)

图 35.2.2 CONFIG_ARCH_ZYNQ

在这个文件当中定义了CONFIG_ATCH_ZYNQ=y。所以对于ZYNQ平台来说,当我们在内核源码目录下执行”make all”或者是”make dtbs”命令的时候,arch/arm/boot/dts/Makefile文件中对应的那些dts文件就会被编译成dtb(二进制文件)文件。但是需要注意并不是说不在这个Makefile文件中列举出来的dts文件就不能被编译,我们在使用make命令的时候也可以指定需要编译的dts文件,例如下面:

  1. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- system-top.dtb

复制代码

fpga怎么产生一组时序(第二十四章Linux设备树)(5)

图 35.2.3 指定dts文件

system-top.dtb表示需要编译出这个dtb文件,那么系统就会去arch/arm/boot/dts目录下找到相对应的.dts文件,也就是system-top.dts,而system-top.dts文件就可以不用记录在arch/arm/boot/dts/Makefile文件中,我们编译的时候已经指定了。关于编译dts文件的问题就说到这里了,这里讲的有点太多了,如果大家还不明白可以去网上找找资料。24.2.4dtb.dtb文件就是将.dts文件编译成二进制数据之后得到的文件,这就跟.c文件编译为.o文件是一样的道理,关于.dtb文件怎么使用这里就不多说了,前面讲解Uboot移植、Linux内核移植的时候已经无数次的提到如何使用.dtb文件了(uboot中使用bootz或bootm命令向Linux内核传递二进制设备树文件(.dtb))。24.3dts语法虽然我们基本上不会从头到尾重写一个.dts文件,大多时候是直接在SOC厂商提供的.dts文件上进行修改。但是DTS文件语法我们还是需要详细的学习一遍,因为我们肯定需要修改.dts文件。大家不要看到要学习新的语法就觉得会很复杂,DTS语法非常的人性化,是一种ASCII文本文件,不管是阅读还是修改都很方便。本节我们就以system-top.dts这个文件为例来讲解一下DTS语法。关于设备树详细的语法规则请参考《Devicetree SpecificationV0.2.pdf》和《Power_ePAPR_APPROVED_v1.12.pdf》这两份文档,此两份文档已经放到了开发板光盘中,路径为:领航者ZYNQ开发板资料盘(A盘)\8_ZYNQ&FPGA参考资料\ARM\ Devicetree SpecificationV0.2.pdf、领航者ZYNQ开发板资料盘(A盘)\8_ZYNQ&FPGA参考资料\ARM\ Power_ePAPR_APPROVED_v1.12.pdf。24.3.1设备树的结构设备树用树状结构描述设备信息,组成设备树的基本单元是node(设备节点),这些node被组织成树状结构,有如下一些特征:一个device tree文件中只有一个root node(根节点);除了root node,每个node都只有一个parent node(父节点);一般来说,开发板上的每一个设备都能够对应到设备树中的一个node;每个node中包含了若干的property-value(键-值对,当然也可以没有value)来描述该node的一些特性;每个node都有自己的node name(节点名字);node之间可以是平行关系,也可以嵌套成父子关系,这样就可以很方便的描述设备间的关系;下面给出一个设备树的简单的结构示意图:示例代码24.3.1.1 设备树结构示意

  1. 1 /{ // 根节点
  2. 2 node1{ // node1节点
  3. 3 property1=value1; // node1节点的属性property1
  4. 4 property2=value2; // node1节点的属性property2
  5. 5 ...
  6. 6 };
  7. 7
  8. 8 node2{ // node2节点
  9. 9 property3=value3; // node2节点的属性property3
  10. 10 ...
  11. 11 node3{ // node2的子节点node3
  12. 12 property4=value4;// node3节点的属性property4
  13. 13 ...
  14. 14 };
  15. 15 };
  16. 16 };

复制代码

第1行当中的’/’就表示设备树的root node(根节点),所以可知node1节点和node2节点的父节点都是root node,而node3节点的父节点则是node2,node2与node3之间形成了父子节点关系。Root node下面的子节点node1和node2可以表示为SoC上的两个控制器,而node3则可以表示挂在node2控制器上的某个设备,例如node2表示ZYNQ PS的一个I2C控制器,而node3则表示挂在该I2C总线下的某个设备,例如eeprom、RTC等。24.3.2节点与属性在设备树文件中如何定义一个节点,节点的命名有什么要求呢?在设备树中节点的命名格式如下:

  1. [label:]node-name[@unit-address] {
  2. [properties definitions]
  3. [child nodes]
  4. };

复制代码

“[]”中的内容表示可选的,可有也可以没有;节点名字前加上”label”则方便在dts文件中被其他的节点引用,我们后面会说这个;其中“node-name”是节点名字,为ASCII字符串,节点名字应该能够清晰的描述出节点的功能,比如“uart1”就表示这个节点是UART1外设。“unit-address”一般表示设备的地址或寄存基地址,如果某个节点没有地址或者寄存器的话“unit-address”可以不要,比如“cpu@0”、“interrupt-controller@00a01000”。每个节点都有若干属性,属性又有相对应的值(值不是必须要有的),而一个节点当中又可以嵌套其它的节点,形成父子节点。例如下面:示例代码24.3.2.1 设备树节点示例

  1. 20 cpus {
  2. 21 #address-cells = <1>;
  3. 22 #size-cells = <0>;
  4. 23
  5. 24 cpu0: cpu@0 {
  6. 25 compatible = "arm,cortex-a9";
  7. 26 device_type = "cpu";
  8. 27 reg = <0>;
  9. 28 clocks = <&clkc 3>;
  10. 29 clock-latency = <1000>;
  11. 30 cpu0-supply = <®ulator_vccpint>;
  12. 31 operating-points = <
  13. 32 /* kHz uV */
  14. 33 666667 1000000
  15. 34 333334 1000000
  16. 35 >;
  17. 36 };
  18. 37
  19. 38 cpu1: cpu@1 {
  20. 39 compatible = "arm,cortex-a9";
  21. 40 device_type = "cpu";
  22. 41 reg = <1>;
  23. 42 clocks = <&clkc 3>;
  24. 43 };
  25. 44 };

复制代码

每一个节点(包括root node)都会使用一组括号”{ }”将自己的属性以及子节点包含在里边,注意括号外需要加上一个分号” ; ”,包括每一个属性都使用一个分号来结束。有点像C语言中的表达式后面的分号。第20行当中的cpus节点,它的名字只有” [label:]node-name[@unit-address]”当中的”node-name”部分,并有其它两部分;第24行节点的定义包含了所有的组成部分,包括label以及unit-address;关于label的作用的我们后面专门讲,这里先不说。cpus节点有两个属性” #address-cells”和” #size-cells”,它们的值分别为” <1>”和” <0>”。例如cpu@0节点中有compatible、device_type、reg、clocks属性等,它们都有对应的值,大家看到这些值可能有点不明白,为啥有的是字符串,有的是尖括号”<>”括起来的东西,下面单独给大家讲解一波。每个节点都有不同属性,不同的属性又有不同的值,那么设备树当中值有哪些形式呢?字符串

  1. compatible = "arm,cortex-a9";

复制代码

字符串使用双引号括起来,例如上面的这个compatible属性的值是” arm,cortex-a9”字符串。32位无符号整形数据

  1. clock-latency = <1000>;
  2. reg = <0x00000000 0x00500000>;

复制代码

32位无符号整形数据使用尖括号括起来,例如属性cock-latency的值是一个32位无符号整形数据1000,而reg属性有两个数据,使用空格隔开,那么这个就可以认为是一个数组,很容易理解!二进制数据

  1. local-mac-address = [00 0a 35 00 1e 53];

复制代码

二进制数据使用方括号括起来,例如上面这个就是一个二进制数据组成的数组。字符串数组

  1. compatible = "n25q512a","micron,m25p80";

复制代码

属性值也可以使用字符串列表,例如上面的这个属性,它的值是一个字符串列表,字符串之间使用逗号分割;混合值

  1. mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;

复制代码

除此之外不同的数据类型还可以混合在一起,以逗号分隔。节点引用除了上面一些数据类型之外,还有一种非常常见的形式,如下所示:

  1. clocks = <&clkc 3>;

复制代码

这其实就是我们上面说到的引用节点的一种形式,”&clkc”就表示引用”clkc”这个节点,而clkc就是前面提到的”label”,引用节点也是使用尖括号来表示,关于节点之间的引用,我们后面还会再讲,这里先告一段落。24.3.3使用注释和宏定义在设备树文件中也可以使用注释,注释的方法和C语言当中是一毛一样的,可以使用” // ”进行单行注释,也可以使用” /* */ ”进行多行注释,如下所示:

  1. 1 // SPDX-License-Identifier: GPL-2.0
  2. 2 /*
  3. 3 * Copyright (C) 2011 - 2015 Xilinx
  4. 4 *
  5. 5 * This software is licensed under the terms of the GNU General Public
  6. 6 * License version 2, as published by the Free Software Foundation, and
  7. 7 * may be copied, distributed, and modified under those terms.
  8. 8 *
  9. 9 * This program is distributed in the hope that it will be useful,
  10. 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. 12 * GNU General Public License for more details.
  13. 13 */
  14. 14
  15. 15 / {
  16. 16 #address-cells = <1>;
  17. 17 #size-cells = <1>;
  18. 18 compatible = "xlnx,zynq-7000";
  19. 19
  20. 20 cpus {
  21. 21 #address-cells = <1>;
  22. 22 #size-cells = <0>;
  23. 23
  24. 24 cpu0: cpu@0 {
  25. 25 compatible = "arm,cortex-a9";
  26. 26 device_type = "cpu";
  27. 27 reg = <0>;
  28. 28 clocks = <&clkc 3>;
  29. 29 clock-latency = <1000>;
  30. 30 cpu0-supply = <®ulator_vccpint>;
  31. 31 operating-points = <
  32. 32 /* kHz uV */
  33. 33 666667 1000000
  34. 34 333334 1000000
  35. 35 >;
  36. 36 };
  37. 37
  38. 38 cpu1: cpu@1 {
  39. 39 compatible = "arm,cortex-a9";
  40. 40 device_type = "cpu";
  41. 41 reg = <1>;
  42. 42 clocks = <&clkc 3>;
  43. 43 };
  44. 44 };

复制代码

前面跟大家讲过,设备树中可以使用”#include”包含dtsi、dts以及C语言的头文件,那我们为什么要包含一个.h的头文件呢?因为在设备树中可以使用宏定义,所以你在arch/arm/boot/dts目录下你会看到很多的设备树文件中都包含了.h头文件,例如下面这个:

fpga怎么产生一组时序(第二十四章Linux设备树)(6)

图 35.3.1 头文件包含

fpga怎么产生一组时序(第二十四章Linux设备树)(7)

图 35.3.2 使用宏定义

关于头文件包含以及宏定义的使用这里就不多说了,本身也非常简单。24.3.4标准属性节点的内容是由一堆的属性组成,不同的设备需要的属性不同,用户可以自定义属性。除了用户自定义属性,有很多属性是标准属性,Linux下的很多外设驱动都会使用这些标准属性,本节我们就来学习一下几个常用的标准属性。1、compatible属性compatible属性也叫做“兼容性”属性,这是非常重要的一个属性!compatible属性的值可以是一个字符串,也可以是一个字符串列表;一般该字符串使用”<制造商>,<型号>”这样的形式进行命名,当然这不是必须要这样,这是要求大家按照这样的形式进行命名,目的是为了指定一个确切的设备,并且包括制造商的名字,以避免命名空间冲突,如下所示:

  1. compatible = "xlnx,xuartps", "cdns,uart-r1p8";

复制代码

例子当中的xlnx和cdns就表示制造商,而后面的xuartps和uart-r1p8就表示具体设备的型号。compatible属性用于将设备和驱动绑定起来,例如该设备首先使用第一个兼容值(xlnx,xuartps)在Linux内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值(cdns,uart-r1p8)查找,直到找到或者查找完整个Linux内核也没有找到对应的驱动。一般驱动程序文件都会有一个OF匹配表,此OF匹配表保存着一些compatible值,如果设备树中的节点的compatible属性值和OF匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。比如在驱动文件drivers/tty/serial/xilinx_uartps.c中有如下内容:示例代码24.3.4.1 drivers/tty/serial/xilinx_uartps.c内容片段

  1. 1342
  2. 1343 /* Match table for of_platform binding */
  3. 1344 static const struct of_device_id cdns_uart_of_match[] = {
  4. 1345 { .compatible = "xlnx,xuartps", },
  5. 1346 { .compatible = "cdns,uart-r1p8", },
  6. 1347 { .compatible = "cdns,uart-r1p12", .data = &zynqmp_uart_def },
  7. 1348 { .compatible = "xlnx,zynqmp-uart", .data = &zynqmp_uart_def },
  8. 1349 {}
  9. 1350 };
  10. 1351 MODULE_DEVICE_TABLE(of, cdns_uart_of_match);
  11. ......
  12. 1703
  13. 1704 static struct platform_driver cdns_uart_platform_driver = {
  14. 1705 .probe = cdns_uart_probe,
  15. 1706 .remove = cdns_uart_remove,
  16. 1707 .driver = {
  17. 1708 .name = CDNS_UART_NAME,
  18. 1709 .of_match_table = cdns_uart_of_match,
  19. 1710 .pm = &cdns_uart_dev_pm_ops,
  20. 1711 },
  21. 1712 };

复制代码

这个驱动文件是ZYNQ PS端的UART设备对应的驱动文件。第1344~1350行定义的数组cdns_uart_of_match就是xilinx_uartps.c这个驱动文件的匹配表,此匹配表有4个匹配值“xlnx,xuartps”、“cdns,uart-r1p8”、“cdns,uart-r1p12”以及“xlnx,zynqmp-uart”。如果在设备树中有哪个节点的compatible属性值与这4个字符串中的某个相同,那么这个节点就会与此驱动文件匹配成功。第1704行,UART采用了platform_driver驱动模式,关于platform_driver驱动后面会讲解。此行设置.of_match_table为cdns_uart_of_match,也就是设置这个platform_driver所使用的OF匹配表。2、model属性model属性值也是一个字符串描述信息,它指定制造商的设备型号,model属性一般定义在根节点下,一般就是对板子的描述信息,没啥实质性的作用,内核在解析设备树的时候会把这个属性对应的字符串信息打印出来。示例代码24.3.4.2 arch/arm/boot/dts/system-top.dts内容片段

  1. 8 /dts-v1/;
  2. 9 #include "zynq-7000.dtsi"
  3. 10 #include "pl.dtsi"
  4. 11 #include "pcw.dtsi"
  5. 12 / {
  6. 13 model = "Alientek ZYNQ Development Board";
  7. 14
  8. 15 chosen {
  9. 16 bootargs = "console=ttyPS0,115200 earlyprintk root=/dev/mmcblk0p2 rw rootwait";
  10. 17 stdout-path = "serial0:115200n8";
  11. 18 };
  12. 19 aliases {
  13. 20 ethernet0 = &gem0;
  14. 21 i2c0 = &i2c_2;
  15. 22 i2c1 = &i2c0;
  16. 23 i2c2 = &i2c1;
  17. 24 serial0 = &uart0;
  18. 25 serial1 = &uart1;
  19. 26 spi0 = &qspi;
  20. 27 };
  21. 28 memory {
  22. 29 device_type = "memory";
  23. 30 reg = <0x0 0x20000000>;
  24. 31 };
  25. 32 };

复制代码

我之前在system-top.dts设备树文件加了一个model属性,它的值等于“Alientek ZYNQ Development Board”,内核启动过程中就会打印出来,如下所示:

fpga怎么产生一组时序(第二十四章Linux设备树)(8)

图 35.3.3 打印model字符串

3、status属性status属性看名字就知道是和设备状态有关的,device tree中的status标识了设备的状态,使用status可以去禁止设备或者启用设备,看下设备树规范中的status可选值:值 描述okay 表明设备是可操作的。启动设备disabled 表明设备当前是不可操作的,但是在未来可以变为可操作的,比如热插拔设备插入以后。至于disabled的具体含义还要看设备的绑定文档。fail 表明设备不可操作,设备检测到了一系列的错误,而且设备也不大可能变得可操作。fail-sss 含义和“fail”相同,后面的sss部分是检测到的错误内容。表 35.3.4.1 status属性值注意如果节点中没有添加status属性,那么它默认就是“status = okay”。4、#address-cells和#size-cells属性这两个属性的值都是无符号32位整形,#address-cells和#size-cells这两个属性可以用在任何拥有子节点的设备节点中,用于描述子节点的地址信息。#address-cells,用来描述子节点"reg"属性的地址表中用来描述首地址的cell的数量;#size-cells,用来描述子节点"reg"属性的地址表中用来描述地址长度的cell的数量。#address-cells和#size-cells表明了子节点应该如何编写reg属性值,一般reg属性都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度,有了这两个属性,子节点中的"reg"属性就可以描述一块连续的地址区域了;reg属性的格式一为:reg = <address1 length1 address2 length2 address3 length3……>每个“address length”组合表示一个地址范围,其中address是起始地址,length是地址长度,#address-cells表明address字段占用的字长,#size-cells表明length这个字段所占用的字长,比如:示例代码24.3.4.3 #address-cells和#size-cells属性

  1. 38 &qspi {
  2. 39 #address-cells = <1>;
  3. 40 #size-cells = <0>;
  4. 41 flash0: flash@0 {
  5. 42 compatible = "n25q512a","micron,m25p80";
  6. 43 reg = <0x0>;
  7. 44 #address-cells = <1>;
  8. 45 #size-cells = <1>;
  9. 46 spi-max-frequency = <50000000>;
  10. 47 partition@0x00000000 {
  11. 48 label = "boot";
  12. 49 reg = <0x00000000 0x00500000>;
  13. 50 ;
  14. 51 partition@0x00500000 {
  15. 52 label = "bootenv";
  16. 53 reg = <0x00500000 0x00020000>;
  17. 54 };
  18. 55 partition@0x00520000 {
  19. 56 label = "kernel";
  20. 57 reg = <0x00520000 0x00a80000>;
  21. 58 };
  22. 59 partition@0x00fa0000 {
  23. 60 label = "spare";
  24. 61 reg = <0x00fa0000 0x00000000>;
  25. 62 };
  26. 63 };
  27. 64 };

复制代码

第39~40行,节点qspi的#address-cells = <1>,#size-cells = <0>,说明qspi的子节点reg属性中起始地址使用一个32bit数据来表示,地址长度没有;第43行,qspi的子节点flash0:flash@0的reg属性值为<0>,因为父节点设置了#address-cells = <1>,#size-cells = <0>,因此addres=0,没有length的值,相当于设置了起始地址,而没有设置地址长度。第44~45行,设置flash0:flash@0节点#address-cells = <1>,#size-cells = <1>,说明flash0:flash@0的子节点起始地址长度所占用的字长为1,地址长度所占用的字长也为1。第49行,flash0:flash@0的子节点partition@0x00000000的reg属性值为reg = <0x00000000 0x00500000>,因为父节点设置了#address-cells = <1>,#size-cells = <1>,所以address使用一个32bit数据来表示,也就address=0x00000000,而length也使用一个32bit数据来表示,也就是length=0x00500000,相当于设置了起始地址为0x00000000,地址长度为0x00500000。5、reg属性reg属性前面已经提到过了,reg属性的值一般是(address,length)对。reg属性一般用于描述设备地址空间资源信息,一般都是描述某个外设的寄存器地址范围信息、flash设备的分区信息等,比如在arch/arm/boot/dts/zynq-7000.dts文件中有如下内容:示例代码24.3.4.4 uart0节点信息

  1. 174 uart0: serial@e0000000 {
  2. 175 compatible = "xlnx,xuartps", "cdns,uart-r1p8";
  3. 176 status = "disabled";
  4. 177 clocks = <&clkc 23>, <&clkc 40>;
  5. 178 clock-names = "uart_clk", "pclk";
  6. 179 reg = <0xE0000000 0x1000>;
  7. 180 interrupts = <0 27 4>;
  8. 181 };

复制代码

上述代码是节点uart0,uart0节点描述了ZYNQ PS端的UART0相关信息,重点是第179行的reg属性。其中uart0的父节点amba设置了#address-cells = <1>、#size-cells = <1>,因此reg属性中address= 0xE0000000,length= 0x1000。查阅ZYNQ的数据手册(领航者ZYNQ开发板资料盘(A盘)\8_ZYNQ&FPGA参考资料\Xilinx\User Guide\ug585-Zynq-7000-TRM.pdf)可知,ZYNQ的UART0寄存器首地址确实为0xE0000000,但是UART0的地址长度(范围)并没有0x1000这么多,这里我们重点是获取UART0寄存器首地址,只要地址空间没有跨越到其它外设的地址空间也没什么影响。6、ranges属性ranges是地址转换表,其中的每个项目是一个子地址、父地址以及在子地址空间的大小的映射。ranges属性值可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵。映射表中的子地址、父地址占用的字长分别由ranges属性所在节点的#address-cells属性和ranges属性所在节点的父节点的#address-cells属性来确定。而子地址空间长度占用的字长由ranges属性所在节点的#address-cells属性决定。child-bus-address:子总线地址空间的物理地址,由ranges属性所在节点的#address-cells属性确定此物理地址占用的字长。parent-bus-address:父总线地址空间的物理地址,由ranges属性所在节点的父节点的#address-cells属性确定此物理地址所占用的字长。length:子地址空间的长度,由ranges属性所在节点的#address-cells属性确定此地址长度所占用的字长。如果ranges属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换,对于我们所使用的ZYNQ来说,子地址空间和父地址空间完全相同,因此会在zynq-7000.dtsi文件中找到大量的值为空的ranges属性,如下所示:示例代码24.3.4.5 zynq-7000.dtsi内容片段

  1. 46 fpga_full: fpga-full {
  2. 47 compatible = "fpga-region";
  3. 48 fpga-mgr = <&devcfg>;
  4. 49 #address-cells = <1>;
  5. 50 #size-cells = <1>;
  6. 51 ranges;
  7. 52 };

复制代码

第51行定义了ranges属性,但是ranges属性值为空。ranges属性不为空的示例代码如下所示:示例代码24.3.4.6 ranges属性不为空

  1. 1 soc {
  2. 2 compatible = "simple-bus";
  3. 3 #address-cells = <1>;
  4. 4 #size-cells = <1>;
  5. 5 ranges = <0x0 0xe0000000 0x00100000>;
  6. 6
  7. 7 serial {
  8. 8 device_type = "serial";
  9. 9 compatible = "ns16550";
  10. 10 reg = <0x4600 0x100>;
  11. 11 clock-frequency = <0>;
  12. 12 interrupts = <0xA 0x8>;
  13. 13 interrupt-parent = <&ipic>;
  14. 14 };
  15. 15 };

复制代码

第5行,节点soc定义的ranges属性,值为<0x0 0xe0000000 0x00100000>,此属性值指定了一个1024KB(0x00100000)的地址范围,子地址空间的物理起始地址为0x0,父地址空间的物理起始地址为0xe0000000。第10行,serial是串口设备节点,reg属性定义了serial设备寄存器的起始地址为0x4600,寄存器长度为0x100。经过地址转换,serial设备可以从0xe0004600开始进行读写操作,0xe0004600=0x4600 0xe0000000。7、device_type属性device_type属性值为字符串,表示节点的类型;此属性在设备树当中用的比较少,一般用于cpu节点或者memory节点。zynq-7000.dtsi文件中的cpu0和cpu1节点用到了此属性,内容如下所示:示例代码24.3.4.7 zynq-7000.dtsi内容片段

  1. 24 cpu0: cpu@0 {
  2. 25 compatible = "arm,cortex-a9";
  3. 26 device_type = "cpu";
  4. 27 reg = <0>;
  5. 28 clocks = <&clkc 3>;
  6. 29 clock-latency = <1000>;
  7. 30 cpu0-supply = <®ulator_vccpint>;
  8. 31 operating-points = <
  9. 32 /* kHz uV */
  10. 33 666667 1000000
  11. 34 333334 1000000
  12. 35 >;
  13. 36 };
  14. 37
  15. 38 cpu1: cpu@1 {
  16. 39 compatible = "arm,cortex-a9";
  17. 40 device_type = "cpu";
  18. 41 reg = <1>;
  19. 42 clocks = <&clkc 3>;
  20. 43 };

复制代码

关于标准属性就讲解这么多,后面还会跟大家介绍一些常常会使用到的节点,例如设备树中的中断控制器、GPIO、I2C总线等。24.3.5根节点compatible属性每个节点都有compatible属性(除了一些特殊用途的节点),根节点“/”也不例外,在zynq-7000.dtsi文件中根节点的compatible属性内容如下所示:示例代码24.3.5.1 zynq-7000.dtsi根节点compatible属性

  1. 15 / {
  2. 16 #address-cells = <1>;
  3. 17 #size-cells = <1>;
  4. 18 compatible = "xlnx,zynq-7000";
  5. ......
  6. 431 };

复制代码

可以看出,compatible有一个值:“xlnx,zynq-7000”。前面我们说了,设备节点的compatible属性值是为了匹配Linux内核中的驱动程序,那么根节点中的compatible属性是为了做什么工作的?同样根节点下的compatible属性的值可以是一个字符串,也可以是一个字符串列表;该字符串也要求以”<制造商>,<型号>”这样的形式进行命名;比如这里使用的是“xlnx”制造的“zynq-7000”系列处理器。通过根节点的compatible属性可以知道我们所使用的处理器型号,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中有如下定义:示例代码24.3.5.2 MX35_3DS设备

  1. 613 MACHINE_START(MX35_3DS, "Freescale MX35PDK")
  2. 614 /* Maintainer: Freescale Semiconductor, Inc */
  3. 615 .atag_offset = 0x100,
  4. 616 .map_io = mx35_map_io,
  5. 617 .init_early = imx35_init_early,
  6. 618 .init_irq = mx35_init_irq,
  7. 619 .init_time = mx35pdk_timer_init,
  8. 620 .init_machine = mx35_3ds_init,
  9. 621 .reserve = mx35_3ds_reserve,
  10. 622 .restart = mxc_restart,
  11. 623 MACHINE_END

复制代码

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

  1. #define MACHINE_START(_type,_name) \
  2. static const struct machine_desc __mach_desc_##_type \
  3. __used \
  4. __attribute__((__section__(".arch.info.init"))) = { \
  5. .nr = MACH_TYPE_##_type, \
  6. .name = _name,
  7. #define MACHINE_END \
  8. };

复制代码

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

  1. 1 static const struct machine_desc __mach_desc_MX35_3DS \
  2. 2 __used \
  3. 3 __attribute__((__section__(".arch.info.init"))) = {
  4. 4 .nr = MACH_TYPE_MX35_3DS,
  5. 5 .name = "Freescale MX35PDK",
  6. 6 /* Maintainer: Freescale Semiconductor, Inc */
  7. 7 .atag_offset = 0x100,
  8. 8 .map_io = mx35_map_io,
  9. 9 .init_early = imx35_init_early,
  10. 10 .init_irq = mx35_init_irq,
  11. 11 .init_time = mx35pdk_timer_init,
  12. 12 .init_machine = mx35_3ds_init,
  13. 13 .reserve = mx35_3ds_reserve,
  14. 14 .restart = mxc_restart,
  15. 15 };

复制代码

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

  1. 15 #define MACH_TYPE_EBSA110 0
  2. 16 #define MACH_TYPE_RISCPC 1
  3. 17 #define MACH_TYPE_EBSA285 4
  4. 18 #define MACH_TYPE_NETWINDER 5
  5. 19 #define MACH_TYPE_CATS 6
  6. 20 #define MACH_TYPE_SHARK 15
  7. 21 #define MACH_TYPE_BRUTUS 16
  8. 22 #define MACH_TYPE_PERSONAL_SERVER 17
  9. ......
  10. 287 #define MACH_TYPE_MX35_3DS 1645
  11. ......
  12. 1000 #define MACH_TYPE_PFLA03 4575

复制代码

第287行就是MACH_TYPE_MX35_3DS的值,为1645。前面说了,uboot会给Linux内核传递machine id这个参数,Linux内核会检查这个machine id,其实就是将machine id与示例代码24.3.4中的这些MACH_TYPE_XXX宏进行对比,看看有没有相等的,如果相等的话就表示Linux内核支持这个硬件平台,如果不支持的话就没法启动Linux内核。2、使用设备树以后的设备匹配方法当Linux内核引入设备树以后就不再使用MACHINE_START了,而是换为了DT_MACHINE_START。DT_MACHINE_START也定义在文件arch/arm/include/asm/mach/arch.h 里面,定义如下:示例代码24.3.5.8 DT_MACHINE_START宏

  1. #define DT_MACHINE_START(_name, _namestr) \
  2. static const struct machine_desc __mach_desc_##_name \
  3. __used \
  4. __attribute__((__section__(".arch.info.init"))) = { \
  5. .nr = ~0, \
  6. .name = _namestr,

复制代码

可以看出,DT_MACHINE_START和MACHINE_START基本相同,只是.nr的设置不同,在DT_MACHINE_START里面直接将.nr设置为~0。说明引入设备树以后不会再根据machine id来检查Linux内核是否支持某个硬件平台了。打开文件arch/arm/mach-zynq/common.c,有如下所示内容:示例代码24.3.5.9 arch/arm/mach-zynq/common.c

  1. 191 static const char * const zynq_dt_match[] = {
  2. 192 "xlnx,zynq-7000",
  3. 193 NULL
  4. 194 };
  5. 195
  6. 196 DT_MACHINE_START(XILINX_EP107, "Xilinx Zynq Platform")
  7. 197 /* 64KB way size, 8-way associativity, parity disabled */
  8. 198 #ifdef CONFIG_XILINX_PREFETCH
  9. 199 .l2c_aux_val = 0x30400000,
  10. 200 .l2c_aux_mask = 0xcfbfffff,
  11. 201 #else
  12. 202 .l2c_aux_val = 0x00400000,
  13. 203 .l2c_aux_mask = 0xffbfffff,
  14. 204 #endif
  15. 205 .smp = smp_ops(zynq_smp_ops),
  16. 206 .map_io = zynq_map_io,
  17. 207 .init_irq = zynq_irq_init,
  18. 208 .init_machine = zynq_init_machine,
  19. 209 .init_late = zynq_init_late,
  20. 210 .init_time = zynq_timer_init,
  21. 211 .dt_compat = zynq_dt_match,
  22. 212 .reserve = zynq_memory_init,
  23. 213 MACHINE_END

复制代码

machine_desc结构体中有个.dt_compat成员变量,此成员变量保存着本硬件平台的兼容属性,示例代码24.3.6中设置.dt_compat = zynq_dt_match,zynq_dt_match数组的定义在第191~194行中,可以看到它匹配的字符串是“xlnx,zynq-7000”。只要某个板子的设备树根节点“/”的compatible属性值与zynq_dt_match表中的任何一个值相等,那么就表示Linux内核支持这个开发板、支持这个硬件平台。前面也跟大家说过了,我们使用的设备树文件是system-top.dts,该文件中使用include包含了zynq-7000.dtsi,在zynq-7000.dtsi文件中根节点的compatible属性值就是“xlnx,zynq-7000”,所以内核是支持我们开发板的如果将zynq-7000.dtsi根节点的compatible属性改为其他的值,那么它就启动不了了。当我们修改了根节点compatible属性内容以后,因为Linux内核找不到对应的硬件平台,因此Linux内核无法启动。接下来我们简单看一下Linux内核是如何根据设备树根节点的compatible属性来匹配出对应的machine_desc,Linux内核调用start_kernel函数来启动内核,start_kernel函数会调用setup_arch函数来匹配machine_desc,setup_arch函数定义在文件arch/arm/kernel/setup.c中,函数内容如下(有缩减):示例代码24.3.5.10 setup_arch函数内容

  1. 913 void __init setup_arch(char **cmdline_p)
  2. 914 {
  3. 915 const struct machine_desc *mdesc;
  4. 916
  5. 917 setup_processor();
  6. 918 mdesc = setup_machine_fdt(__atags_pointer);
  7. 919 if (!mdesc)
  8. 920 mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
  9. 921 machine_desc = mdesc;
  10. 922 machine_name = mdesc->name;
  11. ......
  12. 986 }

复制代码

第918行,调用setup_machine_fdt函数来获取匹配的machine_desc,参数就是atags的首地址,也就是uboot传递给Linux内核的dtb文件首地址,setup_machine_fdt函数的返回值就是找到的已经匹配成功的machine_desc。函数setup_machine_fdt定义在文件arch/arm/kernel/devtree.c中,内容如下(有缩减):示例代码24.3.5.11 setup_machine_fdt函数内容

  1. 204 const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
  2. 205 {
  3. 206 const struct machine_desc *mdesc, *mdesc_best = NULL;
  4. ......
  5. 214
  6. 215 if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))
  7. 216 return NULL;
  8. 217
  9. 218 mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
  10. 219
  11. ......
  12. 247 __machine_arch_type = mdesc->nr;
  13. 248
  14. 249 return mdesc;
  15. 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中,内容如下(有缩减):示例代码24.3.5.12 of_flat_dt_match_machine函数内容

  1. 705 const void * __init of_flat_dt_match_machine(const void *default_match,
  2. 706 const void * (*get_next_compat)(const char * const**))
  3. 707 {
  4. 708 const void *data = NULL;
  5. 709 const void *best_data = default_match;
  6. 710 const char *const *compat;
  7. 711 unsigned long dt_root;
  8. 712 unsigned int best_score = ~1, score = 0;
  9. 713
  10. 714 dt_root = of_get_flat_dt_root();
  11. 715 while ((data = get_next_compat(&compat))) {
  12. 716 score = of_flat_dt_match(dt_root, compat);
  13. 717 if (score > 0 && score < best_score) {
  14. 718 best_data = data;
  15. 719 best_score = score;
  16. 720 }
  17. 721 }
  18. ......
  19. 739
  20. 740 pr_info("Machine model: %s\n", of_flat_dt_get_machine_name());
  21. 741
  22. 742 return best_data;
  23. 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属性找到对应的machine_desc结构体的函数调用过程,如下图所示:

fpga怎么产生一组时序(第二十四章Linux设备树)(9)

图 35.3.4 查找匹配machine_desc的过程

24.3.6引用节点前面说到节点的命名格式如下所示:[label:]node-name[@unit-address]也多次给大家提到“label”字段,引入label的目的就是为了方便访问节点,可以直接通过&label来访问这个节点,例如下面这个模板:示例代码24.3.6.1 设备树模板

  1. 1 / {
  2. 2 aliases {
  3. 3 can0 = &flexcan1;
  4. 4 };
  5. 5
  6. 6 cpus {
  7. 7 #address-cells = <1>;
  8. 8 #size-cells = <0>;
  9. 9
  10. 10 cpu0: cpu@0 {
  11. 11 compatible = "arm,cortex-a7";
  12. 12 device_type = "cpu";
  13. 13 reg = <0>;
  14. 14 };
  15. 15 };
  16. 16
  17. 17 intc: interrupt-controller@00a01000 {
  18. 18 compatible = "arm,cortex-a7-gic";
  19. 19 #interrupt-cells = <3>;
  20. 20 interrupt-controller;
  21. 21 reg = <0x00a01000 0x1000>,
  22. 22 <0x00a02000 0x100>;
  23. 23 };
  24. 24 };

复制代码

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页