Linux设备树简介

1.什么是设备树

设备树(Device Tree),将这个词分开就是"设备"和"树",描述设备树的文件叫做DTS(Device

Tree Source),这个DTS文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如

CPU数量、 内存基地址、IIC接口上接了哪些设备、SPI接口上接了哪些设备等等。如图:

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

以前的Linux内核中ARM架构并没有采用设备树。在没有设备树的时候Linux是如何描述ARM架构中的板级信息呢?在Linux内核源码中大量的arch/arm/mach-xxx和arch/arm/plat-xxx文件夹,这些文件夹里面的文件就是对应平台下的板级信息。

cpp 复制代码
static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {

    .lcdcon5    = S3C2410_LCDCON5_FRM565 | 
              S3C2410_LCDCON5_INVVLINE | 
              S3C2410_LCDCON5_INVVFRAME | 
              S3C2410_LCDCON5_PWREN | 
              S3C2410_LCDCON5_HWSWP, 
};

static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {
    .displays   = &smdk2440_lcd_cfg, 
    .num_displays   = 1, 
    .default_display = 0, 
};

static struct platform_device *smdk2440_devices[] __initdata = { 
    &s3c_device_ohci, 
    &s3c_device_lcd, 
    &s3c_device_wdt, 
    &s3c_device_i2c0, 
    &s3c_device_iis, 
};

上述代码中的结构体变量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社区引入了设备树。

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文件内容如下:

cpp 复制代码
 hostprogs-y := dtc 
 always       := $(hostprogs-y) 
  
 dtc-objs:= dtc.o flattree.o fstree.o data.o livetree.o treesource.o \ 
          srcpos.o checks.o util.o 
 dtc-objs  += dtc-lexer.lex.o dtc-parser.tab.o 
...... 

可以看出,DTC工具依赖于dtc.c、flattree.c、fstree.c等文件,最终编译并链接出DTC这

个主机文件。如果要编译DTS文件的话只需要进入到Linux源码根目录下,然后执行如下命

令:

cpp 复制代码
make all或者make dtbs

"make all"命令是编译Linux源码中的所有东西,包括zImage,.ko驱动模块以及设备

树,如果只是编译设备树的话建议使用"make dtbs"命令。

基于ARM架构的SOC有很多种,一种SOC又可以制作出很多款板子,每个板子都有一

个对应的DTS文件,那么如何确定编译哪一个DTS文件呢?我们就以I.MX6ULL这款芯片对

应的板子为例来看一下,打开arch/arm/boot/dts/Makefile,有如下内容:

cpp 复制代码
dtb-$(CONFIG_SOC_IMX6UL) += \
    imx6ul-14x14-ddr3-arm2.dtb \
    imx6ul-14x14-ddr3-arm2-emmc.dtb \
    ......

dtb-$(CONFIG_SOC_IMX6ULL) += \
    imx6ull-14x14-ddr3-arm2.dtb \
    imx6ull-14x14-ddr3-arm2-adc.dtb \
    imx6ull-14x14-ddr3-arm2-cs42888.dtb \
    imx6ull-14x14-ddr3-arm2-ecspi.dtb \
    imx6ull-14x14-ddr3-arm2-emmc.dtb \
    imx6ull-14x14-ddr3-arm2-epdc.dtb \
    imx6ull-14x14-ddr3-arm2-flexcan2.dtb \
    imx6ull-14x14-ddr3-arm2-gpmi-weim.dtb \
    imx6ull-14x14-ddr3-arm2-lcdif.dtb \
    imx6ull-14x14-ddr3-arm2-ldo.dtb \
    imx6ull-14x14-ddr3-arm2-qspi.dtb \
    imx6ull-14x14-ddr3-arm2-qspi-all.dtb \
    imx6ull-14x14-ddr3-arm2-tsc.dtb \
    imx6ull-14x14-ddr3-arm2-uart2.dtb \
    imx6ull-14x14-ddr3-arm2-usb.dtb \
    imx6ull-14x14-ddr3-arm2-wm8958.dtb \
    imx6ull-14x14-evk.dtb \
    imx6ull-14x14-evk-btwifi.dtb \
    imx6ull-14x14-evk-emmc.dtb \
    imx6ull-14x14-evk-gpmi-weim.dtb \
    imx6ull-14x14-evk-usb-certi.dtb \
    imx6ull-alientek-emmc.dtb \
    imx6ull-alientek-nand.dtb \
    imx6ull-9x9-evk.dtb \
    imx6ull-9x9-evk-btwifi.dtb \
    imx6ull-9x9-evk-ldo.dtb

dtb-$(CONFIG_SOC_IMX6SLL) += \
    imx6sll-lpddr2-arm2.dtb \
    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

文件。

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

3.DTS语法

虽然我们基本上不会从头到尾重写一个.dts文件,大多时候是直接在SOC厂商提供的.dts

文件上进行修改。但是DTS文件语法我们还是需要详细的学习一遍,因为我们肯定需要修改.dts

文件。大家不要看到要学习新的语法就觉得会很复杂,DTS语法非常的人性化,是一种ASCII

文本文件,不管是阅读还是修改都很方便。

本节我们就以imx6ull-alientek-emmc.dts这个文件为例来讲解一下DTS语法。关于设备树

详 细 的 语 法 规 则 请 参 考 《 DevicetreeSpecificationV0.2.pdf 》 和《Power_ePAPR_APPROVED_v1.12.pdf》这两份文档,此两份文档已经放到了开发板光盘中,

路 径为: 4 、参考资 料 ->Devicetree SpecificationV0.2.pdf 、 4 、参 考资料 ->

Power_ePAPR_APPROVED_v1.12.pdf

3.1 .dtsi头文件

和C语言一样,设备树也支持头文件,设备树的头文件扩展名为.dtsi。在imx6ull-alientek-

emmc.dts中有如下所示内容:

cpp 复制代码
 #include <dt-bindings/input/input.h> 
 #include "imx6ull.dtsi"

在.dts设备树文件中,可以通过"#include"来引用.h、.dtsi和.dts文件。只是,我们在编写设备树头文件的时候最好选择.dtsi后缀。

一般.dtsi文件用于描述SOC的内部外设信息,比如CPU架构、主频、外设寄存器地址范

围,比如UART、IIC等等。比如imx6ull.dtsi就是描述I.MX6ULL这颗SOC内部外设情况信息

的,内容如下:

cpp 复制代码
#include <dt-bindings/clock/imx6ul-clock.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include "imx6ull-pinfunc.h"
#include "imx6ull-pinfunc-snvs.h"
#include "skeleton.dtsi"

/ {
    aliases {
        can0 = &flexcan1;
        ......
    };

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;

        cpu0: cpu@0 {
            compatible = "arm,cortex-a7";
            device_type = "cpu";
            ......
        };
    };

    intc: interrupt-controller@00a01000 {
        compatible = "arm,cortex-a7-gic";
        #interrupt-cells = <3>;
        interrupt-controller;
        reg = <0x00a01000 0x1000>,
              <0x00a02000 0x100>;
    };

    clocks {
        #address-cells = <1>;
        #size-cells = <0>;

        ckil: clock@0 {
            compatible = "fixed-clock";
            reg = <0>;
            #clock-cells = <0>;
            clock-frequency = <32768>;
            clock-output-names = "ckil";
        };
        ......
    };

    soc {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "simple-bus";
        interrupt-parent = <&gpc>;
        ranges;

        busfreq {
            compatible = "fsl,imx_busfreq";
            ......
        };

        gpmi: gpmi-nand@01806000{
            compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";
            #address-cells = <1>;
            #size-cells = <1>;
            reg = <0x01806000 0x2000>, <0x01808000 0x4000>;
            ......
        };
        ......
    };
};

示例代码中第 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

等等,关于这些设备节点信息的具体内容我们稍后在详细的讲解。

3.2设备节点

设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设

备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键---值对。以下是从

imx6ull.dtsi文件中缩减出来的设备树文件内容:

cpp 复制代码
/ {
    aliases {
        can0 = &flexcan1;
    };

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;

        cpu0: cpu@0 {
            compatible = "arm,cortex-a7";
            device_type = "cpu";
            reg = <0>;
        };
    };

    intc: interrupt-controller@00a01000 {
        compatible = "arm,cortex-a7-gic";
        #interrupt-cells = <3>;
        interrupt-controller;
        reg = <0x00a01000 0x1000>,
              <0x00a02000 0x100>;
    };
}

第1行,"/"是根节点,每个设备树文件只有一个根节点。细心的同学应该会发现,imx6ull.dtsi

和imx6ull-alientek-emmc.dts这两个文件都有一个"/"根节点,这样不会出错吗?不会的,因为

这两个"/"根节点的内容会合并成一个根节点。

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

cpp 复制代码
node-name@unit-address

其中"node-name"是节点名字,为ASCII字符串,节点名字应该能够清晰的描述出节点的

功能,比如"uart1"就表示这个节点是UART1外设。"unit-address"一般表示设备的地址或寄

存器首地址,如果某个节点没有地址或者寄存器的话"unit-address"可以不要,比如"cpu@0"、

"interrupt-controller@00a01000"。

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

cpp 复制代码
cpu0:cpu@0 

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的子节点。

每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任

意的字节流。设备树源码中常用的几种数据形式如下所示:

cpp 复制代码
1.字符串
compatible = "arm,cortex-a7"; 

2.32位无符号整数
reg = <0>; 
上述代码设置reg属性的值为0,reg的值也可以设置为一组值,比如:
reg = <0 0x123456 100>; 

3.字符串列表
属性值也可以为字符串列表,字符串和字符串之间采用","隔开,如下所示: 
compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand"; 
上述代码设置属性compatible的值为"fsl,imx6ull-gpmi-nand"和"fsl, imx6ul-gpmi-nand"。

3.3 标准属性

节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以

自定义属性。除了用户自定义属性,有很多属性是标准属性,Linux下的很多外设驱动都会使用

这些标准属性,本节我们就来学习一下几个常用的标准属性。

1、compatible属性

compatible属性也叫做"兼容性"属性,这是非常重要的一个属性!compatible属性的值是

一个字符串列表,compatible 属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要

使用的驱动程序,compatible属性的值格式如下所示:

bash 复制代码
"manufacturer,model" 

其中 manufacturer表示厂商,model一般是模块对应的驱动名字。比如 imx6ull-alientek-

emmc.dts中sound节点是I.MX6U-ALPHA开发板的音频设备节点,I.MX6U-ALPHA开发板上

的音频芯片采用的欧胜(WOLFSON)出品的WM8960,sound节点的compatible属性值如下:

bash 复制代码
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内核里面查找,看看能不能找到与之匹配的驱动文件,

如果没有找到的话就使用第二个兼容值查。

一般驱动程序文件都会有一个OF匹配表,此OF匹配表保存着一些compatible值,如果设

备节点的compatible属性值和OF匹配表中的任何一个值相等,那么就表示设备可以使用这个

驱动。比如在文件imx-wm8960.c中有如下内容:

cpp 复制代码
static const struct of_device_id imx_wm8960_dt_ids[] = {
    { .compatible = "fsl,imx-audio-wm8960", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);

static struct platform_driver imx_wm8960_driver = {
    .driver = {
        .name = "imx-wm8960",
        .pm = &snd_soc_pm_ops,
        .of_match_table = imx_wm8960_dt_ids,
    },
    .probe = imx_wm8960_probe,
    .remove = imx_wm8960_remove,
};

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

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属性值也是字符串,字符串是设备的

状态信息,

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

这两个属性的值都是无符号32位整形,#address-cells和#size-cells这两个属性可以用在任

何拥有子节点的设备中,用于描述子节点的地址信息。#address-cells属性值决定了子节点reg属

性中地址信息所占用的字长(32位),#size-cells属性值决定了子节点reg属性中长度信息所占的

字长(32位)。#address-cells和#size-cells表明了子节点应该如何编写reg属性值,一般reg属性

都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度。

每个"address length"组合表示一个地址范围,其中address是起始地址,length是地址长

度,#address-cells表明address这个数据所占用的字长,#size-cells表明length这个数据所占用

的字长,比如:

cpp 复制代码
spi4 {
    compatible = "spi-gpio";
    #address-cells = <1>;
    #size-cells = <0>;

    gpio_spi: gpio_spi@0 {
        compatible = "fairchild,74hc595";
        reg = <0>;
    };
};

aips3: aips-bus@02200000 {
    compatible = "fsl,aips-bus", "simple-bus";
    #address-cells = <1>;
    #size-cells = <1>;

    dcp: dcp@02280000 {
        compatible = "fsl,imx6sl-dcp";
        reg = <0x02280000 0x4000>;
    };
};

节点spi4的#address-cells = <1>,#size-cells = <0>,说明spi4的子节点reg属

性中起始地址所占用的字长为1,地址长度所占用的字长为0。

子节点gpio_spi: gpio_spi@0的reg 属性值为 <0>,因为父节点设置了#address-

cells = <1>,#size-cells = <0>,因此addres=0,没有length的值,相当于设置了起始地址,而没

有设置地址长度。

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

子节点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中有

如下内容:

cpp 复制代码
uart1: serial@02020000 {
    compatible = "fsl,imx6ul-uart", 
                 "fsl,imx6q-uart", "fsl,imx21-uart";
    reg = <0x02020000 0x4000>;
    interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_UART1_IPG>, 
             <&clks IMX6UL_CLK_UART1_SERIAL>;
    clock-names = "ipg", "per";
    status = "disabled";
};

上述代码是节点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属性,如下所示:

cpp 复制代码
soc { 
        #address-cells = <1>; 
        #size-cells = <1>; 
        compatible = "simple-bus"; 
        interrupt-parent = <&gpc>; 
        ranges; 
        ...... 
    } 

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

cpp 复制代码
soc {
    compatible = "simple-bus";
    #address-cells = <1>;
    #size-cells = <1>;
    ranges = <0x0 0xe0000000 0x00100000>;

    serial {
        device_type = "serial";
        compatible = "ns16550";
        reg = <0x4600 0x100>;
        clock-frequency = <0>;
        interrupts = <0xA 0x8>;
        interrupt-parent = <&ipic>;
    };
};

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

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节点用到了此属性,内容如下所示:

cpp 复制代码
cpu0: cpu@0 {
    compatible = "arm,cortex-a7";
    device_type = "cpu";
    reg = <0>;
    ......
};

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

程再讲解。

3.4 根节点compatible属性

每个节点都有compatible属性,根节点"/"也不例外,imx6ull-alientek-emmc.dts文件中根

节点的compatible属性内容如下所示:

cpp 复制代码
/ { 
    model = "Freescale i.MX6 ULL 14x14 EVK Board"; 
    compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull"; 
    ...... 
} 

可以看出,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中有如下定义:

cpp 复制代码
MACHINE_START(MX35_3DS, "Freescale MX35PDK")
    /* Maintainer: Freescale Semiconductor, Inc */
    .atag_offset = 0x100,
    .map_io = mx35_map_io,
    .init_early = imx35_init_early,
    .init_irq = mx35_init_irq,
    .init_time  = mx35pdk_timer_init,
    .init_machine = mx35_3ds_init,
    .reserve = mx35_3ds_reserve,
    .restart    = mxc_restart,
MACHINE_END

上述代码就是定义了"Freescale MX35PDK"这个设备,其中 MACHINE_START 和

MACHINE_END定义在文件arch/arm/include/asm/mach/arch.h中,内容如下:

cpp 复制代码
#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展开后如下所

示:

cpp 复制代码
static const struct machine_desc __mach_desc_MX35_3DS \
__used \
__attribute__((__section__(".arch.info.init"))) = {
    .nr         = MACH_TYPE_MX35_3DS,
    .name       = "Freescale MX35PDK",
    /* Maintainer: Freescale Semiconductor, Inc */
    .atag_offset = 0x100,
    .map_io     = mx35_map_io,
    .init_early = imx35_init_early,
    .init_irq   = mx35_init_irq,
    .init_time  = mx35pdk_timer_init,
    .init_machine = mx35_3ds_init,
    .reserve    = mx35_3ds_reserve,
    .restart    = mxc_restart,
};

从示例代码 中可以看出,这里定义了一个 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,内容如下所示:

cpp 复制代码
#define MACH_TYPE_EBSA110              0
#define MACH_TYPE_RISCPC                1
#define MACH_TYPE_EBSA285               4
#define MACH_TYPE_NETWINDER             5
#define MACH_TYPE_CATS                  6
#define MACH_TYPE_SHARK                 15
#define MACH_TYPE_BRUTUS                16
#define MACH_TYPE_PERSONAL_SERVER     17
......
#define MACH_TYPE_MX35_3DS              1645
......
#define MACH_TYPE_PFLA03                4575

MACH_TYPE_MX35_3DS的值,为1645。

前面说了,uboot会给Linux内核传递machine id这个参数,Linux内核会检查这个machine

id,其实就是将machine id与示例代码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

里面,定义如下:

cpp 复制代码
#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。说明引入设备树以后不会再根据machine

id来检查Linux内核是否支持某个设备了。

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

cpp 复制代码
static const char *imx6ul_dt_compat[] __initconst = {
    "fsl,imx6ul",
    "fsl,imx6ull",
    NULL,
};

DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
    .map_io     = imx6ul_map_io,
    .init_irq   = imx6ul_init_irq,
    .init_machine   = imx6ul_init_machine,
    .init_late  = imx6ul_init_late,
    .dt_compat  = imx6ul_dt_compat,
MACHINE_END

machine_desc结构体中有个.dt_compat成员变量,此成员变量保存着本设备兼容属性,示

例代码中设置.dt_compat = imx6ul_dt_compat,imx6ul_dt_compat表里面有"fsl,imx6ul"

和"fsl,imx6ull"这两个兼容值。只要某个设备(板子)根节点"/"的 compatible 属性值与

imx6ul_dt_compat表中的任何一个值相等,那么就表示Linux内核支持此设备。imx6ull-alientek-

emmc.dts中根节点的compatible属性值如下:

cpp 复制代码
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull"; 

其中"fsl,imx6ull"与imx6ul_dt_compat中的"fsl,imx6ull"匹配,因此I.MX6U-ALPHA开

发板可以正常启动Linux内核。如果将imx6ull-alientek-emmc.dts根节点的compatible属性改为

其他的值,比如:

cpp 复制代码
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ullll" 

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

当我们修改了根节点compatible属性内容以后,因为Linux内核找不到对应的设备,因此

Linux内核无法启动。在uboot输出Starting kernel...以后就再也没有其他信息输出了。

接下来我们简单看一下Linux内核是如何根据设备树根节点的compatible属性来匹配出对

应的 machine_desc,Linux 内核调用 start_kernel 函数来启动内核,start_kernel 函数会调用

setup_arch函数来匹配machine_desc,setup_arch函数定义在文件arch/arm/kernel/setup.c中,函

数内容如下(有缩减):

cpp 复制代码
void __init setup_arch(char **cmdline_p)
{
    const struct machine_desc *mdesc;

    setup_processor();
    mdesc = setup_machine_fdt(__atags_pointer);
    if (!mdesc)
        mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
    machine_desc = mdesc;
    machine_name = mdesc->name;
    ......
}

调用setup_machine_fdt函数来获取匹配的machine_desc,参数就是atags的首

地址,也就是uboot传递给Linux内核的dtb文件首地址,setup_machine_fdt函数的返回值就是

找到的最匹配的machine_desc。

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

cpp 复制代码
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
    const struct machine_desc *mdesc, *mdesc_best = NULL;

    if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))
        return NULL;

    mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);

    __machine_arch_type = mdesc->nr;

    return mdesc;
}

调用函数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中,内

容如下(有缩减):

cpp 复制代码
const void * __init of_flat_dt_match_machine(const void *default_match,
        const void * (*get_next_compat)(const char * const**))
{
    const void *data = NULL;
    const void *best_data = default_match;
    const char *const *compat;
    unsigned long dt_root;
    unsigned int best_score = ~1, score = 0;

    dt_root = of_get_flat_dt_root();
    while ((data = get_next_compat(&compat))) {
        score = of_flat_dt_match(dt_root, compat);
        if (score > 0 && score < best_score) {
            best_data = data;
            best_score = score;
        }
    }

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

    return best_data;
}

通过函数of_get_flat_dt_root获取设备树根节点。

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

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

3.5 向节点追加或修改内容

产品开发过程中可能面临着频繁的需求更改,比如第一版硬件上有一个IIC接口的六轴芯

片MPU6050,第二版硬件又要把这个MPU6050更换为MPU9250等。一旦硬件修改了,我们

就要同步的修改设备树文件,毕竟设备树是描述板子硬件信息的文件。假设现在有个六轴芯片

fxls8471,fxls8471要接到I.MX6U-ALPHA开发板的I2C1接口上,那么相当于需要在i2c1这

个节点上添加一个fxls8471子节点。先看一下I2C1接口对应的节点,打开文件imx6ull.dtsi文

件,找到如下所示内容:

cpp 复制代码
i2c1: i2c@021a0000 {
    #address-cells = <1>;
    #size-cells = <0>;
    compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
    reg = <0x021a0000 0x4000>;
    interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_I2C1>;
    status = "disabled";
};

示例代码就是I.MX6ULL的I2C1节点,现在要在i2c1节点下创建一个子节点,这个子节点就是fxls8471,最简单的方法就是在i2c1下直接添加一个名为fxls8471的子节点,如下所示:

cpp 复制代码
i2c1: i2c@021a0000 {
    #address-cells = <1>;
    #size-cells = <0>;
    compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
    reg = <0x021a0000 0x4000>;
    interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_I2C1>;
    status = "disabled";

    //fxls8471子节点
    fxls8471@1e {
        compatible = "fsl,fxls8471";
        reg = <0x1e>;
    };
};

添加的fxls8471这个芯片对应的子节点。但是这样会有个问题!i2c1节点是定义在imx6ull.dtsi文件中的,而imx6ull.dtsi是设备树头文件,其他所有使用到I.MX6ULL这颗SOC的板子都会引用imx6ull.dtsi这个文件。直接在i2c1节点中添加fxls8471就相当于在其他的所有板子上都添加了fxls8471这个设备,但是其他的板子并没有这个设备啊!因此,按照示例代码这样写肯定是不行的。

这里就要引入另外一个内容,那就是如何向节点追加数据,我们现在要解决的就是如何向

i2c1节点追加一个名为fxls8471的子节点,而且不能影响到其他使用到I.MX6ULL的板子。

I.MX6U-ALPHA 开发板使用的设备树文件为imx6ull-alientek-emmc.dts,因此我们需要在

imx6ull-alientek-emmc.dts文件中完成数据追加的内容,方式如下:

cpp 复制代码
 &i2c1 { 
   /* 要追加或修改的内容 */ 
 }; 

第1行,&i2c1表示要访问i2c1这个label所对应的节点,也就是imx6ull.dtsi中的"i2c1:

i2c@021a0000"。

第2行,花括号内就是要向i2c1这个节点添加的内容,包括修改某些属性的值。

打开imx6ull-alientek-emmc.dts,找到如下所示内容:

cpp 复制代码
&i2c1 {
    clock-frequency = <100000>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c1>;
    status = "okay";

    mag3110@0e {
        compatible = "fsl,mag3110";
        reg = <0x0e>;
        position = <2>;
    };

    fxls8471@1e {
        compatible = "fsl,fxls8471";
        reg = <0x1e>;
        position = <0>;
        interrupt-parent = <&gpio5>;
        interrupts = <0 8>;
    };
};

示例代码就是向i2c1节点添加/修改数据,比如第225行的属性"clock-frequency"就表示i2c1时钟为100KHz。"clock-frequency"就是新添加的属性。

将status属性的值由原来的disabled改为okay。

i2c1子节点mag3110,因为NXP官方开发板在I2C1上接了一个磁力计芯片mag3110,正点原子的I.MX6U-ALPHA开发板并没有使用mag3110。

i2c1子节点fxls8471,同样是因为NXP官方开发板在I2C1上接了fxls8471

这颗六轴芯片。

因为示例代码中的内容是imx6ull-alientek-emmc.dts这个文件内的,所以不会对使用I.MX6ULL这颗SOC的其他板子造成任何影响。这个就是向节点追加或修改内容,重点

就是通过&label来访问节点,然后直接在里面编写要追加或者修改的内容。

4 创建小型模板设备树

上一节已经对DTS的语法做了比较详细的讲解,本节我们就根据前面讲解的语法,从头到

尾编写一个小型的设备树文件。当然了,这个小型设备树没有实际的意义,做这个的目的是为

了掌握设备树的语法。在实际产品开发中,我们是不需要完完全全的重写一个.dts设备树文件,

一般都是使用SOC厂商提供好的.dts文件,我们只需要在上面根据自己的实际情况做相应的修

改即可。在编写设备树之前要先定义一个设备,我们就以I.MX6ULL这个SOC为例,我们需要

在设备树里面描述的内容如下:

①、I.MX6ULL这个Cortex-A7架构的32位CPU。

②、I.MX6ULL内部ocram,起始地址0x00900000,大小为128KB(0x20000)。

③、I.MX6ULL内部aips1域下的ecspi1外设控制器,寄存器起始地址为0x02008000,大

小为0x4000。

④、I.MX6ULL内部aips2域下的usbotg1外设控制器,寄存器起始地址为0x02184000,大

小为0x4000。

⑤、I.MX6ULL内部aips3域下的rngb外设控制器,寄存器起始地址为0x02284000,大小

为0x4000。

为了简单起见,我们就在设备树里面就实现这些内容即可,首先,搭建一个仅含有根节点

"/"的基础的框架,新建一个名为myfirst.dts文件,在里面输入如下所示内容:

cpp 复制代码
 / { 
     
        compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull"; 
 } 

设备树框架很简单,就一个根节点"/",根节点里面只有一个compatible属性。我们就在这

个基础框架上面将上面列出的内容一点点添加进来。

1、添加cpus节点

cpp 复制代码
/ {
    compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull";

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;

        //CPU0节点
        cpu0: cpu@0 {
            compatible = "arm,cortex-a7";
            device_type = "cpu";
            reg = <0>;
        };
    };
}

cpus节点,此节点用于描述SOC内部的所有CPU,因为I.MX6ULL只有一个

CPU,因此只有一个cpu0子节点。

2、添加soc节点

像uart,iic控制器等等这些都属于SOC内部外设,因此一般会创建一个叫做soc的父节点

来管理这些SOC内部外设的子节点,添加soc节点以后的myfirst.dts文件内容如下所示:

cpp 复制代码
/ {
    compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull";
    
    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        
        cpu0: cpu@0 {
            compatible = "arm,cortex-a7";
            device_type = "cpu";
            reg = <0>;
        };
    };
    
    soc {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "simple-bus";
        ranges;
    };
}

soc节点设置#address-cells = <1>,#size-cells = <1>,这样soc子节点的reg属性中起始地占用一个字长,地址空间长度也占用一个字长。

ranges属性,ranges属性为空,说明子空间和父空间地址范围相同。

3、添加ocram节点

根据第②点的要求,添加ocram节点,ocram是I.MX6ULL内部RAM,因此ocram节点应

该是soc节点的子节点。ocram起始地址为0x00900000,大小为128KB(0x20000),添加ocram

节点以后myfirst.dts文件内容如下所示:

cpp 复制代码
/ {
    compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull";
    
    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
 
        cpu0: cpu@0 {
            compatible = "arm,cortex-a7";
            device_type = "cpu";
            reg = <0>;
        };
    };
     
    soc {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "simple-bus";
        ranges;
         
        ocram: sram@00900000 {
            compatible = "fsl,lpm-sram";
            reg = <0x00900000 0x20000>;
        };
    }    
}

ocram节点,第24行节点名字@后面的0x00900000就是ocram的起始地址。第26行的reg属性也指明了ocram内存的起始地址为0x00900000,大小为0x20000。

4、添加aips1、aips2和aips3这三个子节点

I.MX6ULL内部分为三个域:aips1~3,这三个域分管不同的外设控制器.

cpp 复制代码
我们先在设备树中添加这三个域对应的子节点。aips1~3这三个域都属于soc节点的子节点,
完成以后的myfirst.dts文件内容如下所示:
/ {
    compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull";
    
    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
 
        //CPU0节点
        cpu0: cpu@0 {
            compatible = "arm,cortex-a7";
            device_type = "cpu";
            reg = <0>;
        };
    };
     
    //soc节点
    soc {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "simple-bus";
        ranges;
           
        //ocram节点
        ocram: sram@00900000 {
            compatible = "fsl,lpm-sram";
            reg = <0x00900000 0x20000>;
        };
           
        //aips1节点
        aips1: aips-bus@02000000 {
            compatible = "fsl,aips-bus", "simple-bus";
            #address-cells = <1>;
            #size-cells = <1>;
            reg = <0x02000000 0x100000>;
            ranges;  
        }
 
        //aips2节点
        aips2: aips-bus@02100000 {
            compatible = "fsl,aips-bus", "simple-bus";
            #address-cells = <1>;
            #size-cells = <1>;
            reg = <0x02100000 0x100000>;
            ranges;
        }
     
        //aips3节点    
        aips3: aips-bus@02200000 {
            compatible = "fsl,aips-bus", "simple-bus";
            #address-cells = <1>;
            #size-cells = <1>;
            reg = <0x02200000 0x100000>;
            ranges;
        }
    }
}

5、添加ecspi1、usbotg1和rngb这三个外设控制器节点

最后我们在myfirst.dts文件中加入ecspi1,usbotg1和rngb这三个外设控制器对应的节点,

其中ecspi1属于aips1的子节点,usbotg1属于aips2的子节点,rngb属于aips3的子节点。最终

的myfirst.dts文件内容如下:

cpp 复制代码
/ {
    compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull";
    
    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
 
        //CPU0节点
        cpu0: cpu@0 {
            compatible = "arm,cortex-a7";
            device_type = "cpu";
            reg = <0>;
        };
    };
      
    //soc节点
    soc {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "simple-bus";
        ranges;
          
        //ocram节点
        ocram: sram@00900000 {
            compatible = "fsl,lpm-sram";
            reg = <0x00900000 0x20000>;
        };
          
        //aips1节点
        aips1: aips-bus@02000000 {
            compatible = "fsl,aips-bus", "simple-bus";
            #address-cells = <1>;
            #size-cells = <1>;
            reg = <0x02000000 0x100000>;
            ranges;  
              
            //ecspi1节点
            ecspi1: ecspi@02008000 {
                #address-cells = <1>;
                #size-cells = <0>;
                compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
                reg = <0x02008000 0x4000>;
                status = "disabled";
            };
        }
      
        //aips2节点
        aips2: aips-bus@02100000 {
            compatible = "fsl,aips-bus", "simple-bus";
            #address-cells = <1>;
            #size-cells = <1>;
            reg = <0x02100000 0x100000>;
            ranges;
              
            //usbotg1节点
            usbotg1: usb@02184000 {
                compatible = "fsl,imx6ul-usb", "fsl,imx27-usb";
                reg = <0x02184000 0x200>;
                status = "disabled";
            };
        }
      
        //aips3节点    
        aips3: aips-bus@02200000 {
            compatible = "fsl,aips-bus", "simple-bus";
            #address-cells = <1>;
            #size-cells = <1>;
            reg = <0x02200000 0x100000>;
            ranges;
              
            //rngb节点
            rngb: rngb@02284000 {
                compatible = "fsl,imx6sl-rng", "fsl,imx-rng", "imx-rng";
                reg = <0x02284000 0x4000>;
            };
        }
    }
}

至此,myfirst.dts这个小型的模板设备树就编写好了,基本和imx6ull.dtsi很像,可以看做

是imx6ull.dtsi的缩小版。在myfirst.dts里面我们仅仅是编写了I.MX6ULL的外设控制器节点,

像IIC接口,SPI接口下所连接的具体设备我们并没有写,因为具体的设备其设备树属性内容不

同,这个等到具体的实验在详细讲解。

5 设备树在系统中的体现

Linux内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/device-

tree目录下根据节点名字创建不同文件夹,如图

目录/proc/device-tree目录下的内容,/proc/device-tree目录下是根节点"/"的所有属性和子节点,我们依次来看一下这些属性和子节点.

1、根节点"/"各个属性

根节点属性属性表现为一个个的文件(图中细字体文件),比如图的"#address-cells"、"#size-cells"、"compatible"、"model"和"name"这5个文件,它们在设备树中就是根节点的5个属性。既然是文件那么肯定可以查看其内容,输入cat命令来查看model和compatible这两个文件的内容,结果如图

2、根节点"/"各子节点

图中各个文件夹(途中粗字体文件夹)就是根节点"/"的各个子节点,比如"aliases"、

"backlight"、"chosen"和"clocks"等等。大家可以查看一下 imx6ull-alientek-emmc.dts 和

imx6ull.dtsi这两个文件,看看根节点的子节点都有哪些,看看是否和图中的一致。

/proc/device-tree目录就是设备树在根文件系统中的体现,同样是按照树形结构组织的,进

入/proc/device-tree/soc目录中就可以看到soc节点的所有子节点,如图所示:

和根节点"/"一样,图中的所有文件分别为soc节点的属性文件和子节点文件夹。大家可以自行查看一下这些属性文件的内容是否和imx6ull.dtsi中soc节点的属性值相同,也可以进入"busfreq"这样的文件夹里面查看soc节点的子节点信息。

相关推荐
Lana学习中12 小时前
【运维杂记】连接不上远程服务器的问题处理
运维·服务器
1892280486112 小时前
NV023固态MT29F16T08GWLCEJ9-QBES:C
大数据·服务器·人工智能·科技·缓存
AOwhisky12 小时前
MySQL 学习笔记(第一期):数据库基础与 MySQL 初探
运维·数据库·笔记·学习·mysql·云计算
Peace12 小时前
【Prometheus】
linux·运维·prometheus
LZZ and MYY14 小时前
RTS 在windows和Linux之间ShareMem
linux·运维·服务器
aningx14 小时前
openSUSE Leap 16.0 运行 sunshine 报错的解决方法
linux
爱学习的徐徐14 小时前
Linux 基础IO
linux·服务器
蛋蛋的学习记录14 小时前
C#窗体应用中使用EasyModbusCore通讯
服务器·c#·tcp
zt1985q14 小时前
本地部署源代码管理解决方案 Bitbucket Data Center 并实现外部访问
运维·服务器·数据库·网络协议·postgresql·源代码管理
xiaobobo333014 小时前
面向对象:linux内核中函数转数据的用法
linux·面向对象·隔离·函数指针绑定